diff --git a/apis/core/v1alpha1/annotations.go b/apis/core/v1alpha1/annotations.go index 27c7673..08a5167 100644 --- a/apis/core/v1alpha1/annotations.go +++ b/apis/core/v1alpha1/annotations.go @@ -75,11 +75,12 @@ const ( // resource manager will leave the AWS resource intact when the K8s resource // is deleted. AnnotationDeletionPolicy = AnnotationPrefix + "deletion-policy" - // AnnotationReadOnly is an annotation whose value is a boolean indicating - // whether the resource is read-only. If this annotation is set to true on a - // CR, that means the user is indicating to the ACK service controller that - // the resource is read-only and should not be created/patched/deleted by the - // ACK service controller. + // AnnotationReadOnly is an annotation indicating whether the resource is + // read-only. If this annotation is set to "true" on a CR, the resource + // is read-only and will not be created/patched/deleted in AWS, and its + // K8s Spec will not be synced. If the value is "always", it acts as + // read-only but the controller WILL patch the K8s Spec and Metadata to + // reflect the latest observed state from AWS. AnnotationReadOnly = AnnotationPrefix + "read-only" // AnnotationAdoptionPolicy is an annotation whose value is the identifier for whether // we will attempt adoption only (value = adopt-only) or attempt a create if resource diff --git a/pkg/runtime/reconciler.go b/pkg/runtime/reconciler.go index 83f745d..70cdce4 100644 --- a/pkg/runtime/reconciler.go +++ b/pkg/runtime/reconciler.go @@ -609,11 +609,23 @@ func (r *resourceReconciler) Sync( } else if isReadOnly { delta := r.rd.Delta(desired, latest) if delta.DifferentAt("Spec") { - rlog.Info( - "desired resource state has changed, but resource is read-only - skipping update", - "skipped", true, - "diff", delta.Differences, - ) + if IsReadOnlyAlwaysSync(desired) { + rlog.Info( + "desired resource state has changed, resource is read-only: always - syncing Spec", + "skipped", false, + "diff", delta.Differences, + ) + latest, err = r.patchResourceMetadataAndSpec(ctx, rm, desired, latest) + if err != nil { + return latest, err + } + } else { + rlog.Info( + "desired resource state has changed, but resource is read-only - skipping update", + "skipped", true, + "diff", delta.Differences, + ) + } } return latest, nil } else { diff --git a/pkg/runtime/util.go b/pkg/runtime/util.go index c147485..0b3856c 100644 --- a/pkg/runtime/util.go +++ b/pkg/runtime/util.go @@ -98,7 +98,24 @@ func IsReadOnly(res acktypes.AWSResource) bool { } for k, v := range mo.GetAnnotations() { if k == ackv1alpha1.AnnotationReadOnly { - return strings.ToLower(v) == "true" + val := strings.ToLower(v) + return val == "true" || val == "always" + } + } + return false +} + +// IsReadOnlyAlwaysSync returns true if the supplied AWSResource has the +// read-only annotation set to "always". +func IsReadOnlyAlwaysSync(res acktypes.AWSResource) bool { + mo := res.MetaObject() + if mo == nil { + // Should never happen... if it does, it's buggy code. + panic("IsReadOnlyAlwaysSync received resource with nil RuntimeObject") + } + for k, v := range mo.GetAnnotations() { + if k == ackv1alpha1.AnnotationReadOnly { + return strings.ToLower(v) == "always" } } return false