diff --git a/pkg/resource/function/references.go b/pkg/resource/function/references.go index 4d292744..3dfbdabf 100644 --- a/pkg/resource/function/references.go +++ b/pkg/resource/function/references.go @@ -94,6 +94,47 @@ 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 deletes the declared *Ref and stores +// the resolved value in its place. +// +// Only references reached through structs are restored. A top-level *Ref needs no +// help, since every write path starts from a DeepCopy of the object it was handed. +// A *Ref reached through a list is not restored and remains subject to the above: +// it has no fixed address, and replacing the whole list instead would discard +// whatever the service populated inside it. +// +// `desired` must be the declared resource with its references resolved, and must +// not be an object that has been through a resource manager: managers may mutate +// the resource they are handed, and some write API response values into it. +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 && latestKO.Spec.Code != nil && desiredKO.Spec.Code.S3BucketRef != nil && latestKO.Spec.Code.S3BucketRef == nil { + latestKO.Spec.Code.S3BucketRef = desiredKO.Spec.Code.S3BucketRef + } + if desiredKO.Spec.VPCConfig != nil && latestKO.Spec.VPCConfig != nil && len(desiredKO.Spec.VPCConfig.SecurityGroupRefs) > 0 && len(latestKO.Spec.VPCConfig.SecurityGroupRefs) == 0 { + latestKO.Spec.VPCConfig.SecurityGroupRefs = desiredKO.Spec.VPCConfig.SecurityGroupRefs + } + if desiredKO.Spec.VPCConfig != nil && latestKO.Spec.VPCConfig != nil && len(desiredKO.Spec.VPCConfig.SubnetRefs) > 0 && 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..b6eb3e33 100644 --- a/pkg/resource/layer_version/references.go +++ b/pkg/resource/layer_version/references.go @@ -51,6 +51,41 @@ 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 deletes the declared *Ref and stores +// the resolved value in its place. +// +// Only references reached through structs are restored. A top-level *Ref needs no +// help, since every write path starts from a DeepCopy of the object it was handed. +// A *Ref reached through a list is not restored and remains subject to the above: +// it has no fixed address, and replacing the whole list instead would discard +// whatever the service populated inside it. +// +// `desired` must be the declared resource with its references resolved, and must +// not be an object that has been through a resource manager: managers may mutate +// the resource they are handed, and some write API response values into it. +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 && latestKO.Spec.Content != nil && desiredKO.Spec.Content.S3BucketRef != nil && 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