1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package iamclient
16
17 import (
18 "context"
19 "encoding/json"
20 "fmt"
21 "reflect"
22 "strings"
23
24 corekccv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
25 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/iam/v1beta1"
26 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gvks/externalonlygvks"
27 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
28 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/krmtotf"
29 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
30 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/text"
31 tfresource "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/tf/resource"
32 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/util"
33 "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
34 "k8s.io/apimachinery/pkg/api/errors"
35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
37 "k8s.io/apimachinery/pkg/runtime/schema"
38 "k8s.io/apimachinery/pkg/types"
39
40 tfschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
41 "sigs.k8s.io/controller-runtime/pkg/client"
42 )
43
44 type TFIAMClient struct {
45 kubeClient client.Client
46 provider *tfschema.Provider
47 smLoader *servicemappingloader.ServiceMappingLoader
48 }
49
50 func (t *TFIAMClient) SetPolicyMember(ctx context.Context, policyMember *v1beta1.IAMPolicyMember) (*v1beta1.IAMPolicyMember, error) {
51 rc, err := t.getResourceConfigForReferencedResource(ctx, policyMember)
52 if err != nil {
53 return nil, fmt.Errorf("error getting resource config for referenced resource: %w", err)
54 }
55 if !resourceSupportsIAMPolicyMember(rc) {
56 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM policies", rc.Kind)
57 }
58 resource, err := t.newResource(ctx, policyMember, rc)
59 if err != nil {
60 return nil, err
61 }
62 liveState, err := krmtotf.FetchLiveState(ctx, resource, t.provider, t.kubeClient, t.smLoader)
63 if err != nil {
64 return nil, fmt.Errorf("error fetching live state for resource: %w", err)
65 }
66 cfg, _, err := krmtotf.KRMResourceToTFResourceConfig(resource, t.kubeClient, t.smLoader)
67 if err != nil {
68 return nil, fmt.Errorf("error creating resource config: %w", err)
69 }
70 diff, err := resource.TFResource.Diff(ctx, liveState, cfg, t.provider.Meta())
71 if err != nil {
72 return nil, fmt.Errorf("error calculating diff: %w", err)
73 }
74 if !liveState.Empty() && diff.RequiresNew() {
75 return nil, k8s.NewImmutableFieldsMutationError(tfresource.ImmutableFieldsFromDiff(diff))
76 }
77 if diff.Empty() {
78 logger.Info("underlying resource is already up to date", "resource", k8s.GetNamespacedName(policyMember))
79 return policyMember, nil
80 }
81 newState, diagnostics := resource.TFResource.Apply(ctx, liveState, diff, t.provider.Meta())
82 if err := krmtotf.NewErrorFromDiagnostics(diagnostics); err != nil {
83 return nil, fmt.Errorf("error applying changes: %w", err)
84 }
85 return newIAMPolicyMemberFromTFState(resource, newState, policyMember)
86 }
87
88 func (t *TFIAMClient) GetPolicyMember(ctx context.Context, policyMember *v1beta1.IAMPolicyMember) (*v1beta1.IAMPolicyMember, error) {
89 rc, err := t.getResourceConfigForReferencedResource(ctx, policyMember)
90 if err != nil {
91 return nil, fmt.Errorf("error getting resource config for referenced resource: %w", err)
92 }
93 if !resourceSupportsIAMPolicyMember(rc) {
94 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM policies", rc.Kind)
95 }
96 resource, err := t.newResourceSkeleton(ctx, policyMember, rc)
97 if err != nil {
98 return nil, fmt.Errorf("error building resource skeleton for getting IAM resource: %w", err)
99 }
100 liveState, err := krmtotf.FetchLiveState(ctx, resource, t.provider, t.kubeClient, t.smLoader)
101 if err != nil {
102 return nil, fmt.Errorf("error fetching live state for resource: %w", err)
103 }
104 if liveState.Empty() {
105 return nil, NotFoundError
106 }
107 return newIAMPolicyMemberFromTFState(resource, liveState, policyMember)
108 }
109
110 func (t *TFIAMClient) DeletePolicyMember(ctx context.Context, policyMember *v1beta1.IAMPolicyMember) error {
111 rc, err := t.getResourceConfigForReferencedResource(ctx, policyMember)
112 if err != nil {
113 return fmt.Errorf("error getting resource config for referenced resource: %w", err)
114 }
115 if !resourceSupportsIAMPolicyMember(rc) {
116 return fmt.Errorf("invalid resource reference: kind %v does not support IAM policies", rc.Kind)
117 }
118 resource, err := t.newResource(ctx, policyMember, rc)
119 if err != nil {
120 return err
121 }
122 liveState, err := krmtotf.FetchLiveState(ctx, resource, t.provider, t.kubeClient, t.smLoader)
123 if err != nil {
124 return fmt.Errorf("error fetching live state for resource: %w", err)
125 }
126 if liveState.Empty() {
127 return NotFoundError
128 }
129 _, diagnostics := resource.TFResource.Apply(ctx, liveState, &terraform.InstanceDiff{Destroy: true}, t.provider.Meta())
130 if err := krmtotf.NewErrorFromDiagnostics(diagnostics); err != nil {
131 return fmt.Errorf("error deleting IAMPolicyMember: %w", err)
132 }
133 return nil
134 }
135
136 func (t *TFIAMClient) SetPolicy(ctx context.Context, policy *v1beta1.IAMPolicy) (*v1beta1.IAMPolicy, error) {
137 rc, err := t.getResourceConfigForReferencedResource(ctx, policy)
138 if err != nil {
139 return nil, fmt.Errorf("error getting resource config for referenced resource: %w", err)
140 }
141 if !resourceSupportsIAMPolicy(rc) {
142 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM policies", rc.Kind)
143 }
144 if len(policy.Spec.AuditConfigs) > 0 && !resourceSupportsIAMAuditConfigs(rc) {
145 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM audit configs", rc.Kind)
146 }
147 resource, err := t.newResource(ctx, policy, rc)
148 if err != nil {
149 return nil, err
150 }
151 liveState, err := krmtotf.FetchLiveState(ctx, resource, t.provider, t.kubeClient, t.smLoader)
152 if err != nil {
153 return nil, fmt.Errorf("error fetching live state for resource")
154 }
155 cfg, _, err := krmtotf.KRMResourceToTFResourceConfig(resource, t.kubeClient, t.smLoader)
156 if err != nil {
157 return nil, fmt.Errorf("error creating resource config: %w", err)
158 }
159 diff, err := resource.TFResource.Diff(ctx, liveState, cfg, t.provider.Meta())
160 if err != nil {
161 return nil, fmt.Errorf("error calculating diff: %w", err)
162 }
163 if !liveState.Empty() && diff.RequiresNew() {
164 return nil, k8s.NewImmutableFieldsMutationError(tfresource.ImmutableFieldsFromDiff(diff))
165 }
166 if diff.Empty() {
167 logger.Info("underlying resource is already up to date", "resource", k8s.GetNamespacedName(policy))
168 return policy, nil
169 }
170 newState, diagnostics := resource.TFResource.Apply(ctx, liveState, diff, t.provider.Meta())
171 if err := krmtotf.NewErrorFromDiagnostics(diagnostics); err != nil {
172 return nil, fmt.Errorf("error applying changes: %w", err)
173 }
174 return newIAMPolicyFromTFState(resource, newState, policy)
175 }
176
177 func (t *TFIAMClient) GetPolicy(ctx context.Context, policy *v1beta1.IAMPolicy) (*v1beta1.IAMPolicy, error) {
178 rc, err := t.getResourceConfigForReferencedResource(ctx, policy)
179 if err != nil {
180 return nil, fmt.Errorf("error getting resource config for referenced resource: %w", err)
181 }
182 if !resourceSupportsIAMPolicy(rc) {
183 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM policies", rc.Kind)
184 }
185 if len(policy.Spec.AuditConfigs) > 0 && !resourceSupportsIAMAuditConfigs(rc) {
186 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM audit configs", rc.Kind)
187 }
188 resource, err := t.newResourceSkeleton(ctx, policy, rc)
189 if err != nil {
190 return nil, fmt.Errorf("error building resource skeleton for getting IAM resource: %w", err)
191 }
192 liveState, err := krmtotf.FetchLiveState(ctx, resource, t.provider, t.kubeClient, t.smLoader)
193 if err != nil {
194 return nil, fmt.Errorf("error fetching live state for resource: %w", err)
195 }
196 if liveState.Empty() {
197 return nil, NotFoundError
198 }
199 return newIAMPolicyFromTFState(resource, liveState, policy)
200 }
201
202 func (t *TFIAMClient) DeletePolicy(ctx context.Context, policy *v1beta1.IAMPolicy) error {
203 rc, err := t.getResourceConfigForReferencedResource(ctx, policy)
204 if err != nil {
205 return fmt.Errorf("error getting resource config for referenced resource: %w", err)
206 }
207 if !resourceSupportsIAMPolicy(rc) {
208 return fmt.Errorf("invalid resource reference: kind %v does not support IAM policies", rc.Kind)
209 }
210 if len(policy.Spec.AuditConfigs) > 0 && !resourceSupportsIAMAuditConfigs(rc) {
211 return fmt.Errorf("invalid resource reference: kind %v does not support IAM audit configs", rc.Kind)
212 }
213 resource, err := t.newResource(ctx, policy, rc)
214 if err != nil {
215 return err
216 }
217 liveState, err := krmtotf.FetchLiveState(ctx, resource, t.provider, t.kubeClient, t.smLoader)
218 if err != nil {
219 return fmt.Errorf("error fetching live state for resource: %w", err)
220 }
221 if liveState.Empty() {
222 return NotFoundError
223 }
224 _, diagnostics := resource.TFResource.Apply(ctx, liveState, &terraform.InstanceDiff{Destroy: true}, t.provider.Meta())
225 if err := krmtotf.NewErrorFromDiagnostics(diagnostics); err != nil {
226 return fmt.Errorf("error deleting IAMPolicy: %w", err)
227 }
228 return nil
229 }
230
231 func (t *TFIAMClient) SetAuditConfig(ctx context.Context, auditConfig *v1beta1.IAMAuditConfig) (*v1beta1.IAMAuditConfig, error) {
232 rc, err := t.getResourceConfigForReferencedResource(ctx, auditConfig)
233 if err != nil {
234 return nil, fmt.Errorf("error getting resource config for referenced resource: %w", err)
235 }
236 if !resourceSupportsIAMAuditConfigs(rc) {
237 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM audit configs", rc.Kind)
238 }
239 resource, err := t.newResource(ctx, auditConfig, rc)
240 if err != nil {
241 return nil, err
242 }
243 liveState, err := krmtotf.FetchLiveState(ctx, resource, t.provider, t.kubeClient, t.smLoader)
244 if err != nil {
245 return nil, fmt.Errorf("error fetching live state for resource: %w", err)
246 }
247 cfg, _, err := krmtotf.KRMResourceToTFResourceConfig(resource, t.kubeClient, t.smLoader)
248 if err != nil {
249 return nil, fmt.Errorf("error creating resource config: %w", err)
250 }
251 diff, err := resource.TFResource.Diff(ctx, liveState, cfg, t.provider.Meta())
252 if err != nil {
253 return nil, fmt.Errorf("error calculating diff: %w", err)
254 }
255 if !liveState.Empty() && diff.RequiresNew() {
256 return nil, k8s.NewImmutableFieldsMutationError(tfresource.ImmutableFieldsFromDiff(diff))
257 }
258 if diff.Empty() {
259 logger.Info("underlying resource is already up to date", "resource", k8s.GetNamespacedName(auditConfig))
260 return auditConfig, nil
261 }
262 newState, diagnostics := resource.TFResource.Apply(ctx, liveState, diff, t.provider.Meta())
263 if err := krmtotf.NewErrorFromDiagnostics(diagnostics); err != nil {
264 return nil, fmt.Errorf("error applying changes: %w", err)
265 }
266 return newIAMAuditConfigFromTFState(resource, newState, auditConfig)
267 }
268
269 func (t *TFIAMClient) GetAuditConfig(ctx context.Context, auditConfig *v1beta1.IAMAuditConfig) (*v1beta1.IAMAuditConfig, error) {
270 rc, err := t.getResourceConfigForReferencedResource(ctx, auditConfig)
271 if err != nil {
272 return nil, fmt.Errorf("error getting resource config for referenced resource: %w", err)
273 }
274 if !resourceSupportsIAMAuditConfigs(rc) {
275 return nil, fmt.Errorf("invalid resource reference: kind %v does not support IAM audit configs", rc.Kind)
276 }
277 resource, err := t.newResourceSkeleton(ctx, auditConfig, rc)
278 if err != nil {
279 return nil, fmt.Errorf("error building resource skeleton for getting IAM resource: %w", err)
280 }
281 liveState, err := krmtotf.FetchLiveState(ctx, resource, t.provider, t.kubeClient, t.smLoader)
282 if err != nil {
283 return nil, fmt.Errorf("error fetching live state for resource: %w", err)
284 }
285 if liveState.Empty() {
286 return nil, NotFoundError
287 }
288 return newIAMAuditConfigFromTFState(resource, liveState, auditConfig)
289 }
290
291 func (t *TFIAMClient) DeleteAuditConfig(ctx context.Context, auditConfig *v1beta1.IAMAuditConfig) error {
292 rc, err := t.getResourceConfigForReferencedResource(ctx, auditConfig)
293 if err != nil {
294 return fmt.Errorf("error getting resource config for referenced resource: %w", err)
295 }
296 if !resourceSupportsIAMAuditConfigs(rc) {
297 return fmt.Errorf("invalid resource reference: kind %v does not support IAM audit configs", rc.Kind)
298 }
299 resource, err := t.newResource(ctx, auditConfig, rc)
300 if err != nil {
301 return err
302 }
303 liveState, err := krmtotf.FetchLiveState(ctx, resource, t.provider, t.kubeClient, t.smLoader)
304 if err != nil {
305 return fmt.Errorf("error fetching live state for resource: %w", err)
306 }
307 if liveState.Empty() {
308 return NotFoundError
309 }
310 _, diagnostics := resource.TFResource.Apply(ctx, liveState, &terraform.InstanceDiff{Destroy: true}, t.provider.Meta())
311 if err := krmtotf.NewErrorFromDiagnostics(diagnostics); err != nil {
312 return fmt.Errorf("error deleting IAMAuditConfig: %w", err)
313 }
314 return nil
315 }
316
317
318 func (t *TFIAMClient) newResource(ctx context.Context, iamInterface interface{}, rc *corekccv1alpha1.ResourceConfig) (*krmtotf.Resource, error) {
319 SetGVK(iamInterface)
320 unstructSkeleton, err := t.newUnstructuredSkeleton(ctx, iamInterface, rc)
321 if err != nil {
322 return nil, fmt.Errorf("error building a minimal unstructured skeleton for the IAM resource: %w", err)
323 }
324
325
326 switch iamObject := iamInterface.(type) {
327 case *v1beta1.IAMPolicy:
328 krmPolicyUnstruct, err := k8s.MarshalObjectAsUnstructured(iamObject)
329 if err != nil {
330 return nil, err
331 }
332 spec := krmPolicyUnstruct.Object["spec"].(map[string]interface{})
333 policyDataMap := map[string]interface{}{
334 "bindings": spec["bindings"],
335 "auditConfigs": spec["auditConfigs"],
336 }
337 if iamObject.Spec.Etag != "" {
338 policyDataMap["etag"] = iamObject.Spec.Etag
339 }
340 b, err := json.Marshal(policyDataMap)
341 if err != nil {
342 return nil, fmt.Errorf("error marshalling policy data to JSON: %w", err)
343 }
344 unstructured.SetNestedField(unstructSkeleton.Object, string(b), "spec", "policyData")
345 case *v1beta1.IAMPolicyMember:
346
347
348 break
349 case *v1beta1.IAMAuditConfig:
350 auditLogConfigs := iamObject.Spec.AuditLogConfigs
351 var auditLogConfigSlice []interface{}
352 if err := util.Marshal(auditLogConfigs, &auditLogConfigSlice); err != nil {
353 return nil, fmt.Errorf("unable to marshal %v to slice: %w", reflect.TypeOf(auditLogConfigs).Name(), err)
354 }
355 unstructured.SetNestedSlice(unstructSkeleton.Object, auditLogConfigSlice, "spec", "auditLogConfig")
356 default:
357 panic(fmt.Errorf("unknown type: %v", reflect.TypeOf(iamInterface).Name()))
358 }
359
360 iamSM, err := t.newServiceMappingForAssociatedIAMInterface(ctx, iamInterface, rc.IAMConfig)
361 if err != nil {
362 return nil, fmt.Errorf("error building service mapping for IAM resource: %w", err)
363 }
364 return krmtotf.NewResource(unstructSkeleton, iamSM, t.provider)
365 }
366
367
368
369 func (t *TFIAMClient) newResourceSkeleton(ctx context.Context, iamInterface interface{}, rc *corekccv1alpha1.ResourceConfig) (*krmtotf.Resource, error) {
370 SetGVK(iamInterface)
371 unstructSkeleton, err := t.newUnstructuredSkeleton(ctx, iamInterface, rc)
372 if err != nil {
373 return nil, fmt.Errorf("error building unstructured skeleton for getting IAM resource: %w", err)
374 }
375 iamSM, err := t.newServiceMappingForAssociatedIAMInterface(ctx, iamInterface, rc.IAMConfig)
376 if err != nil {
377 return nil, fmt.Errorf("error building service mapping for IAM resource: %w", err)
378 }
379 return krmtotf.NewResource(unstructSkeleton, iamSM, t.provider)
380 }
381
382
383
384 func (t *TFIAMClient) newUnstructuredSkeleton(ctx context.Context, iamInterface interface{}, rc *corekccv1alpha1.ResourceConfig) (*unstructured.Unstructured, error) {
385 namespace, resourceRef := extractNamespaceAndResourceReference(iamInterface)
386 unstructSkeleton, err := t.buildUnstructuredIAMSkeletonFromReference(ctx, iamInterface, namespace, resourceRef, rc)
387 if err != nil {
388 return nil, fmt.Errorf("error building unstructured skeleton from referenced resource: %w", err)
389 }
390 switch iamObject := iamInterface.(type) {
391 case *v1beta1.IAMPolicy:
392
393
394 return unstructSkeleton, nil
395 case *v1beta1.IAMPolicyMember:
396
397 member, err := ResolveMemberIdentity(ctx, iamObject.Spec.Member, iamObject.Spec.MemberFrom, iamObject.Namespace, t)
398 if err != nil {
399 return nil, fmt.Errorf("could not resolve member identity for IAMPolicyMember: %w", err)
400 }
401 unstructured.SetNestedField(unstructSkeleton.Object, member, "spec", "member")
402 unstructured.SetNestedField(unstructSkeleton.Object, iamObject.Spec.Role, "spec", "role")
403 if iamObject.Spec.Condition != nil {
404 condition := iamObject.Spec.Condition
405 var conditionMap map[string]interface{}
406 if err := util.Marshal(condition, &conditionMap); err != nil {
407 return nil, fmt.Errorf("unable to marshal %v to map: %w", reflect.TypeOf(condition).Name(), err)
408 }
409 unstructured.SetNestedMap(unstructSkeleton.Object, conditionMap, "spec", "condition")
410 }
411 return unstructSkeleton, nil
412 case *v1beta1.IAMAuditConfig:
413
414 unstructured.SetNestedField(unstructSkeleton.Object, iamObject.Spec.Service, "spec", "service")
415 return unstructSkeleton, nil
416 default:
417 panic(fmt.Errorf("unknown type: %v", reflect.TypeOf(iamInterface).Name()))
418 }
419 }
420
421 func (t *TFIAMClient) buildUnstructuredIAMSkeletonFromReference(ctx context.Context, iamInterface interface{}, namespace string,
422 resourceRef v1beta1.ResourceReference, rc *corekccv1alpha1.ResourceConfig) (*unstructured.Unstructured, error) {
423 id, err := t.getResourceID(ctx, resourceRef, namespace)
424 if err != nil {
425 return nil, fmt.Errorf("couldn't get resource id for resource reference: %w", err)
426 }
427
428 iamObject := t.newIAMObjectFromInterface(iamInterface)
429 unstruct, err := k8s.MarshalObjectAsUnstructured(iamObject)
430 if err != nil {
431 return nil, err
432 }
433
434 if externalonlygvks.IsExternalOnlyGVK(resourceRef.GroupVersionKind()) {
435 return unstructuredIAMSkeletonForExternalOnlyRef(resourceRef, unstruct)
436 }
437
438 tfResourceName := rc.Name
439 tfInfo := &terraform.InstanceInfo{
440 Type: tfResourceName,
441 }
442 tfStateParsedFromId, err := krmtotf.ImportState(ctx, id, tfInfo, t.provider)
443 if err != nil {
444 return nil, fmt.Errorf("failed to import state given id %v: %w", id, err)
445 }
446
447 refFieldName := text.SnakeCaseToLowerCamelCase(rc.IAMConfig.ReferenceField.Name)
448 refFieldVal, err := getReferenceFieldValue(id, rc)
449 if err != nil {
450 return nil, fmt.Errorf("failed to get reference field value given id %v: %w", id, err)
451 }
452 spec := map[string]interface{}{
453 refFieldName: refFieldVal,
454 }
455 for k, v := range tfStateParsedFromId.Attributes {
456 spec[k] = v
457 }
458
459 unstruct.Object["spec"] = spec
460 return unstruct, nil
461 }
462
463 func (t *TFIAMClient) getResourceID(ctx context.Context, resourceRef v1beta1.ResourceReference, namespace string) (string, error) {
464 if resourceRef.External != "" {
465 return resourceRef.External, nil
466 }
467
468 nn := types.NamespacedName{
469 Namespace: useIfNonEmptyElseDefaultTo(resourceRef.Namespace, namespace),
470 Name: resourceRef.Name,
471 }
472 refResource, err := t.getResource(ctx, resourceRef.GroupVersionKind(), nn)
473 if err != nil {
474 return "", err
475 }
476 id, err := refResource.GetImportID(t.kubeClient, t.smLoader)
477 if err != nil {
478 return "", fmt.Errorf("error getting import ID for referenced resource: %w", err)
479 }
480 if id != "" {
481 return id, nil
482 }
483 return "", fmt.Errorf("couldn't construct id for referenced resource")
484 }
485
486 func (t *TFIAMClient) getResourceConfigForReferencedResource(ctx context.Context, iamInterface interface{}) (*corekccv1alpha1.ResourceConfig, error) {
487 namespace, resourceRef := extractNamespaceAndResourceReference(iamInterface)
488 if resourceRef.External != "" {
489 return t.getResourceConfigForExternalRef(resourceRef)
490 }
491 nn := types.NamespacedName{
492 Namespace: useIfNonEmptyElseDefaultTo(resourceRef.Namespace, namespace),
493 Name: resourceRef.Name,
494 }
495 refResource, err := t.getResource(ctx, resourceRef.GroupVersionKind(), nn)
496 if err != nil {
497 return nil, err
498 }
499 return &refResource.ResourceConfig, nil
500 }
501
502 func (t *TFIAMClient) getResourceConfigForExternalRef(resourceRef v1beta1.ResourceReference) (*corekccv1alpha1.ResourceConfig, error) {
503 if resourceRef.External == "" {
504 return nil, fmt.Errorf("external field is empty")
505 }
506 gvk := resourceRef.GroupVersionKind()
507 if externalonlygvks.IsExternalOnlyGVK(gvk) {
508 return GetResourceConfigForExternalOnlyGVK(gvk)
509 }
510 sm, err := t.smLoader.GetServiceMapping(gvk.Group)
511 if err != nil {
512 return nil, fmt.Errorf("failed to load ServiceMapping for GroupVersionKind %v: %w", gvk, err)
513 }
514 rcs := servicemappingloader.GetResourceConfigsForKind(sm, gvk.Kind)
515 switch len(rcs) {
516 case 0:
517 return nil, fmt.Errorf("couldn't find any ResourceConfig defined for GroupVersionKind %v", gvk)
518 case 1:
519 return rcs[0], nil
520
521
522
523
524
525 default:
526 id := resourceRef.External
527 for _, rc := range rcs {
528 if idMatchesTemplate(id, rc.IDTemplate) {
529 return rc, nil
530 }
531 }
532 return nil, fmt.Errorf("no ResourceConfig found for GroupVersionKind %v with an IDTemplate that matches the id %v", gvk, id)
533 }
534 }
535
536 func (t *TFIAMClient) getResource(ctx context.Context, gvk schema.GroupVersionKind, nn types.NamespacedName) (*krmtotf.Resource, error) {
537 if nn.Name == "" {
538 return t.getResourceForHeadlessKind(ctx, gvk, nn)
539 }
540 u := &unstructured.Unstructured{}
541 u.SetGroupVersionKind(gvk)
542 if err := t.kubeClient.Get(ctx, nn, u); err != nil {
543 if errors.IsNotFound(err) {
544 return nil, k8s.NewReferenceNotFoundError(gvk, nn)
545 }
546 return nil, fmt.Errorf("error retrieving resource '%v' with GroupVersionKind '%v': %w", nn, gvk, err)
547 }
548 sm, err := t.smLoader.GetServiceMapping(gvk.Group)
549 if err != nil {
550 return nil, err
551 }
552 resource, err := krmtotf.NewResource(u, sm, t.provider)
553 if err != nil {
554 return nil, fmt.Errorf("error unmarshalling '%v' to resource: %w", nn, err)
555 }
556 if !k8s.IsResourceReady(&resource.Resource) {
557 return nil, k8s.NewReferenceNotReadyErrorForResource(&resource.Resource)
558 }
559 return resource, nil
560 }
561
562 func (t *TFIAMClient) getResourceForHeadlessKind(ctx context.Context, gvk schema.GroupVersionKind, nn types.NamespacedName) (*krmtotf.Resource, error) {
563 switch gvk.Kind {
564 case ProjectKind:
565 return t.getProjectResource(ctx, gvk, nn)
566 default:
567 return nil, fmt.Errorf("unrecognized IAM kind '%v'", gvk.Kind)
568 }
569 }
570
571 func (t *TFIAMClient) getProjectResource(ctx context.Context, gvk schema.GroupVersionKind, nn types.NamespacedName) (*krmtotf.Resource, error) {
572 projectID, err := k8s.GetProjectIDForNamespace(t.kubeClient, ctx, nn.Namespace)
573 if err != nil {
574 return nil, fmt.Errorf("error getting project ID for namespace: %w", err)
575 }
576 u := unstructured.Unstructured{
577 Object: map[string]interface{}{
578 "kind": ProjectKind,
579 "metadata": map[string]interface{}{
580 "name": projectID,
581 },
582 },
583 }
584 sm, err := t.smLoader.GetServiceMapping(ResourceManagerGroup)
585 if err != nil {
586 return nil, fmt.Errorf("error getting service mapping for kind 'Project': %w", err)
587 }
588 resource, err := krmtotf.NewResource(&u, sm, t.provider)
589 if err != nil {
590 return nil, fmt.Errorf("error unmarshalling '%v' to resource: %w", nn, err)
591 }
592 return resource, nil
593 }
594
595 func (t *TFIAMClient) resolveMemberReference(ctx context.Context, ref *v1beta1.MemberReference,
596 gvk schema.GroupVersionKind, resourceNamespace string) (string, error) {
597
598 nn := types.NamespacedName{
599 Namespace: useIfNonEmptyElseDefaultTo(ref.Namespace, resourceNamespace),
600 Name: ref.Name,
601 }
602 refResource, err := t.getResource(ctx, gvk, nn)
603 if err != nil {
604 return "", err
605 }
606 memberRefConfig := refResource.ResourceConfig.IAMMemberReferenceConfig
607 val, err := resolveTargetFieldValue(refResource, memberRefConfig.TargetField)
608 if err != nil {
609 return "", err
610 }
611 if memberRefConfig.ValueTemplate == "" {
612 return val, nil
613 }
614 return krmtotf.ResolveValueTemplate(memberRefConfig.ValueTemplate, val, refResource, t.kubeClient, t.smLoader)
615 }
616
617 func (t *TFIAMClient) newIAMObjectFromInterface(iamInterface interface{}) metav1.Object {
618 switch iamInterface.(type) {
619 case *v1beta1.IAMPolicy:
620 return &v1beta1.IAMPolicy{
621 TypeMeta: metav1.TypeMeta{
622 APIVersion: v1beta1.IAMPolicyGVK.GroupVersion().String(),
623 Kind: v1beta1.IAMPolicyGVK.Kind,
624 },
625 }
626 case *v1beta1.IAMPolicyMember:
627 return &v1beta1.IAMPolicyMember{
628 TypeMeta: metav1.TypeMeta{
629 APIVersion: v1beta1.IAMPolicyMemberGVK.GroupVersion().String(),
630 Kind: v1beta1.IAMPolicyMemberGVK.Kind,
631 },
632 }
633 case *v1beta1.IAMAuditConfig:
634 return &v1beta1.IAMAuditConfig{
635 TypeMeta: metav1.TypeMeta{
636 APIVersion: v1beta1.IAMAuditConfigGVK.GroupVersion().String(),
637 Kind: v1beta1.IAMAuditConfigGVK.Kind,
638 },
639 }
640 }
641 panic(fmt.Errorf("unknown type: %v", reflect.TypeOf(iamInterface).Name()))
642 }
643
644 func (t *TFIAMClient) newServiceMappingForAssociatedIAMInterface(ctx context.Context, iamInterface interface{}, iamConfig corekccv1alpha1.IAMConfig) (*corekccv1alpha1.ServiceMapping, error) {
645 switch iamInterface.(type) {
646 case *v1beta1.IAMPolicy:
647 return newServiceMappingForGVKAndTFResourceName(v1beta1.IAMPolicyGVK, iamConfig.PolicyName), nil
648 case *v1beta1.IAMPolicyMember:
649 return newServiceMappingForGVKAndTFResourceName(v1beta1.IAMPolicyMemberGVK, iamConfig.PolicyMemberName), nil
650 case *v1beta1.IAMAuditConfig:
651 return newServiceMappingForGVKAndTFResourceName(v1beta1.IAMAuditConfigGVK, iamConfig.AuditConfigName), nil
652 }
653 panic(fmt.Errorf("unknown type: %v", reflect.TypeOf(iamInterface).Name()))
654 }
655
656 func getReferenceFieldValue(id string, rc *corekccv1alpha1.ResourceConfig) (string, error) {
657 switch rc.IAMConfig.ReferenceField.Type {
658 case corekccv1alpha1.IAMReferenceTypeId:
659 return id, nil
660 case corekccv1alpha1.IAMReferenceTypeName:
661 return parseNameFromId(id)
662 default:
663 panic(fmt.Errorf("unknown value type: %v", rc.IAMConfig.ReferenceField.Type))
664 }
665 }
666
667 func resolveTargetFieldValue(r *krmtotf.Resource, targetField string) (string, error) {
668 key := text.SnakeCaseToLowerCamelCase(targetField)
669 switch key {
670 case "":
671 panic(fmt.Errorf("empty target field specified"))
672 default:
673 if val, exists, _ := unstructured.NestedString(r.Spec, strings.Split(key, ".")...); exists {
674 return val, nil
675 }
676 if val, exists, _ := unstructured.NestedString(r.Status, strings.Split(key, ".")...); exists {
677 return val, nil
678 }
679 return "", fmt.Errorf("couldn't resolve the value for target field %v from the referenced resource %v", targetField, r.GetNamespacedName())
680 }
681 }
682
683 func newIAMPolicyFromTFState(resource *krmtotf.Resource, state *terraform.InstanceState, origPolicy *v1beta1.IAMPolicy) (*v1beta1.IAMPolicy, error) {
684 resource.Spec, resource.Status = krmtotf.GetSpecAndStatusFromState(resource, state)
685 if err := embedPolicyData(resource.Spec); err != nil {
686 return nil, err
687 }
688 u, err := resource.MarshalAsUnstructured()
689 if err != nil {
690 return nil, fmt.Errorf("error marshalling resource to unstructured: %w", err)
691 }
692 iamPolicy := v1beta1.IAMPolicy{}
693 if err := util.Marshal(u, &iamPolicy); err != nil {
694 return nil, fmt.Errorf("error marshalling unstructured to iampolicy: %w", err)
695 }
696 iamPolicy.Spec.ResourceReference = origPolicy.Spec.ResourceReference
697 iamPolicy.ObjectMeta = origPolicy.ObjectMeta
698 etag := krmtotf.GetEtagFromState(resource, state)
699 if etag == "" {
700 return nil, fmt.Errorf("unexpected empty etag read from the IAM Policy resource")
701 }
702 iamPolicy.Spec.Etag = etag
703 return &iamPolicy, nil
704 }
705
706 func newIAMPolicyMemberFromTFState(resource *krmtotf.Resource, state *terraform.InstanceState, origPolicyMember *v1beta1.IAMPolicyMember) (*v1beta1.IAMPolicyMember, error) {
707 resource.Spec, resource.Status = krmtotf.GetSpecAndStatusFromState(resource, state)
708 u, err := resource.MarshalAsUnstructured()
709 if err != nil {
710 return nil, fmt.Errorf("error marshalling resource to unstructured: %w", err)
711 }
712 iamPolicyMember := v1beta1.IAMPolicyMember{}
713 if err := util.Marshal(u, &iamPolicyMember); err != nil {
714 return nil, fmt.Errorf("error marshalling unstructured to IAMPolicyMember: %w", err)
715 }
716 if origPolicyMember.Spec.Member == "" {
717 iamPolicyMember.Spec.Member = ""
718 iamPolicyMember.Spec.MemberFrom = origPolicyMember.Spec.MemberFrom
719 }
720 iamPolicyMember.Spec.ResourceReference = origPolicyMember.Spec.ResourceReference
721 iamPolicyMember.ObjectMeta = origPolicyMember.ObjectMeta
722 return &iamPolicyMember, nil
723 }
724
725 func newIAMAuditConfigFromTFState(resource *krmtotf.Resource, state *terraform.InstanceState, origAuditConfig *v1beta1.IAMAuditConfig) (*v1beta1.IAMAuditConfig, error) {
726 resource.Spec, resource.Status = krmtotf.GetSpecAndStatusFromState(resource, state)
727 if auditLogConfigs, ok := resource.Spec["auditLogConfig"]; ok {
728 resource.Spec["auditLogConfigs"] = auditLogConfigs
729 delete(resource.Spec, "auditLogConfig")
730 }
731 u, err := resource.MarshalAsUnstructured()
732 if err != nil {
733 return nil, fmt.Errorf("error marshalling resource to unstructured: %w", err)
734 }
735 iamAuditConfig := v1beta1.IAMAuditConfig{}
736 if err := util.Marshal(u, &iamAuditConfig); err != nil {
737 return nil, fmt.Errorf("error marshalling unstructured to IAMAuditConfig: %w", err)
738 }
739 iamAuditConfig.Spec.ResourceReference = origAuditConfig.Spec.ResourceReference
740 iamAuditConfig.ObjectMeta = origAuditConfig.ObjectMeta
741 return &iamAuditConfig, nil
742 }
743
744 func newServiceMappingForGVKAndTFResourceName(iamGVK schema.GroupVersionKind, tfResourceName string) *corekccv1alpha1.
745 ServiceMapping {
746 return &corekccv1alpha1.ServiceMapping{
747 TypeMeta: metav1.TypeMeta{
748 APIVersion: iamGVK.GroupVersion().String(),
749 Kind: reflect.TypeOf(corekccv1alpha1.ServiceMapping{}).Name(),
750 },
751 ObjectMeta: metav1.ObjectMeta{},
752 Spec: corekccv1alpha1.ServiceMappingSpec{
753 Name: iamGVK.Kind,
754 Version: iamGVK.Version,
755 Resources: []corekccv1alpha1.ResourceConfig{
756 {
757 Name: tfResourceName,
758 Kind: iamGVK.Kind,
759 SkipImport: true,
760 ResourceReferences: []corekccv1alpha1.ReferenceConfig{
761 {
762 TypeConfig: corekccv1alpha1.TypeConfig{
763 Key: "resourceRef",
764 },
765 },
766 },
767 },
768 },
769 },
770 }
771 }
772
View as plain text