diff --git a/charts/Feature.yaml b/charts/Feature.yaml index a9bc256..e6acb52 100644 --- a/charts/Feature.yaml +++ b/charts/Feature.yaml @@ -38,6 +38,12 @@ values: template: | {{ .Env.tenant_domain | quote }} + clusterAlias: + displayName: Cluster aliases + description: Each entry must be colon separeted key:value cluster aliases (e.g. dev:dev-gcp) + config: + type: string_array + reconcilersToEnable: displayName: Reconcilers to enable description: Comma separated list of reconcilers to enable. Changing this value after the reconciler has been registered will not have any effect. @@ -91,7 +97,7 @@ values: gcp.billingAccount: displayName: Billing account computed: - template: '{{ .Env.billing_account | quote }}' + template: "{{ .Env.billing_account | quote }}" gcp.clusters: displayName: Cluster information @@ -113,7 +119,7 @@ values: gcp.workloadIdentityPoolName: displayName: Google workload identity pool name computed: - template: '{{ .Env.nais_identity_pool_name | quote }}' + template: "{{ .Env.nais_identity_pool_name | quote }}" grafana.endpoint: displayName: Grafana API endpoint diff --git a/charts/templates/deployment.yaml b/charts/templates/deployment.yaml index 1421cac..08a33e0 100644 --- a/charts/templates/deployment.yaml +++ b/charts/templates/deployment.yaml @@ -53,6 +53,8 @@ spec: value: {{ .Values.naisAPI.target }} - name: LISTEN_ADDRESS value: :3005 + - name: CLUSTER_ALIAS + value: {{ .Values.clusterAlias | join "," | quote }} - name: TENANT_DOMAIN value: {{ .Values.tenantDomain }} - name: TENANT_NAME diff --git a/charts/values.yaml b/charts/values.yaml index 98ea872..b031ccd 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -10,6 +10,7 @@ logLevel: info logFormat: json googleManagementProjectID: # mapped in fasit tenantDomain: # mapped in fasit +clusterAlias: [] reconcilersToEnable: "google:gcp:project,google:workspace-admin,nais:namespace,nais:deploy,google:gcp:gar,google:gcp:cdn,grafana" fasit: tenant: diff --git a/internal/cmd/reconciler/config/config.go b/internal/cmd/reconciler/config/config.go index ea075ae..e5d52b5 100644 --- a/internal/cmd/reconciler/config/config.go +++ b/internal/cmd/reconciler/config/config.go @@ -127,6 +127,9 @@ type Config struct { // TenantName The name of the tenant. TenantName string `env:"TENANT_NAME,default=example"` + // ClusterAlias The cluster alias for legacy migration + ClusterAlias map[string]string `env:"CLUSTER_ALIAS"` + // Reconcilers to enable the first time it is registered (one time only) in the NAIS API // If you later would like do enable/disable a reconciler, you can do so through the Console ReconcilersToEnable []string `env:"RECONCILERS_TO_ENABLE"` diff --git a/internal/cmd/reconciler/main.go b/internal/cmd/reconciler/main.go index c8051f4..20012e2 100644 --- a/internal/cmd/reconciler/main.go +++ b/internal/cmd/reconciler/main.go @@ -133,7 +133,7 @@ func run(ctx context.Context, cfg *config.Config, log logrus.FieldLogger) error } log.WithField("duration", time.Since(start).String()).Debug("Created NAIS deploy reconciler") - googleGcpReconciler, err := google_gcp_reconciler.New(ctx, cfg.GCP.Clusters, cfg.GCP.ServiceAccountEmail, cfg.TenantDomain, cfg.TenantName, cfg.GCP.CnrmRole, cfg.GCP.BillingAccount, cfg.FeatureFlags) + googleGcpReconciler, err := google_gcp_reconciler.New(ctx, cfg.GCP.Clusters, cfg.GCP.ServiceAccountEmail, cfg.TenantDomain, cfg.TenantName, cfg.GCP.CnrmRole, cfg.GCP.BillingAccount, cfg.ClusterAlias, cfg.FeatureFlags) if err != nil { return fmt.Errorf("error when creating Google GCP reconciler: %w", err) } diff --git a/internal/reconcilers/google/gcp/reconciler.go b/internal/reconcilers/google/gcp/reconciler.go index 8650614..45bc8bc 100644 --- a/internal/reconcilers/google/gcp/reconciler.go +++ b/internal/reconcilers/google/gcp/reconciler.go @@ -60,6 +60,7 @@ type googleGcpReconciler struct { tenantDomain string tenantName string flags config.FeatureFlags + clusterAlias map[string]string } type OptFunc func(*googleGcpReconciler) @@ -70,7 +71,10 @@ func WithGcpServices(gcpServices *GcpServices) OptFunc { } } -func New(ctx context.Context, clusters gcp.Clusters, serviceAccountEmail, tenantDomain, tenantName, cnrmRoleName, billingAccount string, flags config.FeatureFlags, opts ...OptFunc) (reconcilers.Reconciler, error) { +func New(ctx context.Context, clusters gcp.Clusters, serviceAccountEmail, tenantDomain, tenantName, cnrmRoleName, billingAccount string, clusterAlias map[string]string, flags config.FeatureFlags, opts ...OptFunc) (reconcilers.Reconciler, error) { + if clusterAlias == nil { + clusterAlias = make(map[string]string) + } r := &googleGcpReconciler{ billingAccount: billingAccount, clusters: clusters, @@ -78,6 +82,7 @@ func New(ctx context.Context, clusters gcp.Clusters, serviceAccountEmail, tenant tenantDomain: tenantDomain, tenantName: tenantName, flags: flags, + clusterAlias: clusterAlias, } for _, opt := range opts { @@ -136,6 +141,10 @@ func (r *googleGcpReconciler) Reconcile(ctx context.Context, client *apiclient.A continue } + if _, isAlias := r.clusterAlias[env.EnvironmentName]; isAlias { + continue + } + projectID := GenerateProjectID(r.tenantDomain, env.EnvironmentName, naisTeam.Slug) log.WithField("project_id", projectID).Debugf("generated GCP project ID") teamProject, err := r.getOrCreateProject(ctx, client, projectID, env, cluster.TeamsFolderID, naisTeam) @@ -143,13 +152,22 @@ func (r *googleGcpReconciler) Reconcile(ctx context.Context, client *apiclient.A return fmt.Errorf("get or create a GCP project %q for team %q in environment %q: %w", projectID, naisTeam.Slug, env.EnvironmentName, err) } - _, err = client.Teams().SetTeamEnvironmentExternalReferences(ctx, &protoapi.SetTeamEnvironmentExternalReferencesRequest{ - Slug: naisTeam.Slug, - EnvironmentName: env.EnvironmentName, - GcpProjectId: &teamProject.ProjectId, - }) - if err != nil { - return fmt.Errorf("set GCP project ID for team %q in environment %q: %w", naisTeam.Slug, env.EnvironmentName, err) + envList := []string{env.EnvironmentName} + for alias, original := range r.clusterAlias { + if original == env.EnvironmentName { + envList = append(envList, alias) + } + } + + for _, envName := range envList { + _, err = client.Teams().SetTeamEnvironmentExternalReferences(ctx, &protoapi.SetTeamEnvironmentExternalReferencesRequest{ + Slug: naisTeam.Slug, + EnvironmentName: envName, + GcpProjectId: &teamProject.ProjectId, + }) + if err != nil { + return fmt.Errorf("set GCP project ID for team %q in environment %q: %w", naisTeam.Slug, envName, err) + } } labels := map[string]string{ @@ -217,6 +235,11 @@ func (r *googleGcpReconciler) Delete(ctx context.Context, client *apiclient.APIC continue } + if _, isAlias := r.clusterAlias[env.EnvironmentName]; isAlias { + log.Infof("skipping alias environment %q", env.EnvironmentName) + continue + } + projectID := *env.GcpProjectId log := log.WithField("gcp_project_id", projectID).WithField("environment", env.EnvironmentName) diff --git a/internal/reconcilers/google/gcp/reconciler_test.go b/internal/reconcilers/google/gcp/reconciler_test.go index b0b568b..07d3f92 100644 --- a/internal/reconcilers/google/gcp/reconciler_test.go +++ b/internal/reconcilers/google/gcp/reconciler_test.go @@ -56,6 +56,8 @@ var ( Slug: teamSlug, } ctx = context.Background() + + aliasList = map[string]string{"prodalias": env} ) func TestReconcile(t *testing.T) { @@ -68,7 +70,7 @@ func TestReconcile(t *testing.T) { Return(nil, fmt.Errorf("some error")). Once() - reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) + reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, aliasList, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -82,7 +84,7 @@ func TestReconcile(t *testing.T) { log, _ := logrustest.NewNullLogger() apiClient, _ := apiclient.NewMockClient(t) - reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) + reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, aliasList, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -96,7 +98,7 @@ func TestReconcile(t *testing.T) { log, _ := logrustest.NewNullLogger() apiClient, _ := apiclient.NewMockClient(t) - reconcilers, err := google_gcp_reconciler.New(ctx, gcp.Clusters{}, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) + reconcilers, err := google_gcp_reconciler.New(ctx, gcp.Clusters{}, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, aliasList, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -141,6 +143,14 @@ func TestReconcile(t *testing.T) { }). Return(&protoapi.SetTeamEnvironmentExternalReferencesResponse{}, nil). Once() + mockServer.Teams.EXPECT(). + SetTeamEnvironmentExternalReferences(mock.Anything, &protoapi.SetTeamEnvironmentExternalReferencesRequest{ + Slug: teamSlug, + EnvironmentName: "prodalias", + GcpProjectId: &expectedTeamProjectID, + }). + Return(&protoapi.SetTeamEnvironmentExternalReferencesResponse{}, nil). + Once() srv := test.HttpServerWithHandlers(t, []http.HandlerFunc{ // create project request @@ -503,7 +513,7 @@ func TestReconcile(t *testing.T) { ProjectsRolesService: iamService.Projects.Roles, } - reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, flags, google_gcp_reconciler.WithGcpServices(gcpServices)) + reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, aliasList, flags, google_gcp_reconciler.WithGcpServices(gcpServices)) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -523,7 +533,7 @@ func TestDelete(t *testing.T) { Environments(mock.Anything, &protoapi.ListTeamEnvironmentsRequest{Slug: teamSlug, Limit: 100}). Return(nil, fmt.Errorf("some error")). Once() - reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) + reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, aliasList, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -547,7 +557,7 @@ func TestDelete(t *testing.T) { }, nil). Once() - reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) + reconcilers, err := google_gcp_reconciler.New(ctx, clusters, clusterProjectID, tenantDomain, tenantName, cnrmRoleName, billingAccount, aliasList, config.FeatureFlags{}, google_gcp_reconciler.WithGcpServices(&google_gcp_reconciler.GcpServices{})) if err != nil { t.Fatalf("unexpected error: %v", err) }