From a95613fa7e6ddf649c0d63ae6cd1ba9a2b13fdeb Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 7 Oct 2024 12:39:43 +0200 Subject: [PATCH 01/27] Adding deploy-config step --- pipeline-runner/steps/applyconfig/step.go | 4 +- pipeline-runner/steps/deployconfig/step.go | 175 +++++++++++++++++++++ pkg/apis/deployment/externaldns.go | 19 +-- pkg/apis/pipeline/step_type.go | 3 + 4 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 pipeline-runner/steps/deployconfig/step.go diff --git a/pipeline-runner/steps/applyconfig/step.go b/pipeline-runner/steps/applyconfig/step.go index 30d6e52cc..d1562e8d5 100644 --- a/pipeline-runner/steps/applyconfig/step.go +++ b/pipeline-runner/steps/applyconfig/step.go @@ -16,7 +16,7 @@ import ( "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" "github.com/equinor/radix-operator/pkg/apis/pipeline" - application2 "github.com/equinor/radix-operator/pkg/apis/pipeline/application" + pipelineApplication "github.com/equinor/radix-operator/pkg/apis/pipeline/application" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" validate "github.com/equinor/radix-operator/pkg/apis/radixvalidators" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" @@ -76,7 +76,7 @@ func (cli *ApplyConfigStepImplementation) Run(ctx context.Context, pipelineInfo if !ok { return fmt.Errorf("failed load RadixApplication from ConfigMap") } - ra, err := application2.CreateRadixApplication(ctx, cli.GetRadixclient(), pipelineInfo.PipelineArguments.DNSConfig, configFileContent) + ra, err := pipelineApplication.CreateRadixApplication(ctx, cli.GetRadixclient(), pipelineInfo.PipelineArguments.DNSConfig, configFileContent) if err != nil { return err } diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go new file mode 100644 index 000000000..471521819 --- /dev/null +++ b/pipeline-runner/steps/deployconfig/step.go @@ -0,0 +1,175 @@ +package deploy + +import ( + "context" + "fmt" + + "github.com/equinor/radix-common/utils/slice" + "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" + "github.com/equinor/radix-operator/pipeline-runner/model" + "github.com/equinor/radix-operator/pipeline-runner/steps/internal" + "github.com/equinor/radix-operator/pkg/apis/defaults" + "github.com/equinor/radix-operator/pkg/apis/kube" + "github.com/equinor/radix-operator/pkg/apis/pipeline" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/rs/zerolog/log" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// DeployConfigStepImplementation Step to deploy RD into environment +type DeployConfigStepImplementation struct { + stepType pipeline.StepType + namespaceWatcher watcher.NamespaceWatcher + radixDeploymentWatcher watcher.RadixDeploymentWatcher + model.DefaultStepImplementation +} + +// NewDeployConfigStep Constructor +func NewDeployConfigStep(namespaceWatcher watcher.NamespaceWatcher, radixDeploymentWatcher watcher.RadixDeploymentWatcher) model.Step { + return &DeployConfigStepImplementation{ + stepType: pipeline.DeployConfigStep, + namespaceWatcher: namespaceWatcher, + radixDeploymentWatcher: radixDeploymentWatcher, + } +} + +// ImplementationForType Override of default step method +func (cli *DeployConfigStepImplementation) ImplementationForType() pipeline.StepType { + return cli.stepType +} + +// SucceededMsg Override of default step method +func (cli *DeployConfigStepImplementation) SucceededMsg() string { + return fmt.Sprintf("Succeded: deploy application %s", cli.GetAppName()) +} + +// ErrorMsg Override of default step method +func (cli *DeployConfigStepImplementation) ErrorMsg(err error) string { + return fmt.Sprintf("Failed to deploy application %s. Error: %v", cli.GetAppName(), err) +} + +// Run Override of default step method +func (cli *DeployConfigStepImplementation) Run(ctx context.Context, pipelineInfo *model.PipelineInfo) error { + err := cli.deploy(ctx, pipelineInfo) + return err +} + +type envDeployConfig struct { + externalDNSAliases []radixv1.ExternalAlias +} + +// Deploy Handles deploy step of the pipeline +func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineInfo *model.PipelineInfo) error { + appName := cli.GetAppName() + log.Ctx(ctx).Info().Msgf("Deploying app config %s", appName) + + envConfigToDeploy := cli.getEnvConfigToDeploy(pipelineInfo) + if len(envConfigToDeploy) == 0 { + log.Ctx(ctx).Info().Msg("skip deploy step") + return nil + } + + for envName, envConfig := range envConfigToDeploy { + if err := cli.deployToEnv(ctx, appName, envName, envConfig, pipelineInfo); err != nil { + return err + } + } + return nil +} + +func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appName, envName string, envConfig envDeployConfig, pipelineInfo *model.PipelineInfo) error { + defaultEnvVars := getDefaultEnvVars(pipelineInfo) + + if commitID, ok := defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable]; !ok || len(commitID) == 0 { + defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments + } + + radixApplicationHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) + if err != nil { + return err + } + + buildSecretHash, err := internal.CreateBuildSecretHash(pipelineInfo.BuildSecret) + if err != nil { + return err + } + + activeRd, err := internal.GetActiveRadixDeployment(ctx, cli.GetKubeutil(), utils.GetEnvironmentNamespace(appName, envName)) + if err != nil { + return err + } + radixDeployment, err := constructForTargetEnvironment(pipelineInfo, activeRd, envName) + + if err != nil { + return fmt.Errorf("failed to create Radix deployment in environment %s. %w", envName, err) + } + + namespace := utils.GetEnvironmentNamespace(cli.GetAppName(), envName) + if err = cli.namespaceWatcher.WaitFor(ctx, namespace); err != nil { + return fmt.Errorf("failed to get environment namespace %s, for app %s. %w", namespace, appName, err) + } + + radixDeploymentName := radixDeployment.GetName() + log.Ctx(ctx).Info().Msgf("Apply Radix deployment %s to environment %s", radixDeploymentName, envName) + if _, err = cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Create(context.Background(), radixDeployment, metav1.CreateOptions{}); err != nil { + return fmt.Errorf("failed to apply Radix deployment for app %s to environment %s. %w", appName, envName, err) + } + + if err := cli.radixDeploymentWatcher.WaitForActive(ctx, namespace, radixDeploymentName); err != nil { + log.Ctx(ctx).Error().Err(err).Msgf("Failed to activate Radix deployment %s in environment %s. Deleting deployment", radixDeploymentName, envName) + if err := cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Delete(context.Background(), radixDeploymentName, metav1.DeleteOptions{}); err != nil { + log.Ctx(ctx).Error().Err(err).Msgf("Failed to delete Radix deployment") + } + return err + } + return nil +} + +func constructForTargetEnvironment(pipelineInfo *model.PipelineInfo, activeRd *radixv1.RadixDeployment, envVars radixv1.EnvVarsMap) (interface{}, error) { + commitID := envVars[defaults.RadixCommitHashEnvironmentVariable] + gitTags := envVars[defaults.RadixGitTagsEnvironmentVariable] + radixDeployment := activeRd.DeepCopy() + radixConfigHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) + if err != nil { + return nil, err + } + buildSecretHash, err := internal.CreateBuildSecretHash(pipelineInfo.BuildSecret) + if err != nil { + return nil, err + } + radixDeployment.SetAnnotations(map[string]string{ + kube.RadixGitTagsAnnotation: gitTags, + kube.RadixCommitAnnotation: commitID, + kube.RadixBuildSecretHash: buildSecretHash, + kube.RadixConfigHash: radixConfigHash, + }) + radixDeployment.ObjectMeta.Labels[kube.RadixCommitLabel] = commitID + radixDeployment.ObjectMeta.Labels[kube.RadixCommitLabel] = commitID + radixDeployment.ObjectMeta.Labels[kube.RadixJobNameLabel] = pipelineInfo.PipelineArguments.JobName + return radixDeployment, nil +} + +func (cli *DeployConfigStepImplementation) getEnvConfigToDeploy(pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { + accumulation := make(map[string]envDeployConfig) + return slice.Reduce(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, accumulation, func(acc map[string]envDeployConfig, alias radixv1.ExternalAlias) map[string]envDeployConfig { + deployConfig, ok := acc[alias.Environment] + if !ok { + deployConfig = envDeployConfig{} + } + deployConfig.externalDNSAliases = append(deployConfig.externalDNSAliases, alias) + acc[alias.Environment] = deployConfig + return acc + }) +} + +func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { + gitCommitHash := pipelineInfo.GitCommitHash + gitTags := pipelineInfo.GitTags + + envVarsMap := make(radixv1.EnvVarsMap) + envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = gitCommitHash + envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = gitTags + + return envVarsMap +} diff --git a/pkg/apis/deployment/externaldns.go b/pkg/apis/deployment/externaldns.go index 2d2729cf7..0ff3326b5 100644 --- a/pkg/apis/deployment/externaldns.go +++ b/pkg/apis/deployment/externaldns.go @@ -39,17 +39,18 @@ func (deploy *Deployment) syncExternalDnsResources(ctx context.Context) error { if err := deploy.createOrUpdateExternalDnsCertificate(ctx, externalDns); err != nil { return err } - } else { - if err := deploy.garbageCollectExternalDnsCertificate(ctx, externalDns); err != nil { - return err - } + continue + } - secretName := utils.GetExternalDnsTlsSecretName(externalDns) - if err := deploy.createOrUpdateExternalDnsTlsSecret(ctx, externalDns, secretName); err != nil { - return err - } - secretNames = append(secretNames, secretName) + if err := deploy.garbageCollectExternalDnsCertificate(ctx, externalDns); err != nil { + return err + } + + secretName := utils.GetExternalDnsTlsSecretName(externalDns) + if err := deploy.createOrUpdateExternalDnsTlsSecret(ctx, externalDns, secretName); err != nil { + return err } + secretNames = append(secretNames, secretName) } return deploy.grantAccessToExternalDnsSecrets(ctx, secretNames) diff --git a/pkg/apis/pipeline/step_type.go b/pkg/apis/pipeline/step_type.go index d4d0f04cd..99ee17eb0 100644 --- a/pkg/apis/pipeline/step_type.go +++ b/pkg/apis/pipeline/step_type.go @@ -22,4 +22,7 @@ const ( // RunPipelinesStep Step to run pipelines RunPipelinesStep = "run-pipelines" + + // DeployConfigStep Step to deploy the RD for applied config + DeployConfigStep = "deploy-config" ) From 2fa942abd6c60f62b89e091092ef2cb8d88a54df Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 8 Oct 2024 16:46:10 +0200 Subject: [PATCH 02/27] Added deploy conf step --- pipeline-runner/steps/deploy/step.go | 7 +-- pipeline-runner/steps/deployconfig/step.go | 57 ++++++++++++++-------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/pipeline-runner/steps/deploy/step.go b/pipeline-runner/steps/deploy/step.go index d78ddb3a4..8bba595b8 100644 --- a/pipeline-runner/steps/deploy/step.go +++ b/pipeline-runner/steps/deploy/step.go @@ -12,6 +12,7 @@ import ( radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/rs/zerolog/log" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -122,10 +123,10 @@ func (cli *DeployStepImplementation) deployToEnv(ctx context.Context, appName, e return fmt.Errorf("failed to apply Radix deployment for app %s to environment %s. %w", appName, envName, err) } - if err := cli.radixDeploymentWatcher.WaitForActive(ctx, namespace, radixDeploymentName); err != nil { + if err = cli.radixDeploymentWatcher.WaitForActive(ctx, namespace, radixDeploymentName); err != nil { log.Ctx(ctx).Error().Err(err).Msgf("Failed to activate Radix deployment %s in environment %s. Deleting deployment", radixDeploymentName, envName) - if err := cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Delete(context.Background(), radixDeploymentName, metav1.DeleteOptions{}); err != nil { - log.Ctx(ctx).Error().Err(err).Msgf("Failed to delete Radix deployment") + if deleteErr := cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Delete(context.Background(), radixDeploymentName, metav1.DeleteOptions{}); deleteErr != nil && !k8serrors.IsNotFound(deleteErr) { + log.Ctx(ctx).Error().Err(deleteErr).Msgf("Failed to delete Radix deployment") } return err } diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index 471521819..01ddf824b 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -2,7 +2,10 @@ package deploy import ( "context" + "errors" "fmt" + "strings" + "sync" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" @@ -14,6 +17,7 @@ import ( radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/rs/zerolog/log" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -69,11 +73,32 @@ func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineI log.Ctx(ctx).Info().Msg("skip deploy step") return nil } - + var deployedEnvs, notDeployedEnvs []string + var errs []error + var wg sync.WaitGroup + var mu sync.Mutex for envName, envConfig := range envConfigToDeploy { - if err := cli.deployToEnv(ctx, appName, envName, envConfig, pipelineInfo); err != nil { - return err - } + wg.Add(1) + go func() { + defer wg.Done() + defer mu.Unlock() + if err := cli.deployToEnv(ctx, appName, envName, envConfig, pipelineInfo); err != nil { + mu.Lock() + errs = append(errs, err) + notDeployedEnvs = append(notDeployedEnvs, envName) + } else { + mu.Lock() + deployedEnvs = append(deployedEnvs, envName) + } + }() + } + wg.Wait() + if len(deployedEnvs) == 0 { + return errors.Join(errs...) + } + log.Ctx(ctx).Info().Msgf("Deployed app config %s to environments %v", appName, strings.Join(deployedEnvs, ",")) + if errs != nil { + log.Ctx(ctx).Error().Err(errors.Join(errs...)).Msgf("Failed to deploy app config %s to environments %v", appName, strings.Join(notDeployedEnvs, ",")) } return nil } @@ -85,21 +110,14 @@ func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appN defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments } - radixApplicationHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) - if err != nil { - return err - } - - buildSecretHash, err := internal.CreateBuildSecretHash(pipelineInfo.BuildSecret) - if err != nil { - return err - } - activeRd, err := internal.GetActiveRadixDeployment(ctx, cli.GetKubeutil(), utils.GetEnvironmentNamespace(appName, envName)) if err != nil { return err } - radixDeployment, err := constructForTargetEnvironment(pipelineInfo, activeRd, envName) + if activeRd == nil { + return fmt.Errorf("failed to get active Radix deployment in environment %s - the config cannot be applied", envName) + } + radixDeployment, err := constructForTargetEnvironment(pipelineInfo, activeRd) if err != nil { return fmt.Errorf("failed to create Radix deployment in environment %s. %w", envName, err) @@ -116,17 +134,18 @@ func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appN return fmt.Errorf("failed to apply Radix deployment for app %s to environment %s. %w", appName, envName, err) } - if err := cli.radixDeploymentWatcher.WaitForActive(ctx, namespace, radixDeploymentName); err != nil { + if err = cli.radixDeploymentWatcher.WaitForActive(ctx, namespace, radixDeploymentName); err != nil { log.Ctx(ctx).Error().Err(err).Msgf("Failed to activate Radix deployment %s in environment %s. Deleting deployment", radixDeploymentName, envName) - if err := cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Delete(context.Background(), radixDeploymentName, metav1.DeleteOptions{}); err != nil { - log.Ctx(ctx).Error().Err(err).Msgf("Failed to delete Radix deployment") + if deleteErr := cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Delete(context.Background(), radixDeploymentName, metav1.DeleteOptions{}); deleteErr != nil && !k8serrors.IsNotFound(deleteErr) { + log.Ctx(ctx).Error().Err(deleteErr).Msgf("Failed to delete Radix deployment") } return err } return nil } -func constructForTargetEnvironment(pipelineInfo *model.PipelineInfo, activeRd *radixv1.RadixDeployment, envVars radixv1.EnvVarsMap) (interface{}, error) { +func constructForTargetEnvironment(pipelineInfo *model.PipelineInfo, activeRd *radixv1.RadixDeployment) (*radixv1.RadixDeployment, error) { + envVars := getDefaultEnvVars(pipelineInfo) commitID := envVars[defaults.RadixCommitHashEnvironmentVariable] gitTags := envVars[defaults.RadixGitTagsEnvironmentVariable] radixDeployment := activeRd.DeepCopy() From 70c893c0609814f8565d92e49e952607508e12a3 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 8 Oct 2024 17:05:43 +0200 Subject: [PATCH 03/27] Added deploy conf step to apply config pipeline --- pipeline-runner/pipelines/app.go | 2 ++ pipeline-runner/steps/deployconfig/step.go | 2 +- pkg/apis/pipeline/pipeline.go | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pipeline-runner/pipelines/app.go b/pipeline-runner/pipelines/app.go index 2db39f663..aa621b39f 100644 --- a/pipeline-runner/pipelines/app.go +++ b/pipeline-runner/pipelines/app.go @@ -9,6 +9,7 @@ import ( "github.com/equinor/radix-operator/pipeline-runner/steps/applyconfig" "github.com/equinor/radix-operator/pipeline-runner/steps/build" "github.com/equinor/radix-operator/pipeline-runner/steps/deploy" + "github.com/equinor/radix-operator/pipeline-runner/steps/deployconfig" "github.com/equinor/radix-operator/pipeline-runner/steps/preparepipeline" "github.com/equinor/radix-operator/pipeline-runner/steps/promote" "github.com/equinor/radix-operator/pipeline-runner/steps/runpipeline" @@ -125,6 +126,7 @@ func (cli *PipelineRunner) initStepImplementations(ctx context.Context, registra stepImplementations = append(stepImplementations, build.NewBuildStep(nil)) stepImplementations = append(stepImplementations, runpipeline.NewRunPipelinesStep(nil)) stepImplementations = append(stepImplementations, deploy.NewDeployStep(watcher.NewNamespaceWatcherImpl(cli.kubeclient), watcher.NewRadixDeploymentWatcher(cli.radixclient, time.Minute*5))) + stepImplementations = append(stepImplementations, deployconfig.NewDeployConfigStep(watcher.NewNamespaceWatcherImpl(cli.kubeclient), watcher.NewRadixDeploymentWatcher(cli.radixclient, time.Minute*5))) stepImplementations = append(stepImplementations, promote.NewPromoteStep()) for _, stepImplementation := range stepImplementations { diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index 01ddf824b..dc2c429d9 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -1,4 +1,4 @@ -package deploy +package deployconfig import ( "context" diff --git a/pkg/apis/pipeline/pipeline.go b/pkg/apis/pipeline/pipeline.go index e63fb0d52..32f9e05c6 100644 --- a/pkg/apis/pipeline/pipeline.go +++ b/pkg/apis/pipeline/pipeline.go @@ -40,7 +40,8 @@ func GetSupportedPipelines() []Definition { DeployStep}}, {v1.ApplyConfig, []StepType{ PreparePipelinesStep, - ApplyConfigStep}}, + ApplyConfigStep, + DeployConfigStep}}, } } From 2040019c86bf4c927a49326499c94d09c0f6e0a7 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 9 Oct 2024 16:05:38 +0200 Subject: [PATCH 04/27] Taken only applicable envs to deploy config --- pipeline-runner/steps/deployconfig/step.go | 166 ++++++++++++++---- .../steps/deployconfig/step_test.go | 119 +++++++++++++ 2 files changed, 249 insertions(+), 36 deletions(-) create mode 100644 pipeline-runner/steps/deployconfig/step_test.go diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index dc2c429d9..52a78d1d4 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -60,7 +60,8 @@ func (cli *DeployConfigStepImplementation) Run(ctx context.Context, pipelineInfo } type envDeployConfig struct { - externalDNSAliases []radixv1.ExternalAlias + externalDNSAliases []radixv1.ExternalAlias + activeRadixDeployment *radixv1.RadixDeployment } // Deploy Handles deploy step of the pipeline @@ -68,21 +69,35 @@ func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineI appName := cli.GetAppName() log.Ctx(ctx).Info().Msgf("Deploying app config %s", appName) - envConfigToDeploy := cli.getEnvConfigToDeploy(pipelineInfo) + envConfigToDeploy, err := cli.getEnvConfigToDeploy(ctx, pipelineInfo) if len(envConfigToDeploy) == 0 { - log.Ctx(ctx).Info().Msg("skip deploy step") - return nil + log.Ctx(ctx).Info().Msg("no environments to deploy to") + return err } + deployedEnvs, notDeployedEnvs, err := cli.deployEnvs(ctx, pipelineInfo, envConfigToDeploy) + if len(deployedEnvs) == 0 { + return err + } + log.Ctx(ctx).Info().Msgf("Deployed app config %s to environments %v", appName, strings.Join(deployedEnvs, ",")) + if err != nil { + // some environments failed to deploy, but because some of them where - just log an error and not deployed envs. + log.Ctx(ctx).Error().Err(err).Msgf("Failed to deploy app config %s to environments %v", appName, strings.Join(notDeployedEnvs, ",")) + } + return nil +} + +func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipelineInfo *model.PipelineInfo, envConfigToDeploy map[string]envDeployConfig) ([]string, []string, error) { + appName := cli.GetAppName() var deployedEnvs, notDeployedEnvs []string var errs []error var wg sync.WaitGroup var mu sync.Mutex - for envName, envConfig := range envConfigToDeploy { + for envName, deployConfig := range envConfigToDeploy { wg.Add(1) go func() { defer wg.Done() defer mu.Unlock() - if err := cli.deployToEnv(ctx, appName, envName, envConfig, pipelineInfo); err != nil { + if err := cli.deployToEnv(ctx, appName, envName, deployConfig, pipelineInfo); err != nil { mu.Lock() errs = append(errs, err) notDeployedEnvs = append(notDeployedEnvs, envName) @@ -93,31 +108,17 @@ func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineI }() } wg.Wait() - if len(deployedEnvs) == 0 { - return errors.Join(errs...) - } - log.Ctx(ctx).Info().Msgf("Deployed app config %s to environments %v", appName, strings.Join(deployedEnvs, ",")) - if errs != nil { - log.Ctx(ctx).Error().Err(errors.Join(errs...)).Msgf("Failed to deploy app config %s to environments %v", appName, strings.Join(notDeployedEnvs, ",")) - } - return nil + return deployedEnvs, notDeployedEnvs, errors.Join(errs...) } -func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appName, envName string, envConfig envDeployConfig, pipelineInfo *model.PipelineInfo) error { +func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appName, envName string, deployConfig envDeployConfig, pipelineInfo *model.PipelineInfo) error { defaultEnvVars := getDefaultEnvVars(pipelineInfo) if commitID, ok := defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable]; !ok || len(commitID) == 0 { defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments } - activeRd, err := internal.GetActiveRadixDeployment(ctx, cli.GetKubeutil(), utils.GetEnvironmentNamespace(appName, envName)) - if err != nil { - return err - } - if activeRd == nil { - return fmt.Errorf("failed to get active Radix deployment in environment %s - the config cannot be applied", envName) - } - radixDeployment, err := constructForTargetEnvironment(pipelineInfo, activeRd) + radixDeployment, err := constructForTargetEnvironment(pipelineInfo, deployConfig) if err != nil { return fmt.Errorf("failed to create Radix deployment in environment %s. %w", envName, err) @@ -141,21 +142,31 @@ func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appN } return err } + return nil } -func constructForTargetEnvironment(pipelineInfo *model.PipelineInfo, activeRd *radixv1.RadixDeployment) (*radixv1.RadixDeployment, error) { +func constructForTargetEnvironment(pipelineInfo *model.PipelineInfo, deployConfig envDeployConfig) (*radixv1.RadixDeployment, error) { + radixDeployment := deployConfig.activeRadixDeployment.DeepCopy() + + setExternalDNSesToRadixDeployment(radixDeployment, deployConfig) + if err := setAnnotationsAndLabels(pipelineInfo, radixDeployment); err != nil { + return nil, err + } + return radixDeployment, nil +} + +func setAnnotationsAndLabels(pipelineInfo *model.PipelineInfo, radixDeployment *radixv1.RadixDeployment) error { envVars := getDefaultEnvVars(pipelineInfo) commitID := envVars[defaults.RadixCommitHashEnvironmentVariable] gitTags := envVars[defaults.RadixGitTagsEnvironmentVariable] - radixDeployment := activeRd.DeepCopy() radixConfigHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) if err != nil { - return nil, err + return err } buildSecretHash, err := internal.CreateBuildSecretHash(pipelineInfo.BuildSecret) if err != nil { - return nil, err + return err } radixDeployment.SetAnnotations(map[string]string{ kube.RadixGitTagsAnnotation: gitTags, @@ -166,20 +177,103 @@ func constructForTargetEnvironment(pipelineInfo *model.PipelineInfo, activeRd *r radixDeployment.ObjectMeta.Labels[kube.RadixCommitLabel] = commitID radixDeployment.ObjectMeta.Labels[kube.RadixCommitLabel] = commitID radixDeployment.ObjectMeta.Labels[kube.RadixJobNameLabel] = pipelineInfo.PipelineArguments.JobName - return radixDeployment, nil + return nil } -func (cli *DeployConfigStepImplementation) getEnvConfigToDeploy(pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { - accumulation := make(map[string]envDeployConfig) - return slice.Reduce(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, accumulation, func(acc map[string]envDeployConfig, alias radixv1.ExternalAlias) map[string]envDeployConfig { - deployConfig, ok := acc[alias.Environment] - if !ok { - deployConfig = envDeployConfig{} +func setExternalDNSesToRadixDeployment(radixDeployment *radixv1.RadixDeployment, deployConfig envDeployConfig) { + externalAliasesMap := slice.Reduce(deployConfig.externalDNSAliases, make(map[string][]radixv1.ExternalAlias), func(acc map[string][]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string][]radixv1.ExternalAlias { + acc[dnsExternalAlias.Component] = append(acc[dnsExternalAlias.Component], dnsExternalAlias) + return acc + }) + for _, deployComponent := range radixDeployment.Spec.Components { + deployComponent.ExternalDNS = slice.Map(externalAliasesMap[deployComponent.Name], func(externalAlias radixv1.ExternalAlias) radixv1.RadixDeployExternalDNS { + return radixv1.RadixDeployExternalDNS{ + FQDN: externalAlias.Alias, UseCertificateAutomation: externalAlias.UseCertificateAutomation} + }) + } +} + +func (cli *DeployConfigStepImplementation) getEnvConfigToDeploy(ctx context.Context, pipelineInfo *model.PipelineInfo) (map[string]envDeployConfig, error) { + envDeployConfigs := cli.getEnvDeployConfigs(pipelineInfo) + haveActiveRd, err := cli.setActiveRadixDeployments(ctx, envDeployConfigs) + if err != nil { + return nil, err + } + if !haveActiveRd { + log.Ctx(ctx).Error().Err(err).Msg("Failed to get active Radix deployments") + return nil, err + } + if err := cli.setExternalDNSAliasesToEnvDeployConfigs(pipelineInfo, envDeployConfigs); err != nil { + return nil, err + } + applicableEnvDeployConfig := getApplicableEnvDeployConfig(envDeployConfigs, pipelineInfo) + return applicableEnvDeployConfig, nil +} + +func getApplicableEnvDeployConfig(envDeployConfigs map[string]envDeployConfig, pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { + applicableEnvDeployConfig := make(map[string]envDeployConfig) + for envName, deployConfig := range envDeployConfigs { + appExternalAliases := slice.FindAll(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, func(dnsExternalAlias radixv1.ExternalAlias) bool { return dnsExternalAlias.Environment == envName }) + if equalExternalDNSAliases(deployConfig, appExternalAliases) { + continue + } + applicableEnvDeployConfig[envName] = envDeployConfig{ + externalDNSAliases: appExternalAliases, + activeRadixDeployment: deployConfig.activeRadixDeployment, } - deployConfig.externalDNSAliases = append(deployConfig.externalDNSAliases, alias) - acc[alias.Environment] = deployConfig + + } + return applicableEnvDeployConfig +} + +func equalExternalDNSAliases(deployConfig envDeployConfig, appExternalAliases []radixv1.ExternalAlias) bool { + if len(deployConfig.externalDNSAliases) != len(appExternalAliases) { + return false + } + appExternalAliasesMap := slice.Reduce(deployConfig.externalDNSAliases, make(map[string]radixv1.ExternalAlias), func(acc map[string]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string]radixv1.ExternalAlias { + acc[dnsExternalAlias.Alias] = dnsExternalAlias return acc }) + return slice.All(deployConfig.externalDNSAliases, func(dnsExternalAlias radixv1.ExternalAlias) bool { + appExternalAlias, ok := appExternalAliasesMap[dnsExternalAlias.Alias] + return ok && appExternalAlias.UseCertificateAutomation == dnsExternalAlias.UseCertificateAutomation && appExternalAlias.Component == dnsExternalAlias.Component + }) +} + +func (cli *DeployConfigStepImplementation) getEnvDeployConfigs(pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { + return slice.Reduce(pipelineInfo.RadixApplication.Spec.Environments, make(map[string]envDeployConfig), func(acc map[string]envDeployConfig, env radixv1.Environment) map[string]envDeployConfig { + acc[env.Name] = envDeployConfig{} + return acc + }) +} + +func (cli *DeployConfigStepImplementation) setActiveRadixDeployments(ctx context.Context, envDeployConfigs map[string]envDeployConfig) (bool, error) { + var errs []error + hasActiveRd := false + for envName, deployConfig := range envDeployConfigs { + if activeRd, err := internal.GetActiveRadixDeployment(ctx, cli.GetKubeutil(), utils.GetEnvironmentNamespace(cli.GetAppName(), envName)); err != nil { + errs = append(errs, err) + } else if activeRd != nil { + deployConfig.activeRadixDeployment = activeRd + hasActiveRd = true + } + envDeployConfigs[envName] = deployConfig + } + return hasActiveRd, errors.Join(errs...) +} + +func (cli *DeployConfigStepImplementation) setExternalDNSAliasesToEnvDeployConfigs(pipelineInfo *model.PipelineInfo, envDeployConfigs map[string]envDeployConfig) error { + var errs []error + for _, dnsExternalAlias := range pipelineInfo.RadixApplication.Spec.DNSExternalAlias { + deployConfig, ok := envDeployConfigs[dnsExternalAlias.Environment] + if !ok { + errs = append(errs, fmt.Errorf("environment %s used in external DNS not found in RadixApplication", dnsExternalAlias.Environment)) + continue + } + deployConfig.externalDNSAliases = append(deployConfig.externalDNSAliases, dnsExternalAlias) + envDeployConfigs[dnsExternalAlias.Environment] = deployConfig + } + return errors.Join(errs...) } func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go new file mode 100644 index 000000000..1869f3931 --- /dev/null +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -0,0 +1,119 @@ +package deployconfig_test + +import ( + "context" + "testing" + + "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" + "github.com/equinor/radix-operator/pipeline-runner/model" + "github.com/equinor/radix-operator/pipeline-runner/steps/deploy" + "github.com/equinor/radix-operator/pipeline-runner/steps/deployconfig" + "github.com/equinor/radix-operator/pkg/apis/defaults" + "github.com/equinor/radix-operator/pkg/apis/kube" + "github.com/equinor/radix-operator/pkg/apis/utils" + radix "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + "github.com/golang/mock/gomock" + kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" + prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" + "github.com/stretchr/testify/suite" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubefake "k8s.io/client-go/kubernetes/fake" +) + +func Test_RunDeployConfigTestSuite(t *testing.T) { + suite.Run(t, new(deployConfigTestSuite)) +} + +type deployConfigTestSuite struct { + suite.Suite + kubeClient *kubefake.Clientset + radixClient *radix.Clientset + kedaClient *kedafake.Clientset + promClient *prometheusfake.Clientset + kubeUtil *kube.Kube + ctrl *gomock.Controller +} + +func (s *deployConfigTestSuite) SetupTest() { + s.kubeClient = kubefake.NewSimpleClientset() + s.radixClient = radix.NewSimpleClientset() + s.kedaClient = kedafake.NewSimpleClientset() + s.promClient = prometheusfake.NewSimpleClientset() + s.kubeUtil, _ = kube.New(s.kubeClient, s.radixClient, s.kedaClient, nil) + s.ctrl = gomock.NewController(s.T()) +} + +func (s *deployConfigTestSuite) Test_EmptyTargetEnvironments_SkipDeployment() { + appName := "anyappname" + rr := utils.ARadixRegistration().WithName(appName).BuildRR() + pipelineInfo := &model.PipelineInfo{ + TargetEnvironments: []string{}, + } + namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) + namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Times(0) + radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) + radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + cli := deploy.NewDeployStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) +} + +func (s *deployConfigTestSuite) TestDeploy_() { + const appName, envName, branch, jobName, imageTag, commitId, gitTags = "anyapp", "dev", "master", "anyjobname", "anyimagetag", "anycommit", "gittags" + rr := utils.ARadixRegistration(). + WithName(appName). + BuildRR() + + ra := utils.NewRadixApplicationBuilder(). + WithAppName(appName). + WithEnvironment(envName, branch). + WithEnvironment("prod", ""). + WithDNSExternalAlias("alias1", envName, "app", false). + WithComponents( + utils.AnApplicationComponent().WithName("app"), + ). + BuildRA() + + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + JobName: jobName, + }, + RadixApplication: ra, + GitCommitHash: commitId, + GitTags: gitTags, + } + + namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) + namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(1) + radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) + radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) + cli := deployconfig.NewDeployConfigStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + + rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) + s.Require().Len(rds.Items, 1) + rdDev := rds.Items[0] + + s.Run("validate deployment environment variables", func() { + s.Equal(2, len(rdDev.Spec.Components)) + s.Equal(4, len(rdDev.Spec.Components[1].EnvironmentVariables)) + s.Equal("db-dev", rdDev.Spec.Components[1].EnvironmentVariables["DB_HOST"]) + s.Equal("1234", rdDev.Spec.Components[1].EnvironmentVariables["DB_PORT"]) + s.Equal(commitId, rdDev.Spec.Components[1].EnvironmentVariables[defaults.RadixCommitHashEnvironmentVariable]) + s.Equal(gitTags, rdDev.Spec.Components[1].EnvironmentVariables[defaults.RadixGitTagsEnvironmentVariable]) + s.NotEmpty(rdDev.Annotations[kube.RadixBranchAnnotation]) + s.NotEmpty(rdDev.Labels[kube.RadixCommitLabel]) + s.NotEmpty(rdDev.Labels["radix-job-name"]) + s.Equal(branch, rdDev.Annotations[kube.RadixBranchAnnotation]) + s.Equal(commitId, rdDev.Labels[kube.RadixCommitLabel]) + s.Equal(jobName, rdDev.Labels["radix-job-name"]) + }) + + s.Run("validate dns app alias", func() { + s.True(rdDev.Spec.Components[0].DNSAppAlias) + s.False(rdDev.Spec.Components[1].DNSAppAlias) + }) +} From d51e8fcd8b83339938e49379adcff384d2f18874 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 9 Oct 2024 17:06:48 +0200 Subject: [PATCH 05/27] Added unit-tests --- .../steps/deployconfig/step_test.go | 173 ++++++++++++------ 1 file changed, 116 insertions(+), 57 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 1869f3931..c3d6baad1 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -4,14 +4,15 @@ import ( "context" "testing" + "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" "github.com/equinor/radix-operator/pipeline-runner/steps/deploy" "github.com/equinor/radix-operator/pipeline-runner/steps/deployconfig" - "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" - radix "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" "github.com/golang/mock/gomock" kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" @@ -27,7 +28,7 @@ func Test_RunDeployConfigTestSuite(t *testing.T) { type deployConfigTestSuite struct { suite.Suite kubeClient *kubefake.Clientset - radixClient *radix.Clientset + radixClient *radixfake.Clientset kedaClient *kedafake.Clientset promClient *prometheusfake.Clientset kubeUtil *kube.Kube @@ -36,7 +37,7 @@ type deployConfigTestSuite struct { func (s *deployConfigTestSuite) SetupTest() { s.kubeClient = kubefake.NewSimpleClientset() - s.radixClient = radix.NewSimpleClientset() + s.radixClient = radixfake.NewSimpleClientset() s.kedaClient = kedafake.NewSimpleClientset() s.promClient = prometheusfake.NewSimpleClientset() s.kubeUtil, _ = kube.New(s.kubeClient, s.radixClient, s.kedaClient, nil) @@ -59,61 +60,119 @@ func (s *deployConfigTestSuite) Test_EmptyTargetEnvironments_SkipDeployment() { s.Require().NoError(err) } -func (s *deployConfigTestSuite) TestDeploy_() { - const appName, envName, branch, jobName, imageTag, commitId, gitTags = "anyapp", "dev", "master", "anyjobname", "anyimagetag", "anycommit", "gittags" - rr := utils.ARadixRegistration(). - WithName(appName). - BuildRR() - - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment(envName, branch). - WithEnvironment("prod", ""). - WithDNSExternalAlias("alias1", envName, "app", false). - WithComponents( - utils.AnApplicationComponent().WithName("app"), - ). - BuildRA() +type scenario struct { + name string + raBuilder utils.ApplicationBuilder + existingRadixDeploymentBuilders []utils.DeploymentBuilder + expectedRadixDeploymentBuilders []utils.DeploymentBuilder +} - pipelineInfo := &model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - JobName: jobName, - }, - RadixApplication: ra, - GitCommitHash: commitId, - GitTags: gitTags, +func (s *deployConfigTestSuite) TestDeployConfig() { + const ( + appName = "anyapp" + env1 = "env1" + env2 = "env2" + component1 = "component1" + component2 = "component2" + alias1 = "alias1" + alias2 = "alias2" + jobName = "anyjobname" + commitId = "anycommit" + gitTags = "gittags" + ) + rr := utils.ARadixRegistration().WithName(appName).BuildRR() + + scenarios := []scenario{ + func() scenario { + ts := scenario{ + name: "No active deployments", + raBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). + WithEnvironment(env1, "main"). + WithEnvironment(env2, ""). + WithComponents( + utils.AnApplicationComponent().WithName(component1), + ). + WithDNSExternalAlias(alias1, env1, component1, false), + } + ts.existingRadixDeploymentBuilders = nil + ts.expectedRadixDeploymentBuilders = nil + return ts + }(), } - namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) - namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(1) - radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) - radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) - cli := deployconfig.NewDeployConfigStep(namespaceWatcher, radixDeploymentWatcher) - cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - err := cli.Run(context.Background(), pipelineInfo) - s.Require().NoError(err) + for _, ts := range scenarios { + s.T().Run(ts.name, func(t *testing.T) { + ra := ts.raBuilder.BuildRA() + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + JobName: jobName, + }, + RadixApplication: ra, + GitCommitHash: commitId, + GitTags: gitTags, + } + + s.SetupTest() + + for _, radixDeploymentBuilder := range ts.existingRadixDeploymentBuilders { + rd := radixDeploymentBuilder.BuildRD() + _, err := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(rd.Spec.AppName, rd.Spec.Environment)).Create(context.Background(), rd, metav1.CreateOptions{}) + s.Require().NoError(err) + } + + namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) + namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(len(ra.Spec.Environments)) + radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) + radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(len(ts.expectedRadixDeploymentBuilders)) - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rdDev := rds.Items[0] - - s.Run("validate deployment environment variables", func() { - s.Equal(2, len(rdDev.Spec.Components)) - s.Equal(4, len(rdDev.Spec.Components[1].EnvironmentVariables)) - s.Equal("db-dev", rdDev.Spec.Components[1].EnvironmentVariables["DB_HOST"]) - s.Equal("1234", rdDev.Spec.Components[1].EnvironmentVariables["DB_PORT"]) - s.Equal(commitId, rdDev.Spec.Components[1].EnvironmentVariables[defaults.RadixCommitHashEnvironmentVariable]) - s.Equal(gitTags, rdDev.Spec.Components[1].EnvironmentVariables[defaults.RadixGitTagsEnvironmentVariable]) - s.NotEmpty(rdDev.Annotations[kube.RadixBranchAnnotation]) - s.NotEmpty(rdDev.Labels[kube.RadixCommitLabel]) - s.NotEmpty(rdDev.Labels["radix-job-name"]) - s.Equal(branch, rdDev.Annotations[kube.RadixBranchAnnotation]) - s.Equal(commitId, rdDev.Labels[kube.RadixCommitLabel]) - s.Equal(jobName, rdDev.Labels["radix-job-name"]) - }) - - s.Run("validate dns app alias", func() { - s.True(rdDev.Spec.Components[0].DNSAppAlias) - s.False(rdDev.Spec.Components[1].DNSAppAlias) - }) + cli := deployconfig.NewDeployConfigStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + + rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, env1)).List(context.Background(), metav1.ListOptions{}) + if !s.Assert().Len(rds.Items, len(ts.expectedRadixDeploymentBuilders)) { + return + } + rdsByEnvMap := slice.Reduce(rds.Items, make(map[string]radixv1.RadixDeployment), func(acc map[string]radixv1.RadixDeployment, rd radixv1.RadixDeployment) map[string]radixv1.RadixDeployment { + acc[rd.Spec.Environment] = rd + return acc + }) + + for _, builder := range ts.expectedRadixDeploymentBuilders { + expectedRd := builder.BuildRD() + actualRd, ok := rdsByEnvMap[expectedRd.Spec.Environment] + if !s.True(ok, "Expected RadixDeployment for environment %s not found", expectedRd.Spec.Environment) { + continue + } + if s.Len(actualRd.Spec.Components, len(expectedRd.Spec.Components), "Invalid number of components") { + for i := 0; i < len(expectedRd.Spec.Components); i++ { + s.Equal(expectedRd.Spec.Components[i].Name, actualRd.Spec.Components[i].Name) + for envVarName, envVarValue := range expectedRd.Spec.Components[i].EnvironmentVariables { + s.Equal(envVarValue, actualRd.Spec.Components[i].EnvironmentVariables[envVarName], "Invalid or missing an environment variable %s for the env %s and component %s", envVarName, expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) + } + for j := 0; j < len(expectedRd.Spec.Components[i].ExternalDNS); j++ { + s.Equal(expectedRd.Spec.Components[i].ExternalDNS[j].UseCertificateAutomation, actualRd.Spec.Components[i].ExternalDNS[j].UseCertificateAutomation, "Invalid external UseCertificateAutomation for the env %s and component %s", expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) + s.Equal(expectedRd.Spec.Components[i].ExternalDNS[j].FQDN, actualRd.Spec.Components[i].ExternalDNS[j].FQDN, "Invalid external FQDN for the env %s and component %s", expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) + } + } + } + expectedAnnotations := expectedRd.GetAnnotations() + actualAnnotations := actualRd.GetAnnotations() + if s.Len(actualAnnotations, len(expectedAnnotations), "Invalid number of annotations") { + for key, value := range expectedAnnotations { + s.Equal(expectedAnnotations[key], actualAnnotations[value], "Invalid or missing an annotation %s or value %s", key, value) + } + } + expectedLabels := expectedRd.GetLabels() + actualLabels := actualRd.GetLabels() + if s.Len(actualLabels, len(expectedLabels), "Invalid number of labels") { + for key, value := range expectedLabels { + s.Equal(expectedLabels[key], actualLabels[value], "Invalid or missing an label %s or value %s", key, value) + } + } + // TODO check other props + } + }) + } } From 230f8d75233cc5e327a7b62543783302885dcf29 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 11 Oct 2024 12:15:47 +0200 Subject: [PATCH 06/27] Adding tests --- .../steps/deployconfig/step_test.go | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index c3d6baad1..d1e4442a4 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -7,7 +7,6 @@ import ( "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" - "github.com/equinor/radix-operator/pipeline-runner/steps/deploy" "github.com/equinor/radix-operator/pipeline-runner/steps/deployconfig" "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" @@ -44,27 +43,12 @@ func (s *deployConfigTestSuite) SetupTest() { s.ctrl = gomock.NewController(s.T()) } -func (s *deployConfigTestSuite) Test_EmptyTargetEnvironments_SkipDeployment() { - appName := "anyappname" - rr := utils.ARadixRegistration().WithName(appName).BuildRR() - pipelineInfo := &model.PipelineInfo{ - TargetEnvironments: []string{}, - } - namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) - namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Times(0) - radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) - radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) - cli := deploy.NewDeployStep(namespaceWatcher, radixDeploymentWatcher) - cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - err := cli.Run(context.Background(), pipelineInfo) - s.Require().NoError(err) -} - type scenario struct { name string raBuilder utils.ApplicationBuilder existingRadixDeploymentBuilders []utils.DeploymentBuilder expectedRadixDeploymentBuilders []utils.DeploymentBuilder + affectedEnvs []string } func (s *deployConfigTestSuite) TestDeployConfig() { @@ -79,6 +63,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { jobName = "anyjobname" commitId = "anycommit" gitTags = "gittags" + branch1 = "branch1" ) rr := utils.ARadixRegistration().WithName(appName).BuildRR() @@ -87,7 +72,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { ts := scenario{ name: "No active deployments", raBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env1, "main"). + WithEnvironment(env1, branch1). WithEnvironment(env2, ""). WithComponents( utils.AnApplicationComponent().WithName(component1), @@ -96,6 +81,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { } ts.existingRadixDeploymentBuilders = nil ts.expectedRadixDeploymentBuilders = nil + ts.affectedEnvs = nil return ts }(), } @@ -121,7 +107,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { } namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) - namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(len(ra.Spec.Environments)) + namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(len(ts.affectedEnvs)) radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(len(ts.expectedRadixDeploymentBuilders)) From f64a3f9c2a4aaf1d834e140c2b15dae68a9c44cf Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 14 Oct 2024 09:24:39 +0200 Subject: [PATCH 07/27] Adding tests --- charts/radix-operator/Chart.yaml | 4 +- pipeline-runner/steps/deployconfig/step.go | 62 ++++--- .../steps/deployconfig/step_test.go | 174 +++++++++++++----- .../utils/applicationcomponent_builder.go | 2 +- pkg/apis/utils/deploymentcomponent_builder.go | 9 +- 5 files changed, 166 insertions(+), 85 deletions(-) diff --git a/charts/radix-operator/Chart.yaml b/charts/radix-operator/Chart.yaml index fb337f2ea..367ac549f 100644 --- a/charts/radix-operator/Chart.yaml +++ b/charts/radix-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: radix-operator -version: 1.43.0 -appVersion: 1.63.0 +version: 1.44.0 +appVersion: 1.64.0 kubeVersion: ">=1.24.0" description: Radix Operator keywords: diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index 52a78d1d4..1bb6120b1 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -60,7 +60,7 @@ func (cli *DeployConfigStepImplementation) Run(ctx context.Context, pipelineInfo } type envDeployConfig struct { - externalDNSAliases []radixv1.ExternalAlias + appExternalDNSAliases []radixv1.ExternalAlias activeRadixDeployment *radixv1.RadixDeployment } @@ -118,7 +118,7 @@ func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appN defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments } - radixDeployment, err := constructForTargetEnvironment(pipelineInfo, deployConfig) + radixDeployment, err := constructForTargetEnvironment(appName, envName, pipelineInfo, deployConfig) if err != nil { return fmt.Errorf("failed to create Radix deployment in environment %s. %w", envName, err) @@ -146,8 +146,9 @@ func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appN return nil } -func constructForTargetEnvironment(pipelineInfo *model.PipelineInfo, deployConfig envDeployConfig) (*radixv1.RadixDeployment, error) { +func constructForTargetEnvironment(appName, envName string, pipelineInfo *model.PipelineInfo, deployConfig envDeployConfig) (*radixv1.RadixDeployment, error) { radixDeployment := deployConfig.activeRadixDeployment.DeepCopy() + radixDeployment.SetName(utils.GetDeploymentName(appName, envName, pipelineInfo.PipelineArguments.ImageTag)) setExternalDNSesToRadixDeployment(radixDeployment, deployConfig) if err := setAnnotationsAndLabels(pipelineInfo, radixDeployment); err != nil { @@ -181,7 +182,7 @@ func setAnnotationsAndLabels(pipelineInfo *model.PipelineInfo, radixDeployment * } func setExternalDNSesToRadixDeployment(radixDeployment *radixv1.RadixDeployment, deployConfig envDeployConfig) { - externalAliasesMap := slice.Reduce(deployConfig.externalDNSAliases, make(map[string][]radixv1.ExternalAlias), func(acc map[string][]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string][]radixv1.ExternalAlias { + externalAliasesMap := slice.Reduce(deployConfig.appExternalDNSAliases, make(map[string][]radixv1.ExternalAlias), func(acc map[string][]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string][]radixv1.ExternalAlias { acc[dnsExternalAlias.Component] = append(acc[dnsExternalAlias.Component], dnsExternalAlias) return acc }) @@ -203,22 +204,25 @@ func (cli *DeployConfigStepImplementation) getEnvConfigToDeploy(ctx context.Cont log.Ctx(ctx).Error().Err(err).Msg("Failed to get active Radix deployments") return nil, err } - if err := cli.setExternalDNSAliasesToEnvDeployConfigs(pipelineInfo, envDeployConfigs); err != nil { - return nil, err - } applicableEnvDeployConfig := getApplicableEnvDeployConfig(envDeployConfigs, pipelineInfo) return applicableEnvDeployConfig, nil } +type activeDeploymentExternalDNS struct { + externalDNS radixv1.RadixDeployExternalDNS + componentName string +} + func getApplicableEnvDeployConfig(envDeployConfigs map[string]envDeployConfig, pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { applicableEnvDeployConfig := make(map[string]envDeployConfig) for envName, deployConfig := range envDeployConfigs { - appExternalAliases := slice.FindAll(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, func(dnsExternalAlias radixv1.ExternalAlias) bool { return dnsExternalAlias.Environment == envName }) - if equalExternalDNSAliases(deployConfig, appExternalAliases) { + appExternalDNSAliases := slice.FindAll(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, func(dnsExternalAlias radixv1.ExternalAlias) bool { return dnsExternalAlias.Environment == envName }) + activeDeploymentExternalDNSes := getActiveDeploymentExternalDNSes(deployConfig) + if equalExternalDNSAliases(activeDeploymentExternalDNSes, appExternalDNSAliases) { continue } applicableEnvDeployConfig[envName] = envDeployConfig{ - externalDNSAliases: appExternalAliases, + appExternalDNSAliases: appExternalDNSAliases, activeRadixDeployment: deployConfig.activeRadixDeployment, } @@ -226,17 +230,29 @@ func getApplicableEnvDeployConfig(envDeployConfigs map[string]envDeployConfig, p return applicableEnvDeployConfig } -func equalExternalDNSAliases(deployConfig envDeployConfig, appExternalAliases []radixv1.ExternalAlias) bool { - if len(deployConfig.externalDNSAliases) != len(appExternalAliases) { +func getActiveDeploymentExternalDNSes(deployConfig envDeployConfig) []activeDeploymentExternalDNS { + return slice.Reduce(deployConfig.activeRadixDeployment.Spec.Components, make([]activeDeploymentExternalDNS, 0), + func(acc []activeDeploymentExternalDNS, component radixv1.RadixDeployComponent) []activeDeploymentExternalDNS { + externalDNSes := slice.Map(component.ExternalDNS, func(externalDNS radixv1.RadixDeployExternalDNS) activeDeploymentExternalDNS { + return activeDeploymentExternalDNS{componentName: component.Name, externalDNS: externalDNS} + }) + return append(acc, externalDNSes...) + }) +} + +func equalExternalDNSAliases(activeDeploymentExternalDNSes []activeDeploymentExternalDNS, appExternalAliases []radixv1.ExternalAlias) bool { + if len(activeDeploymentExternalDNSes) != len(appExternalAliases) { return false } - appExternalAliasesMap := slice.Reduce(deployConfig.externalDNSAliases, make(map[string]radixv1.ExternalAlias), func(acc map[string]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string]radixv1.ExternalAlias { + appExternalAliasesMap := slice.Reduce(appExternalAliases, make(map[string]radixv1.ExternalAlias), func(acc map[string]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string]radixv1.ExternalAlias { acc[dnsExternalAlias.Alias] = dnsExternalAlias return acc }) - return slice.All(deployConfig.externalDNSAliases, func(dnsExternalAlias radixv1.ExternalAlias) bool { - appExternalAlias, ok := appExternalAliasesMap[dnsExternalAlias.Alias] - return ok && appExternalAlias.UseCertificateAutomation == dnsExternalAlias.UseCertificateAutomation && appExternalAlias.Component == dnsExternalAlias.Component + return slice.All(activeDeploymentExternalDNSes, func(activeExternalDNS activeDeploymentExternalDNS) bool { + appExternalAlias, ok := appExternalAliasesMap[activeExternalDNS.externalDNS.FQDN] + return ok && + appExternalAlias.UseCertificateAutomation == activeExternalDNS.externalDNS.UseCertificateAutomation && + appExternalAlias.Component == activeExternalDNS.componentName }) } @@ -262,20 +278,6 @@ func (cli *DeployConfigStepImplementation) setActiveRadixDeployments(ctx context return hasActiveRd, errors.Join(errs...) } -func (cli *DeployConfigStepImplementation) setExternalDNSAliasesToEnvDeployConfigs(pipelineInfo *model.PipelineInfo, envDeployConfigs map[string]envDeployConfig) error { - var errs []error - for _, dnsExternalAlias := range pipelineInfo.RadixApplication.Spec.DNSExternalAlias { - deployConfig, ok := envDeployConfigs[dnsExternalAlias.Environment] - if !ok { - errs = append(errs, fmt.Errorf("environment %s used in external DNS not found in RadixApplication", dnsExternalAlias.Environment)) - continue - } - deployConfig.externalDNSAliases = append(deployConfig.externalDNSAliases, dnsExternalAlias) - envDeployConfigs[dnsExternalAlias.Environment] = deployConfig - } - return errors.Join(errs...) -} - func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { gitCommitHash := pipelineInfo.GitCommitHash gitTags := pipelineInfo.GitTags diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index d1e4442a4..69bf05a15 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -2,7 +2,9 @@ package deployconfig_test import ( "context" + "strings" "testing" + "time" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" @@ -16,6 +18,7 @@ import ( kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" "github.com/stretchr/testify/suite" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubefake "k8s.io/client-go/kubernetes/fake" ) @@ -44,88 +47,162 @@ func (s *deployConfigTestSuite) SetupTest() { } type scenario struct { - name string - raBuilder utils.ApplicationBuilder - existingRadixDeploymentBuilders []utils.DeploymentBuilder - expectedRadixDeploymentBuilders []utils.DeploymentBuilder - affectedEnvs []string + name string + existingRaBuilder utils.ApplicationBuilder + applyingRaBuilder utils.ApplicationBuilder + existingActiveRadixDeploymentBuilders []utils.DeploymentBuilder + expectedActiveRadixDeploymentBuilders []utils.DeploymentBuilder + affectedEnvs []string } func (s *deployConfigTestSuite) TestDeployConfig() { const ( - appName = "anyapp" - env1 = "env1" - env2 = "env2" - component1 = "component1" - component2 = "component2" - alias1 = "alias1" - alias2 = "alias2" - jobName = "anyjobname" - commitId = "anycommit" - gitTags = "gittags" - branch1 = "branch1" + appName = "anyapp" + env1 = "env1" + env2 = "env2" + component1 = "component1" + component2 = "component2" + alias1 = "alias1" + alias2 = "alias2" + alias3 = "alias3" + jobName = "anyjobname" + commitId = "anycommit" + gitTags = "gittags" + branch1 = "branch1" + existingImageTag = "existingImageTag" + appliedImageTag = "appliedImageTag" ) rr := utils.ARadixRegistration().WithName(appName).BuildRR() - + timeNow := time.Now() scenarios := []scenario{ - func() scenario { - ts := scenario{ - name: "No active deployments", - raBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env1, branch1). - WithEnvironment(env2, ""). - WithComponents( - utils.AnApplicationComponent().WithName(component1), - ). - WithDNSExternalAlias(alias1, env1, component1, false), - } - ts.existingRadixDeploymentBuilders = nil - ts.expectedRadixDeploymentBuilders = nil - ts.affectedEnvs = nil - return ts - }(), + { + name: "No active deployments, no RA changes", + existingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). + WithEnvironment(env1, branch1). + WithEnvironment(env2, ""). + WithComponents( + utils.AnApplicationComponent().WithName(component1), + ). + WithDNSExternalAlias(alias1, env1, component1, false), + applyingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). + WithEnvironment(env1, branch1). + WithEnvironment(env2, ""). + WithComponents( + utils.AnApplicationComponent().WithName(component1), + ). + WithDNSExternalAlias(alias1, env1, component1, false), + + existingActiveRadixDeploymentBuilders: nil, + expectedActiveRadixDeploymentBuilders: nil, + affectedEnvs: nil, + }, + { + name: "Deleted environment", + existingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). + WithEnvironment(env1, branch1). + WithEnvironment(env2, ""). + WithComponents( + utils.AnApplicationComponent().WithName(component1).WithImageTagName(existingImageTag), + ). + WithDNSExternalAlias(alias1, env1, component1, false). + WithDNSExternalAlias(alias3, env2, component1, false), + applyingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). + WithEnvironment(env1, ""). + WithComponents( + utils.AnApplicationComponent().WithName(component1), + ). + WithDNSExternalAlias(alias2, env1, component1, false), + + existingActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ + utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( + utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias1, UseCertificateAutomation: false})). + WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env2).WithImageTag(existingImageTag).WithComponent( + utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias3, UseCertificateAutomation: false})). + WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + }, + expectedActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ + utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( + utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias1, UseCertificateAutomation: false})). + WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env2).WithImageTag(existingImageTag).WithComponent( + utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias3, UseCertificateAutomation: false})). + WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(appliedImageTag).WithComponent( + utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias2, UseCertificateAutomation: false})), + }, + affectedEnvs: []string{env1}, + }, + { + name: "Orphaned environment, no active deployments, no new deployments", + existingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). + WithEnvironment(env1, branch1). + WithEnvironment(env2, ""). + WithComponents( + utils.AnApplicationComponent().WithName(component1), + ). + WithDNSExternalAlias(alias1, env1, component1, false), + applyingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). + WithEnvironment(env2, ""). + WithComponents( + utils.AnApplicationComponent().WithName(component1), + ). + WithDNSExternalAlias(alias1, env1, component1, false), + existingActiveRadixDeploymentBuilders: nil, + expectedActiveRadixDeploymentBuilders: nil, + affectedEnvs: nil, + }, } for _, ts := range scenarios { s.T().Run(ts.name, func(t *testing.T) { - ra := ts.raBuilder.BuildRA() + t.Logf("Running test case '%s'", ts.name) + existingRa := ts.existingRaBuilder.BuildRA() + _, err := s.radixClient.RadixV1().RadixApplications(utils.GetAppNamespace(existingRa.Name)).Create(context.Background(), existingRa, metav1.CreateOptions{}) + s.Require().NoError(err) pipelineInfo := &model.PipelineInfo{ PipelineArguments: model.PipelineArguments{ - JobName: jobName, + JobName: jobName, + ImageTag: appliedImageTag, }, - RadixApplication: ra, + RadixApplication: ts.applyingRaBuilder.BuildRA(), GitCommitHash: commitId, GitTags: gitTags, } s.SetupTest() - for _, radixDeploymentBuilder := range ts.existingRadixDeploymentBuilders { + for _, radixDeploymentBuilder := range ts.existingActiveRadixDeploymentBuilders { rd := radixDeploymentBuilder.BuildRD() - _, err := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(rd.Spec.AppName, rd.Spec.Environment)).Create(context.Background(), rd, metav1.CreateOptions{}) + namespace := utils.GetEnvironmentNamespace(rd.Spec.AppName, rd.Spec.Environment) + _, err := s.kubeUtil.KubeClient().CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, metav1.CreateOptions{}) + s.Require().NoError(err) + _, err = s.radixClient.RadixV1().RadixDeployments(namespace).Create(context.Background(), rd, metav1.CreateOptions{}) s.Require().NoError(err) } namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) - namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(len(ts.affectedEnvs)) radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) - radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(len(ts.expectedRadixDeploymentBuilders)) + namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(len(ts.affectedEnvs)) + radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(len(ts.affectedEnvs)) cli := deployconfig.NewDeployConfigStep(namespaceWatcher, radixDeploymentWatcher) cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - err := cli.Run(context.Background(), pipelineInfo) - s.Require().NoError(err) + if err = cli.Run(context.Background(), pipelineInfo); err != nil { + t.Logf("Error: %v", err) + s.Require().NoError(err) + } - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, env1)).List(context.Background(), metav1.ListOptions{}) - if !s.Assert().Len(rds.Items, len(ts.expectedRadixDeploymentBuilders)) { + rdsList, _ := s.radixClient.RadixV1().RadixDeployments("").List(context.Background(), metav1.ListOptions{}) + if !s.Assert().Len(rdsList.Items, len(ts.expectedActiveRadixDeploymentBuilders)) { return } - rdsByEnvMap := slice.Reduce(rds.Items, make(map[string]radixv1.RadixDeployment), func(acc map[string]radixv1.RadixDeployment, rd radixv1.RadixDeployment) map[string]radixv1.RadixDeployment { + rdsByEnvMap := slice.Reduce(rdsList.Items, make(map[string]radixv1.RadixDeployment), func(acc map[string]radixv1.RadixDeployment, rd radixv1.RadixDeployment) map[string]radixv1.RadixDeployment { acc[rd.Spec.Environment] = rd return acc }) - for _, builder := range ts.expectedRadixDeploymentBuilders { + for _, builder := range ts.expectedActiveRadixDeploymentBuilders { expectedRd := builder.BuildRD() actualRd, ok := rdsByEnvMap[expectedRd.Spec.Environment] if !s.True(ok, "Expected RadixDeployment for environment %s not found", expectedRd.Spec.Environment) { @@ -143,18 +220,19 @@ func (s *deployConfigTestSuite) TestDeployConfig() { } } } + s.ElementsMatch(strings.Split(expectedRd.GetName(), "-")[:2], strings.Split(actualRd.GetName(), "-")[:2], "Invalid name prefix parts") expectedAnnotations := expectedRd.GetAnnotations() actualAnnotations := actualRd.GetAnnotations() if s.Len(actualAnnotations, len(expectedAnnotations), "Invalid number of annotations") { for key, value := range expectedAnnotations { - s.Equal(expectedAnnotations[key], actualAnnotations[value], "Invalid or missing an annotation %s or value %s", key, value) + s.Equal(expectedAnnotations[key], actualAnnotations[key], "Invalid or missing an annotation %s or value %s", key, value) } } expectedLabels := expectedRd.GetLabels() actualLabels := actualRd.GetLabels() if s.Len(actualLabels, len(expectedLabels), "Invalid number of labels") { for key, value := range expectedLabels { - s.Equal(expectedLabels[key], actualLabels[value], "Invalid or missing an label %s or value %s", key, value) + s.Equal(expectedLabels[key], actualLabels[key], "Invalid or missing an label %s or value %s", key, value) } } // TODO check other props diff --git a/pkg/apis/utils/applicationcomponent_builder.go b/pkg/apis/utils/applicationcomponent_builder.go index 5fcbe5e33..bc115de30 100644 --- a/pkg/apis/utils/applicationcomponent_builder.go +++ b/pkg/apis/utils/applicationcomponent_builder.go @@ -12,7 +12,7 @@ type RadixApplicationComponentBuilder interface { WithDockerfileName(string) RadixApplicationComponentBuilder WithImage(string) RadixApplicationComponentBuilder WithImageTagName(imageTagName string) RadixApplicationComponentBuilder - WithPublic(bool) RadixApplicationComponentBuilder // Deprecated: For backwards comptibility WithPublic is still supported, new code should use WithPublicPort instead + WithPublic(bool) RadixApplicationComponentBuilder // Deprecated: For backwards compatibility WithPublic is still supported, new code should use WithPublicPort instead WithPublicPort(string) RadixApplicationComponentBuilder WithPort(string, int32) RadixApplicationComponentBuilder WithPorts([]radixv1.ComponentPort) RadixApplicationComponentBuilder diff --git a/pkg/apis/utils/deploymentcomponent_builder.go b/pkg/apis/utils/deploymentcomponent_builder.go index e2a9dcd5c..38b88eb73 100644 --- a/pkg/apis/utils/deploymentcomponent_builder.go +++ b/pkg/apis/utils/deploymentcomponent_builder.go @@ -58,8 +58,8 @@ type deployComponentBuilder struct { ingressConfiguration []string secrets []string secretRefs v1.RadixSecretRefs - dnsappalias bool - // Deprecated: For backwards comptibility externalAppAlias is still supported, new code should use publicPort instead + dnsAppAlias bool + // Deprecated: For backwards compatibility externalAppAlias is still supported, new code should use externalDNS instead externalAppAlias []string externalDNS []v1.RadixDeployExternalDNS resources v1.ResourceRequirements @@ -113,10 +113,11 @@ func (dcb *deployComponentBuilder) WithAlwaysPullImageOnDeploy(val bool) DeployC } func (dcb *deployComponentBuilder) WithDNSAppAlias(createDNSAppAlias bool) DeployComponentBuilder { - dcb.dnsappalias = createDNSAppAlias + dcb.dnsAppAlias = createDNSAppAlias return dcb } +// Deprecated: For backwards compatibility it is still supported, new code should use WithExternalDNS instead func (dcb *deployComponentBuilder) WithDNSExternalAliases(alias ...string) DeployComponentBuilder { dcb.externalAppAlias = alias return dcb @@ -252,7 +253,7 @@ func (dcb *deployComponentBuilder) BuildComponent() v1.RadixDeployComponent { SecretRefs: dcb.secretRefs, IngressConfiguration: dcb.ingressConfiguration, EnvironmentVariables: dcb.environmentVariables, - DNSAppAlias: dcb.dnsappalias, + DNSAppAlias: dcb.dnsAppAlias, DNSExternalAlias: dcb.externalAppAlias, ExternalDNS: dcb.externalDNS, Resources: dcb.resources, From 112fc8464e2d53111e9ecdc1e6086fafbc4263c1 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 14 Oct 2024 10:09:15 +0200 Subject: [PATCH 08/27] Adding tests --- pipeline-runner/steps/deployconfig/step.go | 2 +- .../steps/deployconfig/step_test.go | 37 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index 1bb6120b1..eed3e70a2 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -91,7 +91,7 @@ func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipel var deployedEnvs, notDeployedEnvs []string var errs []error var wg sync.WaitGroup - var mu sync.Mutex + var mu sync.RWMutex for envName, deployConfig := range envConfigToDeploy { wg.Add(1) go func() { diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 69bf05a15..98538058c 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -111,7 +111,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { WithComponents( utils.AnApplicationComponent().WithName(component1), ). - WithDNSExternalAlias(alias2, env1, component1, false), + WithDNSExternalAlias(alias1, env1, component1, false), existingActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( @@ -121,6 +121,41 @@ func (s *deployConfigTestSuite) TestDeployConfig() { utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias3, UseCertificateAutomation: false})). WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), }, + expectedActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ + utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( + utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias1, UseCertificateAutomation: false})). + WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env2).WithImageTag(existingImageTag).WithComponent( + utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias3, UseCertificateAutomation: false})). + WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + }, + affectedEnvs: nil, + }, + { + name: "Changed alias", + existingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). + WithEnvironment(env1, branch1). + WithEnvironment(env2, ""). + WithComponents( + utils.AnApplicationComponent().WithName(component1).WithImageTagName(existingImageTag), + ). + WithDNSExternalAlias(alias1, env1, component1, false). + WithDNSExternalAlias(alias3, env2, component1, false), + applyingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). + WithEnvironment(env1, ""). + WithEnvironment(env2, ""). + WithComponents( + utils.AnApplicationComponent().WithName(component1).WithImageTagName(existingImageTag), + ). + WithDNSExternalAlias(alias2, env1, component1, false), + existingActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ + utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( + utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias1, UseCertificateAutomation: false})). + WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env2).WithImageTag(existingImageTag).WithComponent( + utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias3, UseCertificateAutomation: false})). + WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + }, expectedActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias1, UseCertificateAutomation: false})). From 85b15564562fe8a1e0c7e424cf2b2d710c53c000 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 14 Oct 2024 16:08:07 +0200 Subject: [PATCH 09/27] Fixed method --- pipeline-runner/steps/deployconfig/step.go | 31 ++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index eed3e70a2..232900b71 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -88,26 +88,41 @@ func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineI func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipelineInfo *model.PipelineInfo, envConfigToDeploy map[string]envDeployConfig) ([]string, []string, error) { appName := cli.GetAppName() - var deployedEnvs, notDeployedEnvs []string + deployedEnvCh, notDeployedEnvCh, errsCh := make(chan string), make(chan string), make(chan error) + var notDeployedEnvs, deployedEnvs []string var errs []error var wg sync.WaitGroup - var mu sync.RWMutex + go func() { + for envName := range deployedEnvCh { + deployedEnvs = append(deployedEnvs, envName) + } + }() + go func() { + for envName := range notDeployedEnvCh { + notDeployedEnvs = append(notDeployedEnvs, envName) + } + }() + go func() { + for err := range errsCh { + errs = append(errs, err) + } + }() for envName, deployConfig := range envConfigToDeploy { wg.Add(1) go func() { defer wg.Done() - defer mu.Unlock() if err := cli.deployToEnv(ctx, appName, envName, deployConfig, pipelineInfo); err != nil { - mu.Lock() - errs = append(errs, err) - notDeployedEnvs = append(notDeployedEnvs, envName) + errsCh <- err + notDeployedEnvCh <- envName } else { - mu.Lock() - deployedEnvs = append(deployedEnvs, envName) + deployedEnvCh <- envName } }() } wg.Wait() + close(deployedEnvCh) + close(notDeployedEnvCh) + close(errsCh) return deployedEnvs, notDeployedEnvs, errors.Join(errs...) } From e18c0a1d6701c1be9de83537341258c15a42abab Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 14 Oct 2024 16:31:57 +0200 Subject: [PATCH 10/27] Fixed method --- pipeline-runner/steps/deployconfig/step.go | 41 +++++++++++----------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index 232900b71..6a64790fe 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -88,28 +88,31 @@ func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineI func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipelineInfo *model.PipelineInfo, envConfigToDeploy map[string]envDeployConfig) ([]string, []string, error) { appName := cli.GetAppName() - deployedEnvCh, notDeployedEnvCh, errsCh := make(chan string), make(chan string), make(chan error) + deployedEnvCh, notDeployedEnvCh, errsCh, done := make(chan string), make(chan string), make(chan error), make(chan bool) + defer close(errsCh) + defer close(deployedEnvCh) + defer close(notDeployedEnvCh) + defer close(done) var notDeployedEnvs, deployedEnvs []string var errs []error - var wg sync.WaitGroup - go func() { - for envName := range deployedEnvCh { - deployedEnvs = append(deployedEnvs, envName) - } - }() - go func() { - for envName := range notDeployedEnvCh { - notDeployedEnvs = append(notDeployedEnvs, envName) - } - }() go func() { - for err := range errsCh { - errs = append(errs, err) + for { + select { + case envName := <-deployedEnvCh: + deployedEnvs = append(deployedEnvs, envName) + case envName := <-notDeployedEnvCh: + notDeployedEnvs = append(notDeployedEnvs, envName) + case err := <-errsCh: + errs = append(errs, err) + case <-done: + return + } } }() - for envName, deployConfig := range envConfigToDeploy { + var wg sync.WaitGroup + for env, deployConfig := range envConfigToDeploy { wg.Add(1) - go func() { + go func(envName string) { defer wg.Done() if err := cli.deployToEnv(ctx, appName, envName, deployConfig, pipelineInfo); err != nil { errsCh <- err @@ -117,12 +120,10 @@ func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipel } else { deployedEnvCh <- envName } - }() + }(env) } wg.Wait() - close(deployedEnvCh) - close(notDeployedEnvCh) - close(errsCh) + done <- true return deployedEnvs, notDeployedEnvs, errors.Join(errs...) } From eb0a73de18eec154cbb40459f98c139ccb5e6ca6 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 15 Oct 2024 12:51:30 +0200 Subject: [PATCH 11/27] Creating unit-tests --- pipeline-runner/steps/deployconfig/step.go | 8 +- .../steps/deployconfig/step_test.go | 429 ++++++++++++------ 2 files changed, 283 insertions(+), 154 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index 6a64790fe..a4881d157 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -87,11 +87,11 @@ func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineI } func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipelineInfo *model.PipelineInfo, envConfigToDeploy map[string]envDeployConfig) ([]string, []string, error) { - appName := cli.GetAppName() - deployedEnvCh, notDeployedEnvCh, errsCh, done := make(chan string), make(chan string), make(chan error), make(chan bool) - defer close(errsCh) + deployedEnvCh, notDeployedEnvCh := make(chan string), make(chan string) + errsCh, done := make(chan error), make(chan bool) defer close(deployedEnvCh) defer close(notDeployedEnvCh) + defer close(errsCh) defer close(done) var notDeployedEnvs, deployedEnvs []string var errs []error @@ -109,7 +109,9 @@ func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipel } } }() + var wg sync.WaitGroup + appName := cli.GetAppName() for env, deployConfig := range envConfigToDeploy { wg.Add(1) go func(envName string) { diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 98538058c..36559e7c9 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/equinor/radix-common/utils/maps" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" @@ -47,174 +48,231 @@ func (s *deployConfigTestSuite) SetupTest() { } type scenario struct { - name string - existingRaBuilder utils.ApplicationBuilder - applyingRaBuilder utils.ApplicationBuilder - existingActiveRadixDeploymentBuilders []utils.DeploymentBuilder - expectedActiveRadixDeploymentBuilders []utils.DeploymentBuilder - affectedEnvs []string + name string + existingRaProps raProps + applyingRaProps raProps + existingRadixDeploymentBuilderProps []radixDeploymentBuildersProps + expectedRadixDeploymentBuilderProps []radixDeploymentBuildersProps + affectedEnvs []string +} + +type raProps struct { + envs []string + componentNames []string + dnsExternalAliases []dnsExternalAlias +} + +type dnsExternalAlias struct { + alias string + envName string + componentName string + useCertificateAutomation bool +} + +const ( + appName = "anyapp" + env1 = "env1" + env2 = "env2" + component1 = "component1" + component2 = "component2" + alias1 = "alias1" + alias2 = "alias2" + alias3 = "alias3" + jobName = "anyjobname" + commitId = "anycommit" + gitTags = "gittags" + branch1 = "branch1" + existingImageTag = "existingImageTag" + appliedImageTag = "appliedImageTag" +) + +type radixDeploymentBuildersProps struct { + envName string + imageTag string + activeFrom time.Time + externalDNSs map[string][]externalDNS +} + +type externalDNS struct { + fqdn string + useCertificateAutomation bool } func (s *deployConfigTestSuite) TestDeployConfig() { - const ( - appName = "anyapp" - env1 = "env1" - env2 = "env2" - component1 = "component1" - component2 = "component2" - alias1 = "alias1" - alias2 = "alias2" - alias3 = "alias3" - jobName = "anyjobname" - commitId = "anycommit" - gitTags = "gittags" - branch1 = "branch1" - existingImageTag = "existingImageTag" - appliedImageTag = "appliedImageTag" - ) rr := utils.ARadixRegistration().WithName(appName).BuildRR() timeNow := time.Now() + inPast := timeNow.Add(-time.Hour * 24 * 7) scenarios := []scenario{ { name: "No active deployments, no RA changes", - existingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env1, branch1). - WithEnvironment(env2, ""). - WithComponents( - utils.AnApplicationComponent().WithName(component1), - ). - WithDNSExternalAlias(alias1, env1, component1, false), - applyingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env1, branch1). - WithEnvironment(env2, ""). - WithComponents( - utils.AnApplicationComponent().WithName(component1), - ). - WithDNSExternalAlias(alias1, env1, component1, false), - - existingActiveRadixDeploymentBuilders: nil, - expectedActiveRadixDeploymentBuilders: nil, - affectedEnvs: nil, + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + }, + }, + applyingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + }, + }, }, { name: "Deleted environment", - existingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env1, branch1). - WithEnvironment(env2, ""). - WithComponents( - utils.AnApplicationComponent().WithName(component1).WithImageTagName(existingImageTag), - ). - WithDNSExternalAlias(alias1, env1, component1, false). - WithDNSExternalAlias(alias3, env2, component1, false), - applyingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env1, ""). - WithComponents( - utils.AnApplicationComponent().WithName(component1), - ). - WithDNSExternalAlias(alias1, env1, component1, false), - - existingActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ - utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( - utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias1, UseCertificateAutomation: false})). - WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), - utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env2).WithImageTag(existingImageTag).WithComponent( - utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias3, UseCertificateAutomation: false})). - WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, }, - expectedActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ - utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( - utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias1, UseCertificateAutomation: false})). - WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), - utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env2).WithImageTag(existingImageTag).WithComponent( - utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias3, UseCertificateAutomation: false})). - WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + applyingRaProps: raProps{ + envs: []string{env1}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: inPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias1, useCertificateAutomation: false}, + }, + }, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: inPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias3, useCertificateAutomation: false}, + }, + }, + }, + }, + expectedRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: inPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias1, useCertificateAutomation: false}, + }, + }, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: inPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias3, useCertificateAutomation: false}, + }, + }, + }, }, - affectedEnvs: nil, }, { name: "Changed alias", - existingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env1, branch1). - WithEnvironment(env2, ""). - WithComponents( - utils.AnApplicationComponent().WithName(component1).WithImageTagName(existingImageTag), - ). - WithDNSExternalAlias(alias1, env1, component1, false). - WithDNSExternalAlias(alias3, env2, component1, false), - applyingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env1, ""). - WithEnvironment(env2, ""). - WithComponents( - utils.AnApplicationComponent().WithName(component1).WithImageTagName(existingImageTag), - ). - WithDNSExternalAlias(alias2, env1, component1, false), - existingActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ - utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( - utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias1, UseCertificateAutomation: false})). - WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), - utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env2).WithImageTag(existingImageTag).WithComponent( - utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias3, UseCertificateAutomation: false})). - WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, }, - expectedActiveRadixDeploymentBuilders: []utils.DeploymentBuilder{ - utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(existingImageTag).WithComponent( - utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias1, UseCertificateAutomation: false})). - WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), - utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env2).WithImageTag(existingImageTag).WithComponent( - utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias3, UseCertificateAutomation: false})). - WithActiveFrom(timeNow.Add(-time.Hour * 24 * 7)), - utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(env1).WithImageTag(appliedImageTag).WithComponent( - utils.NewDeployComponentBuilder().WithName(component1).WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: alias2, UseCertificateAutomation: false})), + applyingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias2, envName: env1, componentName: component1, useCertificateAutomation: false}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: inPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias1, useCertificateAutomation: false}, + }, + }, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: inPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias3, useCertificateAutomation: false}, + }, + }, + }, + }, + expectedRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: inPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias1, useCertificateAutomation: false}, + }, + }, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: inPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias3, useCertificateAutomation: false}, + }, + }, + }, + { + envName: env2, imageTag: appliedImageTag, activeFrom: inPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias2, useCertificateAutomation: false}, + }, + }, + }, }, affectedEnvs: []string{env1}, }, { - name: "Orphaned environment, no active deployments, no new deployments", - existingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env1, branch1). - WithEnvironment(env2, ""). - WithComponents( - utils.AnApplicationComponent().WithName(component1), - ). - WithDNSExternalAlias(alias1, env1, component1, false), - applyingRaBuilder: utils.NewRadixApplicationBuilder().WithAppName(appName). - WithEnvironment(env2, ""). - WithComponents( - utils.AnApplicationComponent().WithName(component1), - ). - WithDNSExternalAlias(alias1, env1, component1, false), - existingActiveRadixDeploymentBuilders: nil, - expectedActiveRadixDeploymentBuilders: nil, - affectedEnvs: nil, + name: "Deleted environment, no active deployments, no new deployments", + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + }, + }, + applyingRaProps: raProps{ + envs: []string{env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + }, + }, }, } for _, ts := range scenarios { s.T().Run(ts.name, func(t *testing.T) { t.Logf("Running test case '%s'", ts.name) - existingRa := ts.existingRaBuilder.BuildRA() - _, err := s.radixClient.RadixV1().RadixApplications(utils.GetAppNamespace(existingRa.Name)).Create(context.Background(), existingRa, metav1.CreateOptions{}) - s.Require().NoError(err) + s.createRadixApplication(ts.existingRaProps) pipelineInfo := &model.PipelineInfo{ PipelineArguments: model.PipelineArguments{ JobName: jobName, ImageTag: appliedImageTag, }, - RadixApplication: ts.applyingRaBuilder.BuildRA(), + RadixApplication: buildRadixApplication(ts.applyingRaProps), GitCommitHash: commitId, GitTags: gitTags, } s.SetupTest() - - for _, radixDeploymentBuilder := range ts.existingActiveRadixDeploymentBuilders { - rd := radixDeploymentBuilder.BuildRD() - namespace := utils.GetEnvironmentNamespace(rd.Spec.AppName, rd.Spec.Environment) - _, err := s.kubeUtil.KubeClient().CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, metav1.CreateOptions{}) - s.Require().NoError(err) - _, err = s.radixClient.RadixV1().RadixDeployments(namespace).Create(context.Background(), rd, metav1.CreateOptions{}) - s.Require().NoError(err) - } + s.createUsedNamespaces(ts) + s.createRadixDeployments(ts.existingRadixDeploymentBuilderProps) namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) @@ -223,38 +281,36 @@ func (s *deployConfigTestSuite) TestDeployConfig() { cli := deployconfig.NewDeployConfigStep(namespaceWatcher, radixDeploymentWatcher) cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - if err = cli.Run(context.Background(), pipelineInfo); err != nil { + if err := cli.Run(context.Background(), pipelineInfo); err != nil { t.Logf("Error: %v", err) s.Require().NoError(err) } - rdsList, _ := s.radixClient.RadixV1().RadixDeployments("").List(context.Background(), metav1.ListOptions{}) - if !s.Assert().Len(rdsList.Items, len(ts.expectedActiveRadixDeploymentBuilders)) { + actualRdsByEnvMap, ok := s.getActualRadixDeploymentsByEnvMap(ts) + if !ok { return } - rdsByEnvMap := slice.Reduce(rdsList.Items, make(map[string]radixv1.RadixDeployment), func(acc map[string]radixv1.RadixDeployment, rd radixv1.RadixDeployment) map[string]radixv1.RadixDeployment { - acc[rd.Spec.Environment] = rd - return acc - }) - for _, builder := range ts.expectedActiveRadixDeploymentBuilders { - expectedRd := builder.BuildRD() - actualRd, ok := rdsByEnvMap[expectedRd.Spec.Environment] + expectedRdList := s.buildRadixDeployments(ts.expectedRadixDeploymentBuilderProps) + for _, expectedRd := range expectedRdList { + actualRd, ok := actualRdsByEnvMap[expectedRd.Spec.Environment] if !s.True(ok, "Expected RadixDeployment for environment %s not found", expectedRd.Spec.Environment) { continue } - if s.Len(actualRd.Spec.Components, len(expectedRd.Spec.Components), "Invalid number of components") { - for i := 0; i < len(expectedRd.Spec.Components); i++ { - s.Equal(expectedRd.Spec.Components[i].Name, actualRd.Spec.Components[i].Name) - for envVarName, envVarValue := range expectedRd.Spec.Components[i].EnvironmentVariables { - s.Equal(envVarValue, actualRd.Spec.Components[i].EnvironmentVariables[envVarName], "Invalid or missing an environment variable %s for the env %s and component %s", envVarName, expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) - } - for j := 0; j < len(expectedRd.Spec.Components[i].ExternalDNS); j++ { - s.Equal(expectedRd.Spec.Components[i].ExternalDNS[j].UseCertificateAutomation, actualRd.Spec.Components[i].ExternalDNS[j].UseCertificateAutomation, "Invalid external UseCertificateAutomation for the env %s and component %s", expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) - s.Equal(expectedRd.Spec.Components[i].ExternalDNS[j].FQDN, actualRd.Spec.Components[i].ExternalDNS[j].FQDN, "Invalid external FQDN for the env %s and component %s", expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) - } + if !s.Len(actualRd.Spec.Components, len(expectedRd.Spec.Components), "Invalid number of components") { + continue + } + for i := 0; i < len(expectedRd.Spec.Components); i++ { + s.Equal(expectedRd.Spec.Components[i].Name, actualRd.Spec.Components[i].Name) + for envVarName, envVarValue := range expectedRd.Spec.Components[i].EnvironmentVariables { + s.Equal(envVarValue, actualRd.Spec.Components[i].EnvironmentVariables[envVarName], "Invalid or missing an environment variable %s for the env %s and component %s", envVarName, expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) + } + for j := 0; j < len(expectedRd.Spec.Components[i].ExternalDNS); j++ { + s.Equal(expectedRd.Spec.Components[i].ExternalDNS[j].UseCertificateAutomation, actualRd.Spec.Components[i].ExternalDNS[j].UseCertificateAutomation, "Invalid external UseCertificateAutomation for the env %s and component %s", expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) + s.Equal(expectedRd.Spec.Components[i].ExternalDNS[j].FQDN, actualRd.Spec.Components[i].ExternalDNS[j].FQDN, "Invalid external FQDN for the env %s and component %s", expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) } } + s.ElementsMatch(strings.Split(expectedRd.GetName(), "-")[:2], strings.Split(actualRd.GetName(), "-")[:2], "Invalid name prefix parts") expectedAnnotations := expectedRd.GetAnnotations() actualAnnotations := actualRd.GetAnnotations() @@ -275,3 +331,74 @@ func (s *deployConfigTestSuite) TestDeployConfig() { }) } } + +func (s *deployConfigTestSuite) getActualRadixDeploymentsByEnvMap(ts scenario) (map[string]radixv1.RadixDeployment, bool) { + rdsList, _ := s.radixClient.RadixV1().RadixDeployments("").List(context.Background(), metav1.ListOptions{}) + if !s.Assert().Len(rdsList.Items, len(ts.expectedRadixDeploymentBuilderProps)) { + return nil, false + } + return slice.Reduce(rdsList.Items, make(map[string]radixv1.RadixDeployment), func(acc map[string]radixv1.RadixDeployment, rd radixv1.RadixDeployment) map[string]radixv1.RadixDeployment { + acc[rd.Spec.Environment] = rd + return acc + }), true +} + +func (s *deployConfigTestSuite) createRadixApplication(props raProps) { + existingRa := buildRadixApplication(props) + _, err := s.radixClient.RadixV1().RadixApplications(utils.GetAppNamespace(existingRa.Name)).Create(context.Background(), existingRa, metav1.CreateOptions{}) + s.Require().NoError(err) +} + +func (s *deployConfigTestSuite) createRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps) { + rdList := s.buildRadixDeployments(deploymentBuildersProps) + for _, rd := range rdList { + _, err := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(rd.Spec.AppName, rd.Spec.Environment)).Create(context.Background(), rd, metav1.CreateOptions{}) + s.Require().NoError(err) + } +} + +func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps) []*radixv1.RadixDeployment { + var rdList []*radixv1.RadixDeployment + for _, rdProps := range deploymentBuildersProps { + builder := utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(rdProps.envName).WithImageTag(rdProps.imageTag).WithActiveFrom(rdProps.activeFrom) + for componentName, externalDNSs := range rdProps.externalDNSs { + componentBuilder := utils.NewDeployComponentBuilder().WithName(componentName) + for _, compExternalDNS := range externalDNSs { + componentBuilder = componentBuilder.WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: compExternalDNS.fqdn, UseCertificateAutomation: compExternalDNS.useCertificateAutomation}) + } + builder = builder.WithComponent(componentBuilder) + } + rdList = append(rdList, builder.BuildRD()) + } + return rdList +} + +func (s *deployConfigTestSuite) createUsedNamespaces(ts scenario) { + usedNamespacesSet := make(map[string]struct{}) + for _, envName := range ts.existingRaProps.envs { + usedNamespacesSet[envName] = struct{}{} + } + for _, envName := range ts.applyingRaProps.envs { + usedNamespacesSet[envName] = struct{}{} + } + usedNamespaces := maps.GetKeysFromMap(usedNamespacesSet) + for _, namespace := range usedNamespaces { + _, err := s.kubeClient.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: utils.GetEnvironmentNamespace(appName, namespace)}}, metav1.CreateOptions{}) + s.Require().NoError(err) + } +} + +func buildRadixApplication(props raProps) *radixv1.RadixApplication { + existingRaBuilder := utils.NewRadixApplicationBuilder().WithAppName(appName) + for _, env := range props.envs { + existingRaBuilder.WithEnvironment(env, branch1) + } + for _, componentName := range props.componentNames { + existingRaBuilder.WithComponents(utils.AnApplicationComponent().WithName(componentName).WithImageTagName(existingImageTag)) + } + for _, item := range props.dnsExternalAliases { + existingRaBuilder.WithDNSExternalAlias(item.alias, item.envName, item.componentName, item.useCertificateAutomation) + } + existingRa := existingRaBuilder.BuildRA() + return existingRa +} From d9f6c35ef8190a17fa00243bc934a27e4e9742fb Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 15 Oct 2024 16:31:19 +0200 Subject: [PATCH 12/27] Creating unit-tests --- pipeline-runner/steps/deployconfig/step.go | 11 +- .../steps/deployconfig/step_test.go | 213 ++++++++---------- 2 files changed, 100 insertions(+), 124 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index a4881d157..a1d920800 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -55,8 +55,7 @@ func (cli *DeployConfigStepImplementation) ErrorMsg(err error) string { // Run Override of default step method func (cli *DeployConfigStepImplementation) Run(ctx context.Context, pipelineInfo *model.PipelineInfo) error { - err := cli.deploy(ctx, pipelineInfo) - return err + return cli.deploy(ctx, pipelineInfo) } type envDeployConfig struct { @@ -167,7 +166,7 @@ func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appN func constructForTargetEnvironment(appName, envName string, pipelineInfo *model.PipelineInfo, deployConfig envDeployConfig) (*radixv1.RadixDeployment, error) { radixDeployment := deployConfig.activeRadixDeployment.DeepCopy() radixDeployment.SetName(utils.GetDeploymentName(appName, envName, pipelineInfo.PipelineArguments.ImageTag)) - + radixDeployment.Status = radixv1.RadixDeployStatus{} setExternalDNSesToRadixDeployment(radixDeployment, deployConfig) if err := setAnnotationsAndLabels(pipelineInfo, radixDeployment); err != nil { return nil, err @@ -194,8 +193,8 @@ func setAnnotationsAndLabels(pipelineInfo *model.PipelineInfo, radixDeployment * kube.RadixConfigHash: radixConfigHash, }) radixDeployment.ObjectMeta.Labels[kube.RadixCommitLabel] = commitID - radixDeployment.ObjectMeta.Labels[kube.RadixCommitLabel] = commitID radixDeployment.ObjectMeta.Labels[kube.RadixJobNameLabel] = pipelineInfo.PipelineArguments.JobName + radixDeployment.ObjectMeta.Labels[kube.RadixImageTagLabel] = pipelineInfo.PipelineArguments.ImageTag return nil } @@ -204,8 +203,8 @@ func setExternalDNSesToRadixDeployment(radixDeployment *radixv1.RadixDeployment, acc[dnsExternalAlias.Component] = append(acc[dnsExternalAlias.Component], dnsExternalAlias) return acc }) - for _, deployComponent := range radixDeployment.Spec.Components { - deployComponent.ExternalDNS = slice.Map(externalAliasesMap[deployComponent.Name], func(externalAlias radixv1.ExternalAlias) radixv1.RadixDeployExternalDNS { + for i := 0; i < len(radixDeployment.Spec.Components); i++ { + radixDeployment.Spec.Components[i].ExternalDNS = slice.Map(externalAliasesMap[radixDeployment.Spec.Components[i].Name], func(externalAlias radixv1.ExternalAlias) radixv1.RadixDeployExternalDNS { return radixv1.RadixDeployExternalDNS{ FQDN: externalAlias.Alias, UseCertificateAutomation: externalAlias.UseCertificateAutomation} }) diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 36559e7c9..0432d3993 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -11,6 +11,7 @@ import ( "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" "github.com/equinor/radix-operator/pipeline-runner/steps/deployconfig" + "github.com/equinor/radix-operator/pipeline-runner/steps/internal" "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" @@ -48,12 +49,12 @@ func (s *deployConfigTestSuite) SetupTest() { } type scenario struct { - name string - existingRaProps raProps - applyingRaProps raProps - existingRadixDeploymentBuilderProps []radixDeploymentBuildersProps - expectedRadixDeploymentBuilderProps []radixDeploymentBuildersProps - affectedEnvs []string + name string + existingRaProps raProps + applyingRaProps raProps + existingRadixDeploymentBuilderProps []radixDeploymentBuildersProps + expectedNewRadixDeploymentBuilderProps []radixDeploymentBuildersProps + affectedEnvs []string } type raProps struct { @@ -101,7 +102,8 @@ type externalDNS struct { func (s *deployConfigTestSuite) TestDeployConfig() { rr := utils.ARadixRegistration().WithName(appName).BuildRR() timeNow := time.Now() - inPast := timeNow.Add(-time.Hour * 24 * 7) + timeInPast := timeNow.Add(-time.Hour * 24 * 7) + zeroTime := time.Time{} scenarios := []scenario{ { name: "No active deployments, no RA changes", @@ -139,38 +141,12 @@ func (s *deployConfigTestSuite) TestDeployConfig() { }, existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ { - envName: env1, imageTag: existingImageTag, activeFrom: inPast, - externalDNSs: map[string][]externalDNS{ - component1: { - {fqdn: alias1, useCertificateAutomation: false}, - }, - }, + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, }, { - envName: env2, imageTag: existingImageTag, activeFrom: inPast, - externalDNSs: map[string][]externalDNS{ - component1: { - {fqdn: alias3, useCertificateAutomation: false}, - }, - }, - }, - }, - expectedRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ - { - envName: env1, imageTag: existingImageTag, activeFrom: inPast, - externalDNSs: map[string][]externalDNS{ - component1: { - {fqdn: alias1, useCertificateAutomation: false}, - }, - }, - }, - { - envName: env2, imageTag: existingImageTag, activeFrom: inPast, - externalDNSs: map[string][]externalDNS{ - component1: { - {fqdn: alias3, useCertificateAutomation: false}, - }, - }, + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias3, useCertificateAutomation: false}}}, }, }, }, @@ -188,51 +164,24 @@ func (s *deployConfigTestSuite) TestDeployConfig() { envs: []string{env1, env2}, componentNames: []string{component1}, dnsExternalAliases: []dnsExternalAlias{ - {alias: alias2, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias2, envName: env1, componentName: component1, useCertificateAutomation: false}, // renamed alias + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, }, }, existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ { - envName: env1, imageTag: existingImageTag, activeFrom: inPast, - externalDNSs: map[string][]externalDNS{ - component1: { - {fqdn: alias1, useCertificateAutomation: false}, - }, - }, + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, }, { - envName: env2, imageTag: existingImageTag, activeFrom: inPast, - externalDNSs: map[string][]externalDNS{ - component1: { - {fqdn: alias3, useCertificateAutomation: false}, - }, - }, + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias3, useCertificateAutomation: false}}}, }, }, - expectedRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ - { - envName: env1, imageTag: existingImageTag, activeFrom: inPast, - externalDNSs: map[string][]externalDNS{ - component1: { - {fqdn: alias1, useCertificateAutomation: false}, - }, - }, - }, + expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ { - envName: env2, imageTag: existingImageTag, activeFrom: inPast, - externalDNSs: map[string][]externalDNS{ - component1: { - {fqdn: alias3, useCertificateAutomation: false}, - }, - }, - }, - { - envName: env2, imageTag: appliedImageTag, activeFrom: inPast, - externalDNSs: map[string][]externalDNS{ - component1: { - {fqdn: alias2, useCertificateAutomation: false}, - }, - }, + envName: env1, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias2, useCertificateAutomation: false}}}, // new RD }, }, affectedEnvs: []string{env1}, @@ -259,7 +208,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { for _, ts := range scenarios { s.T().Run(ts.name, func(t *testing.T) { t.Logf("Running test case '%s'", ts.name) - s.createRadixApplication(ts.existingRaProps) + existingRa := s.createRadixApplication(ts.existingRaProps) pipelineInfo := &model.PipelineInfo{ PipelineArguments: model.PipelineArguments{ JobName: jobName, @@ -272,7 +221,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { s.SetupTest() s.createUsedNamespaces(ts) - s.createRadixDeployments(ts.existingRadixDeploymentBuilderProps) + s.createRadixDeployments(ts.existingRadixDeploymentBuilderProps, existingRa) namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) @@ -286,81 +235,109 @@ func (s *deployConfigTestSuite) TestDeployConfig() { s.Require().NoError(err) } - actualRdsByEnvMap, ok := s.getActualRadixDeploymentsByEnvMap(ts) + actualNewRdsByEnvMap, ok := s.getActualNewRadixDeploymentsByEnvMap(ts) if !ok { return } - expectedRdList := s.buildRadixDeployments(ts.expectedRadixDeploymentBuilderProps) - for _, expectedRd := range expectedRdList { - actualRd, ok := actualRdsByEnvMap[expectedRd.Spec.Environment] - if !s.True(ok, "Expected RadixDeployment for environment %s not found", expectedRd.Spec.Environment) { + expectedRdByEnvMap := getRadixDeploymentsByEnvMap(s.buildRadixDeployments(ts.expectedNewRadixDeploymentBuilderProps, pipelineInfo.RadixApplication)) + for envName, expectedRdList := range expectedRdByEnvMap { + actualRdList, ok := actualNewRdsByEnvMap[envName] + if !ok { + t.Errorf("No Radix Deployments found for environment %s", envName) continue } - if !s.Len(actualRd.Spec.Components, len(expectedRd.Spec.Components), "Invalid number of components") { + if !s.Len(actualRdList, len(expectedRdList), "Invalid number of Radix Deployments for environment %s", envName) { continue } - for i := 0; i < len(expectedRd.Spec.Components); i++ { - s.Equal(expectedRd.Spec.Components[i].Name, actualRd.Spec.Components[i].Name) - for envVarName, envVarValue := range expectedRd.Spec.Components[i].EnvironmentVariables { - s.Equal(envVarValue, actualRd.Spec.Components[i].EnvironmentVariables[envVarName], "Invalid or missing an environment variable %s for the env %s and component %s", envVarName, expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) + for i := 0; i < len(expectedRdList); i++ { + expectedRd := expectedRdList[i] + actualRd := actualRdList[i] + if !s.Len(actualRd.Spec.Components, len(expectedRd.Spec.Components), "Invalid number of components") { + continue } - for j := 0; j < len(expectedRd.Spec.Components[i].ExternalDNS); j++ { - s.Equal(expectedRd.Spec.Components[i].ExternalDNS[j].UseCertificateAutomation, actualRd.Spec.Components[i].ExternalDNS[j].UseCertificateAutomation, "Invalid external UseCertificateAutomation for the env %s and component %s", expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) - s.Equal(expectedRd.Spec.Components[i].ExternalDNS[j].FQDN, actualRd.Spec.Components[i].ExternalDNS[j].FQDN, "Invalid external FQDN for the env %s and component %s", expectedRd.Spec.Environment, expectedRd.Spec.Components[i].Name) + for i := 0; i < len(expectedRd.Spec.Components); i++ { + expectedComponent := expectedRd.Spec.Components[i] + actualComponent := actualRd.Spec.Components[i] + s.Equal(expectedComponent.Name, actualComponent.Name) + for envVarName, envVarValue := range expectedComponent.EnvironmentVariables { + s.Equal(envVarValue, actualComponent.EnvironmentVariables[envVarName], "Invalid or missing an environment variable %s for the env %s and component %s", envVarName, expectedRd.Spec.Environment, expectedComponent.Name) + } + for j := 0; j < len(expectedComponent.ExternalDNS); j++ { + expectedExternalDNS := expectedComponent.ExternalDNS[j] + actualExternalDNS := actualComponent.ExternalDNS[j] + s.Equal(expectedExternalDNS.UseCertificateAutomation, actualExternalDNS.UseCertificateAutomation, "Invalid external UseCertificateAutomation for the env %s and component %s", expectedRd.Spec.Environment, expectedComponent.Name) + s.Equal(expectedExternalDNS.FQDN, actualExternalDNS.FQDN, "Invalid external FQDN for the env %s and component %s", expectedRd.Spec.Environment, expectedComponent.Name) + } } - } - s.ElementsMatch(strings.Split(expectedRd.GetName(), "-")[:2], strings.Split(actualRd.GetName(), "-")[:2], "Invalid name prefix parts") - expectedAnnotations := expectedRd.GetAnnotations() - actualAnnotations := actualRd.GetAnnotations() - if s.Len(actualAnnotations, len(expectedAnnotations), "Invalid number of annotations") { - for key, value := range expectedAnnotations { - s.Equal(expectedAnnotations[key], actualAnnotations[key], "Invalid or missing an annotation %s or value %s", key, value) + s.ElementsMatch(strings.Split(expectedRd.GetName(), "-")[:2], strings.Split(actualRd.GetName(), "-")[:2], "Invalid name prefix parts") + expectedAnnotations := expectedRd.GetAnnotations() + actualAnnotations := actualRd.GetAnnotations() + if s.Len(actualAnnotations, len(expectedAnnotations), "Invalid number of annotations") { + for key, value := range expectedAnnotations { + s.Equal(expectedAnnotations[key], actualAnnotations[key], "Invalid or missing an annotation %s or value %s", key, value) + } } - } - expectedLabels := expectedRd.GetLabels() - actualLabels := actualRd.GetLabels() - if s.Len(actualLabels, len(expectedLabels), "Invalid number of labels") { - for key, value := range expectedLabels { - s.Equal(expectedLabels[key], actualLabels[key], "Invalid or missing an label %s or value %s", key, value) + expectedLabels := expectedRd.GetLabels() + actualLabels := actualRd.GetLabels() + if s.Len(actualLabels, len(expectedLabels), "Invalid number of labels") { + for key, value := range expectedLabels { + s.Equal(expectedLabels[key], actualLabels[key], "Invalid or missing an label %s or value %s", key, value) + } } + // TODO check other props } - // TODO check other props } }) } } -func (s *deployConfigTestSuite) getActualRadixDeploymentsByEnvMap(ts scenario) (map[string]radixv1.RadixDeployment, bool) { - rdsList, _ := s.radixClient.RadixV1().RadixDeployments("").List(context.Background(), metav1.ListOptions{}) - if !s.Assert().Len(rdsList.Items, len(ts.expectedRadixDeploymentBuilderProps)) { +func (s *deployConfigTestSuite) getActualNewRadixDeploymentsByEnvMap(ts scenario) (map[string][]radixv1.RadixDeployment, bool) { + rdsList, err := s.radixClient.RadixV1().RadixDeployments("").List(context.Background(), metav1.ListOptions{}) + s.Require().NoError(err) + radixDeployments := slice.FindAll(rdsList.Items, func(rd radixv1.RadixDeployment) bool { return rd.Status.ActiveFrom.IsZero() }) + if !s.Assert().Len(radixDeployments, len(ts.expectedNewRadixDeploymentBuilderProps)) { return nil, false } - return slice.Reduce(rdsList.Items, make(map[string]radixv1.RadixDeployment), func(acc map[string]radixv1.RadixDeployment, rd radixv1.RadixDeployment) map[string]radixv1.RadixDeployment { - acc[rd.Spec.Environment] = rd + return getRadixDeploymentsByEnvMap(radixDeployments), true +} + +func getRadixDeploymentsByEnvMap(radixDeployments []radixv1.RadixDeployment) map[string][]radixv1.RadixDeployment { + return slice.Reduce(radixDeployments, make(map[string][]radixv1.RadixDeployment), func(acc map[string][]radixv1.RadixDeployment, rd radixv1.RadixDeployment) map[string][]radixv1.RadixDeployment { + acc[rd.Spec.Environment] = append(acc[rd.Spec.Environment], rd) return acc - }), true + }) } -func (s *deployConfigTestSuite) createRadixApplication(props raProps) { - existingRa := buildRadixApplication(props) - _, err := s.radixClient.RadixV1().RadixApplications(utils.GetAppNamespace(existingRa.Name)).Create(context.Background(), existingRa, metav1.CreateOptions{}) +func (s *deployConfigTestSuite) createRadixApplication(props raProps) *radixv1.RadixApplication { + radixApplication := buildRadixApplication(props) + _, err := s.radixClient.RadixV1().RadixApplications(utils.GetAppNamespace(radixApplication.Name)).Create(context.Background(), radixApplication, metav1.CreateOptions{}) s.Require().NoError(err) + return radixApplication } -func (s *deployConfigTestSuite) createRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps) { - rdList := s.buildRadixDeployments(deploymentBuildersProps) +func (s *deployConfigTestSuite) createRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps, ra *radixv1.RadixApplication) { + rdList := s.buildRadixDeployments(deploymentBuildersProps, ra) for _, rd := range rdList { - _, err := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(rd.Spec.AppName, rd.Spec.Environment)).Create(context.Background(), rd, metav1.CreateOptions{}) + _, err := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(rd.Spec.AppName, rd.Spec.Environment)).Create(context.Background(), &rd, metav1.CreateOptions{}) s.Require().NoError(err) } } -func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps) []*radixv1.RadixDeployment { - var rdList []*radixv1.RadixDeployment +func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps, ra *radixv1.RadixApplication) []radixv1.RadixDeployment { + radixConfigHash, _ := internal.CreateRadixApplicationHash(ra) + buildSecretHash, _ := internal.CreateBuildSecretHash(nil) + var rdList []radixv1.RadixDeployment for _, rdProps := range deploymentBuildersProps { - builder := utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(rdProps.envName).WithImageTag(rdProps.imageTag).WithActiveFrom(rdProps.activeFrom) + builder := utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(rdProps.envName).WithImageTag(rdProps.imageTag). + WithActiveFrom(rdProps.activeFrom).WithLabel(kube.RadixCommitLabel, commitId).WithLabel(kube.RadixJobNameLabel, jobName). + WithAnnotations(map[string]string{ + kube.RadixGitTagsAnnotation: gitTags, + kube.RadixCommitAnnotation: commitId, + kube.RadixBuildSecretHash: buildSecretHash, + kube.RadixConfigHash: radixConfigHash, + }) for componentName, externalDNSs := range rdProps.externalDNSs { componentBuilder := utils.NewDeployComponentBuilder().WithName(componentName) for _, compExternalDNS := range externalDNSs { @@ -368,7 +345,7 @@ func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps [] } builder = builder.WithComponent(componentBuilder) } - rdList = append(rdList, builder.BuildRD()) + rdList = append(rdList, *builder.BuildRD()) } return rdList } From 05b2209913cd90e19e5a6d7824023ebd4b95a5e0 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 15 Oct 2024 16:46:21 +0200 Subject: [PATCH 13/27] Creating unit-tests --- .../steps/deployconfig/step_test.go | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 0432d3993..a3410aa53 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -54,7 +54,6 @@ type scenario struct { applyingRaProps raProps existingRadixDeploymentBuilderProps []radixDeploymentBuildersProps expectedNewRadixDeploymentBuilderProps []radixDeploymentBuildersProps - affectedEnvs []string } type raProps struct { @@ -184,10 +183,9 @@ func (s *deployConfigTestSuite) TestDeployConfig() { externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias2, useCertificateAutomation: false}}}, // new RD }, }, - affectedEnvs: []string{env1}, }, { - name: "Deleted environment, no active deployments, no new deployments", + name: "Deleted environment without alias", existingRaProps: raProps{ envs: []string{env1, env2}, componentNames: []string{component1}, @@ -196,10 +194,48 @@ func (s *deployConfigTestSuite) TestDeployConfig() { }, }, applyingRaProps: raProps{ - envs: []string{env2}, + envs: []string{env1}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias3, useCertificateAutomation: false}}}, + }, + }, + }, + { + name: "Deleted environment with alias", + existingRaProps: raProps{ + envs: []string{env1, env2}, componentNames: []string{component1}, dnsExternalAliases: []dnsExternalAlias{ {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, + }, + applyingRaProps: raProps{ + envs: []string{env1}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias3, useCertificateAutomation: false}}}, }, }, }, @@ -222,11 +258,13 @@ func (s *deployConfigTestSuite) TestDeployConfig() { s.SetupTest() s.createUsedNamespaces(ts) s.createRadixDeployments(ts.existingRadixDeploymentBuilderProps, existingRa) + expectedRdByEnvMap := getRadixDeploymentsByEnvMap(s.buildRadixDeployments(ts.expectedNewRadixDeploymentBuilderProps, pipelineInfo.RadixApplication)) + affectedEnvs := maps.GetKeysFromMap(expectedRdByEnvMap) namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) - namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(len(ts.affectedEnvs)) - radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(len(ts.affectedEnvs)) + namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(len(affectedEnvs)) + radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(len(affectedEnvs)) cli := deployconfig.NewDeployConfigStep(namespaceWatcher, radixDeploymentWatcher) cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) @@ -240,7 +278,6 @@ func (s *deployConfigTestSuite) TestDeployConfig() { return } - expectedRdByEnvMap := getRadixDeploymentsByEnvMap(s.buildRadixDeployments(ts.expectedNewRadixDeploymentBuilderProps, pipelineInfo.RadixApplication)) for envName, expectedRdList := range expectedRdByEnvMap { actualRdList, ok := actualNewRdsByEnvMap[envName] if !ok { From 7d22d9a21678616f1a4c5f08216456ed7328f7c7 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 15 Oct 2024 17:03:32 +0200 Subject: [PATCH 14/27] Creating unit-tests --- .../steps/deployconfig/step_test.go | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index a3410aa53..f11f19efd 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -180,7 +180,46 @@ func (s *deployConfigTestSuite) TestDeployConfig() { expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ { envName: env1, imageTag: appliedImageTag, activeFrom: zeroTime, - externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias2, useCertificateAutomation: false}}}, // new RD + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias2, useCertificateAutomation: false}}}, + }, + }, + }, + { + name: "Added alias", + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, + }, + applyingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, // added alias + {alias: alias2, envName: env1, componentName: component1, useCertificateAutomation: true}, + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias3, useCertificateAutomation: false}}}, + }, + }, + expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{component1: { + {fqdn: alias1, useCertificateAutomation: false}, + {fqdn: alias2, useCertificateAutomation: true}, + }}, }, }, }, @@ -377,9 +416,10 @@ func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps [] }) for componentName, externalDNSs := range rdProps.externalDNSs { componentBuilder := utils.NewDeployComponentBuilder().WithName(componentName) - for _, compExternalDNS := range externalDNSs { - componentBuilder = componentBuilder.WithExternalDNS(radixv1.RadixDeployExternalDNS{FQDN: compExternalDNS.fqdn, UseCertificateAutomation: compExternalDNS.useCertificateAutomation}) - } + componentBuilder = componentBuilder.WithExternalDNS( + slice.Map(externalDNSs, func(compExternalDNS externalDNS) radixv1.RadixDeployExternalDNS { + return radixv1.RadixDeployExternalDNS{FQDN: compExternalDNS.fqdn, UseCertificateAutomation: compExternalDNS.useCertificateAutomation} + })...) builder = builder.WithComponent(componentBuilder) } rdList = append(rdList, *builder.BuildRD()) From 99317ab248757b66e0981b637160dcfc6aff2e40 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 16 Oct 2024 16:18:14 +0200 Subject: [PATCH 15/27] Creating unit-tests --- .../steps/deployconfig/step_test.go | 69 ++++++++++++++++--- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index f11f19efd..523ccbd7d 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -73,11 +73,15 @@ const ( appName = "anyapp" env1 = "env1" env2 = "env2" + env3 = "env3" component1 = "component1" component2 = "component2" alias1 = "alias1" alias2 = "alias2" alias3 = "alias3" + alias4 = "alias4" + alias5 = "alias5" + alias6 = "alias6" jobName = "anyjobname" commitId = "anycommit" gitTags = "gittags" @@ -150,7 +154,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { }, }, { - name: "Changed alias", + name: "Added alias", existingRaProps: raProps{ envs: []string{env1, env2}, componentNames: []string{component1}, @@ -163,7 +167,8 @@ func (s *deployConfigTestSuite) TestDeployConfig() { envs: []string{env1, env2}, componentNames: []string{component1}, dnsExternalAliases: []dnsExternalAlias{ - {alias: alias2, envName: env1, componentName: component1, useCertificateAutomation: false}, // renamed alias + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, // added alias + {alias: alias2, envName: env1, componentName: component1, useCertificateAutomation: true}, {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, }, }, @@ -180,12 +185,15 @@ func (s *deployConfigTestSuite) TestDeployConfig() { expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ { envName: env1, imageTag: appliedImageTag, activeFrom: zeroTime, - externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias2, useCertificateAutomation: false}}}, + externalDNSs: map[string][]externalDNS{component1: { + {fqdn: alias1, useCertificateAutomation: false}, + {fqdn: alias2, useCertificateAutomation: true}, + }}, }, }, }, { - name: "Added alias", + name: "Changed alias in one environment", existingRaProps: raProps{ envs: []string{env1, env2}, componentNames: []string{component1}, @@ -198,8 +206,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { envs: []string{env1, env2}, componentNames: []string{component1}, dnsExternalAliases: []dnsExternalAlias{ - {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, // added alias - {alias: alias2, envName: env1, componentName: component1, useCertificateAutomation: true}, + {alias: alias2, envName: env1, componentName: component1, useCertificateAutomation: false}, // renamed alias {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, }, }, @@ -216,10 +223,52 @@ func (s *deployConfigTestSuite) TestDeployConfig() { expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ { envName: env1, imageTag: appliedImageTag, activeFrom: zeroTime, - externalDNSs: map[string][]externalDNS{component1: { - {fqdn: alias1, useCertificateAutomation: false}, - {fqdn: alias2, useCertificateAutomation: true}, - }}, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias2, useCertificateAutomation: false}}}, + }, + }, + }, + { + name: "Changed alias in multiple environments", + existingRaProps: raProps{ + envs: []string{env1, env2, env3}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, + {alias: alias5, envName: env3, componentName: component1, useCertificateAutomation: true}, + }, + }, + applyingRaProps: raProps{ + envs: []string{env1, env2, env3}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias2, envName: env1, componentName: component1, useCertificateAutomation: false}, // renamed alias + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, + {alias: alias6, envName: env3, componentName: component1, useCertificateAutomation: true}, // renamed alias + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias3, useCertificateAutomation: false}}}, + }, + { + envName: env3, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias5, useCertificateAutomation: true}}}, + }, + }, + expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias2, useCertificateAutomation: false}}}, + }, + { + envName: env3, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias6, useCertificateAutomation: true}}}, }, }, }, From ff9a346c9b02767f71398b75f4e3329a789067d7 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 17 Oct 2024 13:00:04 +0200 Subject: [PATCH 16/27] Creating unit-tests --- .../steps/deployconfig/step_test.go | 145 ++++++++++++++++-- 1 file changed, 134 insertions(+), 11 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 523ccbd7d..56b9f7a42 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -272,6 +272,120 @@ func (s *deployConfigTestSuite) TestDeployConfig() { }, }, }, + { + name: "Changed component in DNS", + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1, component2}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, + }, + applyingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1, component2}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias3, envName: env2, componentName: component2, useCertificateAutomation: false}, // changed component + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias3, useCertificateAutomation: false}}}, + }, + }, + expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env2, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{component2: {{fqdn: alias3, useCertificateAutomation: false}}}, + }, + }, + }, + { + name: "Delete DNS", + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias3, envName: env2, componentName: component1, useCertificateAutomation: false}, // deleted + }, + }, + applyingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias3, useCertificateAutomation: false}}}, + }, + }, + expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env2, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{}, + }, + }, + }, + { + name: "Delete multiple DNSes", + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1, component2}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias2, envName: env2, componentName: component1, useCertificateAutomation: false}, // deleted + {alias: alias3, envName: env2, componentName: component2, useCertificateAutomation: false}, + {alias: alias4, envName: env2, componentName: component2, useCertificateAutomation: false}, // deleted + }, + }, + applyingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1, component2}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, // renamed alias + {alias: alias3, envName: env2, componentName: component2, useCertificateAutomation: false}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, + }, + { + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{ + component1: { + {fqdn: alias2, useCertificateAutomation: false}, + }, + component2: { + {fqdn: alias3, useCertificateAutomation: false}, + {fqdn: alias4, useCertificateAutomation: false}, + }, + }, + }, + }, + expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env2, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{component2: {{fqdn: alias3, useCertificateAutomation: false}}}, + }, + }, + }, { name: "Deleted environment without alias", existingRaProps: raProps{ @@ -369,7 +483,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { for envName, expectedRdList := range expectedRdByEnvMap { actualRdList, ok := actualNewRdsByEnvMap[envName] if !ok { - t.Errorf("No Radix Deployments found for environment %s", envName) + t.Logf("Error: No Radix Deployments found for environment %s", envName) continue } if !s.Len(actualRdList, len(expectedRdList), "Invalid number of Radix Deployments for environment %s", envName) { @@ -388,6 +502,9 @@ func (s *deployConfigTestSuite) TestDeployConfig() { for envVarName, envVarValue := range expectedComponent.EnvironmentVariables { s.Equal(envVarValue, actualComponent.EnvironmentVariables[envVarName], "Invalid or missing an environment variable %s for the env %s and component %s", envVarName, expectedRd.Spec.Environment, expectedComponent.Name) } + if !s.Len(actualComponent.ExternalDNS, len(expectedComponent.ExternalDNS), "Invalid number of external DNS") { + continue + } for j := 0; j < len(expectedComponent.ExternalDNS); j++ { expectedExternalDNS := expectedComponent.ExternalDNS[j] actualExternalDNS := actualComponent.ExternalDNS[j] @@ -455,7 +572,7 @@ func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps [] buildSecretHash, _ := internal.CreateBuildSecretHash(nil) var rdList []radixv1.RadixDeployment for _, rdProps := range deploymentBuildersProps { - builder := utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(rdProps.envName).WithImageTag(rdProps.imageTag). + deploymentBuilder := utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(rdProps.envName).WithImageTag(rdProps.imageTag). WithActiveFrom(rdProps.activeFrom).WithLabel(kube.RadixCommitLabel, commitId).WithLabel(kube.RadixJobNameLabel, jobName). WithAnnotations(map[string]string{ kube.RadixGitTagsAnnotation: gitTags, @@ -463,15 +580,21 @@ func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps [] kube.RadixBuildSecretHash: buildSecretHash, kube.RadixConfigHash: radixConfigHash, }) - for componentName, externalDNSs := range rdProps.externalDNSs { - componentBuilder := utils.NewDeployComponentBuilder().WithName(componentName) - componentBuilder = componentBuilder.WithExternalDNS( - slice.Map(externalDNSs, func(compExternalDNS externalDNS) radixv1.RadixDeployExternalDNS { - return radixv1.RadixDeployExternalDNS{FQDN: compExternalDNS.fqdn, UseCertificateAutomation: compExternalDNS.useCertificateAutomation} - })...) - builder = builder.WithComponent(componentBuilder) + for _, component := range ra.Spec.Components { + componentBuilder := utils.NewDeployComponentBuilder().WithName(component.Name) + for varName, varValue := range component.GetVariables() { + componentBuilder = componentBuilder.WithEnvironmentVariable(varName, varValue) + } + if externalDNSs, ok := rdProps.externalDNSs[component.Name]; ok { + componentBuilder = componentBuilder.WithExternalDNS( + slice.Map(externalDNSs, func(compExternalDNS externalDNS) radixv1.RadixDeployExternalDNS { + return radixv1.RadixDeployExternalDNS{FQDN: compExternalDNS.fqdn, UseCertificateAutomation: compExternalDNS.useCertificateAutomation} + })...) + } + // set other props if needed + deploymentBuilder = deploymentBuilder.WithComponent(componentBuilder) } - rdList = append(rdList, *builder.BuildRD()) + rdList = append(rdList, *deploymentBuilder.BuildRD()) } return rdList } @@ -497,7 +620,7 @@ func buildRadixApplication(props raProps) *radixv1.RadixApplication { existingRaBuilder.WithEnvironment(env, branch1) } for _, componentName := range props.componentNames { - existingRaBuilder.WithComponents(utils.AnApplicationComponent().WithName(componentName).WithImageTagName(existingImageTag)) + existingRaBuilder.WithComponent(utils.AnApplicationComponent().WithName(componentName).WithImageTagName(existingImageTag)) } for _, item := range props.dnsExternalAliases { existingRaBuilder.WithDNSExternalAlias(item.alias, item.envName, item.componentName, item.useCertificateAutomation) From 4d8ff9f0e9a6e6d0edcb592d84cfce174b28f5b3 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 17 Oct 2024 15:51:18 +0200 Subject: [PATCH 17/27] Creating unit-tests --- pipeline-runner/steps/deployconfig/step.go | 8 ++- .../steps/deployconfig/step_test.go | 62 ++++++++++++++++++- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index a1d920800..10f54bfdf 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -221,7 +221,7 @@ func (cli *DeployConfigStepImplementation) getEnvConfigToDeploy(ctx context.Cont log.Ctx(ctx).Error().Err(err).Msg("Failed to get active Radix deployments") return nil, err } - applicableEnvDeployConfig := getApplicableEnvDeployConfig(envDeployConfigs, pipelineInfo) + applicableEnvDeployConfig := getApplicableEnvDeployConfig(ctx, envDeployConfigs, pipelineInfo) return applicableEnvDeployConfig, nil } @@ -230,10 +230,14 @@ type activeDeploymentExternalDNS struct { componentName string } -func getApplicableEnvDeployConfig(envDeployConfigs map[string]envDeployConfig, pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { +func getApplicableEnvDeployConfig(ctx context.Context, envDeployConfigs map[string]envDeployConfig, pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { applicableEnvDeployConfig := make(map[string]envDeployConfig) for envName, deployConfig := range envDeployConfigs { appExternalDNSAliases := slice.FindAll(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, func(dnsExternalAlias radixv1.ExternalAlias) bool { return dnsExternalAlias.Environment == envName }) + if len(appExternalDNSAliases) > 0 && deployConfig.activeRadixDeployment == nil { + log.Ctx(ctx).Info().Msgf("External DNS alias(es) exists for the environment %s, but it has no active Radix deployment yet - ignore DNS alias(es).", envName) + continue + } activeDeploymentExternalDNSes := getActiveDeploymentExternalDNSes(deployConfig) if equalExternalDNSAliases(activeDeploymentExternalDNSes, appExternalDNSAliases) { continue diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 56b9f7a42..c0f359759 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -109,19 +109,76 @@ func (s *deployConfigTestSuite) TestDeployConfig() { zeroTime := time.Time{} scenarios := []scenario{ { - name: "No active deployments, no RA changes", + name: "DNS exists for env, which has no active deployments, ignore this env", existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{}, + }, + applyingRaProps: raProps{ envs: []string{env1, env2}, componentNames: []string{component1}, dnsExternalAliases: []dnsExternalAlias{ {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, }, }, + }, + { + name: "DNSes added for two envs, one has no active deployments, deploy only second one", + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{}, + }, applyingRaProps: raProps{ envs: []string{env1, env2}, componentNames: []string{component1}, dnsExternalAliases: []dnsExternalAlias{ {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias2, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{}, + }, + }, + expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env2, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias2, useCertificateAutomation: false}}}, + }, + }, + }, + { + name: "DNS changed in both of two envs, one has no active deployments, deploy only second one with change", + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias2, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, + }, + applyingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias3, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias4, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, + }, + }, + expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias3, useCertificateAutomation: false}}}, }, }, }, @@ -625,6 +682,5 @@ func buildRadixApplication(props raProps) *radixv1.RadixApplication { for _, item := range props.dnsExternalAliases { existingRaBuilder.WithDNSExternalAlias(item.alias, item.envName, item.componentName, item.useCertificateAutomation) } - existingRa := existingRaBuilder.BuildRA() - return existingRa + return existingRaBuilder.BuildRA() } From 5995446f9db97469d3e74509d827c3dde07b9f61 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 17 Oct 2024 16:14:18 +0200 Subject: [PATCH 18/27] Creating unit-tests --- pipeline-runner/steps/deployconfig/step.go | 22 ++++++--------- .../steps/deployconfig/step_test.go | 28 +++++++++++++++++++ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index 10f54bfdf..ffad9d088 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -213,16 +213,10 @@ func setExternalDNSesToRadixDeployment(radixDeployment *radixv1.RadixDeployment, func (cli *DeployConfigStepImplementation) getEnvConfigToDeploy(ctx context.Context, pipelineInfo *model.PipelineInfo) (map[string]envDeployConfig, error) { envDeployConfigs := cli.getEnvDeployConfigs(pipelineInfo) - haveActiveRd, err := cli.setActiveRadixDeployments(ctx, envDeployConfigs) - if err != nil { - return nil, err - } - if !haveActiveRd { - log.Ctx(ctx).Error().Err(err).Msg("Failed to get active Radix deployments") + if err := cli.setActiveRadixDeployments(ctx, envDeployConfigs); err != nil { return nil, err } - applicableEnvDeployConfig := getApplicableEnvDeployConfig(ctx, envDeployConfigs, pipelineInfo) - return applicableEnvDeployConfig, nil + return getApplicableEnvDeployConfig(ctx, envDeployConfigs, pipelineInfo), nil } type activeDeploymentExternalDNS struct { @@ -234,8 +228,10 @@ func getApplicableEnvDeployConfig(ctx context.Context, envDeployConfigs map[stri applicableEnvDeployConfig := make(map[string]envDeployConfig) for envName, deployConfig := range envDeployConfigs { appExternalDNSAliases := slice.FindAll(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, func(dnsExternalAlias radixv1.ExternalAlias) bool { return dnsExternalAlias.Environment == envName }) - if len(appExternalDNSAliases) > 0 && deployConfig.activeRadixDeployment == nil { - log.Ctx(ctx).Info().Msgf("External DNS alias(es) exists for the environment %s, but it has no active Radix deployment yet - ignore DNS alias(es).", envName) + if deployConfig.activeRadixDeployment == nil { + if len(appExternalDNSAliases) > 0 { + log.Ctx(ctx).Info().Msgf("External DNS alias(es) exists for the environment %s, but it has no active Radix deployment yet - ignore DNS alias(es).", envName) + } continue } activeDeploymentExternalDNSes := getActiveDeploymentExternalDNSes(deployConfig) @@ -284,19 +280,17 @@ func (cli *DeployConfigStepImplementation) getEnvDeployConfigs(pipelineInfo *mod }) } -func (cli *DeployConfigStepImplementation) setActiveRadixDeployments(ctx context.Context, envDeployConfigs map[string]envDeployConfig) (bool, error) { +func (cli *DeployConfigStepImplementation) setActiveRadixDeployments(ctx context.Context, envDeployConfigs map[string]envDeployConfig) error { var errs []error - hasActiveRd := false for envName, deployConfig := range envDeployConfigs { if activeRd, err := internal.GetActiveRadixDeployment(ctx, cli.GetKubeutil(), utils.GetEnvironmentNamespace(cli.GetAppName(), envName)); err != nil { errs = append(errs, err) } else if activeRd != nil { deployConfig.activeRadixDeployment = activeRd - hasActiveRd = true } envDeployConfigs[envName] = deployConfig } - return hasActiveRd, errors.Join(errs...) + return errors.Join(errs...) } func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index c0f359759..9c56a5707 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -182,6 +182,34 @@ func (s *deployConfigTestSuite) TestDeployConfig() { }, }, }, + { + name: "DNSes deleted from two envs, one has no active deployments, deploy only second one", + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias2, envName: env2, componentName: component1, useCertificateAutomation: false}, + }, + }, + applyingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{}, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env2, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias2, useCertificateAutomation: false}}}, + }, + }, + expectedNewRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env2, imageTag: appliedImageTag, activeFrom: zeroTime, + externalDNSs: map[string][]externalDNS{}, + }, + }, + }, { name: "Deleted environment", existingRaProps: raProps{ From 1ea9e3103aad4016e98cff0e36668254439c5106 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 17 Oct 2024 16:37:05 +0200 Subject: [PATCH 19/27] Creating unit-tests --- pipeline-runner/steps/deployconfig/step.go | 2 -- .../steps/deployconfig/step_test.go | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index ffad9d088..70782f0e6 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -296,10 +296,8 @@ func (cli *DeployConfigStepImplementation) setActiveRadixDeployments(ctx context func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { gitCommitHash := pipelineInfo.GitCommitHash gitTags := pipelineInfo.GitTags - envVarsMap := make(radixv1.EnvVarsMap) envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = gitCommitHash envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = gitTags - return envVarsMap } diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 9c56a5707..45313e828 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -108,6 +108,31 @@ func (s *deployConfigTestSuite) TestDeployConfig() { timeInPast := timeNow.Add(-time.Hour * 24 * 7) zeroTime := time.Time{} scenarios := []scenario{ + { + name: "No DNSes changes, no deployments", + existingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias2, envName: env2, componentName: component2, useCertificateAutomation: true}, + }, + }, + applyingRaProps: raProps{ + envs: []string{env1, env2}, + componentNames: []string{component1}, + dnsExternalAliases: []dnsExternalAlias{ + {alias: alias1, envName: env1, componentName: component1, useCertificateAutomation: false}, + {alias: alias2, envName: env2, componentName: component2, useCertificateAutomation: true}, + }, + }, + existingRadixDeploymentBuilderProps: []radixDeploymentBuildersProps{ + { + envName: env1, imageTag: existingImageTag, activeFrom: timeInPast, + externalDNSs: map[string][]externalDNS{component1: {{fqdn: alias1, useCertificateAutomation: false}}}, + }, + }, + }, { name: "DNS exists for env, which has no active deployments, ignore this env", existingRaProps: raProps{ From e284a0588c702b92f092f091da5efe1d937231e2 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 21 Oct 2024 13:09:45 +0200 Subject: [PATCH 20/27] Corrected copied RD and unit tests --- pipeline-runner/model/pipelineInfo_test.go | 11 ++++-- pipeline-runner/steps/deployconfig/step.go | 39 ++++++++++--------- pkg/apis/utils/annotations/annotations.go | 10 +++++ .../utils/annotations/annotations_test.go | 6 +++ 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/pipeline-runner/model/pipelineInfo_test.go b/pipeline-runner/model/pipelineInfo_test.go index 69940857d..14b866348 100644 --- a/pipeline-runner/model/pipelineInfo_test.go +++ b/pipeline-runner/model/pipelineInfo_test.go @@ -7,10 +7,12 @@ import ( "github.com/equinor/radix-operator/pkg/apis/pipeline" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( applyConfigStep = &model.DefaultStepImplementation{StepType: pipeline.ApplyConfigStep, SuccessMessage: "config applied"} + deployConfigStep = &model.DefaultStepImplementation{StepType: pipeline.DeployConfigStep, SuccessMessage: "config deployed"} buildStep = &model.DefaultStepImplementation{StepType: pipeline.BuildStep, SuccessMessage: "built"} deployStep = &model.DefaultStepImplementation{StepType: pipeline.DeployStep, SuccessMessage: "deployed"} prepareTektonPipelineStep = &model.DefaultStepImplementation{StepType: pipeline.PreparePipelinesStep, @@ -59,14 +61,17 @@ func Test_BuildAndDefaultNoPushOnlyPipeline(t *testing.T) { } func Test_ApplyConfigPipeline(t *testing.T) { - pipelineType, _ := pipeline.GetPipelineFromName(string(v1.ApplyConfig)) + pipelineType, err := pipeline.GetPipelineFromName(string(v1.ApplyConfig)) + require.NoError(t, err, "Failed to get pipeline type. Error %v", err) - p, _ := model.InitPipeline(pipelineType, getPipelineArguments(), prepareTektonPipelineStep, applyConfigStep) + p, err := model.InitPipeline(pipelineType, getPipelineArguments(), prepareTektonPipelineStep, applyConfigStep, deployConfigStep) + require.NoError(t, err, "Failed to create pipeline. Error %v", err) assert.Equal(t, v1.ApplyConfig, p.Definition.Type) assert.False(t, p.PipelineArguments.PushImage) - assert.Equal(t, 2, len(p.Steps)) + assert.Equal(t, 3, len(p.Steps)) assert.Equal(t, "pipelines prepared", p.Steps[0].SucceededMsg()) assert.Equal(t, "config applied", p.Steps[1].SucceededMsg()) + assert.Equal(t, "config deployed", p.Steps[2].SucceededMsg()) } func Test_GetImageTagNamesFromArgs(t *testing.T) { diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index 70782f0e6..c6acf0e47 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -12,10 +12,11 @@ import ( "github.com/equinor/radix-operator/pipeline-runner/model" "github.com/equinor/radix-operator/pipeline-runner/steps/internal" "github.com/equinor/radix-operator/pkg/apis/defaults" - "github.com/equinor/radix-operator/pkg/apis/kube" "github.com/equinor/radix-operator/pkg/apis/pipeline" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" + radixannotations "github.com/equinor/radix-operator/pkg/apis/utils/annotations" + radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" "github.com/rs/zerolog/log" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -165,19 +166,15 @@ func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appN func constructForTargetEnvironment(appName, envName string, pipelineInfo *model.PipelineInfo, deployConfig envDeployConfig) (*radixv1.RadixDeployment, error) { radixDeployment := deployConfig.activeRadixDeployment.DeepCopy() - radixDeployment.SetName(utils.GetDeploymentName(appName, envName, pipelineInfo.PipelineArguments.ImageTag)) - radixDeployment.Status = radixv1.RadixDeployStatus{} - setExternalDNSesToRadixDeployment(radixDeployment, deployConfig) - if err := setAnnotationsAndLabels(pipelineInfo, radixDeployment); err != nil { + if err := setMetadata(appName, envName, pipelineInfo, radixDeployment); err != nil { return nil, err } + setExternalDNSesToRadixDeployment(radixDeployment, deployConfig) + radixDeployment.Status = radixv1.RadixDeployStatus{} return radixDeployment, nil } -func setAnnotationsAndLabels(pipelineInfo *model.PipelineInfo, radixDeployment *radixv1.RadixDeployment) error { - envVars := getDefaultEnvVars(pipelineInfo) - commitID := envVars[defaults.RadixCommitHashEnvironmentVariable] - gitTags := envVars[defaults.RadixGitTagsEnvironmentVariable] +func setMetadata(appName, envName string, pipelineInfo *model.PipelineInfo, radixDeployment *radixv1.RadixDeployment) error { radixConfigHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) if err != nil { return err @@ -186,15 +183,21 @@ func setAnnotationsAndLabels(pipelineInfo *model.PipelineInfo, radixDeployment * if err != nil { return err } - radixDeployment.SetAnnotations(map[string]string{ - kube.RadixGitTagsAnnotation: gitTags, - kube.RadixCommitAnnotation: commitID, - kube.RadixBuildSecretHash: buildSecretHash, - kube.RadixConfigHash: radixConfigHash, - }) - radixDeployment.ObjectMeta.Labels[kube.RadixCommitLabel] = commitID - radixDeployment.ObjectMeta.Labels[kube.RadixJobNameLabel] = pipelineInfo.PipelineArguments.JobName - radixDeployment.ObjectMeta.Labels[kube.RadixImageTagLabel] = pipelineInfo.PipelineArguments.ImageTag + envVars := getDefaultEnvVars(pipelineInfo) + commitID := envVars[defaults.RadixCommitHashEnvironmentVariable] + gitTags := envVars[defaults.RadixGitTagsEnvironmentVariable] + radixDeployment.ObjectMeta = metav1.ObjectMeta{ + Name: utils.GetDeploymentName(appName, envName, pipelineInfo.PipelineArguments.ImageTag), + Namespace: utils.GetEnvironmentNamespace(appName, envName), + Annotations: radixannotations.ForRadixDeployment(gitTags, commitID, buildSecretHash, radixConfigHash), + Labels: radixlabels.Merge( + radixlabels.ForApplicationName(appName), + radixlabels.ForEnvironmentName(envName), + radixlabels.ForCommitId(commitID), + radixlabels.ForPipelineJobName(pipelineInfo.PipelineArguments.JobName), + radixlabels.ForRadixImageTag(pipelineInfo.PipelineArguments.ImageTag), + ), + } return nil } diff --git a/pkg/apis/utils/annotations/annotations.go b/pkg/apis/utils/annotations/annotations.go index b209f0539..dc4302e9d 100644 --- a/pkg/apis/utils/annotations/annotations.go +++ b/pkg/apis/utils/annotations/annotations.go @@ -66,3 +66,13 @@ func ForClusterAutoscalerSafeToEvict(safeToEvict bool) map[string]string { func forAzureWorkloadIdentityClientId(clientId string) map[string]string { return map[string]string{azureWorkloadIdentityClientIdAnnotation: clientId} } + +// ForRadixDeployment Annotations foor RadixDeployment +func ForRadixDeployment(gitTags, commitID, buildSecretHash, radixConfigHash string) map[string]string { + return map[string]string{ + kube.RadixGitTagsAnnotation: gitTags, + kube.RadixCommitAnnotation: commitID, + kube.RadixBuildSecretHash: buildSecretHash, + kube.RadixConfigHash: radixConfigHash, + } +} diff --git a/pkg/apis/utils/annotations/annotations_test.go b/pkg/apis/utils/annotations/annotations_test.go index b3e8da4c1..d7848d685 100644 --- a/pkg/apis/utils/annotations/annotations_test.go +++ b/pkg/apis/utils/annotations/annotations_test.go @@ -50,3 +50,9 @@ func Test_ForClusterAutoscalerSafeToEvict(t *testing.T) { expected = map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"} assert.Equal(t, expected, actual) } + +func Test_ForRadixDeployment(t *testing.T) { + actual := ForRadixDeployment("any-tag", "any-commit", "any-secret-hash", "any-config-hash") + expected := map[string]string{kube.RadixGitTagsAnnotation: "any-tag", kube.RadixCommitAnnotation: "any-commit", kube.RadixBuildSecretHash: "any-secret-hash", kube.RadixConfigHash: "any-config-hash"} + assert.Equal(t, expected, actual) +} From 03ab04f030069b27588d9bcba39822f16cb72bbd Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 22 Oct 2024 16:19:19 +0200 Subject: [PATCH 21/27] Updated version --- charts/radix-operator/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/radix-operator/Chart.yaml b/charts/radix-operator/Chart.yaml index 367ac549f..63890e577 100644 --- a/charts/radix-operator/Chart.yaml +++ b/charts/radix-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: radix-operator -version: 1.44.0 -appVersion: 1.64.0 +version: 1.45.0 +appVersion: 1.65.0 kubeVersion: ">=1.24.0" description: Radix Operator keywords: From 2ec0f90b7bfdfe76e7cb6173564b7bbf2bc99c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Gustav=20Str=C3=A5b=C3=B8?= Date: Thu, 24 Oct 2024 15:09:45 +0200 Subject: [PATCH 22/27] suggested refactor of deploy-config step --- .vscode/launch.json | 26 + pipeline-runner/main.go | 2 +- pipeline-runner/model/pipelineInfo.go | 7 + pipeline-runner/steps/deploy/step.go | 23 +- pipeline-runner/steps/deployconfig/step.go | 592 ++++++++++++------ pipeline-runner/steps/internal/deployment.go | 20 +- .../steps/internal/deployment_test.go | 33 +- 7 files changed, 449 insertions(+), 254 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 3d220e462..2b3d988aa 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -92,6 +92,32 @@ "--RADIX_RESERVED_DNS_ALIASES=grafana,prometheus,www" ] }, + { + "name": "Launch-applyconfig-pipeline", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/pipeline-runner/main.go", + "env": {}, + "args": [ + "--RADIX_APP=oauth-demo", + "--JOB_NAME=radix-pipeline-20231030091802-mfzoz", + "--PIPELINE_TYPE=apply-config", + "--RADIX_TEKTON_IMAGE=radix-tekton:main-latest", + "--RADIX_PIPELINE_GIT_CLONE_NSLOOKUP_IMAGE=radixdevcache.azurecr.io/alpine:3.20", + "--RADIX_PIPELINE_GIT_CLONE_GIT_IMAGE=radixdevcache.azurecr.io/alpine/git:2.45.2", + "--RADIX_PIPELINE_GIT_CLONE_BASH_IMAGE=radixdevcache.azurecr.io/bash:5.2", + "--DEBUG=true", + "--RADIX_FILE_NAME=/workspace/radixconfig.yaml", + "--RADIX_CLUSTER_TYPE=development", + "--RADIX_ZONE=dev", + "--RADIX_CLUSTERNAME=weekly-43", + "--RADIX_CONTAINER_REGISTRY=radixdev.azurecr.io", + "--RADIX_RESERVED_APP_DNS_ALIASES=api=radix-api,canary=radix-canary-golang,console=radix-web-console,cost-api=radix-cost-allocation-api,webhook=radix-github-webhook", + "--RADIX_RESERVED_DNS_ALIASES=grafana,prometheus,www", + "--PIPELINE_DEPLOYCONFIG_STEP_DEPLOYEXTERNALDNS=true" + ] + }, { "name": "Launch-operator", "type": "go", diff --git a/pipeline-runner/main.go b/pipeline-runner/main.go index 439072363..de5708f95 100755 --- a/pipeline-runner/main.go +++ b/pipeline-runner/main.go @@ -133,7 +133,7 @@ func setPipelineArgsFromArguments(cmd *cobra.Command, pipelineArgs *model.Pipeli cmd.Flags().StringVar(&pipelineArgs.GitCloneNsLookupImage, defaults.RadixGitCloneNsLookupImageEnvironmentVariable, "alpine:latest", "Container image with nslookup used by git clone init containers") cmd.Flags().StringVar(&pipelineArgs.GitCloneGitImage, defaults.RadixGitCloneGitImageEnvironmentVariable, "alpine/git:latest", "Container image with git used by git clone init containers") cmd.Flags().StringVar(&pipelineArgs.GitCloneBashImage, defaults.RadixGitCloneBashImageEnvironmentVariable, "bash:latest", "Container image with bash used by git clone init containers") - + cmd.Flags().BoolVar(&pipelineArgs.DeployConfigStep.DeployExternalDNS, "PIPELINE_DEPLOYCONFIG_STEP_DEPLOYEXTERNALDNS", false, "") // TODO: Remove when both pipeline and operator is released. This flag is only to prevent errors when deprecated flag is passed cmd.Flags().String("USE_CACHE", "0", "Use cache") diff --git a/pipeline-runner/model/pipelineInfo.go b/pipeline-runner/model/pipelineInfo.go index 21b23fa7b..447f7f5c8 100644 --- a/pipeline-runner/model/pipelineInfo.go +++ b/pipeline-runner/model/pipelineInfo.go @@ -55,6 +55,11 @@ type Builder struct { ResourcesRequestsMemory string } +type DeployConfigStepOptions struct { + DeployExternalDNS bool + DeployAppAlias bool +} + // PipelineArguments Holds arguments for the pipeline type PipelineArguments struct { PipelineType string @@ -115,6 +120,8 @@ type PipelineArguments struct { // Name of secret with .dockerconfigjson key containing docker auths. Optional. // Used to authenticate external container registries when using buildkit to build dockerfiles. ExternalContainerRegistryDefaultAuthSecret string + + DeployConfigStep DeployConfigStepOptions } // InitPipeline Initialize pipeline with step implementations diff --git a/pipeline-runner/steps/deploy/step.go b/pipeline-runner/steps/deploy/step.go index 8bba595b8..4f30d9104 100644 --- a/pipeline-runner/steps/deploy/step.go +++ b/pipeline-runner/steps/deploy/step.go @@ -7,9 +7,7 @@ import ( "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" "github.com/equinor/radix-operator/pipeline-runner/steps/internal" - "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/pipeline" - radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/rs/zerolog/log" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -73,10 +71,9 @@ func (cli *DeployStepImplementation) deploy(ctx context.Context, pipelineInfo *m } func (cli *DeployStepImplementation) deployToEnv(ctx context.Context, appName, envName string, pipelineInfo *model.PipelineInfo) error { - defaultEnvVars := getDefaultEnvVars(pipelineInfo) - - if commitID, ok := defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable]; !ok || len(commitID) == 0 { - defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments + commitID, gitTags := pipelineInfo.GitCommitHash, pipelineInfo.GitTags + if len(commitID) == 0 { + commitID = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments } radixApplicationHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) @@ -100,9 +97,10 @@ func (cli *DeployStepImplementation) deployToEnv(ctx context.Context, appName, e pipelineInfo.PipelineArguments.JobName, pipelineInfo.PipelineArguments.ImageTag, pipelineInfo.PipelineArguments.Branch, + commitID, + gitTags, pipelineInfo.DeployEnvironmentComponentImages[envName], envName, - defaultEnvVars, radixApplicationHash, buildSecretHash, pipelineInfo.PrepareBuildContext, @@ -132,14 +130,3 @@ func (cli *DeployStepImplementation) deployToEnv(ctx context.Context, appName, e } return nil } - -func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { - gitCommitHash := pipelineInfo.GitCommitHash - gitTags := pipelineInfo.GitTags - - envVarsMap := make(radixv1.EnvVarsMap) - envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = gitCommitHash - envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = gitTags - - return envVarsMap -} diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index c6acf0e47..c5cc9ce63 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -2,23 +2,19 @@ package deployconfig import ( "context" - "errors" "fmt" - "strings" - "sync" + "slices" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" "github.com/equinor/radix-operator/pipeline-runner/steps/internal" - "github.com/equinor/radix-operator/pkg/apis/defaults" + "github.com/equinor/radix-operator/pkg/apis/kube" "github.com/equinor/radix-operator/pkg/apis/pipeline" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" - radixannotations "github.com/equinor/radix-operator/pkg/apis/utils/annotations" - radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" "github.com/rs/zerolog/log" - k8serrors "k8s.io/apimachinery/pkg/api/errors" + "golang.org/x/sync/errgroup" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -56,251 +52,445 @@ func (cli *DeployConfigStepImplementation) ErrorMsg(err error) string { // Run Override of default step method func (cli *DeployConfigStepImplementation) Run(ctx context.Context, pipelineInfo *model.PipelineInfo) error { - return cli.deploy(ctx, pipelineInfo) + var updaters []Updater + if pipelineInfo.PipelineArguments.DeployConfigStep.DeployExternalDNS { + updaters = append(updaters, &externalDNSDeployer{}) + } + // if pipelineInfo.PipelineArguments.DeployConfigStep.DeployAppAlias { + // updaters = append(updaters, nil) + // } + + handler := deployHandler{updaters: updaters, pipelineInfo: *pipelineInfo, kubeutil: cli.GetKubeutil(), rdWatcher: cli.radixDeploymentWatcher} + return handler.deploy(ctx) + // return cli.deploy(ctx, pipelineInfo) } -type envDeployConfig struct { - appExternalDNSAliases []radixv1.ExternalAlias - activeRadixDeployment *radixv1.RadixDeployment +type Updater interface { + MustDeployEnvironment(envName string, ra *radixv1.RadixApplication, activeRd *radixv1.RadixDeployment) bool + UpdateDeployment(target, source *radixv1.RadixDeployment) error } -// Deploy Handles deploy step of the pipeline -func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineInfo *model.PipelineInfo) error { - appName := cli.GetAppName() - log.Ctx(ctx).Info().Msgf("Deploying app config %s", appName) +type externalDNSDeployer struct{} - envConfigToDeploy, err := cli.getEnvConfigToDeploy(ctx, pipelineInfo) - if len(envConfigToDeploy) == 0 { - log.Ctx(ctx).Info().Msg("no environments to deploy to") - return err - } - deployedEnvs, notDeployedEnvs, err := cli.deployEnvs(ctx, pipelineInfo, envConfigToDeploy) - if len(deployedEnvs) == 0 { - return err +func (d *externalDNSDeployer) MustDeployEnvironment(envName string, ra *radixv1.RadixApplication, activeRd *radixv1.RadixDeployment) bool { + if slices.ContainsFunc(ra.Spec.DNSExternalAlias, func(alias radixv1.ExternalAlias) bool { return alias.Environment == envName }) { + return true } - log.Ctx(ctx).Info().Msgf("Deployed app config %s to environments %v", appName, strings.Join(deployedEnvs, ",")) - if err != nil { - // some environments failed to deploy, but because some of them where - just log an error and not deployed envs. - log.Ctx(ctx).Error().Err(err).Msgf("Failed to deploy app config %s to environments %v", appName, strings.Join(notDeployedEnvs, ",")) + + if activeRd != nil && slices.ContainsFunc(activeRd.Spec.Components, func(comp radixv1.RadixDeployComponent) bool { return len(comp.GetExternalDNS()) > 0 }) { + return true } - return nil + + return false } -func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipelineInfo *model.PipelineInfo, envConfigToDeploy map[string]envDeployConfig) ([]string, []string, error) { - deployedEnvCh, notDeployedEnvCh := make(chan string), make(chan string) - errsCh, done := make(chan error), make(chan bool) - defer close(deployedEnvCh) - defer close(notDeployedEnvCh) - defer close(errsCh) - defer close(done) - var notDeployedEnvs, deployedEnvs []string - var errs []error - go func() { - for { - select { - case envName := <-deployedEnvCh: - deployedEnvs = append(deployedEnvs, envName) - case envName := <-notDeployedEnvCh: - notDeployedEnvs = append(notDeployedEnvs, envName) - case err := <-errsCh: - errs = append(errs, err) - case <-done: - return - } +func (d *externalDNSDeployer) UpdateDeployment(target, source *radixv1.RadixDeployment) error { + for i, targetComp := range target.Spec.Components { + sourceComp, found := slice.FindFirst(source.Spec.Components, func(c radixv1.RadixDeployComponent) bool { return c.Name == targetComp.Name }) + if !found { + return fmt.Errorf("component %s not found in active deployment", targetComp.Name) } - }() - - var wg sync.WaitGroup - appName := cli.GetAppName() - for env, deployConfig := range envConfigToDeploy { - wg.Add(1) - go func(envName string) { - defer wg.Done() - if err := cli.deployToEnv(ctx, appName, envName, deployConfig, pipelineInfo); err != nil { - errsCh <- err - notDeployedEnvCh <- envName - } else { - deployedEnvCh <- envName - } - }(env) + target.Spec.Components[i].ExternalDNS = sourceComp.GetExternalDNS() } - wg.Wait() - done <- true - return deployedEnvs, notDeployedEnvs, errors.Join(errs...) + + return nil } -func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appName, envName string, deployConfig envDeployConfig, pipelineInfo *model.PipelineInfo) error { - defaultEnvVars := getDefaultEnvVars(pipelineInfo) +type envInfo struct { + envName string + activeRd *radixv1.RadixDeployment +} - if commitID, ok := defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable]; !ok || len(commitID) == 0 { - defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments - } +type envInfoList []envInfo - radixDeployment, err := constructForTargetEnvironment(appName, envName, pipelineInfo, deployConfig) +type deployHandler struct { + updaters []Updater + kubeutil *kube.Kube + pipelineInfo model.PipelineInfo + rdWatcher watcher.RadixDeploymentWatcher +} +func (h *deployHandler) deploy(ctx context.Context) error { + envsToDeploy, err := h.getEnvironmentsToDeploy(ctx) if err != nil { - return fmt.Errorf("failed to create Radix deployment in environment %s. %w", envName, err) + return fmt.Errorf("failed to get list of environments to deploy: %w", err) } - namespace := utils.GetEnvironmentNamespace(cli.GetAppName(), envName) - if err = cli.namespaceWatcher.WaitFor(ctx, namespace); err != nil { - return fmt.Errorf("failed to get environment namespace %s, for app %s. %w", namespace, appName, err) + if err := h.validateEnvironmentsToDeploy(envsToDeploy); err != nil { + return fmt.Errorf("failed to validate environments: %w", err) } - radixDeploymentName := radixDeployment.GetName() - log.Ctx(ctx).Info().Msgf("Apply Radix deployment %s to environment %s", radixDeploymentName, envName) - if _, err = cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Create(context.Background(), radixDeployment, metav1.CreateOptions{}); err != nil { - return fmt.Errorf("failed to apply Radix deployment for app %s to environment %s. %w", appName, envName, err) - } + return h.deployEnvironments(ctx, envsToDeploy) +} - if err = cli.radixDeploymentWatcher.WaitForActive(ctx, namespace, radixDeploymentName); err != nil { - log.Ctx(ctx).Error().Err(err).Msgf("Failed to activate Radix deployment %s in environment %s. Deleting deployment", radixDeploymentName, envName) - if deleteErr := cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Delete(context.Background(), radixDeploymentName, metav1.DeleteOptions{}); deleteErr != nil && !k8serrors.IsNotFound(deleteErr) { - log.Ctx(ctx).Error().Err(deleteErr).Msgf("Failed to delete Radix deployment") +func (h *deployHandler) validateEnvironmentsToDeploy(envsToDeploy envInfoList) error { + for _, envInfo := range envsToDeploy { + if envInfo.activeRd == nil { + return fmt.Errorf("cannot deploy to environment %s because it does not have an active Radix deployment", envInfo.envName) } - return err } - return nil } -func constructForTargetEnvironment(appName, envName string, pipelineInfo *model.PipelineInfo, deployConfig envDeployConfig) (*radixv1.RadixDeployment, error) { - radixDeployment := deployConfig.activeRadixDeployment.DeepCopy() - if err := setMetadata(appName, envName, pipelineInfo, radixDeployment); err != nil { - return nil, err +func (h *deployHandler) getEnvironmentsToDeploy(ctx context.Context) (envInfoList, error) { + allEnvs := envInfoList{} + for _, env := range h.pipelineInfo.RadixApplication.Spec.Environments { + envNs := utils.GetEnvironmentNamespace(h.pipelineInfo.RadixApplication.GetName(), env.Name) + rd, err := internal.GetActiveRadixDeployment(ctx, h.kubeutil, envNs) + if err != nil { + return nil, fmt.Errorf("failed to get active Radix deployment for environment %s: %w", env.Name, err) + } + allEnvs = append(allEnvs, envInfo{envName: env.Name, activeRd: rd}) } - setExternalDNSesToRadixDeployment(radixDeployment, deployConfig) - radixDeployment.Status = radixv1.RadixDeployStatus{} - return radixDeployment, nil -} -func setMetadata(appName, envName string, pipelineInfo *model.PipelineInfo, radixDeployment *radixv1.RadixDeployment) error { - radixConfigHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) - if err != nil { - return err + deployEnvs := envInfoList{} + for _, envInfo := range allEnvs { + if slices.ContainsFunc(h.updaters, func(deployer Updater) bool { + return deployer.MustDeployEnvironment(envInfo.envName, h.pipelineInfo.RadixApplication, envInfo.activeRd) + }) { + deployEnvs = append(deployEnvs, envInfo) + } } - buildSecretHash, err := internal.CreateBuildSecretHash(pipelineInfo.BuildSecret) + + return deployEnvs, nil +} + +func (h *deployHandler) deployEnvironments(ctx context.Context, envs envInfoList) error { + rdList, err := h.buildDeployments(ctx, envs) if err != nil { - return err + return fmt.Errorf("failed to build Radix deployments: %w", err) } - envVars := getDefaultEnvVars(pipelineInfo) - commitID := envVars[defaults.RadixCommitHashEnvironmentVariable] - gitTags := envVars[defaults.RadixGitTagsEnvironmentVariable] - radixDeployment.ObjectMeta = metav1.ObjectMeta{ - Name: utils.GetDeploymentName(appName, envName, pipelineInfo.PipelineArguments.ImageTag), - Namespace: utils.GetEnvironmentNamespace(appName, envName), - Annotations: radixannotations.ForRadixDeployment(gitTags, commitID, buildSecretHash, radixConfigHash), - Labels: radixlabels.Merge( - radixlabels.ForApplicationName(appName), - radixlabels.ForEnvironmentName(envName), - radixlabels.ForCommitId(commitID), - radixlabels.ForPipelineJobName(pipelineInfo.PipelineArguments.JobName), - radixlabels.ForRadixImageTag(pipelineInfo.PipelineArguments.ImageTag), - ), + + // Compare new deployments with current active deployments on only deploy changed ones + + // create deployments + for _, rd := range rdList { + log.Ctx(ctx).Info().Msgf("Apply Radix deployment %s to environment %s", rd.GetName(), rd.Spec.Environment) + _, err := h.kubeutil.RadixClient().RadixV1().RadixDeployments(rd.GetNamespace()).Create(ctx, rd, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to apply Radix deployment %s to environment %s: %w", rd.GetName(), rd.Spec.Environment, err) + } } - return nil -} -func setExternalDNSesToRadixDeployment(radixDeployment *radixv1.RadixDeployment, deployConfig envDeployConfig) { - externalAliasesMap := slice.Reduce(deployConfig.appExternalDNSAliases, make(map[string][]radixv1.ExternalAlias), func(acc map[string][]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string][]radixv1.ExternalAlias { - acc[dnsExternalAlias.Component] = append(acc[dnsExternalAlias.Component], dnsExternalAlias) - return acc - }) - for i := 0; i < len(radixDeployment.Spec.Components); i++ { - radixDeployment.Spec.Components[i].ExternalDNS = slice.Map(externalAliasesMap[radixDeployment.Spec.Components[i].Name], func(externalAlias radixv1.ExternalAlias) radixv1.RadixDeployExternalDNS { - return radixv1.RadixDeployExternalDNS{ - FQDN: externalAlias.Alias, UseCertificateAutomation: externalAlias.UseCertificateAutomation} + // wait for activation + var g errgroup.Group + for _, rd := range rdList { + g.Go(func() error { + if err := h.rdWatcher.WaitForActive(ctx, rd.GetNamespace(), rd.GetName()); err != nil { + return fmt.Errorf("failed to wait for activation of Radix deployment %s in environment %s: %w", rd.GetName(), rd.Spec.Environment, err) + } + return nil }) } -} -func (cli *DeployConfigStepImplementation) getEnvConfigToDeploy(ctx context.Context, pipelineInfo *model.PipelineInfo) (map[string]envDeployConfig, error) { - envDeployConfigs := cli.getEnvDeployConfigs(pipelineInfo) - if err := cli.setActiveRadixDeployments(ctx, envDeployConfigs); err != nil { - return nil, err + if err := g.Wait(); err != nil { + return fmt.Errorf("failed to wait for activation of Radix deployments: %w", err) } - return getApplicableEnvDeployConfig(ctx, envDeployConfigs, pipelineInfo), nil -} -type activeDeploymentExternalDNS struct { - externalDNS radixv1.RadixDeployExternalDNS - componentName string + return nil } -func getApplicableEnvDeployConfig(ctx context.Context, envDeployConfigs map[string]envDeployConfig, pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { - applicableEnvDeployConfig := make(map[string]envDeployConfig) - for envName, deployConfig := range envDeployConfigs { - appExternalDNSAliases := slice.FindAll(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, func(dnsExternalAlias radixv1.ExternalAlias) bool { return dnsExternalAlias.Environment == envName }) - if deployConfig.activeRadixDeployment == nil { - if len(appExternalDNSAliases) > 0 { - log.Ctx(ctx).Info().Msgf("External DNS alias(es) exists for the environment %s, but it has no active Radix deployment yet - ignore DNS alias(es).", envName) - } - continue - } - activeDeploymentExternalDNSes := getActiveDeploymentExternalDNSes(deployConfig) - if equalExternalDNSAliases(activeDeploymentExternalDNSes, appExternalDNSAliases) { - continue - } - applicableEnvDeployConfig[envName] = envDeployConfig{ - appExternalDNSAliases: appExternalDNSAliases, - activeRadixDeployment: deployConfig.activeRadixDeployment, +func (h *deployHandler) buildDeployments(ctx context.Context, envs envInfoList) ([]*radixv1.RadixDeployment, error) { + var rdList []*radixv1.RadixDeployment + + for _, envInfo := range envs { + rd, err := h.buildDeployment(ctx, envInfo) + if err != nil { + return nil, fmt.Errorf("failed to build Radix deployment for environment %s: %w", envInfo.envName, err) } + // Validate that the same components and jobs exist in current acxtive and new deployment + + // Compare components (and jobs) in current active and new RD. Add to list if different + rdList = append(rdList, rd) } - return applicableEnvDeployConfig -} -func getActiveDeploymentExternalDNSes(deployConfig envDeployConfig) []activeDeploymentExternalDNS { - return slice.Reduce(deployConfig.activeRadixDeployment.Spec.Components, make([]activeDeploymentExternalDNS, 0), - func(acc []activeDeploymentExternalDNS, component radixv1.RadixDeployComponent) []activeDeploymentExternalDNS { - externalDNSes := slice.Map(component.ExternalDNS, func(externalDNS radixv1.RadixDeployExternalDNS) activeDeploymentExternalDNS { - return activeDeploymentExternalDNS{componentName: component.Name, externalDNS: externalDNS} - }) - return append(acc, externalDNSes...) - }) + return rdList, nil } -func equalExternalDNSAliases(activeDeploymentExternalDNSes []activeDeploymentExternalDNS, appExternalAliases []radixv1.ExternalAlias) bool { - if len(activeDeploymentExternalDNSes) != len(appExternalAliases) { - return false +func (h *deployHandler) buildDeployment(ctx context.Context, envInfo envInfo) (*radixv1.RadixDeployment, error) { + // TODO: should we use the new RA hash or hash from current active RD? + // Both can cause inconsitency issues since we technically do not apply everything from RA to the RDs + // radixApplicationHash, err := internal.CreateRadixApplicationHash(h.pipelineInfo.RadixApplication) + // if err != nil { + // return nil, err + // } + + sourceRd, err := internal.ConstructForTargetEnvironment( + ctx, + h.pipelineInfo.RadixApplication, + envInfo.activeRd, + h.pipelineInfo.PipelineArguments.JobName, + h.pipelineInfo.PipelineArguments.ImageTag, + envInfo.activeRd.Annotations[kube.RadixBranchAnnotation], + envInfo.activeRd.Annotations[kube.RadixCommitAnnotation], + envInfo.activeRd.Annotations[kube.RadixGitTagsAnnotation], + nil, + envInfo.envName, + envInfo.activeRd.Annotations[kube.RadixConfigHash], + envInfo.activeRd.Annotations[kube.RadixBuildSecretHash], + h.pipelineInfo.PrepareBuildContext, + h.pipelineInfo.PipelineArguments.ComponentsToDeploy, + ) + if err != nil { + return nil, fmt.Errorf("failed to construct Radix deployment: %w", err) } - appExternalAliasesMap := slice.Reduce(appExternalAliases, make(map[string]radixv1.ExternalAlias), func(acc map[string]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string]radixv1.ExternalAlias { - acc[dnsExternalAlias.Alias] = dnsExternalAlias - return acc - }) - return slice.All(activeDeploymentExternalDNSes, func(activeExternalDNS activeDeploymentExternalDNS) bool { - appExternalAlias, ok := appExternalAliasesMap[activeExternalDNS.externalDNS.FQDN] - return ok && - appExternalAlias.UseCertificateAutomation == activeExternalDNS.externalDNS.UseCertificateAutomation && - appExternalAlias.Component == activeExternalDNS.componentName - }) -} -func (cli *DeployConfigStepImplementation) getEnvDeployConfigs(pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { - return slice.Reduce(pipelineInfo.RadixApplication.Spec.Environments, make(map[string]envDeployConfig), func(acc map[string]envDeployConfig, env radixv1.Environment) map[string]envDeployConfig { - acc[env.Name] = envDeployConfig{} - return acc - }) -} + targetRd := envInfo.activeRd.DeepCopy() + targetRd.ObjectMeta = *sourceRd.ObjectMeta.DeepCopy() -func (cli *DeployConfigStepImplementation) setActiveRadixDeployments(ctx context.Context, envDeployConfigs map[string]envDeployConfig) error { - var errs []error - for envName, deployConfig := range envDeployConfigs { - if activeRd, err := internal.GetActiveRadixDeployment(ctx, cli.GetKubeutil(), utils.GetEnvironmentNamespace(cli.GetAppName(), envName)); err != nil { - errs = append(errs, err) - } else if activeRd != nil { - deployConfig.activeRadixDeployment = activeRd + for _, updater := range h.updaters { + if err := updater.UpdateDeployment(targetRd, sourceRd); err != nil { + return nil, fmt.Errorf("failed to apply configu to Radix deployment: %w", err) } - envDeployConfigs[envName] = deployConfig } - return errors.Join(errs...) -} -func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { - gitCommitHash := pipelineInfo.GitCommitHash - gitTags := pipelineInfo.GitTags - envVarsMap := make(radixv1.EnvVarsMap) - envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = gitCommitHash - envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = gitTags - return envVarsMap + return targetRd, nil } + +// type envDeployConfig struct { +// appExternalDNSAliases []radixv1.ExternalAlias +// activeRadixDeployment *radixv1.RadixDeployment +// } + +// // Deploy Handles deploy step of the pipeline +// func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineInfo *model.PipelineInfo) error { +// appName := cli.GetAppName() +// log.Ctx(ctx).Info().Msgf("Deploying app config %s", appName) + +// envConfigToDeploy, err := cli.getEnvConfigToDeploy(ctx, pipelineInfo) +// if len(envConfigToDeploy) == 0 { +// log.Ctx(ctx).Info().Msg("no environments to deploy to") +// return err +// } +// deployedEnvs, notDeployedEnvs, err := cli.deployEnvs(ctx, pipelineInfo, envConfigToDeploy) +// if len(deployedEnvs) == 0 { +// return err +// } +// log.Ctx(ctx).Info().Msgf("Deployed app config %s to environments %v", appName, strings.Join(deployedEnvs, ",")) +// if err != nil { +// // some environments failed to deploy, but because some of them where - just log an error and not deployed envs. +// log.Ctx(ctx).Error().Err(err).Msgf("Failed to deploy app config %s to environments %v", appName, strings.Join(notDeployedEnvs, ",")) +// } +// return nil +// } + +// func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipelineInfo *model.PipelineInfo, envConfigToDeploy map[string]envDeployConfig) ([]string, []string, error) { +// deployedEnvCh, notDeployedEnvCh := make(chan string), make(chan string) +// errsCh, done := make(chan error), make(chan bool) +// defer close(deployedEnvCh) +// defer close(notDeployedEnvCh) +// defer close(errsCh) +// defer close(done) +// var notDeployedEnvs, deployedEnvs []string +// var errs []error +// go func() { +// for { +// select { +// case envName := <-deployedEnvCh: +// deployedEnvs = append(deployedEnvs, envName) +// case envName := <-notDeployedEnvCh: +// notDeployedEnvs = append(notDeployedEnvs, envName) +// case err := <-errsCh: +// errs = append(errs, err) +// case <-done: +// return +// } +// } +// }() + +// var wg sync.WaitGroup +// appName := cli.GetAppName() +// for env, deployConfig := range envConfigToDeploy { +// wg.Add(1) +// go func(envName string) { +// defer wg.Done() +// if err := cli.deployToEnv(ctx, appName, envName, deployConfig, pipelineInfo); err != nil { +// errsCh <- err +// notDeployedEnvCh <- envName +// } else { +// deployedEnvCh <- envName +// } +// }(env) +// } +// wg.Wait() +// done <- true +// return deployedEnvs, notDeployedEnvs, errors.Join(errs...) +// } + +// func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appName, envName string, deployConfig envDeployConfig, pipelineInfo *model.PipelineInfo) error { +// defaultEnvVars := getDefaultEnvVars(pipelineInfo) + +// if commitID, ok := defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable]; !ok || len(commitID) == 0 { +// defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments +// } + +// radixDeployment, err := constructForTargetEnvironment(appName, envName, pipelineInfo, deployConfig) + +// if err != nil { +// return fmt.Errorf("failed to create Radix deployment in environment %s. %w", envName, err) +// } + +// namespace := utils.GetEnvironmentNamespace(cli.GetAppName(), envName) +// if err = cli.namespaceWatcher.WaitFor(ctx, namespace); err != nil { +// return fmt.Errorf("failed to get environment namespace %s, for app %s. %w", namespace, appName, err) +// } + +// radixDeploymentName := radixDeployment.GetName() +// log.Ctx(ctx).Info().Msgf("Apply Radix deployment %s to environment %s", radixDeploymentName, envName) +// if _, err = cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Create(context.Background(), radixDeployment, metav1.CreateOptions{}); err != nil { +// return fmt.Errorf("failed to apply Radix deployment for app %s to environment %s. %w", appName, envName, err) +// } + +// if err = cli.radixDeploymentWatcher.WaitForActive(ctx, namespace, radixDeploymentName); err != nil { +// log.Ctx(ctx).Error().Err(err).Msgf("Failed to activate Radix deployment %s in environment %s. Deleting deployment", radixDeploymentName, envName) +// if deleteErr := cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Delete(context.Background(), radixDeploymentName, metav1.DeleteOptions{}); deleteErr != nil && !k8serrors.IsNotFound(deleteErr) { +// log.Ctx(ctx).Error().Err(deleteErr).Msgf("Failed to delete Radix deployment") +// } +// return err +// } + +// return nil +// } + +// func constructForTargetEnvironment(appName, envName string, pipelineInfo *model.PipelineInfo, deployConfig envDeployConfig) (*radixv1.RadixDeployment, error) { +// radixDeployment := deployConfig.activeRadixDeployment.DeepCopy() +// if err := setMetadata(appName, envName, pipelineInfo, radixDeployment); err != nil { +// return nil, err +// } +// setExternalDNSesToRadixDeployment(radixDeployment, deployConfig) +// radixDeployment.Status = radixv1.RadixDeployStatus{} +// return radixDeployment, nil +// } + +// func setMetadata(appName, envName string, pipelineInfo *model.PipelineInfo, radixDeployment *radixv1.RadixDeployment) error { +// radixConfigHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) +// if err != nil { +// return err +// } +// buildSecretHash, err := internal.CreateBuildSecretHash(pipelineInfo.BuildSecret) +// if err != nil { +// return err +// } +// envVars := getDefaultEnvVars(pipelineInfo) +// commitID := envVars[defaults.RadixCommitHashEnvironmentVariable] +// gitTags := envVars[defaults.RadixGitTagsEnvironmentVariable] +// radixDeployment.ObjectMeta = metav1.ObjectMeta{ +// Name: utils.GetDeploymentName(appName, envName, pipelineInfo.PipelineArguments.ImageTag), +// Namespace: utils.GetEnvironmentNamespace(appName, envName), +// Annotations: radixannotations.ForRadixDeployment(gitTags, commitID, buildSecretHash, radixConfigHash), +// Labels: radixlabels.Merge( +// radixlabels.ForApplicationName(appName), +// radixlabels.ForEnvironmentName(envName), +// radixlabels.ForCommitId(commitID), +// radixlabels.ForPipelineJobName(pipelineInfo.PipelineArguments.JobName), +// radixlabels.ForRadixImageTag(pipelineInfo.PipelineArguments.ImageTag), +// ), +// } +// return nil +// } + +// func setExternalDNSesToRadixDeployment(radixDeployment *radixv1.RadixDeployment, deployConfig envDeployConfig) { +// externalAliasesMap := slice.Reduce(deployConfig.appExternalDNSAliases, make(map[string][]radixv1.ExternalAlias), func(acc map[string][]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string][]radixv1.ExternalAlias { +// acc[dnsExternalAlias.Component] = append(acc[dnsExternalAlias.Component], dnsExternalAlias) +// return acc +// }) +// for i := 0; i < len(radixDeployment.Spec.Components); i++ { +// radixDeployment.Spec.Components[i].ExternalDNS = slice.Map(externalAliasesMap[radixDeployment.Spec.Components[i].Name], func(externalAlias radixv1.ExternalAlias) radixv1.RadixDeployExternalDNS { +// return radixv1.RadixDeployExternalDNS{ +// FQDN: externalAlias.Alias, UseCertificateAutomation: externalAlias.UseCertificateAutomation} +// }) +// } +// } + +// func (cli *DeployConfigStepImplementation) getEnvConfigToDeploy(ctx context.Context, pipelineInfo *model.PipelineInfo) (map[string]envDeployConfig, error) { +// envDeployConfigs := cli.getEnvDeployConfigs(pipelineInfo) +// if err := cli.setActiveRadixDeployments(ctx, envDeployConfigs); err != nil { +// return nil, err +// } +// return getApplicableEnvDeployConfig(ctx, envDeployConfigs, pipelineInfo), nil +// } + +// type activeDeploymentExternalDNS struct { +// externalDNS radixv1.RadixDeployExternalDNS +// componentName string +// } + +// func getApplicableEnvDeployConfig(ctx context.Context, envDeployConfigs map[string]envDeployConfig, pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { +// applicableEnvDeployConfig := make(map[string]envDeployConfig) + +// for envName, deployConfig := range envDeployConfigs { +// appExternalDNSAliases := slice.FindAll(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, func(dnsExternalAlias radixv1.ExternalAlias) bool { return dnsExternalAlias.Environment == envName }) +// if deployConfig.activeRadixDeployment == nil { +// if len(appExternalDNSAliases) > 0 { +// log.Ctx(ctx).Info().Msgf("External DNS alias(es) exists for the environment %s, but it has no active Radix deployment yet - ignore DNS alias(es).", envName) +// } +// continue +// } +// activeDeploymentExternalDNSes := getActiveDeploymentExternalDNSes(deployConfig) +// if equalExternalDNSAliases(activeDeploymentExternalDNSes, appExternalDNSAliases) { +// continue +// } +// applicableEnvDeployConfig[envName] = envDeployConfig{ +// appExternalDNSAliases: appExternalDNSAliases, +// activeRadixDeployment: deployConfig.activeRadixDeployment, +// } + +// } +// return applicableEnvDeployConfig +// } + +// func getActiveDeploymentExternalDNSes(deployConfig envDeployConfig) []activeDeploymentExternalDNS { +// return slice.Reduce(deployConfig.activeRadixDeployment.Spec.Components, make([]activeDeploymentExternalDNS, 0), +// func(acc []activeDeploymentExternalDNS, component radixv1.RadixDeployComponent) []activeDeploymentExternalDNS { +// externalDNSes := slice.Map(component.ExternalDNS, func(externalDNS radixv1.RadixDeployExternalDNS) activeDeploymentExternalDNS { +// return activeDeploymentExternalDNS{componentName: component.Name, externalDNS: externalDNS} +// }) +// return append(acc, externalDNSes...) +// }) +// } + +// func equalExternalDNSAliases(activeDeploymentExternalDNSes []activeDeploymentExternalDNS, appExternalAliases []radixv1.ExternalAlias) bool { +// if len(activeDeploymentExternalDNSes) != len(appExternalAliases) { +// return false +// } +// appExternalAliasesMap := slice.Reduce(appExternalAliases, make(map[string]radixv1.ExternalAlias), func(acc map[string]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string]radixv1.ExternalAlias { +// acc[dnsExternalAlias.Alias] = dnsExternalAlias +// return acc +// }) +// return slice.All(activeDeploymentExternalDNSes, func(activeExternalDNS activeDeploymentExternalDNS) bool { +// appExternalAlias, ok := appExternalAliasesMap[activeExternalDNS.externalDNS.FQDN] +// return ok && +// appExternalAlias.UseCertificateAutomation == activeExternalDNS.externalDNS.UseCertificateAutomation && +// appExternalAlias.Component == activeExternalDNS.componentName +// }) +// } + +// func (cli *DeployConfigStepImplementation) getEnvDeployConfigs(pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { +// return slice.Reduce(pipelineInfo.RadixApplication.Spec.Environments, make(map[string]envDeployConfig), func(acc map[string]envDeployConfig, env radixv1.Environment) map[string]envDeployConfig { +// acc[env.Name] = envDeployConfig{} +// return acc +// }) +// } + +// func (cli *DeployConfigStepImplementation) setActiveRadixDeployments(ctx context.Context, envDeployConfigs map[string]envDeployConfig) error { +// var errs []error +// for envName, deployConfig := range envDeployConfigs { +// if activeRd, err := internal.GetActiveRadixDeployment(ctx, cli.GetKubeutil(), utils.GetEnvironmentNamespace(cli.GetAppName(), envName)); err != nil { +// errs = append(errs, err) +// } else if activeRd != nil { +// deployConfig.activeRadixDeployment = activeRd +// } +// envDeployConfigs[envName] = deployConfig +// } +// return errors.Join(errs...) +// } + +// func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { +// gitCommitHash := pipelineInfo.GitCommitHash +// gitTags := pipelineInfo.GitTags +// envVarsMap := make(radixv1.EnvVarsMap) +// envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = gitCommitHash +// envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = gitTags +// return envVarsMap +// } diff --git a/pipeline-runner/steps/internal/deployment.go b/pipeline-runner/steps/internal/deployment.go index 2a29ed0d2..c6e9941ce 100644 --- a/pipeline-runner/steps/internal/deployment.go +++ b/pipeline-runner/steps/internal/deployment.go @@ -25,22 +25,24 @@ type PreservingDeployComponents struct { } // ConstructForTargetEnvironment Will build a deployment for target environment -func ConstructForTargetEnvironment(ctx context.Context, config *radixv1.RadixApplication, activeRadixDeployment *radixv1.RadixDeployment, jobName, imageTag, branch string, componentImages pipeline.DeployComponentImages, envName string, defaultEnvVars radixv1.EnvVarsMap, radixConfigHash, buildSecretHash string, buildContext *model.PrepareBuildContext, componentsToDeploy []string) (*radixv1.RadixDeployment, error) { - preservingDeployComponents, err := getPreservingDeployComponents(ctx, activeRadixDeployment, envName, buildContext, componentsToDeploy) - if err != nil { - return nil, err +func ConstructForTargetEnvironment(ctx context.Context, config *radixv1.RadixApplication, activeRadixDeployment *radixv1.RadixDeployment, jobName, imageTag, branch, commitID, gitTags string, componentImages pipeline.DeployComponentImages, envName string, radixConfigHash, buildSecretHash string, buildContext *model.PrepareBuildContext, componentsToDeploy []string) (*radixv1.RadixDeployment, error) { + preservingDeployComponents := getPreservingDeployComponents(ctx, activeRadixDeployment, envName, buildContext, componentsToDeploy) + + defaultEnvVars := radixv1.EnvVarsMap{ + defaults.RadixCommitHashEnvironmentVariable: commitID, + defaults.RadixGitTagsEnvironmentVariable: gitTags, } - commitID := defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] - gitTags := defaultEnvVars[defaults.RadixGitTagsEnvironmentVariable] deployComponents, err := deployment.GetRadixComponentsForEnv(ctx, config, activeRadixDeployment, envName, componentImages, defaultEnvVars, preservingDeployComponents.DeployComponents) if err != nil { return nil, err } + jobs, err := deployment.NewJobComponentsBuilder(config, envName, componentImages, defaultEnvVars, preservingDeployComponents.DeployJobComponents).JobComponents(ctx) if err != nil { return nil, err } + radixDeployment := constructRadixDeployment(config, envName, jobName, imageTag, branch, commitID, gitTags, deployComponents, jobs, radixConfigHash, buildSecretHash) return radixDeployment, nil } @@ -93,13 +95,13 @@ func constructRadixDeployment(radixApplication *radixv1.RadixApplication, env, j return radixDeployment } -func getPreservingDeployComponents(ctx context.Context, activeRadixDeployment *radixv1.RadixDeployment, envName string, buildContext *model.PrepareBuildContext, componentsToDeploy []string) (PreservingDeployComponents, error) { +func getPreservingDeployComponents(ctx context.Context, activeRadixDeployment *radixv1.RadixDeployment, envName string, buildContext *model.PrepareBuildContext, componentsToDeploy []string) PreservingDeployComponents { preservingDeployComponents := PreservingDeployComponents{} existEnvironmentComponentsToBuild := buildContext != nil && !buildContext.ChangedRadixConfig && slice.Any(buildContext.EnvironmentsToBuild, func(environmentToBuild model.EnvironmentToBuild) bool { return len(environmentToBuild.Components) > 0 }) if activeRadixDeployment == nil || (len(componentsToDeploy) == 0 && !existEnvironmentComponentsToBuild) { - return preservingDeployComponents, nil + return preservingDeployComponents } if len(componentsToDeploy) == 0 && existEnvironmentComponentsToBuild { componentsToDeploy = slice.Reduce(buildContext.EnvironmentsToBuild, make([]string, 0), func(acc []string, envComponentsToBuild model.EnvironmentToBuild) []string { @@ -124,7 +126,7 @@ func getPreservingDeployComponents(ctx context.Context, activeRadixDeployment *r preservingDeployComponents.DeployJobComponents = slice.FindAll(activeRadixDeployment.Spec.Jobs, func(component radixv1.RadixDeployJobComponent) bool { return !componentNames[component.GetName()] }) - return preservingDeployComponents, nil + return preservingDeployComponents } // GetActiveRadixDeployment Returns active RadixDeployment if it exists and if it is available to get diff --git a/pipeline-runner/steps/internal/deployment_test.go b/pipeline-runner/steps/internal/deployment_test.go index 1a20cdc63..cedc82162 100644 --- a/pipeline-runner/steps/internal/deployment_test.go +++ b/pipeline-runner/steps/internal/deployment_test.go @@ -87,12 +87,7 @@ func TestConstructForTargetEnvironment_PicksTheCorrectEnvironmentConfig(t *testi for _, testcase := range testScenarios { t.Run(testcase.environment, func(t *testing.T) { - - envVarsMap := make(radixv1.EnvVarsMap) - envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = testcase.expectedGitCommitHash - envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = testcase.expectedGitTags - - rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimageTag", "anybranch", componentImages, testcase.environment, envVarsMap, "anyhash", "anybuildsecrethash", nil, nil) + rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimageTag", "anybranch", testcase.expectedGitCommitHash, testcase.expectedGitTags, componentImages, testcase.environment, "anyhash", "anybuildsecrethash", nil, nil) require.NoError(t, err) assert.Equal(t, testcase.expectedReplicas, *rd.Spec.Components[0].Replicas, "Number of replicas wasn't as expected") @@ -152,11 +147,7 @@ func TestConstructForTargetEnvironment_AlwaysPullImageOnDeployOverride(t *testin componentImages := make(pipeline.DeployComponentImages) componentImages["app"] = pipeline.DeployComponentImage{ImagePath: "anyImagePath"} - envVarsMap := make(radixv1.EnvVarsMap) - envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = "anycommit" - envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = "anytag" - - rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimageTag", "anybranch", componentImages, "dev", envVarsMap, "anyhash", "anybuildsecrethash", nil, nil) + rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimageTag", "anybranch", "anycommit", "anytag", componentImages, "dev", "anyhash", "anybuildsecrethash", nil, nil) require.NoError(t, err) t.Log(rd.Spec.Components[0].Name) @@ -166,7 +157,7 @@ func TestConstructForTargetEnvironment_AlwaysPullImageOnDeployOverride(t *testin t.Log(rd.Spec.Components[2].Name) assert.False(t, rd.Spec.Components[2].AlwaysPullImageOnDeploy) - rd, err = ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimageTag", "anybranch", componentImages, "prod", envVarsMap, "anyhash", "anybuildsecrethash", nil, nil) + rd, err = ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimageTag", "anybranch", "anycommit", "anytag", componentImages, "prod", "anyhash", "anybuildsecrethash", nil, nil) require.NoError(t, err) t.Log(rd.Spec.Components[0].Name) @@ -184,11 +175,7 @@ func TestConstructForTargetEnvironment_GetCommitID(t *testing.T) { componentImages := make(pipeline.DeployComponentImages) componentImages["app"] = pipeline.DeployComponentImage{ImagePath: "anyImagePath"} - envVarsMap := make(radixv1.EnvVarsMap) - envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = "commit-abc" - envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = "anytag" - - rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimageTag", "anybranch", componentImages, "dev", envVarsMap, "anyhash", "anybuildsecrethash", nil, nil) + rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimageTag", "anybranch", "commit-abc", "anytags", componentImages, "dev", "anyhash", "anybuildsecrethash", nil, nil) require.NoError(t, err) assert.Equal(t, "commit-abc", rd.ObjectMeta.Labels[kube.RadixCommitLabel]) @@ -236,12 +223,8 @@ func TestConstructForTargetEnvironment_GetCommitsToDeploy(t *testing.T) { componentImages["job1"] = pipeline.DeployComponentImage{ImagePath: "job1-image:tag2"} componentImages["job2"] = pipeline.DeployComponentImage{ImagePath: "job2-image:tag2"} - envVarsMap := make(radixv1.EnvVarsMap) - envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = commit2 - envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = gitTag2 - t.Run("deploy only specific components", func(t *testing.T) { - rd, err := ConstructForTargetEnvironment(context.Background(), ra, activeRadixDeployment, "anyjob", "anyimageTag", "anybranch", componentImages, "dev", envVarsMap, "anyhash", "anybuildsecrethash", nil, []string{"comp1", "job1"}) + rd, err := ConstructForTargetEnvironment(context.Background(), ra, activeRadixDeployment, "anyjob", "anyimageTag", "anybranch", commit2, gitTag2, componentImages, "dev", "anyhash", "anybuildsecrethash", nil, []string{"comp1", "job1"}) require.NoError(t, err) comp1, ok := slice.FindFirst(rd.Spec.Components, func(component radixv1.RadixDeployComponent) bool { return component.GetName() == "comp1" }) @@ -270,7 +253,7 @@ func TestConstructForTargetEnvironment_GetCommitsToDeploy(t *testing.T) { }) t.Run("deploy all components", func(t *testing.T) { - rd, err := ConstructForTargetEnvironment(context.Background(), ra, activeRadixDeployment, "anyjob", "anyimageTag", "anybranch", componentImages, "dev", envVarsMap, "anyhash", "anybuildsecrethash", nil, nil) + rd, err := ConstructForTargetEnvironment(context.Background(), ra, activeRadixDeployment, "anyjob", "anyimageTag", "anybranch", commit2, gitTag2, componentImages, "dev", "anyhash", "anybuildsecrethash", nil, nil) require.NoError(t, err) comp1, ok := slice.FindFirst(rd.Spec.Components, func(component radixv1.RadixDeployComponent) bool { return component.GetName() == "comp1" }) @@ -333,7 +316,7 @@ func Test_ConstructForTargetEnvironment_Identity(t *testing.T) { ) } ra := utils.ARadixApplication().WithComponents(component).BuildRA() - rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimage", "anybranch", make(pipeline.DeployComponentImages), envName, make(radixv1.EnvVarsMap), "anyhash", "anybuildsecrethash", nil, nil) + rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimage", "anybranch", "anycommit", "anytags", make(pipeline.DeployComponentImages), envName, "anyhash", "anybuildsecrethash", nil, nil) require.NoError(t, err) assert.Equal(t, scenario.expected, rd.Spec.Components[0].Identity) } @@ -346,7 +329,7 @@ func Test_ConstructForTargetEnvironment_Identity(t *testing.T) { ) } ra := utils.ARadixApplication().WithJobComponents(job).BuildRA() - rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimage", "anybranch", make(pipeline.DeployComponentImages), envName, make(radixv1.EnvVarsMap), "anyhash", "anybuildsecrethash", nil, nil) + rd, err := ConstructForTargetEnvironment(context.Background(), ra, nil, "anyjob", "anyimage", "anybranch", "anycommit", "anytags", make(pipeline.DeployComponentImages), envName, "anyhash", "anybuildsecrethash", nil, nil) require.NoError(t, err) assert.Equal(t, scenario.expected, rd.Spec.Jobs[0].Identity) } From 66c121769e1ab836701926a4324fa29a8772aa15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Gustav=20Str=C3=A5b=C3=B8?= Date: Fri, 25 Oct 2024 13:45:33 +0200 Subject: [PATCH 23/27] implement handler and external dns updater --- pipeline-runner/model/pipelineInfo.go | 2 +- pipeline-runner/pipelines/app.go | 2 +- pipeline-runner/steps/deployconfig/handler.go | 182 ++++++++ pipeline-runner/steps/deployconfig/step.go | 420 +----------------- .../steps/deployconfig/step_test.go | 7 +- pkg/apis/utils/annotations/annotations.go | 10 - .../utils/annotations/annotations_test.go | 6 - 7 files changed, 196 insertions(+), 433 deletions(-) create mode 100644 pipeline-runner/steps/deployconfig/handler.go diff --git a/pipeline-runner/model/pipelineInfo.go b/pipeline-runner/model/pipelineInfo.go index 447f7f5c8..460261247 100644 --- a/pipeline-runner/model/pipelineInfo.go +++ b/pipeline-runner/model/pipelineInfo.go @@ -57,7 +57,7 @@ type Builder struct { type DeployConfigStepOptions struct { DeployExternalDNS bool - DeployAppAlias bool + // DeployAppAlias bool } // PipelineArguments Holds arguments for the pipeline diff --git a/pipeline-runner/pipelines/app.go b/pipeline-runner/pipelines/app.go index aa621b39f..1984e18c7 100644 --- a/pipeline-runner/pipelines/app.go +++ b/pipeline-runner/pipelines/app.go @@ -126,7 +126,7 @@ func (cli *PipelineRunner) initStepImplementations(ctx context.Context, registra stepImplementations = append(stepImplementations, build.NewBuildStep(nil)) stepImplementations = append(stepImplementations, runpipeline.NewRunPipelinesStep(nil)) stepImplementations = append(stepImplementations, deploy.NewDeployStep(watcher.NewNamespaceWatcherImpl(cli.kubeclient), watcher.NewRadixDeploymentWatcher(cli.radixclient, time.Minute*5))) - stepImplementations = append(stepImplementations, deployconfig.NewDeployConfigStep(watcher.NewNamespaceWatcherImpl(cli.kubeclient), watcher.NewRadixDeploymentWatcher(cli.radixclient, time.Minute*5))) + stepImplementations = append(stepImplementations, deployconfig.NewDeployConfigStep(watcher.NewRadixDeploymentWatcher(cli.radixclient, time.Minute*5))) stepImplementations = append(stepImplementations, promote.NewPromoteStep()) for _, stepImplementation := range stepImplementations { diff --git a/pipeline-runner/steps/deployconfig/handler.go b/pipeline-runner/steps/deployconfig/handler.go new file mode 100644 index 000000000..1acefde7a --- /dev/null +++ b/pipeline-runner/steps/deployconfig/handler.go @@ -0,0 +1,182 @@ +package deployconfig + +import ( + "context" + "fmt" + "slices" + + "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" + "github.com/equinor/radix-operator/pipeline-runner/model" + "github.com/equinor/radix-operator/pipeline-runner/steps/internal" + "github.com/equinor/radix-operator/pkg/apis/kube" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/rs/zerolog/log" + "golang.org/x/sync/errgroup" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type envInfo struct { + envName string + activeRd *radixv1.RadixDeployment +} + +type envInfoList []envInfo + +type deployHandler struct { + updaters []DeploymentUpdater + kubeutil *kube.Kube + pipelineInfo model.PipelineInfo + rdWatcher watcher.RadixDeploymentWatcher +} + +func (h *deployHandler) deploy(ctx context.Context) error { + envsToDeploy, err := h.getEnvironmentsToDeploy(ctx) + if err != nil { + return fmt.Errorf("failed to get list of environments to deploy: %w", err) + } + + if err := h.validateEnvironmentsToDeploy(envsToDeploy); err != nil { + return fmt.Errorf("failed to validate environments: %w", err) + } + + return h.deployEnvironments(ctx, envsToDeploy) +} + +func (h *deployHandler) validateEnvironmentsToDeploy(envsToDeploy envInfoList) error { + for _, envInfo := range envsToDeploy { + if envInfo.activeRd == nil { + return fmt.Errorf("cannot deploy to environment %s because it does not have an active Radix deployment", envInfo.envName) + } + } + return nil +} + +func (h *deployHandler) getEnvironmentsToDeploy(ctx context.Context) (envInfoList, error) { + allEnvs := envInfoList{} + for _, env := range h.pipelineInfo.RadixApplication.Spec.Environments { + envNs := utils.GetEnvironmentNamespace(h.pipelineInfo.RadixApplication.GetName(), env.Name) + rd, err := internal.GetActiveRadixDeployment(ctx, h.kubeutil, envNs) + if err != nil { + return nil, fmt.Errorf("failed to get active Radix deployment for environment %s: %w", env.Name, err) + } + allEnvs = append(allEnvs, envInfo{envName: env.Name, activeRd: rd}) + } + + deployEnvs := envInfoList{} + for _, envInfo := range allEnvs { + if slices.ContainsFunc(h.updaters, func(deployer DeploymentUpdater) bool { + return deployer.MustDeployEnvironment(envInfo.envName, h.pipelineInfo.RadixApplication, envInfo.activeRd) + }) { + deployEnvs = append(deployEnvs, envInfo) + } + } + + return deployEnvs, nil +} + +func (h *deployHandler) deployEnvironments(ctx context.Context, envs envInfoList) error { + rdList, err := h.buildDeployments(ctx, envs) + if err != nil { + return fmt.Errorf("failed to build Radix deployments: %w", err) + } + + if err := h.applyDeployments(ctx, rdList); err != nil { + return fmt.Errorf("failed to apply Radix deployments: %w", err) + } + + return nil +} + +func (h *deployHandler) applyDeployments(ctx context.Context, rdList []*radixv1.RadixDeployment) error { + if len(rdList) == 0 { + return nil + } + + for _, rd := range rdList { + log.Ctx(ctx).Info().Msgf("Apply Radix deployment %s to environment %s", rd.GetName(), rd.Spec.Environment) + _, err := h.kubeutil.RadixClient().RadixV1().RadixDeployments(rd.GetNamespace()).Create(ctx, rd, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to apply Radix deployment %s to environment %s: %w", rd.GetName(), rd.Spec.Environment, err) + } + } + + var g errgroup.Group + for _, rd := range rdList { + g.Go(func() error { + if err := h.rdWatcher.WaitForActive(ctx, rd.GetNamespace(), rd.GetName()); err != nil { + return fmt.Errorf("failed to wait for activation of Radix deployment %s in environment %s: %w", rd.GetName(), rd.Spec.Environment, err) + } + return nil + }) + } + + if err := g.Wait(); err != nil { + return fmt.Errorf("failed to wait for activation of Radix deployments: %w", err) + } + + return nil +} + +func (h *deployHandler) buildDeployments(ctx context.Context, envs envInfoList) ([]*radixv1.RadixDeployment, error) { + var rdList []*radixv1.RadixDeployment + + for _, envInfo := range envs { + rd, err := h.buildDeployment(ctx, envInfo) + if err != nil { + return nil, fmt.Errorf("failed to build Radix deployment for environment %s: %w", envInfo.envName, err) + } + + // Add deployment if new deployment spec differs from active deployment + if !cmp.Equal(rd.Spec, envInfo.activeRd.Spec, cmpopts.EquateEmpty()) { + rdList = append(rdList, rd) + } + } + + return rdList, nil +} + +func (h *deployHandler) buildDeployment(ctx context.Context, envInfo envInfo) (*radixv1.RadixDeployment, error) { + // TODO: should we use the new RA hash or hash from current active RD? + // Both can cause inconsitency issues since we technically do not apply everything from RA to the RDs + // radixApplicationHash, err := internal.CreateRadixApplicationHash(h.pipelineInfo.RadixApplication) + // if err != nil { + // return nil, err + // } + + sourceRd, err := internal.ConstructForTargetEnvironment( + ctx, + h.pipelineInfo.RadixApplication, + envInfo.activeRd, + h.pipelineInfo.PipelineArguments.JobName, + h.pipelineInfo.PipelineArguments.ImageTag, + envInfo.activeRd.Annotations[kube.RadixBranchAnnotation], + envInfo.activeRd.Annotations[kube.RadixCommitAnnotation], + envInfo.activeRd.Annotations[kube.RadixGitTagsAnnotation], + nil, + envInfo.envName, + envInfo.activeRd.Annotations[kube.RadixConfigHash], + envInfo.activeRd.Annotations[kube.RadixBuildSecretHash], + h.pipelineInfo.PrepareBuildContext, + h.pipelineInfo.PipelineArguments.ComponentsToDeploy, + ) + if err != nil { + return nil, fmt.Errorf("failed to construct Radix deployment: %w", err) + } + + // TODO: Verify that the same components and jobs exist in both RDs? + + targetRd := envInfo.activeRd.DeepCopy() + targetRd.ObjectMeta = *sourceRd.ObjectMeta.DeepCopy() + + for _, updater := range h.updaters { + if err := updater.UpdateDeployment(targetRd, sourceRd); err != nil { + return nil, fmt.Errorf("failed to apply configu to Radix deployment: %w", err) + } + } + + return targetRd, nil +} diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index c5cc9ce63..790fc5892 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -8,29 +8,21 @@ import ( "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" - "github.com/equinor/radix-operator/pipeline-runner/steps/internal" - "github.com/equinor/radix-operator/pkg/apis/kube" "github.com/equinor/radix-operator/pkg/apis/pipeline" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - "github.com/equinor/radix-operator/pkg/apis/utils" - "github.com/rs/zerolog/log" - "golang.org/x/sync/errgroup" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // DeployConfigStepImplementation Step to deploy RD into environment type DeployConfigStepImplementation struct { stepType pipeline.StepType - namespaceWatcher watcher.NamespaceWatcher radixDeploymentWatcher watcher.RadixDeploymentWatcher model.DefaultStepImplementation } // NewDeployConfigStep Constructor -func NewDeployConfigStep(namespaceWatcher watcher.NamespaceWatcher, radixDeploymentWatcher watcher.RadixDeploymentWatcher) model.Step { +func NewDeployConfigStep(radixDeploymentWatcher watcher.RadixDeploymentWatcher) model.Step { return &DeployConfigStepImplementation{ stepType: pipeline.DeployConfigStep, - namespaceWatcher: namespaceWatcher, radixDeploymentWatcher: radixDeploymentWatcher, } } @@ -52,7 +44,7 @@ func (cli *DeployConfigStepImplementation) ErrorMsg(err error) string { // Run Override of default step method func (cli *DeployConfigStepImplementation) Run(ctx context.Context, pipelineInfo *model.PipelineInfo) error { - var updaters []Updater + var updaters []DeploymentUpdater if pipelineInfo.PipelineArguments.DeployConfigStep.DeployExternalDNS { updaters = append(updaters, &externalDNSDeployer{}) } @@ -61,11 +53,14 @@ func (cli *DeployConfigStepImplementation) Run(ctx context.Context, pipelineInfo // } handler := deployHandler{updaters: updaters, pipelineInfo: *pipelineInfo, kubeutil: cli.GetKubeutil(), rdWatcher: cli.radixDeploymentWatcher} - return handler.deploy(ctx) - // return cli.deploy(ctx, pipelineInfo) + if err := handler.deploy(ctx); err != nil { + return fmt.Errorf("failed to deploy config: %w", err) + } + + return nil } -type Updater interface { +type DeploymentUpdater interface { MustDeployEnvironment(envName string, ra *radixv1.RadixApplication, activeRd *radixv1.RadixDeployment) bool UpdateDeployment(target, source *radixv1.RadixDeployment) error } @@ -95,402 +90,3 @@ func (d *externalDNSDeployer) UpdateDeployment(target, source *radixv1.RadixDepl return nil } - -type envInfo struct { - envName string - activeRd *radixv1.RadixDeployment -} - -type envInfoList []envInfo - -type deployHandler struct { - updaters []Updater - kubeutil *kube.Kube - pipelineInfo model.PipelineInfo - rdWatcher watcher.RadixDeploymentWatcher -} - -func (h *deployHandler) deploy(ctx context.Context) error { - envsToDeploy, err := h.getEnvironmentsToDeploy(ctx) - if err != nil { - return fmt.Errorf("failed to get list of environments to deploy: %w", err) - } - - if err := h.validateEnvironmentsToDeploy(envsToDeploy); err != nil { - return fmt.Errorf("failed to validate environments: %w", err) - } - - return h.deployEnvironments(ctx, envsToDeploy) -} - -func (h *deployHandler) validateEnvironmentsToDeploy(envsToDeploy envInfoList) error { - for _, envInfo := range envsToDeploy { - if envInfo.activeRd == nil { - return fmt.Errorf("cannot deploy to environment %s because it does not have an active Radix deployment", envInfo.envName) - } - } - return nil -} - -func (h *deployHandler) getEnvironmentsToDeploy(ctx context.Context) (envInfoList, error) { - allEnvs := envInfoList{} - for _, env := range h.pipelineInfo.RadixApplication.Spec.Environments { - envNs := utils.GetEnvironmentNamespace(h.pipelineInfo.RadixApplication.GetName(), env.Name) - rd, err := internal.GetActiveRadixDeployment(ctx, h.kubeutil, envNs) - if err != nil { - return nil, fmt.Errorf("failed to get active Radix deployment for environment %s: %w", env.Name, err) - } - allEnvs = append(allEnvs, envInfo{envName: env.Name, activeRd: rd}) - } - - deployEnvs := envInfoList{} - for _, envInfo := range allEnvs { - if slices.ContainsFunc(h.updaters, func(deployer Updater) bool { - return deployer.MustDeployEnvironment(envInfo.envName, h.pipelineInfo.RadixApplication, envInfo.activeRd) - }) { - deployEnvs = append(deployEnvs, envInfo) - } - } - - return deployEnvs, nil -} - -func (h *deployHandler) deployEnvironments(ctx context.Context, envs envInfoList) error { - rdList, err := h.buildDeployments(ctx, envs) - if err != nil { - return fmt.Errorf("failed to build Radix deployments: %w", err) - } - - // Compare new deployments with current active deployments on only deploy changed ones - - // create deployments - for _, rd := range rdList { - log.Ctx(ctx).Info().Msgf("Apply Radix deployment %s to environment %s", rd.GetName(), rd.Spec.Environment) - _, err := h.kubeutil.RadixClient().RadixV1().RadixDeployments(rd.GetNamespace()).Create(ctx, rd, metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("failed to apply Radix deployment %s to environment %s: %w", rd.GetName(), rd.Spec.Environment, err) - } - } - - // wait for activation - var g errgroup.Group - for _, rd := range rdList { - g.Go(func() error { - if err := h.rdWatcher.WaitForActive(ctx, rd.GetNamespace(), rd.GetName()); err != nil { - return fmt.Errorf("failed to wait for activation of Radix deployment %s in environment %s: %w", rd.GetName(), rd.Spec.Environment, err) - } - return nil - }) - } - - if err := g.Wait(); err != nil { - return fmt.Errorf("failed to wait for activation of Radix deployments: %w", err) - } - - return nil -} - -func (h *deployHandler) buildDeployments(ctx context.Context, envs envInfoList) ([]*radixv1.RadixDeployment, error) { - var rdList []*radixv1.RadixDeployment - - for _, envInfo := range envs { - rd, err := h.buildDeployment(ctx, envInfo) - if err != nil { - return nil, fmt.Errorf("failed to build Radix deployment for environment %s: %w", envInfo.envName, err) - } - - // Validate that the same components and jobs exist in current acxtive and new deployment - - // Compare components (and jobs) in current active and new RD. Add to list if different - rdList = append(rdList, rd) - } - - return rdList, nil -} - -func (h *deployHandler) buildDeployment(ctx context.Context, envInfo envInfo) (*radixv1.RadixDeployment, error) { - // TODO: should we use the new RA hash or hash from current active RD? - // Both can cause inconsitency issues since we technically do not apply everything from RA to the RDs - // radixApplicationHash, err := internal.CreateRadixApplicationHash(h.pipelineInfo.RadixApplication) - // if err != nil { - // return nil, err - // } - - sourceRd, err := internal.ConstructForTargetEnvironment( - ctx, - h.pipelineInfo.RadixApplication, - envInfo.activeRd, - h.pipelineInfo.PipelineArguments.JobName, - h.pipelineInfo.PipelineArguments.ImageTag, - envInfo.activeRd.Annotations[kube.RadixBranchAnnotation], - envInfo.activeRd.Annotations[kube.RadixCommitAnnotation], - envInfo.activeRd.Annotations[kube.RadixGitTagsAnnotation], - nil, - envInfo.envName, - envInfo.activeRd.Annotations[kube.RadixConfigHash], - envInfo.activeRd.Annotations[kube.RadixBuildSecretHash], - h.pipelineInfo.PrepareBuildContext, - h.pipelineInfo.PipelineArguments.ComponentsToDeploy, - ) - if err != nil { - return nil, fmt.Errorf("failed to construct Radix deployment: %w", err) - } - - targetRd := envInfo.activeRd.DeepCopy() - targetRd.ObjectMeta = *sourceRd.ObjectMeta.DeepCopy() - - for _, updater := range h.updaters { - if err := updater.UpdateDeployment(targetRd, sourceRd); err != nil { - return nil, fmt.Errorf("failed to apply configu to Radix deployment: %w", err) - } - } - - return targetRd, nil -} - -// type envDeployConfig struct { -// appExternalDNSAliases []radixv1.ExternalAlias -// activeRadixDeployment *radixv1.RadixDeployment -// } - -// // Deploy Handles deploy step of the pipeline -// func (cli *DeployConfigStepImplementation) deploy(ctx context.Context, pipelineInfo *model.PipelineInfo) error { -// appName := cli.GetAppName() -// log.Ctx(ctx).Info().Msgf("Deploying app config %s", appName) - -// envConfigToDeploy, err := cli.getEnvConfigToDeploy(ctx, pipelineInfo) -// if len(envConfigToDeploy) == 0 { -// log.Ctx(ctx).Info().Msg("no environments to deploy to") -// return err -// } -// deployedEnvs, notDeployedEnvs, err := cli.deployEnvs(ctx, pipelineInfo, envConfigToDeploy) -// if len(deployedEnvs) == 0 { -// return err -// } -// log.Ctx(ctx).Info().Msgf("Deployed app config %s to environments %v", appName, strings.Join(deployedEnvs, ",")) -// if err != nil { -// // some environments failed to deploy, but because some of them where - just log an error and not deployed envs. -// log.Ctx(ctx).Error().Err(err).Msgf("Failed to deploy app config %s to environments %v", appName, strings.Join(notDeployedEnvs, ",")) -// } -// return nil -// } - -// func (cli *DeployConfigStepImplementation) deployEnvs(ctx context.Context, pipelineInfo *model.PipelineInfo, envConfigToDeploy map[string]envDeployConfig) ([]string, []string, error) { -// deployedEnvCh, notDeployedEnvCh := make(chan string), make(chan string) -// errsCh, done := make(chan error), make(chan bool) -// defer close(deployedEnvCh) -// defer close(notDeployedEnvCh) -// defer close(errsCh) -// defer close(done) -// var notDeployedEnvs, deployedEnvs []string -// var errs []error -// go func() { -// for { -// select { -// case envName := <-deployedEnvCh: -// deployedEnvs = append(deployedEnvs, envName) -// case envName := <-notDeployedEnvCh: -// notDeployedEnvs = append(notDeployedEnvs, envName) -// case err := <-errsCh: -// errs = append(errs, err) -// case <-done: -// return -// } -// } -// }() - -// var wg sync.WaitGroup -// appName := cli.GetAppName() -// for env, deployConfig := range envConfigToDeploy { -// wg.Add(1) -// go func(envName string) { -// defer wg.Done() -// if err := cli.deployToEnv(ctx, appName, envName, deployConfig, pipelineInfo); err != nil { -// errsCh <- err -// notDeployedEnvCh <- envName -// } else { -// deployedEnvCh <- envName -// } -// }(env) -// } -// wg.Wait() -// done <- true -// return deployedEnvs, notDeployedEnvs, errors.Join(errs...) -// } - -// func (cli *DeployConfigStepImplementation) deployToEnv(ctx context.Context, appName, envName string, deployConfig envDeployConfig, pipelineInfo *model.PipelineInfo) error { -// defaultEnvVars := getDefaultEnvVars(pipelineInfo) - -// if commitID, ok := defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable]; !ok || len(commitID) == 0 { -// defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments -// } - -// radixDeployment, err := constructForTargetEnvironment(appName, envName, pipelineInfo, deployConfig) - -// if err != nil { -// return fmt.Errorf("failed to create Radix deployment in environment %s. %w", envName, err) -// } - -// namespace := utils.GetEnvironmentNamespace(cli.GetAppName(), envName) -// if err = cli.namespaceWatcher.WaitFor(ctx, namespace); err != nil { -// return fmt.Errorf("failed to get environment namespace %s, for app %s. %w", namespace, appName, err) -// } - -// radixDeploymentName := radixDeployment.GetName() -// log.Ctx(ctx).Info().Msgf("Apply Radix deployment %s to environment %s", radixDeploymentName, envName) -// if _, err = cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Create(context.Background(), radixDeployment, metav1.CreateOptions{}); err != nil { -// return fmt.Errorf("failed to apply Radix deployment for app %s to environment %s. %w", appName, envName, err) -// } - -// if err = cli.radixDeploymentWatcher.WaitForActive(ctx, namespace, radixDeploymentName); err != nil { -// log.Ctx(ctx).Error().Err(err).Msgf("Failed to activate Radix deployment %s in environment %s. Deleting deployment", radixDeploymentName, envName) -// if deleteErr := cli.GetRadixclient().RadixV1().RadixDeployments(radixDeployment.GetNamespace()).Delete(context.Background(), radixDeploymentName, metav1.DeleteOptions{}); deleteErr != nil && !k8serrors.IsNotFound(deleteErr) { -// log.Ctx(ctx).Error().Err(deleteErr).Msgf("Failed to delete Radix deployment") -// } -// return err -// } - -// return nil -// } - -// func constructForTargetEnvironment(appName, envName string, pipelineInfo *model.PipelineInfo, deployConfig envDeployConfig) (*radixv1.RadixDeployment, error) { -// radixDeployment := deployConfig.activeRadixDeployment.DeepCopy() -// if err := setMetadata(appName, envName, pipelineInfo, radixDeployment); err != nil { -// return nil, err -// } -// setExternalDNSesToRadixDeployment(radixDeployment, deployConfig) -// radixDeployment.Status = radixv1.RadixDeployStatus{} -// return radixDeployment, nil -// } - -// func setMetadata(appName, envName string, pipelineInfo *model.PipelineInfo, radixDeployment *radixv1.RadixDeployment) error { -// radixConfigHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) -// if err != nil { -// return err -// } -// buildSecretHash, err := internal.CreateBuildSecretHash(pipelineInfo.BuildSecret) -// if err != nil { -// return err -// } -// envVars := getDefaultEnvVars(pipelineInfo) -// commitID := envVars[defaults.RadixCommitHashEnvironmentVariable] -// gitTags := envVars[defaults.RadixGitTagsEnvironmentVariable] -// radixDeployment.ObjectMeta = metav1.ObjectMeta{ -// Name: utils.GetDeploymentName(appName, envName, pipelineInfo.PipelineArguments.ImageTag), -// Namespace: utils.GetEnvironmentNamespace(appName, envName), -// Annotations: radixannotations.ForRadixDeployment(gitTags, commitID, buildSecretHash, radixConfigHash), -// Labels: radixlabels.Merge( -// radixlabels.ForApplicationName(appName), -// radixlabels.ForEnvironmentName(envName), -// radixlabels.ForCommitId(commitID), -// radixlabels.ForPipelineJobName(pipelineInfo.PipelineArguments.JobName), -// radixlabels.ForRadixImageTag(pipelineInfo.PipelineArguments.ImageTag), -// ), -// } -// return nil -// } - -// func setExternalDNSesToRadixDeployment(radixDeployment *radixv1.RadixDeployment, deployConfig envDeployConfig) { -// externalAliasesMap := slice.Reduce(deployConfig.appExternalDNSAliases, make(map[string][]radixv1.ExternalAlias), func(acc map[string][]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string][]radixv1.ExternalAlias { -// acc[dnsExternalAlias.Component] = append(acc[dnsExternalAlias.Component], dnsExternalAlias) -// return acc -// }) -// for i := 0; i < len(radixDeployment.Spec.Components); i++ { -// radixDeployment.Spec.Components[i].ExternalDNS = slice.Map(externalAliasesMap[radixDeployment.Spec.Components[i].Name], func(externalAlias radixv1.ExternalAlias) radixv1.RadixDeployExternalDNS { -// return radixv1.RadixDeployExternalDNS{ -// FQDN: externalAlias.Alias, UseCertificateAutomation: externalAlias.UseCertificateAutomation} -// }) -// } -// } - -// func (cli *DeployConfigStepImplementation) getEnvConfigToDeploy(ctx context.Context, pipelineInfo *model.PipelineInfo) (map[string]envDeployConfig, error) { -// envDeployConfigs := cli.getEnvDeployConfigs(pipelineInfo) -// if err := cli.setActiveRadixDeployments(ctx, envDeployConfigs); err != nil { -// return nil, err -// } -// return getApplicableEnvDeployConfig(ctx, envDeployConfigs, pipelineInfo), nil -// } - -// type activeDeploymentExternalDNS struct { -// externalDNS radixv1.RadixDeployExternalDNS -// componentName string -// } - -// func getApplicableEnvDeployConfig(ctx context.Context, envDeployConfigs map[string]envDeployConfig, pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { -// applicableEnvDeployConfig := make(map[string]envDeployConfig) - -// for envName, deployConfig := range envDeployConfigs { -// appExternalDNSAliases := slice.FindAll(pipelineInfo.RadixApplication.Spec.DNSExternalAlias, func(dnsExternalAlias radixv1.ExternalAlias) bool { return dnsExternalAlias.Environment == envName }) -// if deployConfig.activeRadixDeployment == nil { -// if len(appExternalDNSAliases) > 0 { -// log.Ctx(ctx).Info().Msgf("External DNS alias(es) exists for the environment %s, but it has no active Radix deployment yet - ignore DNS alias(es).", envName) -// } -// continue -// } -// activeDeploymentExternalDNSes := getActiveDeploymentExternalDNSes(deployConfig) -// if equalExternalDNSAliases(activeDeploymentExternalDNSes, appExternalDNSAliases) { -// continue -// } -// applicableEnvDeployConfig[envName] = envDeployConfig{ -// appExternalDNSAliases: appExternalDNSAliases, -// activeRadixDeployment: deployConfig.activeRadixDeployment, -// } - -// } -// return applicableEnvDeployConfig -// } - -// func getActiveDeploymentExternalDNSes(deployConfig envDeployConfig) []activeDeploymentExternalDNS { -// return slice.Reduce(deployConfig.activeRadixDeployment.Spec.Components, make([]activeDeploymentExternalDNS, 0), -// func(acc []activeDeploymentExternalDNS, component radixv1.RadixDeployComponent) []activeDeploymentExternalDNS { -// externalDNSes := slice.Map(component.ExternalDNS, func(externalDNS radixv1.RadixDeployExternalDNS) activeDeploymentExternalDNS { -// return activeDeploymentExternalDNS{componentName: component.Name, externalDNS: externalDNS} -// }) -// return append(acc, externalDNSes...) -// }) -// } - -// func equalExternalDNSAliases(activeDeploymentExternalDNSes []activeDeploymentExternalDNS, appExternalAliases []radixv1.ExternalAlias) bool { -// if len(activeDeploymentExternalDNSes) != len(appExternalAliases) { -// return false -// } -// appExternalAliasesMap := slice.Reduce(appExternalAliases, make(map[string]radixv1.ExternalAlias), func(acc map[string]radixv1.ExternalAlias, dnsExternalAlias radixv1.ExternalAlias) map[string]radixv1.ExternalAlias { -// acc[dnsExternalAlias.Alias] = dnsExternalAlias -// return acc -// }) -// return slice.All(activeDeploymentExternalDNSes, func(activeExternalDNS activeDeploymentExternalDNS) bool { -// appExternalAlias, ok := appExternalAliasesMap[activeExternalDNS.externalDNS.FQDN] -// return ok && -// appExternalAlias.UseCertificateAutomation == activeExternalDNS.externalDNS.UseCertificateAutomation && -// appExternalAlias.Component == activeExternalDNS.componentName -// }) -// } - -// func (cli *DeployConfigStepImplementation) getEnvDeployConfigs(pipelineInfo *model.PipelineInfo) map[string]envDeployConfig { -// return slice.Reduce(pipelineInfo.RadixApplication.Spec.Environments, make(map[string]envDeployConfig), func(acc map[string]envDeployConfig, env radixv1.Environment) map[string]envDeployConfig { -// acc[env.Name] = envDeployConfig{} -// return acc -// }) -// } - -// func (cli *DeployConfigStepImplementation) setActiveRadixDeployments(ctx context.Context, envDeployConfigs map[string]envDeployConfig) error { -// var errs []error -// for envName, deployConfig := range envDeployConfigs { -// if activeRd, err := internal.GetActiveRadixDeployment(ctx, cli.GetKubeutil(), utils.GetEnvironmentNamespace(cli.GetAppName(), envName)); err != nil { -// errs = append(errs, err) -// } else if activeRd != nil { -// deployConfig.activeRadixDeployment = activeRd -// } -// envDeployConfigs[envName] = deployConfig -// } -// return errors.Join(errs...) -// } - -// func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { -// gitCommitHash := pipelineInfo.GitCommitHash -// gitTags := pipelineInfo.GitTags -// envVarsMap := make(radixv1.EnvVarsMap) -// envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = gitCommitHash -// envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = gitTags -// return envVarsMap -// } diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 45313e828..2d6ad9759 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -561,6 +561,9 @@ func (s *deployConfigTestSuite) TestDeployConfig() { PipelineArguments: model.PipelineArguments{ JobName: jobName, ImageTag: appliedImageTag, + DeployConfigStep: model.DeployConfigStepOptions{ + DeployExternalDNS: true, + }, }, RadixApplication: buildRadixApplication(ts.applyingRaProps), GitCommitHash: commitId, @@ -573,12 +576,10 @@ func (s *deployConfigTestSuite) TestDeployConfig() { expectedRdByEnvMap := getRadixDeploymentsByEnvMap(s.buildRadixDeployments(ts.expectedNewRadixDeploymentBuilderProps, pipelineInfo.RadixApplication)) affectedEnvs := maps.GetKeysFromMap(expectedRdByEnvMap) - namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) - namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(len(affectedEnvs)) radixDeploymentWatcher.EXPECT().WaitForActive(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(len(affectedEnvs)) - cli := deployconfig.NewDeployConfigStep(namespaceWatcher, radixDeploymentWatcher) + cli := deployconfig.NewDeployConfigStep(radixDeploymentWatcher) cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) if err := cli.Run(context.Background(), pipelineInfo); err != nil { t.Logf("Error: %v", err) diff --git a/pkg/apis/utils/annotations/annotations.go b/pkg/apis/utils/annotations/annotations.go index dc4302e9d..b209f0539 100644 --- a/pkg/apis/utils/annotations/annotations.go +++ b/pkg/apis/utils/annotations/annotations.go @@ -66,13 +66,3 @@ func ForClusterAutoscalerSafeToEvict(safeToEvict bool) map[string]string { func forAzureWorkloadIdentityClientId(clientId string) map[string]string { return map[string]string{azureWorkloadIdentityClientIdAnnotation: clientId} } - -// ForRadixDeployment Annotations foor RadixDeployment -func ForRadixDeployment(gitTags, commitID, buildSecretHash, radixConfigHash string) map[string]string { - return map[string]string{ - kube.RadixGitTagsAnnotation: gitTags, - kube.RadixCommitAnnotation: commitID, - kube.RadixBuildSecretHash: buildSecretHash, - kube.RadixConfigHash: radixConfigHash, - } -} diff --git a/pkg/apis/utils/annotations/annotations_test.go b/pkg/apis/utils/annotations/annotations_test.go index d7848d685..b3e8da4c1 100644 --- a/pkg/apis/utils/annotations/annotations_test.go +++ b/pkg/apis/utils/annotations/annotations_test.go @@ -50,9 +50,3 @@ func Test_ForClusterAutoscalerSafeToEvict(t *testing.T) { expected = map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"} assert.Equal(t, expected, actual) } - -func Test_ForRadixDeployment(t *testing.T) { - actual := ForRadixDeployment("any-tag", "any-commit", "any-secret-hash", "any-config-hash") - expected := map[string]string{kube.RadixGitTagsAnnotation: "any-tag", kube.RadixCommitAnnotation: "any-commit", kube.RadixBuildSecretHash: "any-secret-hash", kube.RadixConfigHash: "any-config-hash"} - assert.Equal(t, expected, actual) -} From 008d2e1cfa9136734f170ccaa7ad7c6e487274dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Gustav=20Str=C3=A5b=C3=B8?= Date: Fri, 25 Oct 2024 15:39:01 +0200 Subject: [PATCH 24/27] misc changes --- .vscode/launch.json | 2 +- pipeline-runner/main.go | 2 +- pipeline-runner/model/pipelineInfo.go | 5 +- .../internal/externaldns_provider.go | 36 +++++++++ .../deployconfig/internal/feature_provider.go | 14 ++++ .../deployconfig/{ => internal}/handler.go | 81 ++++++++++--------- pipeline-runner/steps/deployconfig/step.go | 48 ++--------- .../steps/deployconfig/step_test.go | 2 +- pkg/apis/defaults/environment_variables.go | 2 + pkg/apis/job/kubejob.go | 2 + pkg/apis/radix/v1/radixjobtypes.go | 31 ++++--- pkg/apis/radix/v1/zz_generated.deepcopy.go | 17 ++++ 12 files changed, 144 insertions(+), 98 deletions(-) create mode 100644 pipeline-runner/steps/deployconfig/internal/externaldns_provider.go create mode 100644 pipeline-runner/steps/deployconfig/internal/feature_provider.go rename pipeline-runner/steps/deployconfig/{ => internal}/handler.go (70%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2b3d988aa..db7a04a92 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -115,7 +115,7 @@ "--RADIX_CONTAINER_REGISTRY=radixdev.azurecr.io", "--RADIX_RESERVED_APP_DNS_ALIASES=api=radix-api,canary=radix-canary-golang,console=radix-web-console,cost-api=radix-cost-allocation-api,webhook=radix-github-webhook", "--RADIX_RESERVED_DNS_ALIASES=grafana,prometheus,www", - "--PIPELINE_DEPLOYCONFIG_STEP_DEPLOYEXTERNALDNS=true" + "--APPLY_CONFIG_DEPLOY_EXTERNALDNS=true" ] }, { diff --git a/pipeline-runner/main.go b/pipeline-runner/main.go index de5708f95..3f274a1e5 100755 --- a/pipeline-runner/main.go +++ b/pipeline-runner/main.go @@ -133,7 +133,7 @@ func setPipelineArgsFromArguments(cmd *cobra.Command, pipelineArgs *model.Pipeli cmd.Flags().StringVar(&pipelineArgs.GitCloneNsLookupImage, defaults.RadixGitCloneNsLookupImageEnvironmentVariable, "alpine:latest", "Container image with nslookup used by git clone init containers") cmd.Flags().StringVar(&pipelineArgs.GitCloneGitImage, defaults.RadixGitCloneGitImageEnvironmentVariable, "alpine/git:latest", "Container image with git used by git clone init containers") cmd.Flags().StringVar(&pipelineArgs.GitCloneBashImage, defaults.RadixGitCloneBashImageEnvironmentVariable, "bash:latest", "Container image with bash used by git clone init containers") - cmd.Flags().BoolVar(&pipelineArgs.DeployConfigStep.DeployExternalDNS, "PIPELINE_DEPLOYCONFIG_STEP_DEPLOYEXTERNALDNS", false, "") + cmd.Flags().BoolVar(&pipelineArgs.ApplyConfigOptions.DeployExternalDNS, defaults.RadixPipelineApplyConfigDeployExternalDNSFlag, false, "Deploy changes to External DNS configuration with the 'apply-config' pipeline") // TODO: Remove when both pipeline and operator is released. This flag is only to prevent errors when deprecated flag is passed cmd.Flags().String("USE_CACHE", "0", "Use cache") diff --git a/pipeline-runner/model/pipelineInfo.go b/pipeline-runner/model/pipelineInfo.go index 460261247..29ed16153 100644 --- a/pipeline-runner/model/pipelineInfo.go +++ b/pipeline-runner/model/pipelineInfo.go @@ -55,9 +55,8 @@ type Builder struct { ResourcesRequestsMemory string } -type DeployConfigStepOptions struct { +type ApplyConfigOptions struct { DeployExternalDNS bool - // DeployAppAlias bool } // PipelineArguments Holds arguments for the pipeline @@ -121,7 +120,7 @@ type PipelineArguments struct { // Used to authenticate external container registries when using buildkit to build dockerfiles. ExternalContainerRegistryDefaultAuthSecret string - DeployConfigStep DeployConfigStepOptions + ApplyConfigOptions ApplyConfigOptions } // InitPipeline Initialize pipeline with step implementations diff --git a/pipeline-runner/steps/deployconfig/internal/externaldns_provider.go b/pipeline-runner/steps/deployconfig/internal/externaldns_provider.go new file mode 100644 index 000000000..c823e4e5b --- /dev/null +++ b/pipeline-runner/steps/deployconfig/internal/externaldns_provider.go @@ -0,0 +1,36 @@ +package internal + +import ( + "fmt" + "slices" + + "github.com/equinor/radix-common/utils/slice" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" +) + +// ExternalDNSFeatureProvider handles external DNS configuration +type ExternalDNSFeatureProvider struct{} + +func (d *ExternalDNSFeatureProvider) IsEnabledForEnvironment(envName string, ra *radixv1.RadixApplication, activeRd *radixv1.RadixDeployment) bool { + if slices.ContainsFunc(ra.Spec.DNSExternalAlias, func(alias radixv1.ExternalAlias) bool { return alias.Environment == envName }) { + return true + } + + if activeRd != nil && slices.ContainsFunc(activeRd.Spec.Components, func(comp radixv1.RadixDeployComponent) bool { return len(comp.GetExternalDNS()) > 0 }) { + return true + } + + return false +} + +func (d *ExternalDNSFeatureProvider) Mutate(target, source *radixv1.RadixDeployment) error { + for i, targetComp := range target.Spec.Components { + sourceComp, found := slice.FindFirst(source.Spec.Components, func(c radixv1.RadixDeployComponent) bool { return c.Name == targetComp.Name }) + if !found { + return fmt.Errorf("component %s not found in active deployment", targetComp.Name) + } + target.Spec.Components[i].ExternalDNS = sourceComp.GetExternalDNS() + } + + return nil +} diff --git a/pipeline-runner/steps/deployconfig/internal/feature_provider.go b/pipeline-runner/steps/deployconfig/internal/feature_provider.go new file mode 100644 index 000000000..4434fe393 --- /dev/null +++ b/pipeline-runner/steps/deployconfig/internal/feature_provider.go @@ -0,0 +1,14 @@ +package internal + +import ( + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" +) + +// FeatureProvider provides methods for checking and mutating Radix features +type FeatureProvider interface { + // Check if feature is enabled for the specified environment by inspecting RadixApplication and active RadixDeployment (if set) + IsEnabledForEnvironment(envName string, ra *radixv1.RadixApplication, activeRd *radixv1.RadixDeployment) bool + + // Mutates target with fields from source + Mutate(target, source *radixv1.RadixDeployment) error +} diff --git a/pipeline-runner/steps/deployconfig/handler.go b/pipeline-runner/steps/deployconfig/internal/handler.go similarity index 70% rename from pipeline-runner/steps/deployconfig/handler.go rename to pipeline-runner/steps/deployconfig/internal/handler.go index 1acefde7a..8b35b9dcb 100644 --- a/pipeline-runner/steps/deployconfig/handler.go +++ b/pipeline-runner/steps/deployconfig/internal/handler.go @@ -1,4 +1,4 @@ -package deployconfig +package internal import ( "context" @@ -27,39 +27,44 @@ type envInfo struct { type envInfoList []envInfo type deployHandler struct { - updaters []DeploymentUpdater - kubeutil *kube.Kube - pipelineInfo model.PipelineInfo - rdWatcher watcher.RadixDeploymentWatcher + pipelineInfo *model.PipelineInfo + kubeUtil *kube.Kube + rdWatcher watcher.RadixDeploymentWatcher + featureProviders []FeatureProvider } -func (h *deployHandler) deploy(ctx context.Context) error { - envsToDeploy, err := h.getEnvironmentsToDeploy(ctx) - if err != nil { - return fmt.Errorf("failed to get list of environments to deploy: %w", err) +func NewHandler(pipelineInfo *model.PipelineInfo, kubeUtil *kube.Kube, rdWatcher watcher.RadixDeploymentWatcher, featureProviders []FeatureProvider) *deployHandler { + return &deployHandler{ + pipelineInfo: pipelineInfo, + kubeUtil: kubeUtil, + rdWatcher: rdWatcher, + featureProviders: featureProviders, } +} - if err := h.validateEnvironmentsToDeploy(envsToDeploy); err != nil { - return fmt.Errorf("failed to validate environments: %w", err) +func (h *deployHandler) Deploy(ctx context.Context) error { + candidates, err := h.getEnvironmentCandidates(ctx) + if err != nil { + return fmt.Errorf("failed to list environments: %w", err) } - return h.deployEnvironments(ctx, envsToDeploy) -} + deployments, err := h.buildDeploymentsForEnvironments(ctx, candidates) + if err != nil { + return fmt.Errorf("failed to build Radix deployments: %w", err) + } -func (h *deployHandler) validateEnvironmentsToDeploy(envsToDeploy envInfoList) error { - for _, envInfo := range envsToDeploy { - if envInfo.activeRd == nil { - return fmt.Errorf("cannot deploy to environment %s because it does not have an active Radix deployment", envInfo.envName) - } + if err := h.applyDeployments(ctx, deployments); err != nil { + return fmt.Errorf("failed to apply Radix deployments: %w", err) } + return nil } -func (h *deployHandler) getEnvironmentsToDeploy(ctx context.Context) (envInfoList, error) { +func (h *deployHandler) getEnvironmentCandidates(ctx context.Context) (envInfoList, error) { allEnvs := envInfoList{} for _, env := range h.pipelineInfo.RadixApplication.Spec.Environments { envNs := utils.GetEnvironmentNamespace(h.pipelineInfo.RadixApplication.GetName(), env.Name) - rd, err := internal.GetActiveRadixDeployment(ctx, h.kubeutil, envNs) + rd, err := internal.GetActiveRadixDeployment(ctx, h.kubeUtil, envNs) if err != nil { return nil, fmt.Errorf("failed to get active Radix deployment for environment %s: %w", env.Name, err) } @@ -68,8 +73,8 @@ func (h *deployHandler) getEnvironmentsToDeploy(ctx context.Context) (envInfoLis deployEnvs := envInfoList{} for _, envInfo := range allEnvs { - if slices.ContainsFunc(h.updaters, func(deployer DeploymentUpdater) bool { - return deployer.MustDeployEnvironment(envInfo.envName, h.pipelineInfo.RadixApplication, envInfo.activeRd) + if slices.ContainsFunc(h.featureProviders, func(deployer FeatureProvider) bool { + return deployer.IsEnabledForEnvironment(envInfo.envName, h.pipelineInfo.RadixApplication, envInfo.activeRd) }) { deployEnvs = append(deployEnvs, envInfo) } @@ -78,16 +83,12 @@ func (h *deployHandler) getEnvironmentsToDeploy(ctx context.Context) (envInfoLis return deployEnvs, nil } -func (h *deployHandler) deployEnvironments(ctx context.Context, envs envInfoList) error { - rdList, err := h.buildDeployments(ctx, envs) - if err != nil { - return fmt.Errorf("failed to build Radix deployments: %w", err) - } - - if err := h.applyDeployments(ctx, rdList); err != nil { - return fmt.Errorf("failed to apply Radix deployments: %w", err) +func (h *deployHandler) validateEnvironments(environments envInfoList) error { + for _, envInfo := range environments { + if envInfo.activeRd == nil { + return fmt.Errorf("cannot deploy to environment %s because it does not have an active Radix deployment", envInfo.envName) + } } - return nil } @@ -98,7 +99,7 @@ func (h *deployHandler) applyDeployments(ctx context.Context, rdList []*radixv1. for _, rd := range rdList { log.Ctx(ctx).Info().Msgf("Apply Radix deployment %s to environment %s", rd.GetName(), rd.Spec.Environment) - _, err := h.kubeutil.RadixClient().RadixV1().RadixDeployments(rd.GetNamespace()).Create(ctx, rd, metav1.CreateOptions{}) + _, err := h.kubeUtil.RadixClient().RadixV1().RadixDeployments(rd.GetNamespace()).Create(ctx, rd, metav1.CreateOptions{}) if err != nil { return fmt.Errorf("failed to apply Radix deployment %s to environment %s: %w", rd.GetName(), rd.Spec.Environment, err) } @@ -121,10 +122,13 @@ func (h *deployHandler) applyDeployments(ctx context.Context, rdList []*radixv1. return nil } -func (h *deployHandler) buildDeployments(ctx context.Context, envs envInfoList) ([]*radixv1.RadixDeployment, error) { - var rdList []*radixv1.RadixDeployment +func (h *deployHandler) buildDeploymentsForEnvironments(ctx context.Context, environments envInfoList) ([]*radixv1.RadixDeployment, error) { + if err := h.validateEnvironments(environments); err != nil { + return nil, fmt.Errorf("failed to validate environments: %w", err) + } - for _, envInfo := range envs { + var rdList []*radixv1.RadixDeployment + for _, envInfo := range environments { rd, err := h.buildDeployment(ctx, envInfo) if err != nil { return nil, fmt.Errorf("failed to build Radix deployment for environment %s: %w", envInfo.envName, err) @@ -167,13 +171,12 @@ func (h *deployHandler) buildDeployment(ctx context.Context, envInfo envInfo) (* return nil, fmt.Errorf("failed to construct Radix deployment: %w", err) } - // TODO: Verify that the same components and jobs exist in both RDs? - targetRd := envInfo.activeRd.DeepCopy() targetRd.ObjectMeta = *sourceRd.ObjectMeta.DeepCopy() + targetRd.Status = radixv1.RadixDeployStatus{} - for _, updater := range h.updaters { - if err := updater.UpdateDeployment(targetRd, sourceRd); err != nil { + for _, updater := range h.featureProviders { + if err := updater.Mutate(targetRd, sourceRd); err != nil { return nil, fmt.Errorf("failed to apply configu to Radix deployment: %w", err) } } diff --git a/pipeline-runner/steps/deployconfig/step.go b/pipeline-runner/steps/deployconfig/step.go index 790fc5892..70c102bdb 100644 --- a/pipeline-runner/steps/deployconfig/step.go +++ b/pipeline-runner/steps/deployconfig/step.go @@ -3,13 +3,11 @@ package deployconfig import ( "context" "fmt" - "slices" - "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" + "github.com/equinor/radix-operator/pipeline-runner/steps/deployconfig/internal" "github.com/equinor/radix-operator/pkg/apis/pipeline" - radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" ) // DeployConfigStepImplementation Step to deploy RD into environment @@ -44,49 +42,15 @@ func (cli *DeployConfigStepImplementation) ErrorMsg(err error) string { // Run Override of default step method func (cli *DeployConfigStepImplementation) Run(ctx context.Context, pipelineInfo *model.PipelineInfo) error { - var updaters []DeploymentUpdater - if pipelineInfo.PipelineArguments.DeployConfigStep.DeployExternalDNS { - updaters = append(updaters, &externalDNSDeployer{}) + var featureProviders []internal.FeatureProvider + if pipelineInfo.PipelineArguments.ApplyConfigOptions.DeployExternalDNS { + featureProviders = append(featureProviders, &internal.ExternalDNSFeatureProvider{}) } - // if pipelineInfo.PipelineArguments.DeployConfigStep.DeployAppAlias { - // updaters = append(updaters, nil) - // } - handler := deployHandler{updaters: updaters, pipelineInfo: *pipelineInfo, kubeutil: cli.GetKubeutil(), rdWatcher: cli.radixDeploymentWatcher} - if err := handler.deploy(ctx); err != nil { + handler := internal.NewHandler(pipelineInfo, cli.GetKubeutil(), cli.radixDeploymentWatcher, featureProviders) + if err := handler.Deploy(ctx); err != nil { return fmt.Errorf("failed to deploy config: %w", err) } return nil } - -type DeploymentUpdater interface { - MustDeployEnvironment(envName string, ra *radixv1.RadixApplication, activeRd *radixv1.RadixDeployment) bool - UpdateDeployment(target, source *radixv1.RadixDeployment) error -} - -type externalDNSDeployer struct{} - -func (d *externalDNSDeployer) MustDeployEnvironment(envName string, ra *radixv1.RadixApplication, activeRd *radixv1.RadixDeployment) bool { - if slices.ContainsFunc(ra.Spec.DNSExternalAlias, func(alias radixv1.ExternalAlias) bool { return alias.Environment == envName }) { - return true - } - - if activeRd != nil && slices.ContainsFunc(activeRd.Spec.Components, func(comp radixv1.RadixDeployComponent) bool { return len(comp.GetExternalDNS()) > 0 }) { - return true - } - - return false -} - -func (d *externalDNSDeployer) UpdateDeployment(target, source *radixv1.RadixDeployment) error { - for i, targetComp := range target.Spec.Components { - sourceComp, found := slice.FindFirst(source.Spec.Components, func(c radixv1.RadixDeployComponent) bool { return c.Name == targetComp.Name }) - if !found { - return fmt.Errorf("component %s not found in active deployment", targetComp.Name) - } - target.Spec.Components[i].ExternalDNS = sourceComp.GetExternalDNS() - } - - return nil -} diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index 2d6ad9759..b3655f382 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -561,7 +561,7 @@ func (s *deployConfigTestSuite) TestDeployConfig() { PipelineArguments: model.PipelineArguments{ JobName: jobName, ImageTag: appliedImageTag, - DeployConfigStep: model.DeployConfigStepOptions{ + ApplyConfigOptions: model.ApplyConfigOptions{ DeployExternalDNS: true, }, }, diff --git a/pkg/apis/defaults/environment_variables.go b/pkg/apis/defaults/environment_variables.go index 25e628107..02ae2e4e1 100644 --- a/pkg/apis/defaults/environment_variables.go +++ b/pkg/apis/defaults/environment_variables.go @@ -159,6 +159,8 @@ const ( // RadixPipelineActionEnvironmentVariable Pipeline action: prepare, run RadixPipelineActionEnvironmentVariable = "RADIX_PIPELINE_ACTION" + RadixPipelineApplyConfigDeployExternalDNSFlag = "APPLY_CONFIG_DEPLOY_EXTERNALDNS" + // OperatorTenantIdEnvironmentVariable Tenant-id of the subscription OperatorTenantIdEnvironmentVariable = "RADIXOPERATOR_TENANT_ID" diff --git a/pkg/apis/job/kubejob.go b/pkg/apis/job/kubejob.go index 13fbf7cd2..68065ef72 100644 --- a/pkg/apis/job/kubejob.go +++ b/pkg/apis/job/kubejob.go @@ -212,6 +212,8 @@ func (job *Job) getPipelineJobArguments(ctx context.Context, appName, jobName st args = append(args, fmt.Sprintf("--%s=%s=%s", defaults.RadixImageTagNameEnvironmentVariable, componentName, imageTagName)) } args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixComponentsToDeployVariable, strings.Join(jobSpec.Deploy.ComponentsToDeploy, ","))) + case radixv1.ApplyConfig: + args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixPipelineApplyConfigDeployExternalDNSFlag, jobSpec.ApplyConfig.DeployExternalDNS)) } return args, nil diff --git a/pkg/apis/radix/v1/radixjobtypes.go b/pkg/apis/radix/v1/radixjobtypes.go index 402e151b3..b00f95173 100644 --- a/pkg/apis/radix/v1/radixjobtypes.go +++ b/pkg/apis/radix/v1/radixjobtypes.go @@ -47,17 +47,18 @@ const ( // RadixJobSpec is the spec for a job type RadixJobSpec struct { - AppName string `json:"appName" yaml:"appName"` - CloneURL string `json:"cloneURL" yaml:"cloneURL"` - TektonImage string `json:"tektonImage" yaml:"tektonImage"` - PipeLineType RadixPipelineType `json:"pipeLineType" yaml:"pipeLineType"` - PipelineImage string `json:"pipelineImage" yaml:"pipelineImage"` - Build RadixBuildSpec `json:"build" yaml:"build"` - Promote RadixPromoteSpec `json:"promote" yaml:"promote"` - Deploy RadixDeploySpec `json:"deploy" yaml:"deploy"` - Stop bool `json:"stop" yaml:"stop"` - TriggeredBy string `json:"triggeredBy" yaml:"triggeredBy"` - RadixConfigFullName string `json:"radixConfigFullName" yaml:"radixConfigFullName"` + AppName string `json:"appName" yaml:"appName"` + CloneURL string `json:"cloneURL" yaml:"cloneURL"` + TektonImage string `json:"tektonImage" yaml:"tektonImage"` + PipeLineType RadixPipelineType `json:"pipeLineType" yaml:"pipeLineType"` + PipelineImage string `json:"pipelineImage" yaml:"pipelineImage"` + Build RadixBuildSpec `json:"build" yaml:"build"` + Promote RadixPromoteSpec `json:"promote" yaml:"promote"` + Deploy RadixDeploySpec `json:"deploy" yaml:"deploy"` + ApplyConfig RadixApplyConfigSpec `json:"applyConfig"` + Stop bool `json:"stop" yaml:"stop"` + TriggeredBy string `json:"triggeredBy" yaml:"triggeredBy"` + RadixConfigFullName string `json:"radixConfigFullName" yaml:"radixConfigFullName"` } // RadixPipelineType Holds the different type of pipeline @@ -148,6 +149,14 @@ type RadixDeploySpec struct { ComponentsToDeploy []string `json:"componentsToDeploy" yaml:"componentsToDeploy"` } +// RadixApplyConfigSpec is the spec for a apply-config job +type RadixApplyConfigSpec struct { + // Deploy External DNS configuration + // + // required: false + DeployExternalDNS bool `json:"deployExternalDNS"` +} + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // RadixJobList is a list of Radix jobs diff --git a/pkg/apis/radix/v1/zz_generated.deepcopy.go b/pkg/apis/radix/v1/zz_generated.deepcopy.go index 408e3a8a4..fa71c6a19 100644 --- a/pkg/apis/radix/v1/zz_generated.deepcopy.go +++ b/pkg/apis/radix/v1/zz_generated.deepcopy.go @@ -964,6 +964,22 @@ func (in *RadixApplicationSpec) DeepCopy() *RadixApplicationSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RadixApplyConfigSpec) DeepCopyInto(out *RadixApplyConfigSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RadixApplyConfigSpec. +func (in *RadixApplyConfigSpec) DeepCopy() *RadixApplyConfigSpec { + if in == nil { + return nil + } + out := new(RadixApplyConfigSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RadixAzureFileVolumeMount) DeepCopyInto(out *RadixAzureFileVolumeMount) { *out = *in @@ -2665,6 +2681,7 @@ func (in *RadixJobSpec) DeepCopyInto(out *RadixJobSpec) { in.Build.DeepCopyInto(&out.Build) out.Promote = in.Promote in.Deploy.DeepCopyInto(&out.Deploy) + out.ApplyConfig = in.ApplyConfig return } From 39bd61d8c3c09eb8bfe7ad0ec703988ea6f2f971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20Gustav=20Str=C3=A5b=C3=B8?= Date: Mon, 28 Oct 2024 14:28:52 +0100 Subject: [PATCH 25/27] ignore environment without active RD --- .../internal/externaldns_provider.go | 6 ++-- .../deployconfig/internal/feature_provider.go | 4 +-- .../steps/deployconfig/internal/handler.go | 31 ++++++++----------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/pipeline-runner/steps/deployconfig/internal/externaldns_provider.go b/pipeline-runner/steps/deployconfig/internal/externaldns_provider.go index c823e4e5b..0748845cf 100644 --- a/pipeline-runner/steps/deployconfig/internal/externaldns_provider.go +++ b/pipeline-runner/steps/deployconfig/internal/externaldns_provider.go @@ -11,19 +11,19 @@ import ( // ExternalDNSFeatureProvider handles external DNS configuration type ExternalDNSFeatureProvider struct{} -func (d *ExternalDNSFeatureProvider) IsEnabledForEnvironment(envName string, ra *radixv1.RadixApplication, activeRd *radixv1.RadixDeployment) bool { +func (d *ExternalDNSFeatureProvider) IsEnabledForEnvironment(envName string, ra radixv1.RadixApplication, activeRd radixv1.RadixDeployment) bool { if slices.ContainsFunc(ra.Spec.DNSExternalAlias, func(alias radixv1.ExternalAlias) bool { return alias.Environment == envName }) { return true } - if activeRd != nil && slices.ContainsFunc(activeRd.Spec.Components, func(comp radixv1.RadixDeployComponent) bool { return len(comp.GetExternalDNS()) > 0 }) { + if slices.ContainsFunc(activeRd.Spec.Components, func(comp radixv1.RadixDeployComponent) bool { return len(comp.GetExternalDNS()) > 0 }) { return true } return false } -func (d *ExternalDNSFeatureProvider) Mutate(target, source *radixv1.RadixDeployment) error { +func (d *ExternalDNSFeatureProvider) Mutate(target, source radixv1.RadixDeployment) error { for i, targetComp := range target.Spec.Components { sourceComp, found := slice.FindFirst(source.Spec.Components, func(c radixv1.RadixDeployComponent) bool { return c.Name == targetComp.Name }) if !found { diff --git a/pipeline-runner/steps/deployconfig/internal/feature_provider.go b/pipeline-runner/steps/deployconfig/internal/feature_provider.go index 4434fe393..617732128 100644 --- a/pipeline-runner/steps/deployconfig/internal/feature_provider.go +++ b/pipeline-runner/steps/deployconfig/internal/feature_provider.go @@ -7,8 +7,8 @@ import ( // FeatureProvider provides methods for checking and mutating Radix features type FeatureProvider interface { // Check if feature is enabled for the specified environment by inspecting RadixApplication and active RadixDeployment (if set) - IsEnabledForEnvironment(envName string, ra *radixv1.RadixApplication, activeRd *radixv1.RadixDeployment) bool + IsEnabledForEnvironment(envName string, ra radixv1.RadixApplication, activeRd radixv1.RadixDeployment) bool // Mutates target with fields from source - Mutate(target, source *radixv1.RadixDeployment) error + Mutate(target, source radixv1.RadixDeployment) error } diff --git a/pipeline-runner/steps/deployconfig/internal/handler.go b/pipeline-runner/steps/deployconfig/internal/handler.go index 8b35b9dcb..e2c2cc008 100644 --- a/pipeline-runner/steps/deployconfig/internal/handler.go +++ b/pipeline-runner/steps/deployconfig/internal/handler.go @@ -21,7 +21,7 @@ import ( type envInfo struct { envName string - activeRd *radixv1.RadixDeployment + activeRd radixv1.RadixDeployment } type envInfoList []envInfo @@ -43,6 +43,10 @@ func NewHandler(pipelineInfo *model.PipelineInfo, kubeUtil *kube.Kube, rdWatcher } func (h *deployHandler) Deploy(ctx context.Context) error { + if len(h.featureProviders) == 0 { + return nil + } + candidates, err := h.getEnvironmentCandidates(ctx) if err != nil { return fmt.Errorf("failed to list environments: %w", err) @@ -68,13 +72,17 @@ func (h *deployHandler) getEnvironmentCandidates(ctx context.Context) (envInfoLi if err != nil { return nil, fmt.Errorf("failed to get active Radix deployment for environment %s: %w", env.Name, err) } - allEnvs = append(allEnvs, envInfo{envName: env.Name, activeRd: rd}) + if rd != nil { + allEnvs = append(allEnvs, envInfo{envName: env.Name, activeRd: *rd}) + } else { + log.Ctx(ctx).Info().Msgf("Ignoring environment %s because does not have an active Radix deploymewnt", env.Name) + } } deployEnvs := envInfoList{} for _, envInfo := range allEnvs { if slices.ContainsFunc(h.featureProviders, func(deployer FeatureProvider) bool { - return deployer.IsEnabledForEnvironment(envInfo.envName, h.pipelineInfo.RadixApplication, envInfo.activeRd) + return deployer.IsEnabledForEnvironment(envInfo.envName, *h.pipelineInfo.RadixApplication, envInfo.activeRd) }) { deployEnvs = append(deployEnvs, envInfo) } @@ -83,15 +91,6 @@ func (h *deployHandler) getEnvironmentCandidates(ctx context.Context) (envInfoLi return deployEnvs, nil } -func (h *deployHandler) validateEnvironments(environments envInfoList) error { - for _, envInfo := range environments { - if envInfo.activeRd == nil { - return fmt.Errorf("cannot deploy to environment %s because it does not have an active Radix deployment", envInfo.envName) - } - } - return nil -} - func (h *deployHandler) applyDeployments(ctx context.Context, rdList []*radixv1.RadixDeployment) error { if len(rdList) == 0 { return nil @@ -123,10 +122,6 @@ func (h *deployHandler) applyDeployments(ctx context.Context, rdList []*radixv1. } func (h *deployHandler) buildDeploymentsForEnvironments(ctx context.Context, environments envInfoList) ([]*radixv1.RadixDeployment, error) { - if err := h.validateEnvironments(environments); err != nil { - return nil, fmt.Errorf("failed to validate environments: %w", err) - } - var rdList []*radixv1.RadixDeployment for _, envInfo := range environments { rd, err := h.buildDeployment(ctx, envInfo) @@ -154,7 +149,7 @@ func (h *deployHandler) buildDeployment(ctx context.Context, envInfo envInfo) (* sourceRd, err := internal.ConstructForTargetEnvironment( ctx, h.pipelineInfo.RadixApplication, - envInfo.activeRd, + &envInfo.activeRd, h.pipelineInfo.PipelineArguments.JobName, h.pipelineInfo.PipelineArguments.ImageTag, envInfo.activeRd.Annotations[kube.RadixBranchAnnotation], @@ -176,7 +171,7 @@ func (h *deployHandler) buildDeployment(ctx context.Context, envInfo envInfo) (* targetRd.Status = radixv1.RadixDeployStatus{} for _, updater := range h.featureProviders { - if err := updater.Mutate(targetRd, sourceRd); err != nil { + if err := updater.Mutate(*targetRd, *sourceRd); err != nil { return nil, fmt.Errorf("failed to apply configu to Radix deployment: %w", err) } } From f4993a7d635bf354c1380cb59fa766128ffcb758 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Mon, 28 Oct 2024 16:24:37 +0100 Subject: [PATCH 26/27] Fixed unit-tests --- go.mod | 2 +- .../steps/deployconfig/internal/handler.go | 7 ----- .../steps/deployconfig/step_test.go | 29 +++++++++++++++---- pipeline-runner/steps/internal/deployment.go | 2 +- pipeline-runner/steps/promote/step.go | 2 +- pkg/apis/job/kubejob.go | 2 +- pkg/apis/utils/deployment_builder.go | 2 +- pkg/apis/utils/deployments.go | 2 +- 8 files changed, 29 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index e16292af3..6208c5e5b 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/cert-manager/cert-manager v1.14.2 github.com/equinor/radix-common v1.9.4 github.com/golang/mock v1.6.0 + github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/kedacore/keda/v2 v2.15.1 github.com/pkg/errors v0.9.1 @@ -50,7 +51,6 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/imdario/mergo v0.3.16 // indirect diff --git a/pipeline-runner/steps/deployconfig/internal/handler.go b/pipeline-runner/steps/deployconfig/internal/handler.go index e2c2cc008..02de75826 100644 --- a/pipeline-runner/steps/deployconfig/internal/handler.go +++ b/pipeline-runner/steps/deployconfig/internal/handler.go @@ -139,13 +139,6 @@ func (h *deployHandler) buildDeploymentsForEnvironments(ctx context.Context, env } func (h *deployHandler) buildDeployment(ctx context.Context, envInfo envInfo) (*radixv1.RadixDeployment, error) { - // TODO: should we use the new RA hash or hash from current active RD? - // Both can cause inconsitency issues since we technically do not apply everything from RA to the RDs - // radixApplicationHash, err := internal.CreateRadixApplicationHash(h.pipelineInfo.RadixApplication) - // if err != nil { - // return nil, err - // } - sourceRd, err := internal.ConstructForTargetEnvironment( ctx, h.pipelineInfo.RadixApplication, diff --git a/pipeline-runner/steps/deployconfig/step_test.go b/pipeline-runner/steps/deployconfig/step_test.go index b3655f382..4aa44dc80 100644 --- a/pipeline-runner/steps/deployconfig/step_test.go +++ b/pipeline-runner/steps/deployconfig/step_test.go @@ -572,8 +572,8 @@ func (s *deployConfigTestSuite) TestDeployConfig() { s.SetupTest() s.createUsedNamespaces(ts) - s.createRadixDeployments(ts.existingRadixDeploymentBuilderProps, existingRa) - expectedRdByEnvMap := getRadixDeploymentsByEnvMap(s.buildRadixDeployments(ts.expectedNewRadixDeploymentBuilderProps, pipelineInfo.RadixApplication)) + existingRdByEnvMap := s.createRadixDeployments(ts.existingRadixDeploymentBuilderProps, existingRa) + expectedRdByEnvMap := getRadixDeploymentsByEnvMap(s.buildRadixDeployments(ts.expectedNewRadixDeploymentBuilderProps, pipelineInfo.RadixApplication, existingRdByEnvMap)) affectedEnvs := maps.GetKeysFromMap(expectedRdByEnvMap) radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) @@ -670,24 +670,37 @@ func (s *deployConfigTestSuite) createRadixApplication(props raProps) *radixv1.R return radixApplication } -func (s *deployConfigTestSuite) createRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps, ra *radixv1.RadixApplication) { - rdList := s.buildRadixDeployments(deploymentBuildersProps, ra) +func (s *deployConfigTestSuite) createRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps, ra *radixv1.RadixApplication) map[string]radixv1.RadixDeployment { + rdList := s.buildRadixDeployments(deploymentBuildersProps, ra, nil) for _, rd := range rdList { _, err := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(rd.Spec.AppName, rd.Spec.Environment)).Create(context.Background(), &rd, metav1.CreateOptions{}) s.Require().NoError(err) } + return slice.Reduce(rdList, make(map[string]radixv1.RadixDeployment), func(acc map[string]radixv1.RadixDeployment, rd radixv1.RadixDeployment) map[string]radixv1.RadixDeployment { + acc[rd.Spec.Environment] = rd + return acc + }) } -func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps, ra *radixv1.RadixApplication) []radixv1.RadixDeployment { +func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps []radixDeploymentBuildersProps, ra *radixv1.RadixApplication, sourceEnvMap map[string]radixv1.RadixDeployment) []radixv1.RadixDeployment { radixConfigHash, _ := internal.CreateRadixApplicationHash(ra) buildSecretHash, _ := internal.CreateBuildSecretHash(nil) var rdList []radixv1.RadixDeployment for _, rdProps := range deploymentBuildersProps { + if sourceRd, ok := sourceEnvMap[rdProps.envName]; ok { + if sourceRdRadixConfigHash, ok := sourceRd.GetAnnotations()[kube.RadixConfigHash]; ok && len(sourceRdRadixConfigHash) > 0 { + radixConfigHash = sourceRdRadixConfigHash + } + if sourceBuildSecretHash, ok := sourceRd.GetAnnotations()[kube.RadixBuildSecretHash]; ok && len(sourceBuildSecretHash) > 0 { + buildSecretHash = sourceBuildSecretHash + } + } deploymentBuilder := utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment(rdProps.envName).WithImageTag(rdProps.imageTag). WithActiveFrom(rdProps.activeFrom).WithLabel(kube.RadixCommitLabel, commitId).WithLabel(kube.RadixJobNameLabel, jobName). WithAnnotations(map[string]string{ kube.RadixGitTagsAnnotation: gitTags, kube.RadixCommitAnnotation: commitId, + kube.RadixBranchAnnotation: "", kube.RadixBuildSecretHash: buildSecretHash, kube.RadixConfigHash: radixConfigHash, }) @@ -705,7 +718,11 @@ func (s *deployConfigTestSuite) buildRadixDeployments(deploymentBuildersProps [] // set other props if needed deploymentBuilder = deploymentBuilder.WithComponent(componentBuilder) } - rdList = append(rdList, *deploymentBuilder.BuildRD()) + rd := deploymentBuilder.BuildRD() + labels := rd.GetLabels() + delete(labels, kube.RadixImageTagLabel) // HACK - not actually used label + rd.SetLabels(labels) + rdList = append(rdList, *rd) } return rdList } diff --git a/pipeline-runner/steps/internal/deployment.go b/pipeline-runner/steps/internal/deployment.go index c6e9941ce..cb708bb9f 100644 --- a/pipeline-runner/steps/internal/deployment.go +++ b/pipeline-runner/steps/internal/deployment.go @@ -59,7 +59,7 @@ func GetGitCommitHashFromDeployment(radixDeployment *radixv1.RadixDeployment) st func constructRadixDeployment(radixApplication *radixv1.RadixApplication, env, jobName, imageTag, branch, commitID, gitTags string, components []radixv1.RadixDeployComponent, jobs []radixv1.RadixDeployJobComponent, radixConfigHash, buildSecretHash string) *radixv1.RadixDeployment { appName := radixApplication.GetName() - deployName := utils.GetDeploymentName(appName, env, imageTag) + deployName := utils.GetDeploymentName(env, imageTag) imagePullSecrets := []corev1.LocalObjectReference{} if len(radixApplication.Spec.PrivateImageHubs) > 0 { imagePullSecrets = append(imagePullSecrets, corev1.LocalObjectReference{Name: defaults.PrivateImageHubSecretName}) diff --git a/pipeline-runner/steps/promote/step.go b/pipeline-runner/steps/promote/step.go index 6c8b4d228..3d674da59 100644 --- a/pipeline-runner/steps/promote/step.go +++ b/pipeline-runner/steps/promote/step.go @@ -82,7 +82,7 @@ func (cli *PromoteStepImplementation) Run(ctx context.Context, pipelineInfo *mod } radixDeployment = rd.DeepCopy() - radixDeployment.Name = utils.GetDeploymentName(cli.GetAppName(), pipelineInfo.PipelineArguments.ToEnvironment, pipelineInfo.PipelineArguments.ImageTag) + radixDeployment.Name = utils.GetDeploymentName(pipelineInfo.PipelineArguments.ToEnvironment, pipelineInfo.PipelineArguments.ImageTag) activeRadixDeployment, err := cli.GetKubeutil().GetActiveDeployment(ctx, toNs) if err != nil { diff --git a/pkg/apis/job/kubejob.go b/pkg/apis/job/kubejob.go index 68065ef72..bf6fdc004 100644 --- a/pkg/apis/job/kubejob.go +++ b/pkg/apis/job/kubejob.go @@ -213,7 +213,7 @@ func (job *Job) getPipelineJobArguments(ctx context.Context, appName, jobName st } args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixComponentsToDeployVariable, strings.Join(jobSpec.Deploy.ComponentsToDeploy, ","))) case radixv1.ApplyConfig: - args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixPipelineApplyConfigDeployExternalDNSFlag, jobSpec.ApplyConfig.DeployExternalDNS)) + args = append(args, fmt.Sprintf("--%s=%v", defaults.RadixPipelineApplyConfigDeployExternalDNSFlag, jobSpec.ApplyConfig.DeployExternalDNS)) } return args, nil diff --git a/pkg/apis/utils/deployment_builder.go b/pkg/apis/utils/deployment_builder.go index 0d8a84cb1..e0d1244fe 100644 --- a/pkg/apis/utils/deployment_builder.go +++ b/pkg/apis/utils/deployment_builder.go @@ -240,7 +240,7 @@ func (db *DeploymentBuilderStruct) BuildRD() *v1.RadixDeployment { } deployName := db.DeploymentName if deployName == "" { - deployName = GetDeploymentName(db.AppName, db.Environment, db.ImageTag) + deployName = GetDeploymentName(db.Environment, db.ImageTag) } status := v1.RadixDeployStatus{} if !db.emptyStatus { diff --git a/pkg/apis/utils/deployments.go b/pkg/apis/utils/deployments.go index 7eda97718..a2798c8f3 100644 --- a/pkg/apis/utils/deployments.go +++ b/pkg/apis/utils/deployments.go @@ -6,7 +6,7 @@ import ( ) // GetDeploymentName Function to get deployment name -func GetDeploymentName(appName, env, tag string) string { +func GetDeploymentName(env, tag string) string { random := strings.ToLower(RandString(8)) return fmt.Sprintf("%s-%s-%s", env, tag, random) } From 659ddbb5ceadfee47837aff5da11226c6f1cadca Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 30 Oct 2024 11:37:20 +0100 Subject: [PATCH 27/27] Update pipeline-runner/steps/deployconfig/internal/handler.go Co-authored-by: Richard Hagen --- pipeline-runner/steps/deployconfig/internal/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline-runner/steps/deployconfig/internal/handler.go b/pipeline-runner/steps/deployconfig/internal/handler.go index 02de75826..4cbc944aa 100644 --- a/pipeline-runner/steps/deployconfig/internal/handler.go +++ b/pipeline-runner/steps/deployconfig/internal/handler.go @@ -165,7 +165,7 @@ func (h *deployHandler) buildDeployment(ctx context.Context, envInfo envInfo) (* for _, updater := range h.featureProviders { if err := updater.Mutate(*targetRd, *sourceRd); err != nil { - return nil, fmt.Errorf("failed to apply configu to Radix deployment: %w", err) + return nil, fmt.Errorf("failed to apply configuration to Radix deployment: %w", err) } }