From 5b5928438dde0f5871d1122d73d43feae4b11dbc Mon Sep 17 00:00:00 2001 From: michaelhtm <98621731+michaelhtm@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:44:18 -0700 Subject: [PATCH] Add support for creating non namespaced resources This change will ensure we find out whether a resource is namespaced or not before attempting to create it --- .../instance/controller_reconcile.go | 12 ++++++++-- internal/resourcegroup/builder.go | 23 ++++++++++++++++++- internal/resourcegroup/resource.go | 1 + internal/resourcegroup/schema/resolver.go | 6 ++--- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/internal/controller/instance/controller_reconcile.go b/internal/controller/instance/controller_reconcile.go index 55988879..c0fbe4d5 100644 --- a/internal/controller/instance/controller_reconcile.go +++ b/internal/controller/instance/controller_reconcile.go @@ -182,8 +182,16 @@ func (igr *InstanceGraphReconciler) reconcileResource(ctx context.Context, resou rUnstructured := igr.runtime.Resources[resourceID] gvr := k8smetadata.GVKtoGVR(resourceMeta.GroupVersionKind) - namespace := igr.getResourceNamespace(resourceID) - rc := igr.client.Resource(gvr).Namespace(namespace) + + var rc dynamic.ResourceInterface + var namespace string + if igr.rg.Resources[resourceID].Namespaced { + namespace = igr.getResourceNamespace(resourceID) + rc = igr.client.Resource(gvr).Namespace(namespace) + } else { + rc = igr.client.Resource(gvr) + namespace = "non-namespaced" + } log.V(1).Info("Checking resource existence", "namespace", namespace, "name", rUnstructured.GetName()) observed, err := rc.Get(ctx, rUnstructured.GetName(), metav1.GetOptions{}) diff --git a/internal/resourcegroup/builder.go b/internal/resourcegroup/builder.go index 53708bac..581b552e 100644 --- a/internal/resourcegroup/builder.go +++ b/internal/resourcegroup/builder.go @@ -21,8 +21,10 @@ import ( extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + k8sSchema "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apiserver/pkg/cel/openapi/resolver" + "k8s.io/client-go/discovery" "k8s.io/client-go/rest" "github.com/aws-controllers-k8s/symphony/api/v1alpha1" @@ -40,7 +42,7 @@ import ( func NewResourceGroupBuilder( clientConfig *rest.Config, ) (*GraphBuilder, error) { - schemaResolver, err := schema.NewCombinedResolver(clientConfig) + schemaResolver, dc, err := schema.NewCombinedResolver(clientConfig) if err != nil { return nil, fmt.Errorf("failed to create schema resolver: %w", err) } @@ -50,6 +52,7 @@ func NewResourceGroupBuilder( rgBuilder := &GraphBuilder{ resourceEmulator: resourceEmulator, schemaResolver: schemaResolver, + discoveryClient: dc, } return rgBuilder, nil } @@ -57,6 +60,7 @@ func NewResourceGroupBuilder( type GraphBuilder struct { schemaResolver resolver.SchemaResolver resourceEmulator *emulator.Emulator + discoveryClient *discovery.DiscoveryClient } func (b *GraphBuilder) NewResourceGroup(rg *v1alpha1.ResourceGroup) (*ResourceGroup, error) { @@ -91,6 +95,20 @@ func (b *GraphBuilder) NewResourceGroup(rg *v1alpha1.ResourceGroup) (*ResourceGr // 3. Extract CEL expressions from the schema. // 4. Build the resource object. + namespacedResources := map[k8sSchema.GroupVersionKind]bool{} + + apiResourceList, err := b.discoveryClient.ServerPreferredNamespacedResources() + if err != nil { + return nil, fmt.Errorf("failed to retrieve Kubernetes namespaced resources: %w", err) + } + + for _, resourceList := range apiResourceList { + for _, r := range resourceList.APIResources { + gvk := k8sSchema.FromAPIVersionAndKind(resourceList.GroupVersion, r.Kind) + namespacedResources[gvk] = r.Namespaced + } + } + // we'll also store the resources in a map for easy access later. resources := make(map[string]*Resource) for _, rgResource := range resourceGroupCR.Spec.Resources { @@ -157,6 +175,8 @@ func (b *GraphBuilder) NewResourceGroup(rg *v1alpha1.ResourceGroup) (*ResourceGr } } + _, isNamespaced := namespacedResources[gvk] + resources[rgResource.Name] = &Resource{ ID: rgResource.Name, GroupVersionKind: gvk, @@ -164,6 +184,7 @@ func (b *GraphBuilder) NewResourceGroup(rg *v1alpha1.ResourceGroup) (*ResourceGr EmulatedObject: emulatedResource, OriginalObject: unstructuredResource, Variables: resourceVariables, + Namespaced: isNamespaced, } } diff --git a/internal/resourcegroup/resource.go b/internal/resourcegroup/resource.go index a4d49e9d..edbf95d0 100644 --- a/internal/resourcegroup/resource.go +++ b/internal/resourcegroup/resource.go @@ -36,6 +36,7 @@ type Resource struct { Variables []*ResourceVariable Dependencies []string + Namespaced bool } func (r *Resource) HasDependency(dep string) bool { diff --git a/internal/resourcegroup/schema/resolver.go b/internal/resourcegroup/schema/resolver.go index 94bdb506..05477b34 100644 --- a/internal/resourcegroup/schema/resolver.go +++ b/internal/resourcegroup/schema/resolver.go @@ -22,10 +22,10 @@ import ( ) // NewCombinedResolver creates a new schema resolver that can resolve both core and client types. -func NewCombinedResolver(clientConfig *rest.Config) (resolver.SchemaResolver, error) { +func NewCombinedResolver(clientConfig *rest.Config) (resolver.SchemaResolver, *discovery.DiscoveryClient, error) { discoveryClient, err := discovery.NewDiscoveryClientForConfig(clientConfig) if err != nil { - return nil, err + return nil, nil, err } // ClientResolver is a resolver that uses the discovery client to resolve @@ -46,5 +46,5 @@ func NewCombinedResolver(clientConfig *rest.Config) (resolver.SchemaResolver, er // Combine the two resolvers to create a single resolver that can resolve // both core and client types. combinedResolver := coreResolver.Combine(clientResolver) - return combinedResolver, nil + return combinedResolver, discoveryClient, nil }