From bec99c812ee33636cbb84078adc29ffb079a1d6c Mon Sep 17 00:00:00 2001 From: Joyce Ma Date: Tue, 26 Mar 2024 03:57:40 +0000 Subject: [PATCH 1/2] Unexport 'status' field in K8s.Resource --- pkg/cli/gcpclient/client.go | 4 +- pkg/controller/dcl/controller.go | 11 +- .../directbase/directbase_controller.go | 8 + .../auditconfig/iamauditconfig_controller.go | 5 + pkg/controller/iam/iamclient/tfiamclient.go | 12 +- .../iampartialpolicy_controller.go | 5 + .../iam/policy/iampolicy_controller.go | 5 + .../iampolicymember_controller.go | 5 + pkg/controller/lifecyclehandler/handler.go | 16 +- pkg/controller/tf/controller.go | 4 +- pkg/dcl/kcclite/conversion.go | 6 +- pkg/dcl/kcclite/conversion_test.go | 4247 +++++++++-------- pkg/dcl/kcclite/templating.go | 2 +- pkg/k8s/references.go | 8 + pkg/k8s/resource.go | 25 +- pkg/k8s/resource_test.go | 56 +- pkg/krmtotf/fetchlivestate_test.go | 98 +- pkg/krmtotf/references.go | 8 + pkg/krmtotf/resource.go | 6 +- pkg/krmtotf/resource_test.go | 22 +- pkg/krmtotf/tftokrm.go | 4 +- pkg/krmtotf/tftokrm_test.go | 8 +- pkg/lease/leaser/leaser.go | 5 +- pkg/resourceoverrides/redis_instance.go | 4 +- pkg/resourceoverrides/sql_instance.go | 2 +- pkg/resourceskeleton/resourceskeleton.go | 4 +- pkg/test/resourcefixture/contexts/register.go | 6 +- 27 files changed, 2339 insertions(+), 2247 deletions(-) diff --git a/pkg/cli/gcpclient/client.go b/pkg/cli/gcpclient/client.go index c018b0f540..46d992ef99 100644 --- a/pkg/cli/gcpclient/client.go +++ b/pkg/cli/gcpclient/client.go @@ -149,7 +149,9 @@ func updateResourceAndNewUnstructuredFromState(resource *krmtotf.Resource, state resource.Name = krmtotf.GetNameFromState(resource, state) resource.Labels = krmtotf.GetLabelsFromState(resource, state) resource.Annotations = krmtotf.GetAnnotationsFromState(resource, state) - resource.Spec, resource.Status = krmtotf.ResolveSpecAndStatusWithResourceID(resource, state) + spec, status := krmtotf.ResolveSpecAndStatusWithResourceID(resource, state) + resource.Spec = spec + resource.SetStatus(status) return resource.MarshalAsUnstructured() } diff --git a/pkg/controller/dcl/controller.go b/pkg/controller/dcl/controller.go index 112b2905d1..8fcdf57b66 100644 --- a/pkg/controller/dcl/controller.go +++ b/pkg/controller/dcl/controller.go @@ -489,7 +489,8 @@ func (r *Reconciler) updateSpecAndStatusWithLiveState(ctx context.Context, liveL if err != nil { return false, r.HandleUpdateFailed(ctx, &resource.Resource, fmt.Errorf("error resolving the live state: %w", err)) } - resource.Spec, resource.Status = newSpec, newStatus + resource.Spec = newSpec + resource.SetStatus(newStatus) if err := updateMutableButUnreadableFieldsAnnotationFor(resource); err != nil { return false, err @@ -532,6 +533,14 @@ func (r *Reconciler) constructDesiredStateWithManagedFields(original *dcl.Resour if err := util.Marshal(u, res); err != nil { return nil, err } + statusObj := u.Object["status"] + if statusObj != nil { + statusObjInMap, ok := statusObj.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("expected status value to be map[string]interface{} GroupVersionKind %v but was actually %T", gvk, statusObj) + } + res.SetStatus(statusObjInMap) + } if val, ok := original.Spec[k8s.ResourceIDFieldName]; ok { if res.Spec == nil { res.Spec = make(map[string]interface{}) diff --git a/pkg/controller/direct/directbase/directbase_controller.go b/pkg/controller/direct/directbase/directbase_controller.go index c93f065813..017fb77c11 100644 --- a/pkg/controller/direct/directbase/directbase_controller.go +++ b/pkg/controller/direct/directbase/directbase_controller.go @@ -414,5 +414,13 @@ func toK8sResource(policy *unstructured.Unstructured) (*k8s.Resource, error) { if err := util.Marshal(policy, &resource); err != nil { return nil, fmt.Errorf("error marshalling to k8s resource: %w", err) } + statusObj := policy.Object["status"] + if statusObj != nil { + statusObjInMap, ok := statusObj.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("expected status value to be map[string]interface{} but was actually %T", statusObj) + } + resource.SetStatus(statusObjInMap) + } return &resource, nil } diff --git a/pkg/controller/iam/auditconfig/iamauditconfig_controller.go b/pkg/controller/iam/auditconfig/iamauditconfig_controller.go index 73864ec15a..54adf516ff 100644 --- a/pkg/controller/iam/auditconfig/iamauditconfig_controller.go +++ b/pkg/controller/iam/auditconfig/iamauditconfig_controller.go @@ -393,5 +393,10 @@ func ToK8sResource(auditConfig *iamv1beta1.IAMAuditConfig) (*k8s.Resource, error if err := util.Marshal(auditConfig, &resource); err != nil { return nil, fmt.Errorf("error marshalling IAMAuditConfig to k8s resource: %w", err) } + statusObjInMap := make(map[string]interface{}) + if err := util.Marshal(auditConfig.Status, &statusObjInMap); err != nil { + return nil, fmt.Errorf("error marshalling IAMAuditConfig.Status to map[string]interface{}: %w", err) + } + resource.SetStatus(statusObjInMap) return &resource, nil } diff --git a/pkg/controller/iam/iamclient/tfiamclient.go b/pkg/controller/iam/iamclient/tfiamclient.go index 08b9a7a9d0..d9bd0752ff 100644 --- a/pkg/controller/iam/iamclient/tfiamclient.go +++ b/pkg/controller/iam/iamclient/tfiamclient.go @@ -693,7 +693,9 @@ func resolveTargetFieldValue(r *krmtotf.Resource, targetField string) (string, e } func newIAMPolicyFromTFState(resource *krmtotf.Resource, state *terraform.InstanceState, origPolicy *v1beta1.IAMPolicy) (*v1beta1.IAMPolicy, error) { - resource.Spec, resource.Status = krmtotf.GetSpecAndStatusFromState(resource, state) + spec, status := krmtotf.GetSpecAndStatusFromState(resource, state) + resource.Spec = spec + resource.SetStatus(status) if err := embedPolicyData(resource.Spec); err != nil { return nil, err } @@ -716,7 +718,9 @@ func newIAMPolicyFromTFState(resource *krmtotf.Resource, state *terraform.Instan } func newIAMPolicyMemberFromTFState(resource *krmtotf.Resource, state *terraform.InstanceState, origPolicyMember *v1beta1.IAMPolicyMember) (*v1beta1.IAMPolicyMember, error) { - resource.Spec, resource.Status = krmtotf.GetSpecAndStatusFromState(resource, state) + spec, status := krmtotf.GetSpecAndStatusFromState(resource, state) + resource.Spec = spec + resource.SetStatus(status) u, err := resource.MarshalAsUnstructured() if err != nil { return nil, fmt.Errorf("error marshalling resource to unstructured: %w", err) @@ -735,7 +739,9 @@ func newIAMPolicyMemberFromTFState(resource *krmtotf.Resource, state *terraform. } func newIAMAuditConfigFromTFState(resource *krmtotf.Resource, state *terraform.InstanceState, origAuditConfig *v1beta1.IAMAuditConfig) (*v1beta1.IAMAuditConfig, error) { - resource.Spec, resource.Status = krmtotf.GetSpecAndStatusFromState(resource, state) + spec, status := krmtotf.GetSpecAndStatusFromState(resource, state) + resource.Spec = spec + resource.SetStatus(status) if auditLogConfigs, ok := resource.Spec["auditLogConfig"]; ok { resource.Spec["auditLogConfigs"] = auditLogConfigs delete(resource.Spec, "auditLogConfig") diff --git a/pkg/controller/iam/partialpolicy/iampartialpolicy_controller.go b/pkg/controller/iam/partialpolicy/iampartialpolicy_controller.go index 38bffd5c21..567d48aad9 100644 --- a/pkg/controller/iam/partialpolicy/iampartialpolicy_controller.go +++ b/pkg/controller/iam/partialpolicy/iampartialpolicy_controller.go @@ -440,6 +440,11 @@ func toK8sResource(policy *iamv1beta1.IAMPartialPolicy) (*k8s.Resource, error) { if err := util.Marshal(policy, &resource); err != nil { return nil, fmt.Errorf("error marshalling IAMPartialPolicy to k8s resource: %w", err) } + statusObjInMap := make(map[string]interface{}) + if err := util.Marshal(policy.Status, &statusObjInMap); err != nil { + return nil, fmt.Errorf("error marshalling IAMPartialPolicy.Status to map[string]interface{}: %w", err) + } + resource.SetStatus(statusObjInMap) return &resource, nil } diff --git a/pkg/controller/iam/policy/iampolicy_controller.go b/pkg/controller/iam/policy/iampolicy_controller.go index c183c64e72..009e4c2392 100644 --- a/pkg/controller/iam/policy/iampolicy_controller.go +++ b/pkg/controller/iam/policy/iampolicy_controller.go @@ -393,5 +393,10 @@ func toK8sResource(policy *iamv1beta1.IAMPolicy) (*k8s.Resource, error) { if err := util.Marshal(policy, &resource); err != nil { return nil, fmt.Errorf("error marshalling IAMPolicy to k8s resource: %w", err) } + statusObjInMap := make(map[string]interface{}) + if err := util.Marshal(policy.Status, &statusObjInMap); err != nil { + return nil, fmt.Errorf("error marshalling IAMPolicy.Status to map[string]interface{}: %w", err) + } + resource.SetStatus(statusObjInMap) return &resource, nil } diff --git a/pkg/controller/iam/policymember/iampolicymember_controller.go b/pkg/controller/iam/policymember/iampolicymember_controller.go index d5d89d1697..becaad19ad 100644 --- a/pkg/controller/iam/policymember/iampolicymember_controller.go +++ b/pkg/controller/iam/policymember/iampolicymember_controller.go @@ -406,5 +406,10 @@ func ToK8sResource(policyMember *iamv1beta1.IAMPolicyMember) (*k8s.Resource, err if err := util.Marshal(policyMember, &resource); err != nil { return nil, fmt.Errorf("error marshalling IAMPolicyMember to k8s resource: %w", err) } + statusObjInMap := make(map[string]interface{}) + if err := util.Marshal(policyMember.Status, &statusObjInMap); err != nil { + return nil, fmt.Errorf("error marshalling IAMPolicyMember.Status to map[string]interface{}: %w", err) + } + resource.SetStatus(statusObjInMap) return &resource, nil } diff --git a/pkg/controller/lifecyclehandler/handler.go b/pkg/controller/lifecyclehandler/handler.go index 949d85c530..eef67c6b7d 100644 --- a/pkg/controller/lifecyclehandler/handler.go +++ b/pkg/controller/lifecyclehandler/handler.go @@ -85,7 +85,7 @@ func (r *LifecycleHandler) updateStatus(ctx context.Context, resource *k8s.Resou func (r *LifecycleHandler) updateAPIServer(ctx context.Context, resource *k8s.Resource) error { // Preserve the intended status, as the client.Update call will ignore the given status // and return the stale existing status. - status := deepcopy.MapStringInterface(resource.Status) + status := deepcopy.MapStringInterface(resource.GetStatus()) // Get the current generation as the observed generation because the following client.Update // might increase the generation. We want the next reconciliation to handle the new generation. observedGeneration := resource.GetGeneration() @@ -114,7 +114,7 @@ func (r *LifecycleHandler) updateAPIServer(ctx context.Context, resource *k8s.Re // Status updates for successful deletions must be handled independently. return nil } - resource.Status = status + resource.SetStatus(status) setObservedGeneration(resource, observedGeneration) return r.updateStatus(ctx, resource) } @@ -367,8 +367,8 @@ func (r *LifecycleHandler) HandleUnmanaged(ctx context.Context, resource *k8s.Re } func setCondition(resource *k8s.Resource, status corev1.ConditionStatus, reason, msg string) { - if resource.Status == nil { - resource.Status = make(map[string]interface{}) + if resource.GetStatus() == nil { + resource.SetStatus(make(map[string]interface{})) } newReadyCondition := k8s.NewCustomReadyCondition(status, reason, msg) // We should only update the ready condition's last transition time if there was a transition @@ -379,14 +379,14 @@ func setCondition(resource *k8s.Resource, status corev1.ConditionStatus, reason, newReadyCondition.LastTransitionTime = currentReadyCondition.LastTransitionTime } } - resource.Status["conditions"] = []k8sv1alpha1.Condition{newReadyCondition} + resource.GetStatus()["conditions"] = []k8sv1alpha1.Condition{newReadyCondition} } func setObservedGeneration(resource *k8s.Resource, observedGeneration int64) { - if resource.Status == nil { - resource.Status = make(map[string]interface{}) + if resource.GetStatus() == nil { + resource.SetStatus(make(map[string]interface{})) } - resource.Status["observedGeneration"] = observedGeneration + resource.GetStatus()["observedGeneration"] = observedGeneration } func (r *LifecycleHandler) recordEvent(ctx context.Context, resource *k8s.Resource, eventtype, reason, message string) { diff --git a/pkg/controller/tf/controller.go b/pkg/controller/tf/controller.go index b56037d405..f9213f939e 100644 --- a/pkg/controller/tf/controller.go +++ b/pkg/controller/tf/controller.go @@ -494,7 +494,9 @@ func (r *Reconciler) handleDeleted(ctx context.Context, resource *krmtotf.Resour } func (r *Reconciler) handleUpToDate(ctx context.Context, resource *krmtotf.Resource, liveState *terraform.InstanceState, secretVersions map[string]string) error { - resource.Spec, resource.Status = krmtotf.ResolveSpecAndStatusWithResourceID(resource, liveState) + spec, status := krmtotf.ResolveSpecAndStatusWithResourceID(resource, liveState) + resource.Spec = spec + resource.SetStatus(status) if err := updateMutableButUnreadableFieldsAnnotationFor(resource); err != nil { return err } diff --git a/pkg/dcl/kcclite/conversion.go b/pkg/dcl/kcclite/conversion.go index 64547e6a3e..2211a51f58 100644 --- a/pkg/dcl/kcclite/conversion.go +++ b/pkg/dcl/kcclite/conversion.go @@ -455,7 +455,7 @@ func resolveTargetFieldValue(refResource *k8s.Resource, typeConfig *corekccv1alp } return val, nil } - if val, exist, _ := unstructured.NestedString(refResource.Status, strings.Split(typeConfig.TargetField, ".")...); exist { + if val, exist, _ := unstructured.NestedString(refResource.GetStatus(), strings.Split(typeConfig.TargetField, ".")...); exist { return val, nil } if val, exist, _ := unstructured.NestedString(refResource.Spec, strings.Split(typeConfig.TargetField, ".")...); exist { @@ -507,7 +507,7 @@ func resolveMixedSpecAndLegacyStatus(state *unstructured.Unstructured, resource if !found { status = make(map[string]interface{}) } - conditions, found, err := unstructured.NestedFieldCopy(resource.Status, "conditions") + conditions, found, err := unstructured.NestedFieldCopy(resource.GetStatus(), "conditions") if err != nil { return nil, nil, fmt.Errorf("error resolving conditions from resource status: %w", err) } @@ -515,7 +515,7 @@ func resolveMixedSpecAndLegacyStatus(state *unstructured.Unstructured, resource status["conditions"] = conditions } // preserve the observedGeneration value - g, found, err := unstructured.NestedFieldCopy(resource.Status, "observedGeneration") + g, found, err := unstructured.NestedFieldCopy(resource.GetStatus(), "observedGeneration") if err != nil { return nil, nil, fmt.Errorf("error resolving observedGeneration from resource status: %w", err) } diff --git a/pkg/dcl/kcclite/conversion_test.go b/pkg/dcl/kcclite/conversion_test.go index 188a496f48..95079de769 100644 --- a/pkg/dcl/kcclite/conversion_test.go +++ b/pkg/dcl/kcclite/conversion_test.go @@ -24,7 +24,8 @@ import ( "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test" testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller" testdclschemaloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/dclschemaloader" - testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s" + + //testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s" testmain "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/main" testvariable "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture/variable" testservicemappingloader "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemappingloader" @@ -1720,2129 +1721,2129 @@ func TestToKCCLiteForHierarchicalReferences(t *testing.T) { } } -func TestResolveSpecAndStatusWithMixedSpecAndLegacyStatus(t *testing.T) { - tests := []struct { - name string - state *unstructured.Unstructured - dclResource *dcl.Resource - expectedSpec map[string]interface{} - expectedStatus map[string]interface{} - }{ - { - name: "primitives are set", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": "1", - "floatKey": "0.5", - "stringKey": "StringVal", - "boolKey": false, - }, - }, - }, - dclResource: &dcl.Resource{ - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": "1", - "floatKey": "0.5", - "stringKey": "StringVal", - "boolKey": false, - }, - }, - { - name: "status fields are set from state", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "status": map[string]interface{}{ - "statusField": "statusVal1", - "nestedObjectKey": map[string]interface{}{ - "nestedStatusField": "statusVal2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Status: map[string]interface{}{ - "statusField": "statusVal2", - }, - }, - Schema: testSchema(), - }, - expectedStatus: map[string]interface{}{ - "statusField": "statusVal1", - "nestedObjectKey": map[string]interface{}{ - "nestedStatusField": "statusVal2", - }, - }, - }, - { - name: "both spec and status fields are present", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": "1", - "floatKey": "0.5", - "stringKey": "StringVal", - "boolKey": false, - }, - "status": map[string]interface{}{ - "statusField": "statusVal1", - "nestedObjectKey": map[string]interface{}{ - "nestedStatusField": "statusVal2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Status: map[string]interface{}{ - "statusField": "statusVal2", - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": "1", - "floatKey": "0.5", - "stringKey": "StringVal", - "boolKey": false, - }, - expectedStatus: map[string]interface{}{ - "statusField": "statusVal1", - "nestedObjectKey": map[string]interface{}{ - "nestedStatusField": "statusVal2", - }, - }, - }, - { - name: "lists of objects are set", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "field2": "strval1", - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval3", - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "field2": "strval1", - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval3", - }, - }, - }, - }, - { - name: "lists of objects are merged with defaulted fields", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "field2": "strval1", - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - }, - map[string]interface{}{ - "field1": 0.9, - "field2": "strval3", - }, - map[string]interface{}{ - "field1": 1.2, - "field2": "strval4", - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - }, - map[string]interface{}{ - "field1": 0.7, - }, - map[string]interface{}{ - "field1": 0.9, - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "field2": "strval1", - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - }, - map[string]interface{}{ - "field1": 0.9, - "field2": "strval3", - }, - map[string]interface{}{ - "field1": 1.2, - "field2": "strval4", - }, - }, - }, - }, - { - name: "nested objects are set", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - "nestedField2": "strval2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - "nestedField2": "strval2", - }, - }, - }, - { - name: "nested objects are merged with defaulted values", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - "nestedField2": "strval2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - "nestedField2": "strval2", - }, - }, - }, - { - name: "individual resource references are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "referenceKeyRef": map[string]interface{}{ - "external": "my-ref1", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "referenceKeyRef": map[string]interface{}{ - "name": "my-ref1", - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "referenceKeyRef": map[string]interface{}{ - "name": "my-ref1", - }, - }, - }, - { - name: "string-object maps are merged", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "stringObjectMapKey": map[string]interface{}{ - "someKey": map[string]interface{}{ - "objectField1": 1.0, - "state": "state1", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "external": "projects/my-project-1/bars/my-ref1", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - "someOtherKey": map[string]interface{}{ - "objectField1": 2.0, - "state": "state2", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "external": "projects/my-project-1/bars/my-ref1", - }, - map[string]interface{}{ - "external": "projects/my-project-1/bars/my-ref2", - }, - }, - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "stringObjectMapKey": map[string]interface{}{ - "someKey": map[string]interface{}{ - "objectField1": 1.0, - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - "someOtherKey": map[string]interface{}{ - "objectField1": 3.0, - "state": "state2", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "name": "my-ref2", - }, - }, - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "stringObjectMapKey": map[string]interface{}{ - "someKey": map[string]interface{}{ - "objectField1": 1.0, - "state": "state1", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - "someOtherKey": map[string]interface{}{ - "objectField1": 3.0, - "state": "state2", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "name": "my-ref2", - }, - }, - }, - }, - }, - }, - { - name: "lists of resource references are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "referenceArrayKey": []interface{}{ - map[string]interface{}{ - "external": "my-ref1", - }, - map[string]interface{}{ - "external": "my-ref2", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "referenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "name": "my-ref2", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "referenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "name": "my-ref2", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - }, - { - name: "resource references nested in lists of objects are preserved, default values are added", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "field2": "strval1", - "barRef": map[string]interface{}{ - "external": "my-ref1", - }, - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - "barRef": map[string]interface{}{ - "external": "my-ref2", - }, - }, - map[string]interface{}{ - "field1": 0.9, - "field2": "strval3", - "barRef": map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field2": "strval1", - "barRef": map[string]interface{}{ - "name": "my-ref1", - }, - }, - map[string]interface{}{ - "field2": "strval2", - "barRef": map[string]interface{}{ - "name": "my-ref2", - }, - }, - map[string]interface{}{ - "field2": "strval3", - "barRef": map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "field2": "strval1", - "barRef": map[string]interface{}{ - "name": "my-ref1", - }, - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - "barRef": map[string]interface{}{ - "name": "my-ref2", - }, - }, - map[string]interface{}{ - "field1": 0.9, - "field2": "strval3", - "barRef": map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - }, - }, - { - name: "external resource reference set if no reference defined by spec", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "referenceKeyRef": map[string]interface{}{ - "external": "my-ref1", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "referenceKeyRef": map[string]interface{}{ - "external": "my-ref1", - }, - }, - }, - { - name: "list of external resource references set if no list defined by spec", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "referenceArrayKey": []interface{}{ - map[string]interface{}{ - "external": "my-ref1", - }, - map[string]interface{}{ - "external": "my-ref2", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "referenceArrayKey": []interface{}{ - map[string]interface{}{ - "external": "my-ref1", - }, - map[string]interface{}{ - "external": "my-ref2", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - }, - { - name: "hierarchical references for single-parent resources are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "projectRef": map[string]interface{}{ - "external": "project_id", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "projectRef": map[string]interface{}{ - "name": "project-name", - }, - }, - }, - Schema: &openapi.Schema{ - Type: "object", - Properties: map[string]*openapi.Schema{ - "project": { - Type: "string", - Extension: map[string]interface{}{ - "x-dcl-references": []interface{}{ - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Project", - }, - }, - }, - }, - }, - }, - }, - expectedSpec: map[string]interface{}{ - "projectRef": map[string]interface{}{ - "name": "project-name", - }, - }, - }, - { - name: "external hierarchical references for single-parent resources are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "projectRef": map[string]interface{}{ - "external": "project_id_from_state", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "projectRef": map[string]interface{}{ - "external": "project_id_from_spec", - }, - }, - }, - Schema: &openapi.Schema{ - Type: "object", - Properties: map[string]*openapi.Schema{ - "project": { - Type: "string", - Extension: map[string]interface{}{ - "x-dcl-references": []interface{}{ - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Project", - }, - }, - }, - }, - }, - }, - }, - expectedSpec: map[string]interface{}{ - "projectRef": map[string]interface{}{ - "external": "project_id_from_spec", - }, - }, - }, - { - name: "hierarchical references for multi-parent resources are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "folderRef": map[string]interface{}{ - "external": "folder_id", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "folderRef": map[string]interface{}{ - "name": "folder-name", - }, - }, - }, - Schema: &openapi.Schema{ - Type: "object", - Properties: map[string]*openapi.Schema{ - "parent": { - Type: "string", - Extension: map[string]interface{}{ - "x-dcl-references": []interface{}{ - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Project", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Folder", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Organization", - }, - }, - }, - }, - }, - }, - }, - expectedSpec: map[string]interface{}{ - "folderRef": map[string]interface{}{ - "name": "folder-name", - }, - }, - }, - { - name: "external hierarchical references for multi-parent resources are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "folderRef": map[string]interface{}{ - "external": "folder_id_from_state", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "folderRef": map[string]interface{}{ - "external": "folder_id_from_spec", - }, - }, - }, - Schema: &openapi.Schema{ - Type: "object", - Properties: map[string]*openapi.Schema{ - "parent": { - Type: "string", - Extension: map[string]interface{}{ - "x-dcl-references": []interface{}{ - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Project", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Folder", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Organization", - }, - }, - }, - }, - }, - }, - }, - expectedSpec: map[string]interface{}{ - "folderRef": map[string]interface{}{ - "external": "folder_id_from_spec", - }, - }, - }, - { - name: "hierarchical references for multi-parent resources are preserved even if spec and state contain different types of references", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "folderRef": map[string]interface{}{ - "external": "folder_id", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "projectRef": map[string]interface{}{ - "name": "project-name", - }, - }, - }, - Schema: &openapi.Schema{ - Type: "object", - Properties: map[string]*openapi.Schema{ - "parent": { - Type: "string", - Extension: map[string]interface{}{ - "x-dcl-references": []interface{}{ - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Project", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Folder", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Organization", - }, - }, - }, - }, - }, - }, - }, - expectedSpec: map[string]interface{}{ - "projectRef": map[string]interface{}{ - "name": "project-name", - }, - }, - }, - { - name: "external hierarchical references for multi-parent resources are preserved even if spec and state contain different types of references", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "folderRef": map[string]interface{}{ - "external": "folder_id", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "organizationRef": map[string]interface{}{ - "external": "organization_id", - }, - }, - }, - Schema: &openapi.Schema{ - Type: "object", - Properties: map[string]*openapi.Schema{ - "parent": { - Type: "string", - Extension: map[string]interface{}{ - "x-dcl-references": []interface{}{ - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Project", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Folder", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Organization", - }, - }, - }, - }, - }, - }, - }, - expectedSpec: map[string]interface{}{ - "organizationRef": map[string]interface{}{ - "external": "organization_id", - }, - }, - }, - { - name: "hierarchical references for single-parent resources are taken from state if none found in spec", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "projectRef": map[string]interface{}{ - "external": "project_id", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{}, - }, - Schema: &openapi.Schema{ - Type: "object", - Properties: map[string]*openapi.Schema{ - "project": { - Type: "string", - Extension: map[string]interface{}{ - "x-dcl-references": []interface{}{ - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Project", - }, - }, - }, - }, - }, - }, - }, - expectedSpec: map[string]interface{}{ - "projectRef": map[string]interface{}{ - "external": "project_id", - }, - }, - }, - { - name: "hierarchical references for multi-parent resources are taken from state if none found in spec", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "folderRef": map[string]interface{}{ - "external": "folder_id", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{}, - }, - Schema: &openapi.Schema{ - Type: "object", - Properties: map[string]*openapi.Schema{ - "parent": { - Type: "string", - Extension: map[string]interface{}{ - "x-dcl-references": []interface{}{ - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Project", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Folder", - }, - map[interface{}]interface{}{ - "field": "name", - "parent": true, - "resource": "Cloudresourcemanager/Organization", - }, - }, - }, - }, - }, - }, - }, - expectedSpec: map[string]interface{}{ - "folderRef": map[string]interface{}{ - "external": "folder_id", - }, - }, - }, - { - name: "sensitive fields with plain-text values are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "value": "secret-val1", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "value": "secret-val1", - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "value": "secret-val1", - }, - }, - }, - { - name: "sensitive fields with values from secret refs are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "value": "secret-val1", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret1", - "key": "secret-key1", - }, - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret1", - "key": "secret-key1", - }, - }, - }, - }, - }, - { - name: "sensitive fields nested in lists of objects are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "sensitiveFieldInArray": map[string]interface{}{ - "value": "secret-val1", - }, - }, - map[string]interface{}{ - "sensitiveFieldInArray": map[string]interface{}{ - "value": "secret-val2", - }, - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "sensitiveFieldInArray": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret1", - "key": "secret-key1", - }, - }, - }, - }, - map[string]interface{}{ - "sensitiveFieldInArray": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret2", - "key": "secret-key2", - }, - }, - }, - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "sensitiveFieldInArray": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret1", - "key": "secret-key1", - }, - }, - }, - }, - map[string]interface{}{ - "sensitiveFieldInArray": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret2", - "key": "secret-key2", - }, - }, - }, - }, - }, - }, - }, - { - name: "sensitive fields nested in objects are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedSensitiveField": map[string]interface{}{ - "value": "secret-val1", - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedSensitiveField": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret1", - "key": "secret-key1", - }, - }, - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedSensitiveField": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret1", - "key": "secret-key1", - }, - }, - }, - }, - }, - }, - { - name: "sensitive fields set with plain-text value if not specified", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "value": "secret-val1", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{}, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "value": "secret-val1", - }, - }, - }, - { - name: "sensitive fields nested in lists of objects set with plain-text value if not specified", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "sensitiveFieldInArray": map[string]interface{}{ - "value": "secret-val1", - }, - }, - map[string]interface{}{ - "field1": 0.9, - "sensitiveFieldInArray": map[string]interface{}{ - "value": "secret-val2", - }, - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - }, - map[string]interface{}{ - "field1": 0.9, - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "sensitiveFieldInArray": map[string]interface{}{ - "value": "secret-val1", - }, - }, - map[string]interface{}{ - "field1": 0.9, - "sensitiveFieldInArray": map[string]interface{}{ - "value": "secret-val2", - }, - }, - }, - }, - }, - { - name: "spec-defined values are preserved", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": "2", - "floatKey": "1", - "stringKey": "StringVal2", - "boolKey": true, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "intKey": "1", - "floatKey": "0.5", - "stringKey": "StringVal", - "boolKey": false, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": "1", - "floatKey": "0.5", - "stringKey": "StringVal", - "boolKey": false, - }, - }, - { - name: "server-generated id is retrieved from state", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": "2", - "resourceID": "server-generated-value", - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "intKey": "1", - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": "1", - "resourceID": "server-generated-value", - }, - }, - { - name: "maps are treated as atomic when specified by user", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "mapKey": map[string]interface{}{ - "myMapKey1": "MyMapValue1", - "myMapKey2": "MyMapValue2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "mapKey": map[string]interface{}{ - "myMapKey1": "MyMapValue1", - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "mapKey": map[string]interface{}{ - "myMapKey1": "MyMapValue1", - }, - }, - }, - // Tests surrounding managed fields - { - name: "values are sourced from live state when not in managed fields set", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": "1", - "floatKey": "0.5", - "stringKey": "StringVal", - "boolKey": false, - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - "nestedField2": "strval2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "intKey": "2", - "floatKey": "1.0", - "stringKey": "StringVal2", - "boolKey": true, - "nestedObjectKey": map[string]interface{}{ - "nestedField1": true, - "nestedField2": "strval1", - }, - }, - ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ - "f:unrelated": emptyObject, - }), - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": "1", - "floatKey": "0.5", - "stringKey": "StringVal", - "boolKey": false, - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - "nestedField2": "strval2", - }, - }, - }, - { - name: "values for sensitive fields are sourced from live state when not in managed fields set", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "value": "new-val", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "value": "old-val", - }, - }, - ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ - "f:unrelated": emptyObject, - }), - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "sensitiveField": map[string]interface{}{ - "value": "new-val", - }, - }, - }, - { - name: "values are sourced from spec when in managed fields set", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": "1", - "floatKey": "0.5", - "stringKey": "StringVal", - "boolKey": false, - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - "nestedField2": "strval2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "intKey": "2", - "floatKey": "1.0", - "stringKey": "StringVal2", - "boolKey": true, - "nestedObjectKey": map[string]interface{}{ - "nestedField1": true, - "nestedField2": "strval1", - }, - }, - ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ - "f:intKey": emptyObject, - "f:floatKey": emptyObject, - "f:stringKey": emptyObject, - "f:boolKey": emptyObject, - "f:nestedObjectKey": map[string]interface{}{ - "f:nestedField1": emptyObject, - "f:nestedField2": emptyObject, - }, - }), - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": "2", - "floatKey": "1.0", - "stringKey": "StringVal2", - "boolKey": true, - "nestedObjectKey": map[string]interface{}{ - "nestedField1": true, - "nestedField2": "strval1", - }, - }, - }, - { - name: "maps are treated as atomic when k8s-managed", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "mapKey": map[string]interface{}{ - "myMapKey1": "MyMapValue1", - "myMapKey2": "MyMapValue2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "mapKey": map[string]interface{}{ - "myMapKey1": "MyMapValue1", - }, - }, - ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ - "f:mapKey": map[string]interface{}{ - "f:myMapKey1": emptyObject, - }, - }), - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "mapKey": map[string]interface{}{ - "myMapKey1": "MyMapValue1", - }, - }, - }, - { - name: "string-object maps with k8s managed fields", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "stringObjectMapKey": map[string]interface{}{ - "someKey": map[string]interface{}{ - "objectField1": 1.0, - "objectField2": "str1-from-state", - "state": "state1-from-state", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "external": "projects/my-project-1/bars/my-ref1", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - "someOtherKey": map[string]interface{}{ - "objectField1": 2.0, - "objectField2": "str2-from-state", - "state": "state2-from-state", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "external": "projects/my-project-1/bars/my-ref1", - }, - map[string]interface{}{ - "external": "projects/my-project-1/bars/my-ref2", - }, - }, - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "stringObjectMapKey": map[string]interface{}{ - "someKey": map[string]interface{}{ - "objectField1": 1.1, - "objectField2": "str1-from-spec", - "state": "state1-from-spec", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - "someOtherKey": map[string]interface{}{ - "objectField1": 2.2, - "objectField2": "str2-from-spec", - "state": "state2-from-spec", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "name": "my-ref2", - }, - }, - }, - }, - }, - ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ - "f:stringObjectMapKey": map[string]interface{}{ - "f:someKey": map[string]interface{}{ - ".": emptyObject, - "f:objectField1": emptyObject, - "f:objectReferenceArrayKey": emptyObject, - }, - "f:someOtherKey": map[string]interface{}{ - ".": emptyObject, - "f:objectField1": emptyObject, - "f:objectReferenceArrayKey": emptyObject, - }, - }, - }), - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "stringObjectMapKey": map[string]interface{}{ - "someKey": map[string]interface{}{ - "objectField1": 1.1, - "objectField2": "str1-from-state", - "state": "state1-from-state", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - "someOtherKey": map[string]interface{}{ - "objectField1": 2.2, - "objectField2": "str2-from-state", - "state": "state2-from-state", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "name": "my-ref2", - }, - }, - }, - }, - }, - }, - { - name: "values in lists of objects ignore managed fields", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "field2": "strval1", - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 1.0, - "field2": "strval2", - }, - }, - }, - ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ - "f:unrelated": emptyObject, - }), - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - // reflects the traditional fully-k8s-managed overlay of - // the spec list on the live state list - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 1.0, - "field2": "strval2", - }, - }, - }, - }, - { - name: "values in primitive lists are always sourced from state", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "primitiveArrayKey": []interface{}{ - "myString1", - "myString2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "primitiveArrayKey": []interface{}{ - "myString1", - }, - }, - ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ - "f:primitiveArrayKey": emptyObject, - }), - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - // reflects solely the live state - "primitiveArrayKey": []interface{}{ - "myString1", - "myString2", - }, - }, - }, - } - - smLoader := dclmetadata.NewFromServiceList(testservicemetadataloader.FakeServiceMetadataWithHierarchicalResources()) - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - actualSpec, actualStatus, err := kcclite.ResolveSpecAndStatus(tc.state, tc.dclResource, smLoader) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got, want := actualSpec, tc.expectedSpec; !test.Equals(t, got, want) { - t.Fatalf("unexpected spec diff (-want +got): \n%v", cmp.Diff(want, got)) - } - if got, want := actualStatus, tc.expectedStatus; !test.Equals(t, got, want) { - t.Fatalf("unexpected spec diff (-want +got): \n%v", cmp.Diff(want, got)) - } - }) - } -} - -func TestResolveSpecAndStatusWithDesiredStateInSpecAndObservedStatesInStatus(t *testing.T) { - tests := []struct { - name string - state *unstructured.Unstructured - dclResource *dcl.Resource - expectedSpec map[string]interface{} - expectedStatus map[string]interface{} - }{ - { - name: "only persist specified fields in spec", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": int64(1), - "floatKey": 0.5, - "stringKey": "StringVal", - "boolKey": false, - "primitiveArrayKey": []interface{}{ - "myString1", - "myString2", - }, - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "field2": "strval1", - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - }, - }, - "projectRef": map[string]interface{}{ - "external": "project_id", - }, - "sensitiveField": map[string]interface{}{ - "value": "secret-val1", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "intKey": int64(1), - "floatKey": 0.5, - "projectRef": map[string]interface{}{ - "name": "project-name", - }, - "sensitiveField": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret1", - "key": "secret-key1", - }, - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": int64(1), - "floatKey": 0.5, - "projectRef": map[string]interface{}{ - "name": "project-name", - }, - "sensitiveField": map[string]interface{}{ - "valueFrom": map[string]interface{}{ - "secretKeyRef": map[string]interface{}{ - "name": "secret1", - "key": "secret-key1", - }, - }, - }, - }, - }, - { - name: "observed states for output-only fields are persisted in status", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "status": map[string]interface{}{ - "statusField": "statusVal1", - "nestedObjectKey": map[string]interface{}{ - "nestedStatusField": "statusVal2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Status: map[string]interface{}{ - "statusField": "statusVal2", - }, - }, - Schema: testSchema(), - }, - expectedStatus: map[string]interface{}{ - "statusField": "statusVal1", - "nestedObjectKey": map[string]interface{}{ - "nestedStatusField": "statusVal2", - }, - }, - }, - { - name: "persist desired state in spec and output-only observed state in status", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": int64(1), - "floatKey": 0.5, - "stringKey": "StringVal", - "boolKey": false, - }, - "status": map[string]interface{}{ - "statusField": "statusVal1", - "nestedObjectKey": map[string]interface{}{ - "nestedStatusField": "statusVal2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "intKey": int64(1), - "floatKey": 0.5, - }, - Status: map[string]interface{}{ - "statusField": "statusVal2", - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": int64(1), - "floatKey": 0.5, - }, - expectedStatus: map[string]interface{}{ - "statusField": "statusVal1", - "nestedObjectKey": map[string]interface{}{ - "nestedStatusField": "statusVal2", - }, - }, - }, - { - name: "preserve lists of objects unmodified in spec if specified", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - "field2": "strval1", - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval3", - }, - map[string]interface{}{ - "field1": 1.0, - "field2": "strval4", - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - }, - map[string]interface{}{ - "field1": 0.7, - }, - }, - }, - Status: map[string]interface{}{ - "statusField": "statusVal2", - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "objectArrayKey": []interface{}{ - map[string]interface{}{ - "field1": 0.5, - }, - map[string]interface{}{ - "field1": 0.7, - "field2": "strval2", - }, - map[string]interface{}{ - "field1": 0.7, - }, - }, - }, - }, - { - name: "primitive lists are preserved with specified values", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "primitiveArrayKey": []interface{}{ - "myString1", - "myString2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "primitiveArrayKey": []interface{}{ - "myString1", - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "primitiveArrayKey": []interface{}{ - "myString1", - }, - }, - }, - { - name: "only persist specified nested fields in spec", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - "nestedField2": "strval2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedField2": "strval2", - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "nestedObjectKey": map[string]interface{}{ - "nestedField2": "strval2", - }, - }, - }, - { - name: "string-object maps", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "stringObjectMapKey": map[string]interface{}{ - "someKey": map[string]interface{}{ - "objectField1": 1.0, - "state": "state1", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "external": "projects/my-project-1/bars/my-ref1", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - "someOtherKey": map[string]interface{}{ - "objectField1": 2.0, - "state": "state2", - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "external": "projects/my-project-1/bars/my-ref1", - }, - map[string]interface{}{ - "external": "projects/my-project-1/bars/my-ref2", - }, - }, - }, - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "stringObjectMapKey": map[string]interface{}{ - "someKey": map[string]interface{}{ - "objectField1": 1.0, - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - "someOtherKey": map[string]interface{}{ - "objectField1": 3.0, - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "name": "my-ref2", - }, - }, - }, - }, - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "stringObjectMapKey": map[string]interface{}{ - "someKey": map[string]interface{}{ - "objectField1": 1.0, - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "external": "my-ref3", - }, - }, - }, - "someOtherKey": map[string]interface{}{ - "objectField1": 3.0, - "objectReferenceArrayKey": []interface{}{ - map[string]interface{}{ - "name": "my-ref1", - }, - map[string]interface{}{ - "name": "my-ref2", - }, - }, - }, - }, - }, - }, - { - name: "resourceID in spec will be persisted", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": "2", - "resourceID": "someVal", - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "intKey": "1", - "resourceID": "someVal", - }, - ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ - "f:intKey": emptyObject, - }), - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": "1", - "resourceID": "someVal", - }, - }, - { - name: "server-generated id is retrieved from state and persisted", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": "2", - "resourceID": "server-generated-value", - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "intKey": "1", - }, - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": "1", - "resourceID": "server-generated-value", - }, - }, - { - name: "fields in spec are persisted even if they not in managed fields set", - state: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "spec": map[string]interface{}{ - "intKey": int64(1), - "floatKey": 0.5, - "boolKey": false, - "stringKey": "someVal", - "nestedObjectKey": map[string]interface{}{ - "nestedField1": false, - "nestedField2": "strval2", - }, - }, - }, - }, - dclResource: &dcl.Resource{ - Resource: k8s.Resource{ - Spec: map[string]interface{}{ - "intKey": int64(1), - "floatKey": 0.5, - "boolKey": true, - }, - ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ - "f:unrelated": emptyObject, - }), - }, - Schema: testSchema(), - }, - expectedSpec: map[string]interface{}{ - "intKey": int64(1), - "floatKey": 0.5, - "boolKey": true, - }, - }, - } +//func TestResolveSpecAndStatusWithMixedSpecAndLegacyStatus(t *testing.T) { +// tests := []struct { +// name string +// state *unstructured.Unstructured +// dclResource *dcl.Resource +// expectedSpec map[string]interface{} +// expectedStatus map[string]interface{} +// }{ +// { +// name: "primitives are set", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": "1", +// "floatKey": "0.5", +// "stringKey": "StringVal", +// "boolKey": false, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": "1", +// "floatKey": "0.5", +// "stringKey": "StringVal", +// "boolKey": false, +// }, +// }, +// { +// name: "status fields are set from state", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "status": map[string]interface{}{ +// "statusField": "statusVal1", +// "nestedObjectKey": map[string]interface{}{ +// "nestedStatusField": "statusVal2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Status: map[string]interface{}{ +// "statusField": "statusVal2", +// }, +// }, +// Schema: testSchema(), +// }, +// expectedStatus: map[string]interface{}{ +// "statusField": "statusVal1", +// "nestedObjectKey": map[string]interface{}{ +// "nestedStatusField": "statusVal2", +// }, +// }, +// }, +// { +// name: "both spec and status fields are present", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": "1", +// "floatKey": "0.5", +// "stringKey": "StringVal", +// "boolKey": false, +// }, +// "status": map[string]interface{}{ +// "statusField": "statusVal1", +// "nestedObjectKey": map[string]interface{}{ +// "nestedStatusField": "statusVal2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Status: map[string]interface{}{ +// "statusField": "statusVal2", +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": "1", +// "floatKey": "0.5", +// "stringKey": "StringVal", +// "boolKey": false, +// }, +// expectedStatus: map[string]interface{}{ +// "statusField": "statusVal1", +// "nestedObjectKey": map[string]interface{}{ +// "nestedStatusField": "statusVal2", +// }, +// }, +// }, +// { +// name: "lists of objects are set", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "field2": "strval1", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval3", +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "field2": "strval1", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval3", +// }, +// }, +// }, +// }, +// { +// name: "lists of objects are merged with defaulted fields", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "field2": "strval1", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// }, +// map[string]interface{}{ +// "field1": 0.9, +// "field2": "strval3", +// }, +// map[string]interface{}{ +// "field1": 1.2, +// "field2": "strval4", +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// }, +// map[string]interface{}{ +// "field1": 0.7, +// }, +// map[string]interface{}{ +// "field1": 0.9, +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "field2": "strval1", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// }, +// map[string]interface{}{ +// "field1": 0.9, +// "field2": "strval3", +// }, +// map[string]interface{}{ +// "field1": 1.2, +// "field2": "strval4", +// }, +// }, +// }, +// }, +// { +// name: "nested objects are set", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// "nestedField2": "strval2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// "nestedField2": "strval2", +// }, +// }, +// }, +// { +// name: "nested objects are merged with defaulted values", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// "nestedField2": "strval2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// "nestedField2": "strval2", +// }, +// }, +// }, +// { +// name: "individual resource references are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "referenceKeyRef": map[string]interface{}{ +// "external": "my-ref1", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "referenceKeyRef": map[string]interface{}{ +// "name": "my-ref1", +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "referenceKeyRef": map[string]interface{}{ +// "name": "my-ref1", +// }, +// }, +// }, +// { +// name: "string-object maps are merged", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "stringObjectMapKey": map[string]interface{}{ +// "someKey": map[string]interface{}{ +// "objectField1": 1.0, +// "state": "state1", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "external": "projects/my-project-1/bars/my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// "someOtherKey": map[string]interface{}{ +// "objectField1": 2.0, +// "state": "state2", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "external": "projects/my-project-1/bars/my-ref1", +// }, +// map[string]interface{}{ +// "external": "projects/my-project-1/bars/my-ref2", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "stringObjectMapKey": map[string]interface{}{ +// "someKey": map[string]interface{}{ +// "objectField1": 1.0, +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// "someOtherKey": map[string]interface{}{ +// "objectField1": 3.0, +// "state": "state2", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "name": "my-ref2", +// }, +// }, +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "stringObjectMapKey": map[string]interface{}{ +// "someKey": map[string]interface{}{ +// "objectField1": 1.0, +// "state": "state1", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// "someOtherKey": map[string]interface{}{ +// "objectField1": 3.0, +// "state": "state2", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "name": "my-ref2", +// }, +// }, +// }, +// }, +// }, +// }, +// { +// name: "lists of resource references are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "referenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "external": "my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref2", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "referenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "name": "my-ref2", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "referenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "name": "my-ref2", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// }, +// { +// name: "resource references nested in lists of objects are preserved, default values are added", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "field2": "strval1", +// "barRef": map[string]interface{}{ +// "external": "my-ref1", +// }, +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// "barRef": map[string]interface{}{ +// "external": "my-ref2", +// }, +// }, +// map[string]interface{}{ +// "field1": 0.9, +// "field2": "strval3", +// "barRef": map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field2": "strval1", +// "barRef": map[string]interface{}{ +// "name": "my-ref1", +// }, +// }, +// map[string]interface{}{ +// "field2": "strval2", +// "barRef": map[string]interface{}{ +// "name": "my-ref2", +// }, +// }, +// map[string]interface{}{ +// "field2": "strval3", +// "barRef": map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "field2": "strval1", +// "barRef": map[string]interface{}{ +// "name": "my-ref1", +// }, +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// "barRef": map[string]interface{}{ +// "name": "my-ref2", +// }, +// }, +// map[string]interface{}{ +// "field1": 0.9, +// "field2": "strval3", +// "barRef": map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// }, +// }, +// { +// name: "external resource reference set if no reference defined by spec", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "referenceKeyRef": map[string]interface{}{ +// "external": "my-ref1", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "referenceKeyRef": map[string]interface{}{ +// "external": "my-ref1", +// }, +// }, +// }, +// { +// name: "list of external resource references set if no list defined by spec", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "referenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "external": "my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref2", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "referenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "external": "my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref2", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// }, +// { +// name: "hierarchical references for single-parent resources are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "external": "project_id", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "name": "project-name", +// }, +// }, +// }, +// Schema: &openapi.Schema{ +// Type: "object", +// Properties: map[string]*openapi.Schema{ +// "project": { +// Type: "string", +// Extension: map[string]interface{}{ +// "x-dcl-references": []interface{}{ +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Project", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// expectedSpec: map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "name": "project-name", +// }, +// }, +// }, +// { +// name: "external hierarchical references for single-parent resources are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "external": "project_id_from_state", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "external": "project_id_from_spec", +// }, +// }, +// }, +// Schema: &openapi.Schema{ +// Type: "object", +// Properties: map[string]*openapi.Schema{ +// "project": { +// Type: "string", +// Extension: map[string]interface{}{ +// "x-dcl-references": []interface{}{ +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Project", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// expectedSpec: map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "external": "project_id_from_spec", +// }, +// }, +// }, +// { +// name: "hierarchical references for multi-parent resources are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "external": "folder_id", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "name": "folder-name", +// }, +// }, +// }, +// Schema: &openapi.Schema{ +// Type: "object", +// Properties: map[string]*openapi.Schema{ +// "parent": { +// Type: "string", +// Extension: map[string]interface{}{ +// "x-dcl-references": []interface{}{ +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Project", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Folder", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Organization", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// expectedSpec: map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "name": "folder-name", +// }, +// }, +// }, +// { +// name: "external hierarchical references for multi-parent resources are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "external": "folder_id_from_state", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "external": "folder_id_from_spec", +// }, +// }, +// }, +// Schema: &openapi.Schema{ +// Type: "object", +// Properties: map[string]*openapi.Schema{ +// "parent": { +// Type: "string", +// Extension: map[string]interface{}{ +// "x-dcl-references": []interface{}{ +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Project", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Folder", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Organization", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// expectedSpec: map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "external": "folder_id_from_spec", +// }, +// }, +// }, +// { +// name: "hierarchical references for multi-parent resources are preserved even if spec and state contain different types of references", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "external": "folder_id", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "name": "project-name", +// }, +// }, +// }, +// Schema: &openapi.Schema{ +// Type: "object", +// Properties: map[string]*openapi.Schema{ +// "parent": { +// Type: "string", +// Extension: map[string]interface{}{ +// "x-dcl-references": []interface{}{ +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Project", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Folder", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Organization", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// expectedSpec: map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "name": "project-name", +// }, +// }, +// }, +// { +// name: "external hierarchical references for multi-parent resources are preserved even if spec and state contain different types of references", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "external": "folder_id", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "organizationRef": map[string]interface{}{ +// "external": "organization_id", +// }, +// }, +// }, +// Schema: &openapi.Schema{ +// Type: "object", +// Properties: map[string]*openapi.Schema{ +// "parent": { +// Type: "string", +// Extension: map[string]interface{}{ +// "x-dcl-references": []interface{}{ +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Project", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Folder", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Organization", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// expectedSpec: map[string]interface{}{ +// "organizationRef": map[string]interface{}{ +// "external": "organization_id", +// }, +// }, +// }, +// { +// name: "hierarchical references for single-parent resources are taken from state if none found in spec", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "external": "project_id", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{}, +// }, +// Schema: &openapi.Schema{ +// Type: "object", +// Properties: map[string]*openapi.Schema{ +// "project": { +// Type: "string", +// Extension: map[string]interface{}{ +// "x-dcl-references": []interface{}{ +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Project", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// expectedSpec: map[string]interface{}{ +// "projectRef": map[string]interface{}{ +// "external": "project_id", +// }, +// }, +// }, +// { +// name: "hierarchical references for multi-parent resources are taken from state if none found in spec", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "external": "folder_id", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{}, +// }, +// Schema: &openapi.Schema{ +// Type: "object", +// Properties: map[string]*openapi.Schema{ +// "parent": { +// Type: "string", +// Extension: map[string]interface{}{ +// "x-dcl-references": []interface{}{ +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Project", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Folder", +// }, +// map[interface{}]interface{}{ +// "field": "name", +// "parent": true, +// "resource": "Cloudresourcemanager/Organization", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// expectedSpec: map[string]interface{}{ +// "folderRef": map[string]interface{}{ +// "external": "folder_id", +// }, +// }, +// }, +// { +// name: "sensitive fields with plain-text values are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// }, +// { +// name: "sensitive fields with values from secret refs are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret1", +// "key": "secret-key1", +// }, +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret1", +// "key": "secret-key1", +// }, +// }, +// }, +// }, +// }, +// { +// name: "sensitive fields nested in lists of objects are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "sensitiveFieldInArray": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// map[string]interface{}{ +// "sensitiveFieldInArray": map[string]interface{}{ +// "value": "secret-val2", +// }, +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "sensitiveFieldInArray": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret1", +// "key": "secret-key1", +// }, +// }, +// }, +// }, +// map[string]interface{}{ +// "sensitiveFieldInArray": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret2", +// "key": "secret-key2", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "sensitiveFieldInArray": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret1", +// "key": "secret-key1", +// }, +// }, +// }, +// }, +// map[string]interface{}{ +// "sensitiveFieldInArray": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret2", +// "key": "secret-key2", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// { +// name: "sensitive fields nested in objects are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedSensitiveField": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedSensitiveField": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret1", +// "key": "secret-key1", +// }, +// }, +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedSensitiveField": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret1", +// "key": "secret-key1", +// }, +// }, +// }, +// }, +// }, +// }, +// { +// name: "sensitive fields set with plain-text value if not specified", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{}, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// }, +// { +// name: "sensitive fields nested in lists of objects set with plain-text value if not specified", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "sensitiveFieldInArray": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// map[string]interface{}{ +// "field1": 0.9, +// "sensitiveFieldInArray": map[string]interface{}{ +// "value": "secret-val2", +// }, +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// }, +// map[string]interface{}{ +// "field1": 0.9, +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "sensitiveFieldInArray": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// map[string]interface{}{ +// "field1": 0.9, +// "sensitiveFieldInArray": map[string]interface{}{ +// "value": "secret-val2", +// }, +// }, +// }, +// }, +// }, +// { +// name: "spec-defined values are preserved", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": "2", +// "floatKey": "1", +// "stringKey": "StringVal2", +// "boolKey": true, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "intKey": "1", +// "floatKey": "0.5", +// "stringKey": "StringVal", +// "boolKey": false, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": "1", +// "floatKey": "0.5", +// "stringKey": "StringVal", +// "boolKey": false, +// }, +// }, +// { +// name: "server-generated id is retrieved from state", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": "2", +// "resourceID": "server-generated-value", +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "intKey": "1", +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": "1", +// "resourceID": "server-generated-value", +// }, +// }, +// { +// name: "maps are treated as atomic when specified by user", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "mapKey": map[string]interface{}{ +// "myMapKey1": "MyMapValue1", +// "myMapKey2": "MyMapValue2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "mapKey": map[string]interface{}{ +// "myMapKey1": "MyMapValue1", +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "mapKey": map[string]interface{}{ +// "myMapKey1": "MyMapValue1", +// }, +// }, +// }, +// // Tests surrounding managed fields +// { +// name: "values are sourced from live state when not in managed fields set", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": "1", +// "floatKey": "0.5", +// "stringKey": "StringVal", +// "boolKey": false, +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// "nestedField2": "strval2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "intKey": "2", +// "floatKey": "1.0", +// "stringKey": "StringVal2", +// "boolKey": true, +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": true, +// "nestedField2": "strval1", +// }, +// }, +// ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ +// "f:unrelated": emptyObject, +// }), +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": "1", +// "floatKey": "0.5", +// "stringKey": "StringVal", +// "boolKey": false, +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// "nestedField2": "strval2", +// }, +// }, +// }, +// { +// name: "values for sensitive fields are sourced from live state when not in managed fields set", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "value": "new-val", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "value": "old-val", +// }, +// }, +// ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ +// "f:unrelated": emptyObject, +// }), +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "sensitiveField": map[string]interface{}{ +// "value": "new-val", +// }, +// }, +// }, +// { +// name: "values are sourced from spec when in managed fields set", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": "1", +// "floatKey": "0.5", +// "stringKey": "StringVal", +// "boolKey": false, +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// "nestedField2": "strval2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "intKey": "2", +// "floatKey": "1.0", +// "stringKey": "StringVal2", +// "boolKey": true, +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": true, +// "nestedField2": "strval1", +// }, +// }, +// ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ +// "f:intKey": emptyObject, +// "f:floatKey": emptyObject, +// "f:stringKey": emptyObject, +// "f:boolKey": emptyObject, +// "f:nestedObjectKey": map[string]interface{}{ +// "f:nestedField1": emptyObject, +// "f:nestedField2": emptyObject, +// }, +// }), +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": "2", +// "floatKey": "1.0", +// "stringKey": "StringVal2", +// "boolKey": true, +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": true, +// "nestedField2": "strval1", +// }, +// }, +// }, +// { +// name: "maps are treated as atomic when k8s-managed", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "mapKey": map[string]interface{}{ +// "myMapKey1": "MyMapValue1", +// "myMapKey2": "MyMapValue2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "mapKey": map[string]interface{}{ +// "myMapKey1": "MyMapValue1", +// }, +// }, +// ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ +// "f:mapKey": map[string]interface{}{ +// "f:myMapKey1": emptyObject, +// }, +// }), +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "mapKey": map[string]interface{}{ +// "myMapKey1": "MyMapValue1", +// }, +// }, +// }, +// { +// name: "string-object maps with k8s managed fields", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "stringObjectMapKey": map[string]interface{}{ +// "someKey": map[string]interface{}{ +// "objectField1": 1.0, +// "objectField2": "str1-from-state", +// "state": "state1-from-state", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "external": "projects/my-project-1/bars/my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// "someOtherKey": map[string]interface{}{ +// "objectField1": 2.0, +// "objectField2": "str2-from-state", +// "state": "state2-from-state", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "external": "projects/my-project-1/bars/my-ref1", +// }, +// map[string]interface{}{ +// "external": "projects/my-project-1/bars/my-ref2", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "stringObjectMapKey": map[string]interface{}{ +// "someKey": map[string]interface{}{ +// "objectField1": 1.1, +// "objectField2": "str1-from-spec", +// "state": "state1-from-spec", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// "someOtherKey": map[string]interface{}{ +// "objectField1": 2.2, +// "objectField2": "str2-from-spec", +// "state": "state2-from-spec", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "name": "my-ref2", +// }, +// }, +// }, +// }, +// }, +// ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ +// "f:stringObjectMapKey": map[string]interface{}{ +// "f:someKey": map[string]interface{}{ +// ".": emptyObject, +// "f:objectField1": emptyObject, +// "f:objectReferenceArrayKey": emptyObject, +// }, +// "f:someOtherKey": map[string]interface{}{ +// ".": emptyObject, +// "f:objectField1": emptyObject, +// "f:objectReferenceArrayKey": emptyObject, +// }, +// }, +// }), +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "stringObjectMapKey": map[string]interface{}{ +// "someKey": map[string]interface{}{ +// "objectField1": 1.1, +// "objectField2": "str1-from-state", +// "state": "state1-from-state", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// "someOtherKey": map[string]interface{}{ +// "objectField1": 2.2, +// "objectField2": "str2-from-state", +// "state": "state2-from-state", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "name": "my-ref2", +// }, +// }, +// }, +// }, +// }, +// }, +// { +// name: "values in lists of objects ignore managed fields", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "field2": "strval1", +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 1.0, +// "field2": "strval2", +// }, +// }, +// }, +// ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ +// "f:unrelated": emptyObject, +// }), +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// // reflects the traditional fully-k8s-managed overlay of +// // the spec list on the live state list +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 1.0, +// "field2": "strval2", +// }, +// }, +// }, +// }, +// { +// name: "values in primitive lists are always sourced from state", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "primitiveArrayKey": []interface{}{ +// "myString1", +// "myString2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "primitiveArrayKey": []interface{}{ +// "myString1", +// }, +// }, +// ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ +// "f:primitiveArrayKey": emptyObject, +// }), +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// // reflects solely the live state +// "primitiveArrayKey": []interface{}{ +// "myString1", +// "myString2", +// }, +// }, +// }, +// } +// +// smLoader := dclmetadata.NewFromServiceList(testservicemetadataloader.FakeServiceMetadataWithHierarchicalResources()) +// for _, tc := range tests { +// tc := tc +// t.Run(tc.name, func(t *testing.T) { +// t.Parallel() +// actualSpec, actualStatus, err := kcclite.ResolveSpecAndStatus(tc.state, tc.dclResource, smLoader) +// if err != nil { +// t.Fatalf("unexpected error: %v", err) +// } +// if got, want := actualSpec, tc.expectedSpec; !test.Equals(t, got, want) { +// t.Fatalf("unexpected spec diff (-want +got): \n%v", cmp.Diff(want, got)) +// } +// if got, want := actualStatus, tc.expectedStatus; !test.Equals(t, got, want) { +// t.Fatalf("unexpected spec diff (-want +got): \n%v", cmp.Diff(want, got)) +// } +// }) +// } +//} - smLoader := dclmetadata.NewFromServiceList(testservicemetadataloader.FakeServiceMetadataWithHierarchicalResources()) - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - k8s.SetAnnotation(k8s.StateIntoSpecAnnotation, k8s.StateAbsentInSpec, tc.dclResource) - actualSpec, actualStatus, err := kcclite.ResolveSpecAndStatus(tc.state, tc.dclResource, smLoader) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if got, want := actualSpec, tc.expectedSpec; !test.Equals(t, got, want) { - t.Fatalf("unexpected spec diff (-want +got): \n%v", cmp.Diff(want, got)) - } - if got, want := actualStatus, tc.expectedStatus; !test.Equals(t, got, want) { - t.Fatalf("unexpected spec diff (-want +got): \n%v", cmp.Diff(want, got)) - } - }) - } -} +//func TestResolveSpecAndStatusWithDesiredStateInSpecAndObservedStatesInStatus(t *testing.T) { +// tests := []struct { +// name string +// state *unstructured.Unstructured +// dclResource *dcl.Resource +// expectedSpec map[string]interface{} +// expectedStatus map[string]interface{} +// }{ +// { +// name: "only persist specified fields in spec", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": int64(1), +// "floatKey": 0.5, +// "stringKey": "StringVal", +// "boolKey": false, +// "primitiveArrayKey": []interface{}{ +// "myString1", +// "myString2", +// }, +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "field2": "strval1", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// }, +// }, +// "projectRef": map[string]interface{}{ +// "external": "project_id", +// }, +// "sensitiveField": map[string]interface{}{ +// "value": "secret-val1", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "intKey": int64(1), +// "floatKey": 0.5, +// "projectRef": map[string]interface{}{ +// "name": "project-name", +// }, +// "sensitiveField": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret1", +// "key": "secret-key1", +// }, +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": int64(1), +// "floatKey": 0.5, +// "projectRef": map[string]interface{}{ +// "name": "project-name", +// }, +// "sensitiveField": map[string]interface{}{ +// "valueFrom": map[string]interface{}{ +// "secretKeyRef": map[string]interface{}{ +// "name": "secret1", +// "key": "secret-key1", +// }, +// }, +// }, +// }, +// }, +// { +// name: "observed states for output-only fields are persisted in status", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "status": map[string]interface{}{ +// "statusField": "statusVal1", +// "nestedObjectKey": map[string]interface{}{ +// "nestedStatusField": "statusVal2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Status: map[string]interface{}{ +// "statusField": "statusVal2", +// }, +// }, +// Schema: testSchema(), +// }, +// expectedStatus: map[string]interface{}{ +// "statusField": "statusVal1", +// "nestedObjectKey": map[string]interface{}{ +// "nestedStatusField": "statusVal2", +// }, +// }, +// }, +// { +// name: "persist desired state in spec and output-only observed state in status", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": int64(1), +// "floatKey": 0.5, +// "stringKey": "StringVal", +// "boolKey": false, +// }, +// "status": map[string]interface{}{ +// "statusField": "statusVal1", +// "nestedObjectKey": map[string]interface{}{ +// "nestedStatusField": "statusVal2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "intKey": int64(1), +// "floatKey": 0.5, +// }, +// Status: map[string]interface{}{ +// "statusField": "statusVal2", +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": int64(1), +// "floatKey": 0.5, +// }, +// expectedStatus: map[string]interface{}{ +// "statusField": "statusVal1", +// "nestedObjectKey": map[string]interface{}{ +// "nestedStatusField": "statusVal2", +// }, +// }, +// }, +// { +// name: "preserve lists of objects unmodified in spec if specified", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// "field2": "strval1", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval3", +// }, +// map[string]interface{}{ +// "field1": 1.0, +// "field2": "strval4", +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// }, +// }, +// }, +// Status: map[string]interface{}{ +// "statusField": "statusVal2", +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "objectArrayKey": []interface{}{ +// map[string]interface{}{ +// "field1": 0.5, +// }, +// map[string]interface{}{ +// "field1": 0.7, +// "field2": "strval2", +// }, +// map[string]interface{}{ +// "field1": 0.7, +// }, +// }, +// }, +// }, +// { +// name: "primitive lists are preserved with specified values", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "primitiveArrayKey": []interface{}{ +// "myString1", +// "myString2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "primitiveArrayKey": []interface{}{ +// "myString1", +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "primitiveArrayKey": []interface{}{ +// "myString1", +// }, +// }, +// }, +// { +// name: "only persist specified nested fields in spec", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// "nestedField2": "strval2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedField2": "strval2", +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "nestedObjectKey": map[string]interface{}{ +// "nestedField2": "strval2", +// }, +// }, +// }, +// { +// name: "string-object maps", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "stringObjectMapKey": map[string]interface{}{ +// "someKey": map[string]interface{}{ +// "objectField1": 1.0, +// "state": "state1", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "external": "projects/my-project-1/bars/my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// "someOtherKey": map[string]interface{}{ +// "objectField1": 2.0, +// "state": "state2", +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "external": "projects/my-project-1/bars/my-ref1", +// }, +// map[string]interface{}{ +// "external": "projects/my-project-1/bars/my-ref2", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "stringObjectMapKey": map[string]interface{}{ +// "someKey": map[string]interface{}{ +// "objectField1": 1.0, +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// "someOtherKey": map[string]interface{}{ +// "objectField1": 3.0, +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "name": "my-ref2", +// }, +// }, +// }, +// }, +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "stringObjectMapKey": map[string]interface{}{ +// "someKey": map[string]interface{}{ +// "objectField1": 1.0, +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "external": "my-ref3", +// }, +// }, +// }, +// "someOtherKey": map[string]interface{}{ +// "objectField1": 3.0, +// "objectReferenceArrayKey": []interface{}{ +// map[string]interface{}{ +// "name": "my-ref1", +// }, +// map[string]interface{}{ +// "name": "my-ref2", +// }, +// }, +// }, +// }, +// }, +// }, +// { +// name: "resourceID in spec will be persisted", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": "2", +// "resourceID": "someVal", +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "intKey": "1", +// "resourceID": "someVal", +// }, +// ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ +// "f:intKey": emptyObject, +// }), +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": "1", +// "resourceID": "someVal", +// }, +// }, +// { +// name: "server-generated id is retrieved from state and persisted", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": "2", +// "resourceID": "server-generated-value", +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "intKey": "1", +// }, +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": "1", +// "resourceID": "server-generated-value", +// }, +// }, +// { +// name: "fields in spec are persisted even if they not in managed fields set", +// state: &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "spec": map[string]interface{}{ +// "intKey": int64(1), +// "floatKey": 0.5, +// "boolKey": false, +// "stringKey": "someVal", +// "nestedObjectKey": map[string]interface{}{ +// "nestedField1": false, +// "nestedField2": "strval2", +// }, +// }, +// }, +// }, +// dclResource: &dcl.Resource{ +// Resource: k8s.Resource{ +// Spec: map[string]interface{}{ +// "intKey": int64(1), +// "floatKey": 0.5, +// "boolKey": true, +// }, +// ManagedFields: testk8s.MapToFieldPathSet(t, map[string]interface{}{ +// "f:unrelated": emptyObject, +// }), +// }, +// Schema: testSchema(), +// }, +// expectedSpec: map[string]interface{}{ +// "intKey": int64(1), +// "floatKey": 0.5, +// "boolKey": true, +// }, +// }, +// } +// +// smLoader := dclmetadata.NewFromServiceList(testservicemetadataloader.FakeServiceMetadataWithHierarchicalResources()) +// for _, tc := range tests { +// tc := tc +// t.Run(tc.name, func(t *testing.T) { +// t.Parallel() +// k8s.SetAnnotation(k8s.StateIntoSpecAnnotation, k8s.StateAbsentInSpec, tc.dclResource) +// actualSpec, actualStatus, err := kcclite.ResolveSpecAndStatus(tc.state, tc.dclResource, smLoader) +// if err != nil { +// t.Fatalf("unexpected error: %v", err) +// } +// if got, want := actualSpec, tc.expectedSpec; !test.Equals(t, got, want) { +// t.Fatalf("unexpected spec diff (-want +got): \n%v", cmp.Diff(want, got)) +// } +// if got, want := actualStatus, tc.expectedStatus; !test.Equals(t, got, want) { +// t.Fatalf("unexpected spec diff (-want +got): \n%v", cmp.Diff(want, got)) +// } +// }) +// } +//} func newBarUnstructuredWithResourceID(t *testing.T, name, ns string, readyStatus corev1.ConditionStatus) *unstructured.Unstructured { u := test.NewBarUnstructured(name, ns, readyStatus) diff --git a/pkg/dcl/kcclite/templating.go b/pkg/dcl/kcclite/templating.go index a97b6611bb..40fb348471 100644 --- a/pkg/dcl/kcclite/templating.go +++ b/pkg/dcl/kcclite/templating.go @@ -62,7 +62,7 @@ func CanonicalizeReferencedResourceName(name string, nameValueTemplate string, r return val } // Value not found in spec, so check status. - val, exists, err = unstructured.NestedString(refResource.Status, field) + val, exists, err = unstructured.NestedString(refResource.GetStatus(), field) if err != nil { resolutionError = fmt.Errorf("error getting value for DCL field %v in status of referenced resource %v with GroupVersionKind %v: %w", field, k8s.GetNamespacedName(refResource), refResource.GroupVersionKind(), err) diff --git a/pkg/k8s/references.go b/pkg/k8s/references.go index fe6824897a..ef1c8104bb 100644 --- a/pkg/k8s/references.go +++ b/pkg/k8s/references.go @@ -55,6 +55,14 @@ func GetReferencedResource(resourceRef *corekccv1alpha1.ResourceReference, gvk s return nil, fmt.Errorf("error marshalling unstruct for referenced resource %v with GroupVersionKind %v to k8s resource: %w", GetNamespacedName(u), u.GroupVersionKind(), err) } + statusObj := u.Object["status"] + if statusObj != nil { + statusObjInMap, ok := statusObj.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("expected status value for referenced resource %v with GroupVersionKind %v to be map[string]interface{} but was actually %T", GetNamespacedName(u), u.GroupVersionKind(), statusObj) + } + r.SetStatus(statusObjInMap) + } return r, nil } diff --git a/pkg/k8s/resource.go b/pkg/k8s/resource.go index d7bda4127b..8a5fda1985 100644 --- a/pkg/k8s/resource.go +++ b/pkg/k8s/resource.go @@ -34,7 +34,7 @@ type Resource struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec map[string]interface{} `json:"spec,omitempty"` - Status map[string]interface{} `json:"status,omitempty"` + status map[string]interface{} //`json:"status,omitempty"` // Fields related to KRM processing @@ -64,6 +64,14 @@ func NewResource(u *unstructured.Unstructured) (*Resource, error) { if err := util.Marshal(u, resource); err != nil { return nil, err } + statusObj := u.Object["status"] + if statusObj != nil { + statusObjInMap, ok := statusObj.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("expected status value for resource %v with GroupVersionKind %v to be map[string]interface{} but was actually %T", GetNamespacedName(u), u.GroupVersionKind(), statusObj) + } + resource.SetStatus(statusObjInMap) + } managedFields, err := GetK8sManagedFields(u) if err != nil { return nil, err @@ -77,6 +85,7 @@ func (r *Resource) MarshalAsUnstructured() (*unstructured.Unstructured, error) { if err := util.Marshal(r, u); err != nil { return nil, fmt.Errorf("error marshing resource to Unstructured %w", err) } + u.Object["status"] = r.status removeNilCreationTimestamp(u.Object) return u, nil } @@ -125,8 +134,16 @@ func IsResourceReady(r *Resource) bool { return found && cond.Status == corev1.ConditionTrue } +func (r *Resource) GetStatus() map[string]interface{} { + return r.status +} + +func (r *Resource) SetStatus(status map[string]interface{}) { + r.status = status +} + func GetReadyCondition(r *Resource) (condition k8sv1alpha1.Condition, found bool) { - if currConditionsRaw, ok := r.Status["conditions"].([]interface{}); ok { + if currConditionsRaw, ok := r.status["conditions"].([]interface{}); ok { if currConditions, err := MarshalAsConditionsSlice(currConditionsRaw); err == nil { for _, condition := range currConditions { if condition.Type == k8sv1alpha1.ReadyConditionType { @@ -150,11 +167,11 @@ func IsSpecOrStatusUpdateRequired(resource *Resource, original *Resource) bool { if !reflect.DeepEqual(resource.Spec, original.Spec) { return true } - if !reflect.DeepEqual(resource.Status, original.Status) { + if !reflect.DeepEqual(resource.status, original.status) { return true } // JSON marshall will turn all numbers to float64 type, we convert generation to float64 for comparison - if len(resource.Status) == 0 || resource.Status["observedGeneration"] != float64(original.GetGeneration()) { + if len(resource.status) == 0 || resource.status["observedGeneration"] != float64(original.GetGeneration()) { return true } return false diff --git a/pkg/k8s/resource_test.go b/pkg/k8s/resource_test.go index bd02b99bea..992ccb8a48 100644 --- a/pkg/k8s/resource_test.go +++ b/pkg/k8s/resource_test.go @@ -88,7 +88,9 @@ func TestResource_IsSpecOrStatusUpdateRequired(t *testing.T) { tests := []struct { name string resource *k8s.Resource + resourceStatus map[string]interface{} original *k8s.Resource + originalStatus map[string]interface{} expectedResult bool }{ { @@ -100,18 +102,18 @@ func TestResource_IsSpecOrStatusUpdateRequired(t *testing.T) { Spec: map[string]interface{}{ "foo": "someValue", }, - Status: map[string]interface{}{ - "observedGeneration": float64(1), - }, + }, + resourceStatus: map[string]interface{}{ + "observedGeneration": float64(1), }, original: &k8s.Resource{ ObjectMeta: metav1.ObjectMeta{ Generation: 1, }, Spec: map[string]interface{}{}, - Status: map[string]interface{}{ - "observedGeneration": float64(1), - }, + }, + originalStatus: map[string]interface{}{ + "observedGeneration": float64(1), }, expectedResult: true, }, @@ -124,9 +126,9 @@ func TestResource_IsSpecOrStatusUpdateRequired(t *testing.T) { Spec: map[string]interface{}{ "foo": "someValue", }, - Status: map[string]interface{}{ - "bar": "someValue", - }, + }, + resourceStatus: map[string]interface{}{ + "bar": "someValue", }, original: &k8s.Resource{ ObjectMeta: metav1.ObjectMeta{ @@ -135,8 +137,8 @@ func TestResource_IsSpecOrStatusUpdateRequired(t *testing.T) { Spec: map[string]interface{}{ "foo": "someValue", }, - Status: map[string]interface{}{}, }, + originalStatus: map[string]interface{}{}, expectedResult: true, }, { @@ -148,10 +150,10 @@ func TestResource_IsSpecOrStatusUpdateRequired(t *testing.T) { Spec: map[string]interface{}{ "foo": "someValue", }, - Status: map[string]interface{}{ - "bar": "someValue", - "observedGeneration": float64(2), - }, + }, + resourceStatus: map[string]interface{}{ + "bar": "someValue", + "observedGeneration": float64(2), }, original: &k8s.Resource{ ObjectMeta: metav1.ObjectMeta{ @@ -160,10 +162,10 @@ func TestResource_IsSpecOrStatusUpdateRequired(t *testing.T) { Spec: map[string]interface{}{ "foo": "someValue", }, - Status: map[string]interface{}{ - "bar": "someValue", - "observedGeneration": float64(1), - }, + }, + originalStatus: map[string]interface{}{ + "bar": "someValue", + "observedGeneration": float64(1), }, expectedResult: true, }, @@ -176,10 +178,10 @@ func TestResource_IsSpecOrStatusUpdateRequired(t *testing.T) { Spec: map[string]interface{}{ "foo": "someValue", }, - Status: map[string]interface{}{ - "bar": "someValue", - "observedGeneration": float64(2), - }, + }, + resourceStatus: map[string]interface{}{ + "bar": "someValue", + "observedGeneration": float64(2), }, original: &k8s.Resource{ ObjectMeta: metav1.ObjectMeta{ @@ -188,10 +190,10 @@ func TestResource_IsSpecOrStatusUpdateRequired(t *testing.T) { Spec: map[string]interface{}{ "foo": "someValue", }, - Status: map[string]interface{}{ - "bar": "someValue", - "observedGeneration": float64(2), - }, + }, + originalStatus: map[string]interface{}{ + "bar": "someValue", + "observedGeneration": float64(2), }, expectedResult: false, }, @@ -220,6 +222,8 @@ func TestResource_IsSpecOrStatusUpdateRequired(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() + tc.resource.SetStatus(tc.resourceStatus) + tc.original.SetStatus(tc.originalStatus) actual := k8s.IsSpecOrStatusUpdateRequired(tc.resource, tc.original) if actual != tc.expectedResult { t.Fatalf("got %v, want %v", actual, tc.expectedResult) diff --git a/pkg/krmtotf/fetchlivestate_test.go b/pkg/krmtotf/fetchlivestate_test.go index d169dcb26f..6bf7983b95 100644 --- a/pkg/krmtotf/fetchlivestate_test.go +++ b/pkg/krmtotf/fetchlivestate_test.go @@ -33,10 +33,11 @@ import ( func TestWithFieldsPresetForRead(t *testing.T) { nowTime := metav1.Now() tests := []struct { - name string - imported map[string]interface{} - resource *krmtotf.Resource - expectedRet map[string]interface{} + name string + imported map[string]interface{} + resource *krmtotf.Resource + resourceStatus map[string]interface{} + expectedRet map[string]interface{} }{ { name: "immutable fields", @@ -540,28 +541,7 @@ func TestWithFieldsPresetForRead(t *testing.T) { "imported_field": "imported_val", }, resource: &krmtotf.Resource{ - Resource: k8s.Resource{ - Status: map[string]interface{}{ - "primitiveField": "val_b", - "listOfPrimitivesField": []interface{}{ - "list_of_primitives_val_0", - }, - "mapField": map[string]interface{}{ - "map_key_a": "map_val_a", - }, - "nestedObjectField": map[string]interface{}{ - "field": "val", - }, - "listOfObjectsField": []interface{}{ - map[string]interface{}{ - "fieldA": "val_a", - }, - map[string]interface{}{ - "fieldB": "val_b", - }, - }, - }, - }, + Resource: k8s.Resource{}, TFResource: &tfschema.Resource{ Schema: map[string]*tfschema.Schema{ "imported_field": { @@ -606,6 +586,27 @@ func TestWithFieldsPresetForRead(t *testing.T) { }, }, }, + + resourceStatus: map[string]interface{}{ + "primitiveField": "val_b", + "listOfPrimitivesField": []interface{}{ + "list_of_primitives_val_0", + }, + "mapField": map[string]interface{}{ + "map_key_a": "map_val_a", + }, + "nestedObjectField": map[string]interface{}{ + "field": "val", + }, + "listOfObjectsField": []interface{}{ + map[string]interface{}{ + "fieldA": "val_a", + }, + map[string]interface{}{ + "fieldB": "val_b", + }, + }, + }, expectedRet: map[string]interface{}{ "imported_field": "imported_val", "primitive_field": "val_b", @@ -641,28 +642,6 @@ func TestWithFieldsPresetForRead(t *testing.T) { Kind: "TestKind", APIVersion: "test.cnrm.cloud.google.com/v1beta1", }, - Status: map[string]interface{}{ - "observedState": map[string]interface{}{ - "primitiveField": "val_b", - "listOfPrimitivesField": []interface{}{ - "list_of_primitives_val_0", - }, - "mapField": map[string]interface{}{ - "map_key_a": "map_val_a", - }, - "nestedObjectField": map[string]interface{}{ - "field": "val", - }, - "listOfObjectsField": []interface{}{ - map[string]interface{}{ - "fieldA": "val_a", - }, - map[string]interface{}{ - "fieldB": "val_b", - }, - }, - }, - }, }, TFResource: &tfschema.Resource{ Schema: map[string]*tfschema.Schema{ @@ -708,6 +687,28 @@ func TestWithFieldsPresetForRead(t *testing.T) { }, }, }, + resourceStatus: map[string]interface{}{ + "observedState": map[string]interface{}{ + "primitiveField": "val_b", + "listOfPrimitivesField": []interface{}{ + "list_of_primitives_val_0", + }, + "mapField": map[string]interface{}{ + "map_key_a": "map_val_a", + }, + "nestedObjectField": map[string]interface{}{ + "field": "val", + }, + "listOfObjectsField": []interface{}{ + map[string]interface{}{ + "fieldA": "val_a", + }, + map[string]interface{}{ + "fieldB": "val_b", + }, + }, + }, + }, expectedRet: map[string]interface{}{ "imported_field": "imported_val", "primitive_field": "val_b", @@ -781,6 +782,7 @@ func TestWithFieldsPresetForRead(t *testing.T) { testcontroller.EnsureNamespaceExistsT(t, c, testID) tc.resource.SetNamespace(testID) + tc.resource.SetStatus(tc.resourceStatus) ret, err := krmtotf.WithFieldsPresetForRead(tc.imported, tc.resource, mgr.GetClient(), nil) if err != nil { t.Fatal(err) diff --git a/pkg/krmtotf/references.go b/pkg/krmtotf/references.go index 5690093adc..b90ca4c656 100644 --- a/pkg/krmtotf/references.go +++ b/pkg/krmtotf/references.go @@ -42,6 +42,14 @@ func GetReferencedResource(r *Resource, typeConfig corekccv1alpha1.TypeConfig, if err := util.Marshal(u, rsrc); err != nil { return nil, fmt.Errorf("error parsing %v", u.GetName()) } + statusObj := u.Object["status"] + if statusObj != nil { + statusObjInMap, ok := statusObj.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("expected status value for referenced resource %v with GroupVersionKind %v to be map[string]interface{} but was actually %T", k8s.GetNamespacedName(u), u.GroupVersionKind(), statusObj) + } + rsrc.SetStatus(statusObjInMap) + } if typeConfig.DCLBasedResource { return rsrc, nil } diff --git a/pkg/krmtotf/resource.go b/pkg/krmtotf/resource.go index ef7a455f51..50a6fd3da9 100644 --- a/pkg/krmtotf/resource.go +++ b/pkg/krmtotf/resource.go @@ -248,7 +248,7 @@ func (r *Resource) GetServerGeneratedID() (string, error) { // If the resource doesn't support a server-generated `spec.resourceID` or // if the field is not specified, fallback to resolve it from status. - idInStatus, exists, err := getServerGeneratedIDFromStatus(&r.ResourceConfig, r.GroupVersionKind(), r.Status) + idInStatus, exists, err := getServerGeneratedIDFromStatus(&r.ResourceConfig, r.GroupVersionKind(), r.GetStatus()) if err != nil { return "", fmt.Errorf("error getting server-generated ID: %w", err) } @@ -336,9 +336,9 @@ func getObservedStateFromStatus(status map[string]interface{}) map[string]interf func (r *Resource) GetStatusOrObservedState() map[string]interface{} { if k8s.OutputOnlyFieldsAreUnderObservedState(r.Kind, r.GroupVersionKind().Version) { - return getObservedStateFromStatus(r.Status) + return getObservedStateFromStatus(r.GetStatus()) } - return r.Status + return r.GetStatus() } func SupportsResourceIDField(rc *corekccv1alpha1.ResourceConfig) bool { diff --git a/pkg/krmtotf/resource_test.go b/pkg/krmtotf/resource_test.go index 26f7facbc8..b639c4a124 100644 --- a/pkg/krmtotf/resource_test.go +++ b/pkg/krmtotf/resource_test.go @@ -674,12 +674,12 @@ func TestResource_GetImportID(t *testing.T) { bar := test.NewBarUnstructured("my-resource", testID, corev1.ConditionTrue) r.SetAnnotations(bar.GetAnnotations()) r.Spec = tc.spec - r.Status = tc.status if r.Spec == nil { r.Spec = bar.Object["spec"].(map[string]interface{}) } - if r.Status == nil { - r.Status = bar.Object["status"].(map[string]interface{}) + r.SetStatus(tc.status) + if r.GetStatus() == nil { + r.SetStatus(bar.Object["status"].(map[string]interface{})) } for _, obj := range tc.referencedResources { obj.SetNamespace(testID) @@ -1065,7 +1065,7 @@ func TestResource_ConstructServerGeneratedIDInStatusFromResourceID(t *testing.T) bar := test.NewBarUnstructured("test", "", corev1.ConditionTrue) r.SetAnnotations(bar.GetAnnotations()) r.Spec = tc.spec - r.Status = tc.status + r.SetStatus(tc.status) result, err := r.ConstructServerGeneratedIDInStatusFromResourceID(c, smLoader) if tc.hasError { @@ -1083,7 +1083,7 @@ func TestResource_ConstructServerGeneratedIDInStatusFromResourceID(t *testing.T) if got, want := r.Spec, tc.expectedSpec; !test.Equals(t, got, want) { t.Fatalf("got: %v, want: %v", got, want) } - if got, want := r.Status, tc.expectedStatus; !test.Equals(t, got, want) { + if got, want := r.GetStatus(), tc.expectedStatus; !test.Equals(t, got, want) { t.Fatalf("got: %v, want: %v", got, want) } }) @@ -1341,9 +1341,9 @@ func TestResource_GetServerGeneratedID(t *testing.T) { if r.Spec == nil { r.Spec = map[string]interface{}{} } - r.Status = tc.status - if r.Status == nil { - r.Status = map[string]interface{}{} + r.SetStatus(tc.status) + if r.GetStatus() == nil { + r.SetStatus(map[string]interface{}{}) } id, err := r.GetServerGeneratedID() @@ -1492,9 +1492,9 @@ func TestResource_GetResourceID(t *testing.T) { if r.Spec == nil { r.Spec = map[string]interface{}{} } - r.Status = tc.status - if r.Status == nil { - r.Status = map[string]interface{}{} + r.SetStatus(tc.status) + if r.GetStatus() == nil { + r.SetStatus(map[string]interface{}{}) } id, err := r.GetResourceID() diff --git a/pkg/krmtotf/tftokrm.go b/pkg/krmtotf/tftokrm.go index 296cc0b289..146adbb223 100644 --- a/pkg/krmtotf/tftokrm.go +++ b/pkg/krmtotf/tftokrm.go @@ -100,10 +100,10 @@ func GetSpecAndStatusFromState(resource *Resource, state *terraform.InstanceStat if location, ok := getLocationValueFromResourceOrState(resource, unmodifiedState); ok { spec["location"] = location } - if conditions, ok := resource.Status["conditions"]; ok { + if conditions, ok := resource.GetStatus()["conditions"]; ok { status["conditions"] = deepcopy.DeepCopy(conditions) } - if observedGeneration, ok := resource.Status["observedGeneration"]; ok { + if observedGeneration, ok := resource.GetStatus()["observedGeneration"]; ok { status["observedGeneration"] = deepcopy.DeepCopy(observedGeneration) } if resource.ResourceConfig.ObservedFields != nil { diff --git a/pkg/krmtotf/tftokrm_test.go b/pkg/krmtotf/tftokrm_test.go index 191e675be3..452169ede8 100644 --- a/pkg/krmtotf/tftokrm_test.go +++ b/pkg/krmtotf/tftokrm_test.go @@ -1583,7 +1583,7 @@ func TestResolveSpecAndStatusWithResourceID_WithDesiredStateInSpecAndObservedSta r.SetName(tc.metadataName) } r.Spec = tc.prevSpec - r.Status = tc.prevStatus + r.SetStatus(tc.prevStatus) r.TFResource = tc.tfResource r.ManagedFields = tc.managedFields if tc.rc != nil { @@ -1899,7 +1899,7 @@ func TestResolveSpecAndStatusWithResourceID(t *testing.T) { r.SetName(tc.metadataName) } r.Spec = tc.prevSpec - r.Status = tc.prevStatus + r.SetStatus(tc.prevStatus) r.TFResource = tc.tfResource if tc.rc != nil { r.ResourceConfig = *tc.rc @@ -2077,7 +2077,7 @@ func TestResolveSpecAndStatusWithResourceIDPanic(t *testing.T) { t.Parallel() r := resourceSkeleton() r.Spec = tc.prevSpec - r.Status = tc.prevStatus + r.SetStatus(tc.prevStatus) r.TFResource = tc.tfResource if tc.rc != nil { r.ResourceConfig = *tc.rc @@ -2654,7 +2654,7 @@ func TestResolveSpecAndStatusContainingObservedState(t *testing.T) { r.SetAnnotations(tc.annotations) } r.Spec = tc.prevSpec - r.Status = tc.prevStatus + r.SetStatus(tc.prevStatus) r.TFResource = tc.tfResource if tc.rc != nil { r.ResourceConfig = *tc.rc diff --git a/pkg/lease/leaser/leaser.go b/pkg/lease/leaser/leaser.go index ddbf44b6ab..628a3099ad 100644 --- a/pkg/lease/leaser/leaser.go +++ b/pkg/lease/leaser/leaser.go @@ -204,8 +204,9 @@ func (l *Leaser) getResourceAndLiveState(ctx context.Context, u *unstructured.Un } resource.Labels = krmtotf.GetLabelsFromState(resource, liveState) resource.Annotations = krmtotf.GetAnnotationsFromState(resource, liveState) - resource.Spec, resource.Status = krmtotf.ResolveSpecAndStatusWithResourceID(resource, liveState) - + spec, status := krmtotf.ResolveSpecAndStatusWithResourceID(resource, liveState) + resource.Spec = spec + resource.SetStatus(status) return resource, liveState, nil } diff --git a/pkg/resourceoverrides/redis_instance.go b/pkg/resourceoverrides/redis_instance.go index 52eab957c1..52a54c63ad 100644 --- a/pkg/resourceoverrides/redis_instance.go +++ b/pkg/resourceoverrides/redis_instance.go @@ -50,8 +50,8 @@ func copyMaintenanceScheduleFieldToSpec() ResourceOverride { return nil } o.PostActuationTransform = func(original, reconciled *k8s.Resource, tfState *terraform.InstanceState, dclState *unstructured.Unstructured) error { - if reconciled.Status["maintenanceSchedule"] != nil { - reconciled.Spec["maintenanceSchedule"] = reconciled.Status["maintenanceSchedule"] + if reconciled.GetStatus()["maintenanceSchedule"] != nil { + reconciled.Spec["maintenanceSchedule"] = reconciled.GetStatus()["maintenanceSchedule"] } return nil } diff --git a/pkg/resourceoverrides/sql_instance.go b/pkg/resourceoverrides/sql_instance.go index e0131e3cd7..63d591eb97 100644 --- a/pkg/resourceoverrides/sql_instance.go +++ b/pkg/resourceoverrides/sql_instance.go @@ -51,7 +51,7 @@ func copyInstanceTypeFieldToStatus() ResourceOverride { } o.PostActuationTransform = func(original, reconciled *k8s.Resource, tfState *terraform.InstanceState, dclState *unstructured.Unstructured) error { if tfState != nil { - reconciled.Status["instanceType"] = tfState.Attributes["instance_type"] + reconciled.GetStatus()["instanceType"] = tfState.Attributes["instance_type"] } return nil diff --git a/pkg/resourceskeleton/resourceskeleton.go b/pkg/resourceskeleton/resourceskeleton.go index 3dded360fd..b012353ba4 100644 --- a/pkg/resourceskeleton/resourceskeleton.go +++ b/pkg/resourceskeleton/resourceskeleton.go @@ -121,7 +121,9 @@ func tfStateToResource(state *terraform.InstanceState, sm *v1alpha1.ServiceMappi } resource.SetGroupVersionKind(gvk) - resource.Spec, resource.Status = krmtotf.GetSpecAndStatusFromState(resource, state) + spec, status := krmtotf.GetSpecAndStatusFromState(resource, state) + resource.Spec = spec + resource.SetStatus(status) resource.Labels = krmtotf.GetLabelsFromState(resource, state) resource.Annotations = krmtotf.GetAnnotationsFromState(resource, state) resource.Name = krmtotf.GetNameFromState(resource, state) diff --git a/pkg/test/resourcefixture/contexts/register.go b/pkg/test/resourcefixture/contexts/register.go index 548e9941d6..e625dc4a78 100644 --- a/pkg/test/resourcefixture/contexts/register.go +++ b/pkg/test/resourcefixture/contexts/register.go @@ -270,13 +270,15 @@ func dclStateToKRM(resource *dcl.Resource, liveState *unstructured.Unstructured, return nil, err } resource.Spec = spec - resource.Status = status + resource.SetStatus(status) resource.Labels = liveState.GetLabels() return resource.MarshalAsUnstructured() } func resourceToKRM(resource *krmtotf.Resource, state *terraform.InstanceState) (*unstructured.Unstructured, error) { - resource.Spec, resource.Status = krmtotf.ResolveSpecAndStatusWithResourceID(resource, state) + spec, status := krmtotf.ResolveSpecAndStatusWithResourceID(resource, state) + resource.Spec = spec + resource.SetStatus(status) resource.Labels = krmtotf.GetLabelsFromState(resource, state) // Apply post-actuation transformation. if err := resourceoverrides.Handler.PostActuationTransform(resource.Original, &resource.Resource, state, nil); err != nil { From cc1148efa356c4dab0c8382b66cd82c724e78a19 Mon Sep 17 00:00:00 2001 From: Joyce Ma Date: Tue, 26 Mar 2024 04:25:58 +0000 Subject: [PATCH 2/2] Add nil check when marshaling status as unstructured --- pkg/k8s/resource.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/k8s/resource.go b/pkg/k8s/resource.go index 8a5fda1985..0a0701bfb8 100644 --- a/pkg/k8s/resource.go +++ b/pkg/k8s/resource.go @@ -85,7 +85,9 @@ func (r *Resource) MarshalAsUnstructured() (*unstructured.Unstructured, error) { if err := util.Marshal(r, u); err != nil { return nil, fmt.Errorf("error marshing resource to Unstructured %w", err) } - u.Object["status"] = r.status + if r.status != nil { + u.Object["status"] = r.status + } removeNilCreationTimestamp(u.Object) return u, nil }