From 6fced8ca7b04eeb419154195e9908d44af97be91 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Tue, 27 May 2025 12:01:47 -0700 Subject: [PATCH] feat: create relationship between parent cluster and vclusters --- cmd/ctrlc/root/sync/kubernetes/vcluster.go | 54 ++++++++++++++++------ internal/api/resource_provider.go | 5 ++ internal/kinds/db.go | 19 ++++++++ 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/cmd/ctrlc/root/sync/kubernetes/vcluster.go b/cmd/ctrlc/root/sync/kubernetes/vcluster.go index 8831eaf..67f36c4 100644 --- a/cmd/ctrlc/root/sync/kubernetes/vcluster.go +++ b/cmd/ctrlc/root/sync/kubernetes/vcluster.go @@ -9,6 +9,7 @@ import ( "github.com/MakeNowJust/heredoc/v2" "github.com/Masterminds/semver" + "github.com/charmbracelet/log" "github.com/ctrlplanedev/cli/internal/api" "github.com/ctrlplanedev/cli/internal/kinds" @@ -19,10 +20,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - vclusterKind = "VCluster" -) - func getParentClusterResource(ctx context.Context, ctrlplaneClient *api.ClientWithResponses, workspaceId string, clusterIdentifier string) (ClusterResource, error) { clusterResourceResponse, err := ctrlplaneClient.GetResourceByIdentifierWithResponse(ctx, workspaceId, clusterIdentifier) if err != nil { @@ -84,7 +81,7 @@ func generateVclusterMetadata(vcluster find.VCluster, clusterMetadata api.Metada metadata[kinds.VClusterMetadataNamespace] = vcluster.Namespace metadata[kinds.VClusterMetadataStatus] = getNormalizedVclusterStatus(vcluster.Status) metadata[kinds.VClusterMetadataCreated] = vcluster.Created.Format(time.RFC3339) - metadata[kinds.K8SMetadataFlavor] = vclusterKind + metadata[kinds.K8SMetadataFlavor] = "vcluster" if vcluster.Labels != nil { for key, value := range vcluster.Labels { @@ -126,6 +123,10 @@ type ClusterResource struct { Version string } +func generateVclusterKind(clusterResource ClusterResource) string { + return fmt.Sprintf("%s/%s", clusterResource.Kind, kinds.KindVCluster) +} + func getCreateResourceFromVcluster(vcluster find.VCluster, clusterResource ClusterResource) (api.CreateResource, error) { metadata, err := generateVclusterMetadata(vcluster, clusterResource.Metadata) if err != nil { @@ -140,7 +141,7 @@ func getCreateResourceFromVcluster(vcluster find.VCluster, clusterResource Clust resource := api.CreateResource{ Name: fmt.Sprintf("%s/%s/%s", clusterResource.Name, vcluster.Namespace, vcluster.Name), Identifier: fmt.Sprintf("%s/%s/vcluster/%s", clusterResource.Identifier, vcluster.Namespace, vcluster.Name), - Kind: fmt.Sprintf("%s/%s", clusterResource.Kind, vclusterKind), + Kind: generateVclusterKind(clusterResource), Version: "ctrlplane.dev/kubernetes/cluster/v1", Metadata: metadata, Config: generateVclusterConfig(vcluster, clusterResource.Name, clonedParentConfig), @@ -149,6 +150,34 @@ func getCreateResourceFromVcluster(vcluster find.VCluster, clusterResource Clust return resource, nil } +func createResourceRelationshipRule(ctx context.Context, resourceProvider *api.ResourceProvider, clusterResource ClusterResource) error { + var metadataKey string + switch clusterResource.Kind { + case kinds.KindGoogleKubernetesEngine: + metadataKey = kinds.GoogleMetadataSelfLink + case kinds.KindAmazonElasticKubernetesService: + metadataKey = kinds.AWSMetadataARN + case kinds.KindAzureKubernetesService: + metadataKey = kinds.AzureMetadataId + default: + return fmt.Errorf("unsupported cluster kind: %s", clusterResource.Kind) + } + + metadataKeysMatches := []string{metadataKey} + + resourceRelationshipRule := api.CreateResourceRelationshipRule{ + DependencyType: api.ProvisionedIn, + Reference: "vcluster", + TargetKind: clusterResource.Kind, + TargetVersion: clusterResource.Version, + SourceKind: generateVclusterKind(clusterResource), + SourceVersion: clusterResource.Version, + MetadataKeysMatches: &metadataKeysMatches, + } + + return resourceProvider.AddResourceRelationshipRule(ctx, []api.CreateResourceRelationshipRule{resourceRelationshipRule}) +} + func NewSyncVclusterCmd() *cobra.Command { var clusterIdentifier string var providerName string @@ -197,9 +226,6 @@ func NewSyncVclusterCmd() *cobra.Command { if err != nil { return err } - - fmt.Printf("Found %d vclusters in namespace %s\n", len(vclusters), namespace) - if providerName == "" { providerName = fmt.Sprintf("%s-vcluster-scanner", clusterResource.Name) } @@ -218,12 +244,14 @@ func NewSyncVclusterCmd() *cobra.Command { resourcesToUpsert = append(resourcesToUpsert, resource) } - upsertResp, err := rp.UpsertResource(cmd.Context(), resourcesToUpsert) - if err != nil { + if _, err := rp.UpsertResource(cmd.Context(), resourcesToUpsert); err != nil { return fmt.Errorf("failed to upsert resources: %w", err) } - fmt.Printf("Response from upserting resources: %v\n", upsertResp.StatusCode) - fmt.Printf("Upserted %d resources\n", len(resourcesToUpsert)) + log.Infof("Upserted %d resources", len(resourcesToUpsert)) + + if err := createResourceRelationshipRule(cmd.Context(), rp, clusterResource); err != nil { + return fmt.Errorf("failed to create resource relationship rule: %w", err) + } return nil }, } diff --git a/internal/api/resource_provider.go b/internal/api/resource_provider.go index 7be3a27..73bd01c 100644 --- a/internal/api/resource_provider.go +++ b/internal/api/resource_provider.go @@ -71,6 +71,10 @@ func (r *ResourceProvider) AddResourceRelationshipRule(ctx context.Context, rule for _, rule := range rules { rule.WorkspaceId = r.workspaceId resp, err := r.client.CreateResourceRelationshipRuleWithResponse(ctx, rule) + if resp.StatusCode() == http.StatusConflict { + log.Info("Resource relationship rule already exists, skipped creation") + return nil + } if err != nil { return err } @@ -78,5 +82,6 @@ func (r *ResourceProvider) AddResourceRelationshipRule(ctx context.Context, rule return fmt.Errorf("failed to upsert resource relationship rule: %s", string(resp.Body)) } } + log.Info("Successfully created resource relationship rules") return nil } diff --git a/internal/kinds/db.go b/internal/kinds/db.go index 946df8e..cd70365 100644 --- a/internal/kinds/db.go +++ b/internal/kinds/db.go @@ -73,6 +73,18 @@ const ( K8SMetadataFlavor = "kubernetes/flavor" ) +const ( + GoogleMetadataSelfLink = "google/self-link" +) + +const ( + AWSMetadataARN = "aws/arn" +) + +const ( + AzureMetadataId = "azure/id" +) + const ( VClusterMetadataVersion = "vcluster/version" VClusterMetadataVersionMajor = "vcluster/version-major" @@ -83,3 +95,10 @@ const ( VClusterMetadataStatus = "vcluster/status" VClusterMetadataCreated = "vcluster/created" ) + +const ( + KindAmazonElasticKubernetesService = "AmazonElasticKubernetesService" + KindGoogleKubernetesEngine = "GoogleKubernetesEngine" + KindAzureKubernetesService = "AzureKubernetesService" + KindVCluster = "VCluster" +)