From e4cd30f377ff070ebcf5394c27ed21528cd2a9ab Mon Sep 17 00:00:00 2001 From: Barni S Date: Wed, 5 Feb 2025 03:08:39 -0500 Subject: [PATCH] Add support for external references. - Add ReadOnly flag to resource. True indicates an external resource we want to read (no create/update/delete) - Reuse template to describe the external resource (gvkn). Reason: we could templatize the name part of the external resource - Use as much of existing resource reconcile flow as possible. Reason: we can optionally support ReadyWhen and IncludeWhen rules for the external resource --- api/v1alpha1/types.go | 5 ++++- .../crd/bases/kro.run_resourcegraphdefinitions.yaml | 5 ++++- helm/crds/kro.run_resourcegraphdefinitions.yaml | 5 ++++- pkg/controller/instance/controller_reconcile.go | 12 ++++++++++++ pkg/graph/builder.go | 1 + pkg/graph/resource.go | 8 ++++++++ pkg/runtime/interfaces.go | 4 ++++ pkg/runtime/runtime_test.go | 10 ++++++++++ 8 files changed, 47 insertions(+), 3 deletions(-) diff --git a/api/v1alpha1/types.go b/api/v1alpha1/types.go index 77aa01f7..9c844358 100644 --- a/api/v1alpha1/types.go +++ b/api/v1alpha1/types.go @@ -88,8 +88,11 @@ type Validation struct { type Resource struct { // +kubebuilder:validation:Required ID string `json:"id,omitempty"` - // +kubebuilder:validation:Required + // +kubebuilder:validation:Optional Template runtime.RawExtension `json:"template,omitempty"` + // ReadOnly indicates an external resource we want to read and use in the Graph + // +kubebuilder:validation:Optional + ReadOnly bool `json:"readOnly,omitempty"` // +kubebuilder:validation:Optional ReadyWhen []string `json:"readyWhen,omitempty"` // +kubebuilder:validation:Optional diff --git a/config/crd/bases/kro.run_resourcegraphdefinitions.yaml b/config/crd/bases/kro.run_resourcegraphdefinitions.yaml index b26d4d63..e8381bf5 100644 --- a/config/crd/bases/kro.run_resourcegraphdefinitions.yaml +++ b/config/crd/bases/kro.run_resourcegraphdefinitions.yaml @@ -79,6 +79,10 @@ spec: items: type: string type: array + readOnly: + description: ReadOnly indicates an external resource we want + to read and use in the Graph + type: boolean readyWhen: items: type: string @@ -88,7 +92,6 @@ spec: x-kubernetes-preserve-unknown-fields: true required: - id - - template type: object type: array schema: diff --git a/helm/crds/kro.run_resourcegraphdefinitions.yaml b/helm/crds/kro.run_resourcegraphdefinitions.yaml index b26d4d63..e8381bf5 100644 --- a/helm/crds/kro.run_resourcegraphdefinitions.yaml +++ b/helm/crds/kro.run_resourcegraphdefinitions.yaml @@ -79,6 +79,10 @@ spec: items: type: string type: array + readOnly: + description: ReadOnly indicates an external resource we want + to read and use in the Graph + type: boolean readyWhen: items: type: string @@ -88,7 +92,6 @@ spec: x-kubernetes-preserve-unknown-fields: true required: - id - - template type: object type: array schema: diff --git a/pkg/controller/instance/controller_reconcile.go b/pkg/controller/instance/controller_reconcile.go index 789bae10..2a510b7b 100644 --- a/pkg/controller/instance/controller_reconcile.go +++ b/pkg/controller/instance/controller_reconcile.go @@ -196,6 +196,12 @@ func (igr *instanceGraphReconciler) handleResourceReconciliation( } resourceState.State = "SYNCED" + + // For read-only resources, don't perform updates + if igr.runtime.ResourceDescriptor(resourceID).IsReadOnly() { + return nil + } + return igr.updateResource(ctx, rc, resource, observed, resourceID, resourceState) } @@ -354,6 +360,12 @@ func (igr *instanceGraphReconciler) deleteResourcesInOrder(ctx context.Context) continue } + // Skip deletion for read-only resources + if igr.runtime.ResourceDescriptor(resourceID).IsReadOnly() { + igr.state.ResourceStates[resourceID].State = "DELETED" + continue + } + if err := igr.deleteResource(ctx, resourceID); err != nil { return err } diff --git a/pkg/graph/builder.go b/pkg/graph/builder.go index 9de7b045..e2d3fca5 100644 --- a/pkg/graph/builder.go +++ b/pkg/graph/builder.go @@ -340,6 +340,7 @@ func (b *Builder) buildRGResource(rgResource *v1alpha1.Resource, namespacedResou includeWhenExpressions: includeWhen, namespaced: isNamespaced, order: order, + readOnly: rgResource.ReadOnly, }, nil } diff --git a/pkg/graph/resource.go b/pkg/graph/resource.go index 8c39c0e2..c34fb1b3 100644 --- a/pkg/graph/resource.go +++ b/pkg/graph/resource.go @@ -73,6 +73,8 @@ type Resource struct { // order reflects the original order in which the resources were specified, // and lets us keep the client-specified ordering where the dependencies allow. order int + // readOnly indicates if the resource should only be read and not created/updated + readOnly bool } // GetDependencies returns the dependencies of the resource. @@ -164,6 +166,11 @@ func (r *Resource) IsNamespaced() bool { return r.namespaced } +// IsReadOnly returns whether the resource is read-only +func (r *Resource) IsReadOnly() bool { + return r.readOnly +} + // DeepCopy returns a deep copy of the resource. func (r *Resource) DeepCopy() *Resource { return &Resource{ @@ -177,5 +184,6 @@ func (r *Resource) DeepCopy() *Resource { readyWhenExpressions: slices.Clone(r.readyWhenExpressions), includeWhenExpressions: slices.Clone(r.includeWhenExpressions), namespaced: r.namespaced, + readOnly: r.readOnly, } } diff --git a/pkg/runtime/interfaces.go b/pkg/runtime/interfaces.go index 281d9fee..fe026a1e 100644 --- a/pkg/runtime/interfaces.go +++ b/pkg/runtime/interfaces.go @@ -116,6 +116,10 @@ type ResourceDescriptor interface { // IsNamespaced returns true if the resource is namespaced, and false if it's // cluster-scoped. IsNamespaced() bool + + // IsReadOnly returns true if the resource is marked as read only + // This is used for external references + IsReadOnly() bool } // Resource extends `ResourceDescriptor` to include the actual resource data. diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index b042935b..e5367928 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -2613,6 +2613,7 @@ type mockResource struct { conditions []string topLevelFields []string namespaced bool + readOnly bool obj *unstructured.Unstructured } @@ -2656,6 +2657,10 @@ func (m *mockResource) Unstructured() *unstructured.Unstructured { return m.obj } +func (m *mockResource) IsReadOnly() bool { + return m.readOnly +} + type mockResourceOption func(*mockResource) /* func withGVR(group, version, resource string) mockResourceOption { @@ -2686,6 +2691,11 @@ func withReadyExpressions(exprs []string) mockResourceOption { } } +func withReadOnly(ro bool) mockResourceOption { + return func(m *mockResource) { + m.readOnly = ro + } +} func withConditions(conditions []string) mockResourceOption { return func(m *mockResource) { m.conditions = conditions