diff --git a/pkg/resource/function/references.go b/pkg/resource/function/references.go index 4d292744..3361968e 100644 --- a/pkg/resource/function/references.go +++ b/pkg/resource/function/references.go @@ -94,6 +94,66 @@ func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) ack return &resource{ko} } +// EnsureReferences restores, onto a copy of `latest`, the cross-resource reference +// (*Ref) fields it is missing, taking them from `desired`. Only reference fields are +// written, so every concrete value on `latest` stands. +// +// A *Ref is a sibling of the concrete field it resolves into, so rebuilding the +// containing struct from an AWS API response drops it. That disables +// ClearResolvedReferences, which suppresses a resolved value only while the sibling +// *Ref is visible, so the spec patch would otherwise delete the declared *Ref and +// store the resolved value in its place. +// +// Only references reached through structs are restored, and each containing struct +// is created on `latest` when `desired` has it and `latest` does not -- generated +// set-output code nils a struct when the response omits it. A top-level *Ref needs +// no help, since generated set-output code overwrites only the concrete field. One +// reached through a list is not restored: it has no fixed address, and replacing the +// whole list would discard whatever the service populated inside it. +// +// Nothing is written unless `desired` actually holds the reference, so a source that +// declares none leaves `latest` untouched. +func (rm *resourceManager) EnsureReferences( + desired acktypes.AWSResource, + latest acktypes.AWSResource, +) acktypes.AWSResource { + // Deep copy the source as well, so a reference handed over below does not + // alias the caller's declared object. + desiredKO := rm.concreteResource(desired).ko.DeepCopy() + latestKO := rm.concreteResource(latest).ko.DeepCopy() + + if desiredKO.Spec.Code != nil { + if desiredKO.Spec.Code.S3BucketRef != nil { + if latestKO.Spec.Code == nil { + latestKO.Spec.Code = &svcapitypes.FunctionCode{} + } + if latestKO.Spec.Code.S3BucketRef == nil { + latestKO.Spec.Code.S3BucketRef = desiredKO.Spec.Code.S3BucketRef + } + } + } + if desiredKO.Spec.VPCConfig != nil { + if len(desiredKO.Spec.VPCConfig.SecurityGroupRefs) > 0 { + if latestKO.Spec.VPCConfig == nil { + latestKO.Spec.VPCConfig = &svcapitypes.VPCConfig{} + } + if len(latestKO.Spec.VPCConfig.SecurityGroupRefs) == 0 { + latestKO.Spec.VPCConfig.SecurityGroupRefs = desiredKO.Spec.VPCConfig.SecurityGroupRefs + } + } + if len(desiredKO.Spec.VPCConfig.SubnetRefs) > 0 { + if latestKO.Spec.VPCConfig == nil { + latestKO.Spec.VPCConfig = &svcapitypes.VPCConfig{} + } + if len(latestKO.Spec.VPCConfig.SubnetRefs) == 0 { + latestKO.Spec.VPCConfig.SubnetRefs = desiredKO.Spec.VPCConfig.SubnetRefs + } + } + } + + return &resource{latestKO} +} + // ResolveReferences finds if there are any Reference field(s) present // inside AWSResource passed in the parameter and attempts to resolve those // reference field(s) into their respective target field(s). It returns a diff --git a/pkg/resource/layer_version/references.go b/pkg/resource/layer_version/references.go index a3b10f59..f9b3c805 100644 --- a/pkg/resource/layer_version/references.go +++ b/pkg/resource/layer_version/references.go @@ -51,6 +51,48 @@ func (rm *resourceManager) ClearResolvedReferences(res acktypes.AWSResource) ack return &resource{ko} } +// EnsureReferences restores, onto a copy of `latest`, the cross-resource reference +// (*Ref) fields it is missing, taking them from `desired`. Only reference fields are +// written, so every concrete value on `latest` stands. +// +// A *Ref is a sibling of the concrete field it resolves into, so rebuilding the +// containing struct from an AWS API response drops it. That disables +// ClearResolvedReferences, which suppresses a resolved value only while the sibling +// *Ref is visible, so the spec patch would otherwise delete the declared *Ref and +// store the resolved value in its place. +// +// Only references reached through structs are restored, and each containing struct +// is created on `latest` when `desired` has it and `latest` does not -- generated +// set-output code nils a struct when the response omits it. A top-level *Ref needs +// no help, since generated set-output code overwrites only the concrete field. One +// reached through a list is not restored: it has no fixed address, and replacing the +// whole list would discard whatever the service populated inside it. +// +// Nothing is written unless `desired` actually holds the reference, so a source that +// declares none leaves `latest` untouched. +func (rm *resourceManager) EnsureReferences( + desired acktypes.AWSResource, + latest acktypes.AWSResource, +) acktypes.AWSResource { + // Deep copy the source as well, so a reference handed over below does not + // alias the caller's declared object. + desiredKO := rm.concreteResource(desired).ko.DeepCopy() + latestKO := rm.concreteResource(latest).ko.DeepCopy() + + if desiredKO.Spec.Content != nil { + if desiredKO.Spec.Content.S3BucketRef != nil { + if latestKO.Spec.Content == nil { + latestKO.Spec.Content = &svcapitypes.LayerVersionContentInput{} + } + if latestKO.Spec.Content.S3BucketRef == nil { + latestKO.Spec.Content.S3BucketRef = desiredKO.Spec.Content.S3BucketRef + } + } + } + + return &resource{latestKO} +} + // ResolveReferences finds if there are any Reference field(s) present // inside AWSResource passed in the parameter and attempts to resolve those // reference field(s) into their respective target field(s). It returns a