diff --git a/.vscode/launch.json b/.vscode/launch.json index 85e59e885..e1ab957e3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,28 +15,29 @@ "--DEBUG=true", "--RADIX_TEKTON_IMAGE=radix-tekton:main-latest", "--RADIX_IMAGE_BUILDER=radix-image-builder:master-latest", - "--RADIX_BUILDAH_IMAGE_BUILDER=quay.io/buildah/stable:v1.31", + "--RADIX_BUILDKIT_IMAGE_BUILDER=radix-buildkit-builder:main-latest", + // "--RADIX_BUILDAH_IMAGE_BUILDER=quay.io/buildah/stable:v1.31", "--SECCOMP_PROFILE_FILENAME=allow-buildah.json", "--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", "--RADIX_CLUSTER_TYPE=development", "--RADIX_ZONE=dev", - "--RADIX_CLUSTERNAME=weekly-23", + "--RADIX_CLUSTERNAME=weekly-37", "--RADIX_CONTAINER_REGISTRY=radixdev.azurecr.io", "--RADIX_APP_CONTAINER_REGISTRY=radixdevapp.azurecr.io", "--AZURE_SUBSCRIPTION_ID=16ede44b-1f74-40a5-b428-46cca9a5741b", - "--IMAGE_TAG=abcdw", + "--IMAGE_TAG=abcde", "--BRANCH=main", // "--COMMIT_ID=4069bf49619be55ee7dbdd426194cc14c30fde10", "--PUSH_IMAGE=true", - "--USE_CACHE=true", "--RADIX_FILE_NAME=/workspace/radixconfig.yaml", "--TO_ENVIRONMENT=dev", // "--IMAGE_TAG_NAME=server=1.23-alpine-slim", // "--IMAGE_TAG_NAME=server2=1.22.1-alpine-perl", "--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" + "--RADIX_RESERVED_DNS_ALIASES=grafana,prometheus,www", + "--RADIX_EXTERNAL_REGISTRY_DEFAULT_AUTH_SECRET=radix-external-registry-default-auth" ] }, { @@ -144,7 +145,8 @@ "RADIX_DEPLOYMENTS_PER_ENVIRONMENT_HISTORY_LIMIT": "10", "RADIX_PIPELINE_JOBS_HISTORY_LIMIT": "5", "SECCOMP_PROFILE_FILENAME": "allow-buildah.json", - "RADIX_BUILDAH_IMAGE_BUILDER": "quay.io/buildah/stable:v1.31", + // "RADIX_BUILDAH_IMAGE_BUILDER": "quay.io/buildah/stable:v1.31", + "RADIX_BUILDKIT_IMAGE_BUILDER": "radix-buildkit-builder:main-latest", "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", "RADIXOPERATOR_CERTIFICATE_AUTOMATION_CLUSTER_ISSUER": "digicert-http01", diff --git a/Makefile b/Makefile index f0ef6e498..2d964ef67 100644 --- a/Makefile +++ b/Makefile @@ -89,17 +89,19 @@ mocks: bootstrap mockgen -source ./pkg/apis/job/job_history.go -destination ./radix-operator/job/job_history_mock.go -package job mockgen -source ./pipeline-runner/internal/wait/job.go -destination ./pipeline-runner/internal/wait/job_mock.go -package wait mockgen -source ./pipeline-runner/internal/watcher/radix_deployment_watcher.go -destination ./pipeline-runner/internal/watcher/radix_deployment_watcher_mock.go -package watcher + mockgen -source ./pipeline-runner/internal/watcher/namespace.go -destination ./pipeline-runner/internal/watcher/namespace_mock.go -package watcher + mockgen -source ./pipeline-runner/internal/jobs/build/interface.go -destination ./pipeline-runner/internal/jobs/build/mock/job.go -package mock + + .PHONY: build-pipeline build-pipeline: - docker build -t $(DOCKER_REGISTRY)/radix-pipeline:$(VERSION) -t $(DOCKER_REGISTRY)/radix-pipeline:$(BRANCH)-$(VERSION) -t $(DOCKER_REGISTRY)/radix-pipeline:$(TAG) -f pipeline.Dockerfile . + docker buildx build -t $(DOCKER_REGISTRY)/radix-pipeline:$(VERSION) -t $(DOCKER_REGISTRY)/radix-pipeline:$(BRANCH)-$(VERSION) -t $(DOCKER_REGISTRY)/radix-pipeline:$(TAG) --platform linux/arm64,linux/amd64 -f pipeline.Dockerfile . .PHONY: deploy-pipeline -deploy-pipeline: build-pipeline +deploy-pipeline: az acr login --name $(CONTAINER_REPO) - docker push $(DOCKER_REGISTRY)/radix-pipeline:$(BRANCH)-$(VERSION) - docker push $(DOCKER_REGISTRY)/radix-pipeline:$(VERSION) - docker push $(DOCKER_REGISTRY)/radix-pipeline:$(TAG) + docker buildx build -t $(DOCKER_REGISTRY)/radix-pipeline:$(VERSION) -t $(DOCKER_REGISTRY)/radix-pipeline:$(BRANCH)-$(VERSION) -t $(DOCKER_REGISTRY)/radix-pipeline:$(TAG) --platform linux/arm64,linux/amd64 -f pipeline.Dockerfile --push . .PHONY: build-operator build-operator: diff --git a/charts/radix-operator/Chart.yaml b/charts/radix-operator/Chart.yaml index fecf5fc32..f0a6591f4 100644 --- a/charts/radix-operator/Chart.yaml +++ b/charts/radix-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: radix-operator -version: 1.38.4 -appVersion: 1.58.4 +version: 1.39.0 +appVersion: 1.59.0 kubeVersion: ">=1.24.0" description: Radix Operator keywords: diff --git a/charts/radix-operator/templates/deployment.yaml b/charts/radix-operator/templates/deployment.yaml index e23848a9d..49d91da8e 100644 --- a/charts/radix-operator/templates/deployment.yaml +++ b/charts/radix-operator/templates/deployment.yaml @@ -95,8 +95,6 @@ spec: value: {{ .Values.imageBuilder }} - name: RADIXOPERATOR_JOB_SCHEDULER value: {{ .Values.jobScheduler }} - - name: USE_CACHE - value: {{ .Values.useImageBuilderCache | quote }} - name: LOG_LEVEL value: {{ .Values.logLevel }} - name: LOG_PRETTY @@ -157,8 +155,10 @@ spec: {{- end }} - name: SECCOMP_PROFILE_FILENAME value: {{ .Values.seccompProfile.fileNameOnNode }} - - name: RADIX_BUILDAH_IMAGE_BUILDER + - name: RADIX_BUILDAH_IMAGE_BUILDER # TODO: Deprecated, remove value: {{ .Values.buildahImageBuilder }} + - name: RADIX_BUILDKIT_IMAGE_BUILDER + value: {{ .Values.buildKitImageBuilder }} - name: RADIX_PIPELINE_GIT_CLONE_NSLOOKUP_IMAGE value: {{ .Values.gitCloneNsLookupImage }} - name: RADIX_PIPELINE_GIT_CLONE_GIT_IMAGE diff --git a/charts/radix-operator/values.yaml b/charts/radix-operator/values.yaml index ab2ab2bca..aded710c8 100644 --- a/charts/radix-operator/values.yaml +++ b/charts/radix-operator/values.yaml @@ -16,7 +16,8 @@ kubeClientRateLimitQPS: 5 configToMap: radix-config-2-map:master-latest imageBuilder: radix-image-builder:master-latest -buildahImageBuilder: xx +buildKitImageBuilder: radix-buildkit-builder:main-latest # TODO: Configure in radix-flux +buildahImageBuilder: xx # TODO: Deprecated, remove jobScheduler: radix-job-scheduler:main-latest radixTekton: radix-tekton:main-latest @@ -25,7 +26,6 @@ gitCloneNsLookupImage: "" # Image containing nslookup, e.g. "alpine:3.20". Defau gitCloneGitImage: "" # Image containing git, e.g. "alpine/git:2.45.2". Defaults to "alpine/git:latest" if not set gitCloneBashImage: "" # Image containing bash, e.g. "bash:5.2". Defaults to "bash:latest" if not set -useImageBuilderCache: 0 reservedAppDNSAlias: api: radix-api canary: radix-canary-golang diff --git a/go.mod b/go.mod index 0e8e58c57..e16292af3 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.26.0 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/sync v0.8.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.31.0 @@ -80,7 +81,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect diff --git a/operator.Dockerfile b/operator.Dockerfile index 72e5b07e1..5c78422f7 100644 --- a/operator.Dockerfile +++ b/operator.Dockerfile @@ -18,7 +18,4 @@ FROM gcr.io/distroless/static WORKDIR /app COPY --from=builder /build/radix-operator . USER 1000 -ENTRYPOINT ["/app/radix-operator"] - - - +ENTRYPOINT ["/app/radix-operator"] \ No newline at end of file diff --git a/pipeline-runner/internal/commandbuilder/command.go b/pipeline-runner/internal/commandbuilder/command.go deleted file mode 100644 index 90168f542..000000000 --- a/pipeline-runner/internal/commandbuilder/command.go +++ /dev/null @@ -1,65 +0,0 @@ -package commandbuilder - -import ( - "fmt" - "strings" - - "github.com/equinor/radix-common/utils/slice" -) - -type CommandList struct{ cmds []*Command } - -type Command struct { - args []string -} - -/* Command List */ - -func NewCommandList() *CommandList { - return &CommandList{cmds: make([]*Command, 0)} -} - -func (cl *CommandList) AddCmd(command *Command) *CommandList { - cl.cmds = append(cl.cmds, command) - return cl -} -func (cl *CommandList) AddStrCmd(format string, a ...any) *CommandList { - cl.cmds = append(cl.cmds, NewCommand(format, a...)) - return cl -} - -func (cl *CommandList) String() string { - cmds := slice.Map(cl.cmds, func(c *Command) string { return c.String() }) - - return strings.Join(cmds, " && ") -} - -/* Command */ - -func NewCommand(formattedCmd string, a ...any) *Command { - c := Command{} - c.AddArgf(formattedCmd, a...) - - return &c -} - -func (c *Command) AddArg(arg string) *Command { - if arg == "" { - return c - } - c.args = append(c.args, arg) - return c -} - -func (c *Command) AddArgf(format string, a ...any) *Command { - if format == "" { - return c - } - - c.args = append(c.args, fmt.Sprintf(format, a...)) - return c -} - -func (c *Command) String() string { - return strings.Join(c.args, " ") -} diff --git a/pipeline-runner/internal/commandbuilder/command_test.go b/pipeline-runner/internal/commandbuilder/command_test.go deleted file mode 100644 index d755d3a1f..000000000 --- a/pipeline-runner/internal/commandbuilder/command_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package commandbuilder_test - -import ( - "strings" - "testing" - - "github.com/equinor/radix-operator/pipeline-runner/internal/commandbuilder" - "github.com/stretchr/testify/assert" -) - -func TestNewCommand_empty(t *testing.T) { - assert.Empty(t, commandbuilder.NewCommand("").String()) -} - -func TestNewCommand_seperator(t *testing.T) { - assert.Equal(t, "a b", commandbuilder.NewCommand("a").AddArgf("b").String()) -} - -func TestAdd1000Elements(t *testing.T) { - a := commandbuilder.NewCommand("") - - for i := 0; i < 1000; i++ { - a.AddArgf("---arg---") - } - - actual := a.String() - msgCount := strings.Count(actual, "---arg---") - sepCount := strings.Count(actual, " ") - - assert.Equal(t, 1000, msgCount) - assert.Equal(t, 999, sepCount) -} - -func Test_JoinHasCorrectElements(t *testing.T) { - a := commandbuilder.NewCommand("") - - a.AddArgf("Start") - - for i := 0; i < 10; i++ { - a.AddArgf("Hei!") - } - - a.AddArgf("Stop") - - actual := a.String() - - assert.True(t, strings.HasPrefix(actual, "Start")) - assert.True(t, strings.HasSuffix(actual, "Stop")) -} - -func Test_CommandList_JoinsCorrectly(t *testing.T) { - cl := commandbuilder.NewCommandList().AddStrCmd("echo 'hello world'").AddCmd(commandbuilder.NewCommand("echo test")) - assert.Equal(t, cl.String(), "echo 'hello world' && echo test") -} diff --git a/pipeline-runner/internal/jobs/build/acr.go b/pipeline-runner/internal/jobs/build/acr.go new file mode 100644 index 000000000..fc7db5a06 --- /dev/null +++ b/pipeline-runner/internal/jobs/build/acr.go @@ -0,0 +1,255 @@ +package build + +import ( + "fmt" + "path" + "strings" + "time" + + "github.com/equinor/radix-common/utils/pointers" + "github.com/equinor/radix-common/utils/slice" + internalgit "github.com/equinor/radix-operator/pipeline-runner/internal/git" + "github.com/equinor/radix-operator/pipeline-runner/internal/jobs/build/internal" + "github.com/equinor/radix-operator/pipeline-runner/model" + "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/securitycontext" + "github.com/equinor/radix-operator/pkg/apis/utils" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +const ( + azureServicePrincipleContext = "/radix-image-builder/.azure" + acrHomeVolumeName = "radix-image-builder-home" + acrHomePath = "/home/radix-image-builder" +) + +// NewBuildKit returns a JobBuilder implementation for building components and jobs using radix-image-builder (https://github.com/equinor/radix-image-builder) +func NewACR() JobsBuilder { + return &acr{} +} + +type acr struct{} + +func (c *acr) BuildJobs(useBuildCache bool, pipelineArgs model.PipelineArguments, cloneURL, gitCommitHash, gitTags string, componentImages []pipeline.BuildComponentImage, buildSecrets []string) []batchv1.Job { + props := &acrKubeJobProps{ + pipelineArgs: pipelineArgs, + componentImages: componentImages, + cloneURL: cloneURL, + gitCommitHash: gitCommitHash, + gitTags: gitTags, + buildSecrets: buildSecrets, + } + + return []batchv1.Job{internal.BuildKubeJob(props)} +} + +var _ internal.KubeJobProps = &acrKubeJobProps{} + +type acrKubeJobProps struct { + pipelineArgs model.PipelineArguments + componentImages []pipeline.BuildComponentImage + cloneURL string + gitCommitHash string + gitTags string + buildSecrets []string +} + +func (c *acrKubeJobProps) JobName() string { + hash := strings.ToLower(utils.RandStringStrSeed(5, c.pipelineArgs.JobName)) + return getJobName(time.Now(), c.pipelineArgs.ImageTag, hash) +} + +func (c *acrKubeJobProps) JobLabels() map[string]string { + return getCommonJobLabels(c.pipelineArgs.AppName, c.pipelineArgs.JobName, c.pipelineArgs.ImageTag) +} + +func (c *acrKubeJobProps) JobAnnotations() map[string]string { + return getCommonJobAnnotations(c.pipelineArgs.Branch, c.componentImages...) +} + +func (c *acrKubeJobProps) PodLabels() map[string]string { + return getCommonPodLabels(c.pipelineArgs.JobName) +} + +func (c *acrKubeJobProps) PodAnnotations() map[string]string { + return getCommonPodAnnotations() +} + +func (c *acrKubeJobProps) PodTolerations() []corev1.Toleration { + return getCommonPodTolerations() +} + +func (c *acrKubeJobProps) PodAffinity() *corev1.Affinity { + return getCommonPodAffinity(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}) +} + +func (*acrKubeJobProps) PodSecurityContext() *corev1.PodSecurityContext { + return securitycontext.Pod( + securitycontext.WithPodFSGroup(1000), + securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault)) +} + +func (c *acrKubeJobProps) PodVolumes() []corev1.Volume { + volumes := getCommonPodVolumes(c.componentImages) + + volumes = append(volumes, + corev1.Volume{ + Name: defaults.AzureACRServicePrincipleSecretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: defaults.AzureACRServicePrincipleSecretName, + }, + }, + }, + corev1.Volume{ + Name: acrHomeVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + SizeLimit: resource.NewScaledQuantity(5, resource.Mega), + }, + }, + }, + ) + + return volumes +} + +func (c *acrKubeJobProps) PodInitContainers() []corev1.Container { + cloneCfg := internalgit.CloneConfigFromPipelineArgs(c.pipelineArgs) + return getCommonPodInitContainers(c.cloneURL, c.pipelineArgs.Branch, cloneCfg) +} + +func (c *acrKubeJobProps) PodContainers() []corev1.Container { + return slice.Map(c.componentImages, c.getPodContainer) +} + +func (c *acrKubeJobProps) getPodContainer(componentImage pipeline.BuildComponentImage) corev1.Container { + return corev1.Container{ + Name: componentImage.ContainerName, + Image: fmt.Sprintf("%s/%s", c.pipelineArgs.ContainerRegistry, c.pipelineArgs.ImageBuilder), + ImagePullPolicy: corev1.PullAlways, + Env: c.getPodContainerEnvVars(componentImage), + VolumeMounts: c.getPodContainerVolumeMounts(componentImage), + SecurityContext: c.getPodContainerSecurityContext(), + } +} + +func (*acrKubeJobProps) getPodContainerSecurityContext() *corev1.SecurityContext { + return securitycontext.Container( + securitycontext.WithContainerDropAllCapabilities(), + securitycontext.WithContainerSeccompProfileType(corev1.SeccompProfileTypeRuntimeDefault), + securitycontext.WithContainerRunAsUser(1000), + securitycontext.WithContainerRunAsGroup(1000), + securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), + ) +} + +func (c *acrKubeJobProps) getPodContainerVolumeMounts(componentImage pipeline.BuildComponentImage) []corev1.VolumeMount { + volumeMounts := getCommonPodContainerVolumeMounts(componentImage) + + volumeMounts = append(volumeMounts, + corev1.VolumeMount{ + Name: defaults.AzureACRServicePrincipleSecretName, + MountPath: azureServicePrincipleContext, + ReadOnly: true, + }, + // .azure folder is created in the user home folder + corev1.VolumeMount{ + Name: acrHomeVolumeName, + MountPath: acrHomePath, + ReadOnly: false, + }, + ) + + return volumeMounts +} + +func (c *acrKubeJobProps) getPodContainerEnvVars(componentImage pipeline.BuildComponentImage) []corev1.EnvVar { + var push string + if c.pipelineArgs.PushImage { + push = "--push" + } + firstPartContainerRegistry := strings.Split(c.pipelineArgs.ContainerRegistry, ".")[0] + envVars := []corev1.EnvVar{ + { + Name: defaults.RadixBranchEnvironmentVariable, + Value: c.pipelineArgs.Branch, + }, + { + Name: defaults.RadixPipelineTargetEnvironmentsVariable, + Value: componentImage.EnvName, + }, + { + Name: defaults.RadixCommitHashEnvironmentVariable, + Value: c.gitCommitHash, + }, + { + Name: defaults.RadixGitTagsEnvironmentVariable, + Value: c.gitTags, + }, + { + Name: "AZURE_CREDENTIALS", + Value: path.Join(azureServicePrincipleContext, "sp_credentials.json"), + }, + { + Name: "SUBSCRIPTION_ID", + Value: c.pipelineArgs.SubscriptionId, + }, + { + Name: "DOCKER_FILE_NAME", + Value: componentImage.Dockerfile, + }, + { + Name: "DOCKER_REGISTRY", + Value: firstPartContainerRegistry, + }, + { + Name: "IMAGE", + Value: componentImage.ImagePath, + }, + { + Name: "CLUSTERTYPE_IMAGE", + Value: componentImage.ClusterTypeImagePath, + }, + { + Name: "CLUSTERNAME_IMAGE", + Value: componentImage.ClusterNameImagePath, + }, + { + Name: "CONTEXT", + Value: componentImage.Context, + }, + { + Name: "PUSH", + Value: push, + }, + { + Name: defaults.RadixZoneEnvironmentVariable, + Value: c.pipelineArgs.RadixZone, + }, + } + + envVars = append(envVars, c.getPodContainerBuildSecretEnvVars()...) + + return envVars +} + +func (c *acrKubeJobProps) getPodContainerBuildSecretEnvVars() []corev1.EnvVar { + return slice.Map(c.buildSecrets, func(secret string) corev1.EnvVar { + return corev1.EnvVar{ + Name: defaults.BuildSecretPrefix + secret, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: defaults.BuildSecretsName, + }, + Key: secret, + }, + }, + } + }) +} diff --git a/pipeline-runner/internal/jobs/build/acr_test.go b/pipeline-runner/internal/jobs/build/acr_test.go new file mode 100644 index 000000000..28ee4ce01 --- /dev/null +++ b/pipeline-runner/internal/jobs/build/acr_test.go @@ -0,0 +1,184 @@ +package build_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/equinor/radix-common/utils/pointers" + "github.com/equinor/radix-common/utils/slice" + "github.com/equinor/radix-operator/pipeline-runner/internal/jobs/build" + "github.com/equinor/radix-operator/pipeline-runner/model" + "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/securitycontext" + "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/equinor/radix-operator/pkg/apis/utils/annotations" + "github.com/equinor/radix-operator/pkg/apis/utils/git" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +func Test_ACR_JobSpec(t *testing.T) { + t.Run("push", func(t *testing.T) { assertACRJobSpec(t, true) }) + t.Run("nopush", func(t *testing.T) { assertACRJobSpec(t, false) }) +} + +func assertACRJobSpec(t *testing.T, pushImage bool) { + const ( + cloneURL = "anycloneurl" + gitCommitHash = "anygitcommithash" + gitTags = "anygittags" + ) + + args := model.PipelineArguments{ + AppName: "anyappname", + PipelineType: "anypipelinetype", + JobName: "anyjobname", + Branch: "anybranch", + CommitID: "anycommitid", + ImageTag: "anyimagetag", + PushImage: pushImage, + ImageBuilder: "anyimagebuilder", + GitCloneNsLookupImage: "anynslookupimage", + GitCloneGitImage: "anygitcloneimage", + GitCloneBashImage: "anybashimage", + Clustertype: "anyclustertype", + Clustername: "anyclustername", + ContainerRegistry: "anycontainerregistry", + SubscriptionId: "anysubscriptionid", + RadixZone: "anyradixzone", + } + require.Equal(t, pushImage, args.PushImage) + componentImages := []pipeline.BuildComponentImage{ + {ComponentName: "c1", EnvName: "c1env", ContainerName: "c1container", Context: "c1ctx", Dockerfile: "c1dockerfile", ImageName: "c1imagename", ImagePath: "c1image", ClusterTypeImagePath: "c1clustertypeimage", ClusterNameImagePath: "c1clusternameimage"}, + {ComponentName: "c2", EnvName: "c2env", ContainerName: "c2container", Context: "c2ctx", Dockerfile: "c2dockerfile", ImageName: "c2imagename", ImagePath: "c2image", ClusterTypeImagePath: "c2clustertypeimage", ClusterNameImagePath: "c2clusternameimage"}, + } + buildSecrets := []string{"secret1", "secret2"} + + sut := build.NewACR() + jobs := sut.BuildJobs(false, args, cloneURL, gitCommitHash, gitTags, componentImages, buildSecrets) + require.Len(t, jobs, 1) + job := jobs[0] + + // Check job + expectedJobLabels := map[string]string{ + kube.RadixJobNameLabel: args.JobName, + kube.RadixAppLabel: args.AppName, + kube.RadixImageTagLabel: args.ImageTag, + kube.RadixJobTypeLabel: kube.RadixJobTypeBuild, + } + assert.Equal(t, expectedJobLabels, job.Labels) + componentImagesAnnotation, _ := json.Marshal(componentImages) + expectedJobAnnotations := map[string]string{ + kube.RadixBranchAnnotation: args.Branch, + kube.RadixBuildComponentsAnnotation: string(componentImagesAnnotation), + } + assert.Equal(t, expectedJobAnnotations, job.Annotations) + assert.Equal(t, pointers.Ptr[int32](0), job.Spec.BackoffLimit) + + // Check pod template + expectedPodLabels := map[string]string{ + kube.RadixJobNameLabel: args.JobName, + } + assert.Equal(t, expectedPodLabels, job.Spec.Template.Labels) + expectedPodAnnotations := annotations.ForClusterAutoscalerSafeToEvict(false) + assert.Equal(t, expectedPodAnnotations, job.Spec.Template.Annotations) + + assert.Equal(t, corev1.RestartPolicyNever, job.Spec.Template.Spec.RestartPolicy) + expectedAffinity := &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{MatchExpressions: []corev1.NodeSelectorRequirement{ + {Key: kube.RadixJobNodeLabel, Operator: corev1.NodeSelectorOpExists}, + {Key: corev1.LabelOSStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorOS}}, + {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{string(radixv1.RuntimeArchitectureArm64)}}, + }}}}}} + assert.Equal(t, expectedAffinity, job.Spec.Template.Spec.Affinity) + assert.ElementsMatch(t, utils.GetPipelineJobPodSpecTolerations(), job.Spec.Template.Spec.Tolerations) + expectedPodSecurityContext := securitycontext.Pod( + securitycontext.WithPodFSGroup(1000), + securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault)) + assert.Equal(t, expectedPodSecurityContext, job.Spec.Template.Spec.SecurityContext) + expectedVolumes := []corev1.Volume{ + {Name: git.BuildContextVolumeName}, + {Name: git.GitSSHKeyVolumeName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: git.GitSSHKeyVolumeName, DefaultMode: pointers.Ptr[int32](256)}}}, + {Name: defaults.AzureACRServicePrincipleSecretName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: defaults.AzureACRServicePrincipleSecretName}}}, + {Name: "radix-image-builder-home", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(5, resource.Mega)}}}, + } + for _, image := range componentImages { + expectedVolumes = append(expectedVolumes, + corev1.Volume{Name: fmt.Sprintf("tmp-%s", image.ContainerName), VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, + corev1.Volume{Name: fmt.Sprintf("var-%s", image.ContainerName), VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, + ) + } + assert.ElementsMatch(t, expectedVolumes, job.Spec.Template.Spec.Volumes) + + // Check init containers + assert.ElementsMatch(t, []string{"internal-nslookup", "clone", "internal-chmod"}, slice.Map(job.Spec.Template.Spec.InitContainers, func(c corev1.Container) string { return c.Name })) + cloneContainer, _ := slice.FindFirst(job.Spec.Template.Spec.InitContainers, func(c corev1.Container) bool { return c.Name == "clone" }) + assert.Equal(t, args.GitCloneGitImage, cloneContainer.Image) + assert.Equal(t, []string{"git", "clone", "--recurse-submodules", cloneURL, "-b", args.Branch, "--verbose", "--progress", git.Workspace}, cloneContainer.Command) + assert.Empty(t, cloneContainer.Args) + expectedCloneVolumeMounts := []corev1.VolumeMount{ + {Name: git.BuildContextVolumeName, MountPath: git.Workspace}, + {Name: git.GitSSHKeyVolumeName, MountPath: "/.ssh", ReadOnly: true}, + } + assert.ElementsMatch(t, expectedCloneVolumeMounts, cloneContainer.VolumeMounts) + + // Check containers + var pushArg string + if args.PushImage { + pushArg = "--push" + } + assert.Len(t, job.Spec.Template.Spec.Containers, len(componentImages)) + for _, ci := range componentImages { + t.Run(fmt.Sprintf("check container %s", ci.ContainerName), func(t *testing.T) { + c, ok := slice.FindFirst(job.Spec.Template.Spec.Containers, func(c corev1.Container) bool { return c.Name == ci.ContainerName }) + require.True(t, ok) + assert.Equal(t, ci.ContainerName, c.Name) + assert.Equal(t, fmt.Sprintf("%s/%s", args.ContainerRegistry, args.ImageBuilder), c.Image) + assert.Equal(t, corev1.PullAlways, c.ImagePullPolicy) + expectedSecurityCtx := securitycontext.Container( + securitycontext.WithContainerDropAllCapabilities(), + securitycontext.WithContainerSeccompProfileType(corev1.SeccompProfileTypeRuntimeDefault), + securitycontext.WithContainerRunAsUser(1000), + securitycontext.WithContainerRunAsGroup(1000), + securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), + ) + assert.Equal(t, expectedSecurityCtx, c.SecurityContext) + expectedEnvs := []corev1.EnvVar{ + {Name: "DOCKER_FILE_NAME", Value: ci.Dockerfile}, + {Name: "DOCKER_REGISTRY", Value: args.ContainerRegistry}, + {Name: "IMAGE", Value: ci.ImagePath}, + {Name: "CONTEXT", Value: ci.Context}, + {Name: "PUSH", Value: pushArg}, + {Name: "AZURE_CREDENTIALS", Value: "/radix-image-builder/.azure/sp_credentials.json"}, + {Name: "SUBSCRIPTION_ID", Value: args.SubscriptionId}, + {Name: "CLUSTERTYPE_IMAGE", Value: ci.ClusterTypeImagePath}, + {Name: "CLUSTERNAME_IMAGE", Value: ci.ClusterNameImagePath}, + {Name: "RADIX_ZONE", Value: args.RadixZone}, + {Name: "BRANCH", Value: args.Branch}, + {Name: "TARGET_ENVIRONMENTS", Value: ci.EnvName}, + {Name: "RADIX_GIT_COMMIT_HASH", Value: gitCommitHash}, + {Name: "RADIX_GIT_TAGS", Value: gitTags}, + } + for _, s := range buildSecrets { + expectedEnvs = append(expectedEnvs, corev1.EnvVar{ + Name: defaults.BuildSecretPrefix + s, + ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{Key: s, LocalObjectReference: corev1.LocalObjectReference{Name: defaults.BuildSecretsName}}}, + }) + } + assert.ElementsMatch(t, expectedEnvs, c.Env) + expectedVolumeMounts := []corev1.VolumeMount{ + {Name: git.BuildContextVolumeName, MountPath: git.Workspace}, + {Name: fmt.Sprintf("tmp-%s", ci.ContainerName), MountPath: "/tmp", ReadOnly: false}, + {Name: fmt.Sprintf("var-%s", ci.ContainerName), MountPath: "/var", ReadOnly: false}, + {Name: defaults.AzureACRServicePrincipleSecretName, MountPath: "/radix-image-builder/.azure", ReadOnly: true}, + {Name: "radix-image-builder-home", MountPath: "/home/radix-image-builder", ReadOnly: false}, + } + assert.ElementsMatch(t, expectedVolumeMounts, c.VolumeMounts) + }) + } +} diff --git a/pipeline-runner/internal/jobs/build/buildkit.go b/pipeline-runner/internal/jobs/build/buildkit.go new file mode 100644 index 000000000..b3a0dda26 --- /dev/null +++ b/pipeline-runner/internal/jobs/build/buildkit.go @@ -0,0 +1,368 @@ +package build + +import ( + "fmt" + "path" + "strings" + "time" + + "github.com/equinor/radix-common/utils/pointers" + internalgit "github.com/equinor/radix-operator/pipeline-runner/internal/git" + "github.com/equinor/radix-operator/pipeline-runner/internal/jobs/build/internal" + "github.com/equinor/radix-operator/pipeline-runner/model" + "github.com/equinor/radix-operator/pkg/apis/defaults" + "github.com/equinor/radix-operator/pkg/apis/pipeline" + "github.com/equinor/radix-operator/pkg/apis/securitycontext" + "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/equinor/radix-operator/pkg/apis/utils/labels" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +const ( + buildKitRunVolumeName = "build-kit-run" + buildKitRootVolumeName = "build-kit-root" + buildKitHomeVolumeName = "radix-image-builder-home" + buildKitHomePath = "/home/build" + buildKitBuildSecretsPath = "/build-secrets" + privateImageHubDockerAuthPath = "/radix-private-image-hubs" + defaultExternalRegistryAuthPath = "/radix-default-external-registry-auth" +) + +// NewBuildKit returns a JobBuilder implementation for building components and jobs using radix-buildkit-builder (https://github.com/equinor/radix-buildkit-builder) +func NewBuildKit() JobsBuilder { + return &buildKit{} +} + +type buildKit struct{} + +func (c *buildKit) BuildJobs(useBuildCache bool, pipelineArgs model.PipelineArguments, cloneURL, gitCommitHash, gitTags string, componentImages []pipeline.BuildComponentImage, buildSecrets []string) []batchv1.Job { + var jobs []batchv1.Job + + for _, componentImage := range componentImages { + job := c.buildJob(componentImage, useBuildCache, pipelineArgs, cloneURL, gitCommitHash, gitTags, buildSecrets) + jobs = append(jobs, job) + } + + return jobs +} + +func (c *buildKit) buildJob(componentImage pipeline.BuildComponentImage, useBuildCache bool, pipelineArgs model.PipelineArguments, cloneURL, gitCommitHash, gitTags string, buildSecrets []string) batchv1.Job { + props := &buildKitKubeJobProps{ + pipelineArgs: pipelineArgs, + componentImage: componentImage, + cloneURL: cloneURL, + gitCommitHash: gitCommitHash, + gitTags: gitTags, + buildSecrets: buildSecrets, + useBuildCache: useBuildCache, + } + + return internal.BuildKubeJob(props) +} + +var _ internal.KubeJobProps = &buildKitKubeJobProps{} + +type buildKitKubeJobProps struct { + pipelineArgs model.PipelineArguments + componentImage pipeline.BuildComponentImage + cloneURL string + gitCommitHash string + gitTags string + buildSecrets []string + useBuildCache bool +} + +func (c *buildKitKubeJobProps) JobName() string { + hash := strings.ToLower(utils.RandStringStrSeed(5, fmt.Sprintf("%s-%s-%s", c.pipelineArgs.JobName, c.componentImage.EnvName, c.componentImage.ComponentName))) + return getJobName(time.Now(), c.pipelineArgs.ImageTag, hash) +} + +func (c *buildKitKubeJobProps) JobLabels() map[string]string { + return labels.Merge( + getCommonJobLabels(c.pipelineArgs.AppName, c.pipelineArgs.JobName, c.pipelineArgs.ImageTag), + labels.ForEnvironmentName(c.componentImage.EnvName), + labels.ForComponentName(c.componentImage.ComponentName), + ) +} + +func (c *buildKitKubeJobProps) JobAnnotations() map[string]string { + return getCommonJobAnnotations(c.pipelineArgs.Branch, c.componentImage) +} + +func (c *buildKitKubeJobProps) PodLabels() map[string]string { + return getCommonPodLabels(c.pipelineArgs.JobName) +} + +func (c *buildKitKubeJobProps) PodAnnotations() map[string]string { + annotations := getCommonPodAnnotations() + annotations[fmt.Sprintf("container.apparmor.security.beta.kubernetes.io/%s", c.componentImage.ContainerName)] = "unconfined" + return annotations +} + +func (c *buildKitKubeJobProps) PodTolerations() []corev1.Toleration { + return getCommonPodTolerations() +} + +func (c *buildKitKubeJobProps) PodAffinity() *corev1.Affinity { + return getCommonPodAffinity(c.componentImage.Runtime) +} + +func (*buildKitKubeJobProps) PodSecurityContext() *corev1.PodSecurityContext { + return securitycontext.Pod( + securitycontext.WithPodFSGroup(1000), + securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault), + securitycontext.WithPodRunAsNonRoot(pointers.Ptr(false))) +} + +func (c *buildKitKubeJobProps) PodVolumes() []corev1.Volume { + volumes := getCommonPodVolumes([]pipeline.BuildComponentImage{c.componentImage}) + + volumes = append(volumes, + corev1.Volume{ + Name: defaults.PrivateImageHubSecretName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: defaults.PrivateImageHubSecretName, + }, + }, + }, + corev1.Volume{ + Name: buildKitHomeVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + SizeLimit: resource.NewScaledQuantity(5, resource.Mega), + }, + }, + }, + corev1.Volume{ + Name: buildKitRunVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + SizeLimit: resource.NewScaledQuantity(100, resource.Giga), // buildah puts container overlays there, which can be as large as several gigabytes + }, + }, + }, + corev1.Volume{ + Name: buildKitRootVolumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + SizeLimit: resource.NewScaledQuantity(100, resource.Giga), // buildah puts container overlays there, which can be as large as several gigabytes + }, + }, + }, + ) + + if len(c.pipelineArgs.ExternalContainerRegistryDefaultAuthSecret) > 0 { + volumes = append(volumes, + corev1.Volume{ + Name: c.pipelineArgs.ExternalContainerRegistryDefaultAuthSecret, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: c.pipelineArgs.ExternalContainerRegistryDefaultAuthSecret, + }, + }, + }, + ) + } + + if len(c.buildSecrets) > 0 { + volumes = append(volumes, + corev1.Volume{ + Name: defaults.BuildSecretsName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: defaults.BuildSecretsName, + }, + }, + }, + ) + } + + return volumes +} + +func (c *buildKitKubeJobProps) PodInitContainers() []corev1.Container { + cloneCfg := internalgit.CloneConfigFromPipelineArgs(c.pipelineArgs) + return getCommonPodInitContainers(c.cloneURL, c.pipelineArgs.Branch, cloneCfg) +} + +func (c *buildKitKubeJobProps) PodContainers() []corev1.Container { + container := corev1.Container{ + Name: c.componentImage.ContainerName, + Image: fmt.Sprintf("%s/%s", c.pipelineArgs.ContainerRegistry, c.pipelineArgs.BuildKitImageBuilder), + ImagePullPolicy: corev1.PullAlways, + Args: c.getPodContainerArgs(), + Env: c.getPodContainerEnvVars(), + VolumeMounts: c.getPodContainerVolumeMounts(), + SecurityContext: c.getPodContainerSecurityContext(), + Resources: c.getPodContainerResources(), + } + + return []corev1.Container{container} +} + +func (c *buildKitKubeJobProps) getPodContainerArgs() []string { + args := []string{ + "--registry", c.pipelineArgs.ContainerRegistry, + "--registry-username", "$(BUILDAH_USERNAME)", + "--registry-password", "$(BUILDAH_PASSWORD)", + "--cache-registry", c.pipelineArgs.AppContainerRegistry, + "--cache-registry-username", "$(BUILDAH_CACHE_USERNAME)", + "--cache-registry-password", "$(BUILDAH_CACHE_PASSWORD)", + "--cache-repository", utils.GetImageCachePath(c.pipelineArgs.AppContainerRegistry, c.pipelineArgs.AppName), + "--tag", c.componentImage.ImagePath, + "--cluster-type-tag", c.componentImage.ClusterTypeImagePath, + "--cluster-name-tag", c.componentImage.ClusterNameImagePath, + "--secrets-path", buildKitBuildSecretsPath, + "--dockerfile", c.componentImage.Dockerfile, + "--context", c.componentImage.Context, + "--branch", c.pipelineArgs.Branch, + "--git-commit-hash", c.gitCommitHash, + "--git-tags", c.gitTags, + "--target-environments", c.componentImage.EnvName, + } + + if c.useBuildCache { + args = append(args, "--use-cache") + } + + if c.pipelineArgs.PushImage { + args = append(args, "--push") + } + + for _, secret := range c.buildSecrets { + args = append(args, "--secret", secret) + } + + // The order of auth-files matters when multiple are defined: + // When multiple files contains credentials for the same registry (e.g. docker.io), credentials from the last file is used + var authFiles []string + if len(c.pipelineArgs.ExternalContainerRegistryDefaultAuthSecret) > 0 { + authFiles = append(authFiles, path.Join(defaultExternalRegistryAuthPath, corev1.DockerConfigJsonKey)) + } + authFiles = append(authFiles, path.Join(privateImageHubDockerAuthPath, corev1.DockerConfigJsonKey)) + for _, authFile := range authFiles { + args = append(args, "--auth-file", authFile) + } + + return args +} + +func (c *buildKitKubeJobProps) getPodContainerResources() corev1.ResourceRequirements { + return corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceCPU: resource.MustParse(c.pipelineArgs.Builder.ResourcesRequestsCPU), + corev1.ResourceMemory: resource.MustParse(c.pipelineArgs.Builder.ResourcesRequestsMemory), + }, + Limits: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse(c.pipelineArgs.Builder.ResourcesLimitsMemory), + }, + } +} + +func (c *buildKitKubeJobProps) getPodContainerSecurityContext() *corev1.SecurityContext { + return securitycontext.Container( + securitycontext.WithContainerDropAllCapabilities(), + securitycontext.WithContainerCapabilities([]corev1.Capability{"SETUID", "SETGID", "SETFCAP"}), + securitycontext.WithContainerSeccompProfile(corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeLocalhost, + LocalhostProfile: utils.StringPtr(c.pipelineArgs.SeccompProfileFileName), + }), + securitycontext.WithContainerRunAsNonRoot(pointers.Ptr(false)), + securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), + ) +} + +func (c *buildKitKubeJobProps) getPodContainerEnvVars() []corev1.EnvVar { + envVars := []corev1.EnvVar{ + { + Name: "BUILDAH_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}, + Key: "username", + }, + }, + }, + { + Name: "BUILDAH_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}, + Key: "password", + }, + }, + }, + { + Name: "BUILDAH_CACHE_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}, + Key: "username", + }, + }, + }, + { + Name: "BUILDAH_CACHE_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}, + Key: "password", + }, + }, + }, + } + + return envVars +} + +func (c *buildKitKubeJobProps) getPodContainerVolumeMounts() []corev1.VolumeMount { + volumeMounts := getCommonPodContainerVolumeMounts(c.componentImage) + + volumeMounts = append(volumeMounts, + corev1.VolumeMount{ + Name: buildKitRunVolumeName, // buildah creates folder container overlays and secrets there + MountPath: "/run", + ReadOnly: false, + }, + corev1.VolumeMount{ + Name: buildKitRootVolumeName, // Required by buildah + MountPath: "/root", + ReadOnly: false, + }, + corev1.VolumeMount{ + Name: defaults.PrivateImageHubSecretName, + MountPath: privateImageHubDockerAuthPath, + ReadOnly: true, + }, + corev1.VolumeMount{ + Name: buildKitHomeVolumeName, + MountPath: buildKitHomePath, // Writable directory where buildah's auth.json file is stored + ReadOnly: false, + }, + ) + + if len(c.pipelineArgs.ExternalContainerRegistryDefaultAuthSecret) > 0 { + volumeMounts = append(volumeMounts, + corev1.VolumeMount{ + Name: c.pipelineArgs.ExternalContainerRegistryDefaultAuthSecret, + MountPath: defaultExternalRegistryAuthPath, + ReadOnly: true, + }, + ) + } + + if len(c.buildSecrets) > 0 { + volumeMounts = append(volumeMounts, + corev1.VolumeMount{ + Name: defaults.BuildSecretsName, + MountPath: buildKitBuildSecretsPath, + ReadOnly: true, + }, + ) + } + + return volumeMounts +} diff --git a/pipeline-runner/internal/jobs/build/buildkit_test.go b/pipeline-runner/internal/jobs/build/buildkit_test.go new file mode 100644 index 000000000..f62fc0e65 --- /dev/null +++ b/pipeline-runner/internal/jobs/build/buildkit_test.go @@ -0,0 +1,278 @@ +package build_test + +import ( + "encoding/json" + "fmt" + "path" + "testing" + + "github.com/equinor/radix-common/utils/pointers" + "github.com/equinor/radix-common/utils/slice" + "github.com/equinor/radix-operator/pipeline-runner/internal/jobs/build" + "github.com/equinor/radix-operator/pipeline-runner/model" + "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/securitycontext" + "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/equinor/radix-operator/pkg/apis/utils/annotations" + "github.com/equinor/radix-operator/pkg/apis/utils/git" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +func Test_BuildKit_JobSpec(t *testing.T) { + t.Run("nocache-nopush", func(t *testing.T) { assertBuildKitJobSpec(t, false, false, nil, "") }) + t.Run("cache-nopush", func(t *testing.T) { assertBuildKitJobSpec(t, true, false, nil, "") }) + t.Run("nocache-push", func(t *testing.T) { assertBuildKitJobSpec(t, false, true, nil, "") }) + t.Run("cache-push", func(t *testing.T) { assertBuildKitJobSpec(t, true, true, nil, "") }) + t.Run("with buildsecrets", func(t *testing.T) { assertBuildKitJobSpec(t, true, true, []string{"secret1", "secret2"}, "") }) + t.Run("with buildsecrets", func(t *testing.T) { assertBuildKitJobSpec(t, true, true, nil, "anyexternalregsecret") }) +} + +func assertBuildKitJobSpec(t *testing.T, useCache, pushImage bool, buildSecrets []string, externalRegistrySecret string) { + const ( + cloneURL = "anycloneurl" + gitCommitHash = "anygitcommithash" + gitTags = "anygittags" + ) + + args := model.PipelineArguments{ + AppName: "anyappname", + PipelineType: "anypipelinetype", + JobName: "anyjobname", + Branch: "anybranch", + CommitID: "anycommitid", + ImageTag: "anyimagetag", + PushImage: pushImage, + BuildKitImageBuilder: "anyimagebuilder", + GitCloneNsLookupImage: "anynslookupimage", + GitCloneGitImage: "anygitcloneimage", + GitCloneBashImage: "anybashimage", + Clustertype: "anyclustertype", + Clustername: "anyclustername", + ContainerRegistry: "anycontainerregistry", + AppContainerRegistry: "anyappcontainerregistry", + SeccompProfileFileName: "anyseccompprofilefile", + ExternalContainerRegistryDefaultAuthSecret: externalRegistrySecret, + Builder: model.Builder{ResourcesLimitsMemory: "100M", ResourcesRequestsCPU: "50m", ResourcesRequestsMemory: "50M"}, + } + require.Equal(t, pushImage, args.PushImage) + require.Equal(t, externalRegistrySecret, args.ExternalContainerRegistryDefaultAuthSecret) + componentImages := []pipeline.BuildComponentImage{ + {ComponentName: "c1", EnvName: "c1env", ContainerName: "c1container", Context: "c1ctx", Dockerfile: "c1dockerfile", ImageName: "c1imagename", ImagePath: "c1image", ClusterTypeImagePath: "c1clustertypeimage", ClusterNameImagePath: "c1clusternameimage"}, + {ComponentName: "c2", EnvName: "c2env", ContainerName: "c2container", Context: "c2ctx", Dockerfile: "c2dockerfile", ImageName: "c2imagename", ImagePath: "c2image", ClusterTypeImagePath: "c2clustertypeimage", ClusterNameImagePath: "c2clusternameimage", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}}, + {ComponentName: "c3", EnvName: "c3env", ContainerName: "c3container", Context: "c3ctx", Dockerfile: "c3dockerfile", ImageName: "c2imagename", ImagePath: "c2image", ClusterTypeImagePath: "c3clustertypeimage", ClusterNameImagePath: "c3clusternameimage", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, + } + + sut := build.NewBuildKit() + jobs := sut.BuildJobs(useCache, args, cloneURL, gitCommitHash, gitTags, componentImages, buildSecrets) + require.Len(t, jobs, len(componentImages)) + + for _, ci := range componentImages { + t.Run(fmt.Sprintf("%s-%s", ci.EnvName, ci.ComponentName), func(t *testing.T) { + job, ok := slice.FindFirst(jobs, func(j batchv1.Job) bool { + return j.Labels[kube.RadixEnvLabel] == ci.EnvName && j.Labels[kube.RadixComponentLabel] == ci.ComponentName + }) + require.True(t, ok) + assert.NotEmpty(t, job) + + // Check job + expectedJobLabels := map[string]string{ + kube.RadixJobNameLabel: args.JobName, + kube.RadixAppLabel: args.AppName, + kube.RadixImageTagLabel: args.ImageTag, + kube.RadixJobTypeLabel: kube.RadixJobTypeBuild, + kube.RadixEnvLabel: ci.EnvName, + kube.RadixComponentLabel: ci.ComponentName, + } + assert.Equal(t, expectedJobLabels, job.Labels) + componentImagesAnnotation, _ := json.Marshal([]pipeline.BuildComponentImage{ci}) + expectedJobAnnotations := map[string]string{ + kube.RadixBranchAnnotation: args.Branch, + kube.RadixBuildComponentsAnnotation: string(componentImagesAnnotation), + } + assert.Equal(t, expectedJobAnnotations, job.Annotations) + assert.Equal(t, pointers.Ptr[int32](0), job.Spec.BackoffLimit) + + // Check pod template + expectedPodLabels := map[string]string{ + kube.RadixJobNameLabel: args.JobName, + } + assert.Equal(t, expectedPodLabels, job.Spec.Template.Labels) + expectedPodAnnotations := annotations.ForClusterAutoscalerSafeToEvict(false) + expectedPodAnnotations[fmt.Sprintf("container.apparmor.security.beta.kubernetes.io/%s", ci.ContainerName)] = "unconfined" + assert.Equal(t, expectedPodAnnotations, job.Spec.Template.Annotations) + assert.Equal(t, corev1.RestartPolicyNever, job.Spec.Template.Spec.RestartPolicy) + arch := radixv1.RuntimeArchitectureAmd64 + if ci.Runtime != nil { + arch = ci.Runtime.Architecture + } + expectedAffinity := &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{MatchExpressions: []corev1.NodeSelectorRequirement{ + {Key: kube.RadixJobNodeLabel, Operator: corev1.NodeSelectorOpExists}, + {Key: corev1.LabelOSStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorOS}}, + {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{string(arch)}}, + }}}}}} + assert.Equal(t, expectedAffinity, job.Spec.Template.Spec.Affinity) + assert.ElementsMatch(t, utils.GetPipelineJobPodSpecTolerations(), job.Spec.Template.Spec.Tolerations) + expectedPodSecurityContext := securitycontext.Pod( + securitycontext.WithPodFSGroup(1000), + securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault), + securitycontext.WithPodRunAsNonRoot(pointers.Ptr(false))) + assert.Equal(t, expectedPodSecurityContext, job.Spec.Template.Spec.SecurityContext) + expectedVolumes := []corev1.Volume{ + {Name: git.BuildContextVolumeName}, + {Name: git.GitSSHKeyVolumeName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: git.GitSSHKeyVolumeName, DefaultMode: pointers.Ptr[int32](256)}}}, + {Name: fmt.Sprintf("tmp-%s", ci.ContainerName), VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, + {Name: fmt.Sprintf("var-%s", ci.ContainerName), VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, + {Name: defaults.PrivateImageHubSecretName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: defaults.PrivateImageHubSecretName}}}, + {Name: "radix-image-builder-home", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(5, resource.Mega)}}}, + {Name: "build-kit-run", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, + {Name: "build-kit-root", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, + } + if len(args.ExternalContainerRegistryDefaultAuthSecret) > 0 { + expectedVolumes = append(expectedVolumes, corev1.Volume{ + Name: args.ExternalContainerRegistryDefaultAuthSecret, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: args.ExternalContainerRegistryDefaultAuthSecret}}, + }) + } + if len(buildSecrets) > 0 { + expectedVolumes = append(expectedVolumes, corev1.Volume{ + Name: defaults.BuildSecretsName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: defaults.BuildSecretsName}}, + }) + } + assert.ElementsMatch(t, expectedVolumes, job.Spec.Template.Spec.Volumes) + + // Check init containers + assert.ElementsMatch(t, []string{"internal-nslookup", "clone", "internal-chmod"}, slice.Map(job.Spec.Template.Spec.InitContainers, func(c corev1.Container) string { return c.Name })) + cloneContainer, _ := slice.FindFirst(job.Spec.Template.Spec.InitContainers, func(c corev1.Container) bool { return c.Name == "clone" }) + assert.Equal(t, args.GitCloneGitImage, cloneContainer.Image) + assert.Equal(t, []string{"git", "clone", "--recurse-submodules", cloneURL, "-b", args.Branch, "--verbose", "--progress", git.Workspace}, cloneContainer.Command) + assert.Empty(t, cloneContainer.Args) + expectedCloneVolumeMounts := []corev1.VolumeMount{ + {Name: git.BuildContextVolumeName, MountPath: git.Workspace}, + {Name: git.GitSSHKeyVolumeName, MountPath: "/.ssh", ReadOnly: true}, + } + assert.ElementsMatch(t, expectedCloneVolumeMounts, cloneContainer.VolumeMounts) + + // Check container + require.Len(t, job.Spec.Template.Spec.Containers, 1) + c := job.Spec.Template.Spec.Containers[0] + assert.Equal(t, ci.ContainerName, c.Name) + assert.Equal(t, fmt.Sprintf("%s/%s", args.ContainerRegistry, args.BuildKitImageBuilder), c.Image) + assert.Equal(t, corev1.PullAlways, c.ImagePullPolicy) + expectedResources := corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceCPU: resource.MustParse(args.Builder.ResourcesRequestsCPU), + corev1.ResourceMemory: resource.MustParse(args.Builder.ResourcesRequestsMemory), + }, + Limits: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceMemory: resource.MustParse(args.Builder.ResourcesLimitsMemory), + }, + } + assert.Equal(t, expectedResources, c.Resources) + expectedSecurityContext := securitycontext.Container( + securitycontext.WithContainerDropAllCapabilities(), + securitycontext.WithContainerCapabilities([]corev1.Capability{"SETUID", "SETGID", "SETFCAP"}), + securitycontext.WithContainerSeccompProfile(corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeLocalhost, + LocalhostProfile: utils.StringPtr(args.SeccompProfileFileName), + }), + securitycontext.WithContainerRunAsNonRoot(pointers.Ptr(false)), + securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), + ) + assert.Equal(t, expectedSecurityContext, c.SecurityContext) + expectedEnvVars := []corev1.EnvVar{ + { + Name: "BUILDAH_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}, + Key: "username", + }, + }, + }, + { + Name: "BUILDAH_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}, + Key: "password", + }, + }, + }, + { + Name: "BUILDAH_CACHE_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}, + Key: "username", + }, + }, + }, + { + Name: "BUILDAH_CACHE_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}, + Key: "password", + }, + }, + }, + } + assert.ElementsMatch(t, expectedEnvVars, c.Env) + expectedVolumeMounts := []corev1.VolumeMount{ + {Name: git.BuildContextVolumeName, MountPath: git.Workspace}, + {Name: fmt.Sprintf("tmp-%s", ci.ContainerName), MountPath: "/tmp", ReadOnly: false}, + {Name: fmt.Sprintf("var-%s", ci.ContainerName), MountPath: "/var", ReadOnly: false}, + {Name: "build-kit-run", MountPath: "/run", ReadOnly: false}, + {Name: "build-kit-root", MountPath: "/root", ReadOnly: false}, + {Name: defaults.PrivateImageHubSecretName, MountPath: "/radix-private-image-hubs", ReadOnly: true}, + {Name: "radix-image-builder-home", MountPath: "/home/build", ReadOnly: false}, + } + if len(args.ExternalContainerRegistryDefaultAuthSecret) > 0 { + expectedVolumeMounts = append(expectedVolumeMounts, corev1.VolumeMount{Name: args.ExternalContainerRegistryDefaultAuthSecret, MountPath: "/radix-default-external-registry-auth", ReadOnly: true}) + } + if len(buildSecrets) > 0 { + expectedVolumeMounts = append(expectedVolumeMounts, corev1.VolumeMount{Name: defaults.BuildSecretsName, MountPath: "/build-secrets", ReadOnly: true}) + } + assert.ElementsMatch(t, expectedVolumeMounts, c.VolumeMounts) + expectedArgs := []string{ + "--registry", args.ContainerRegistry, + "--registry-username", "$(BUILDAH_USERNAME)", + "--registry-password", "$(BUILDAH_PASSWORD)", + "--cache-registry", args.AppContainerRegistry, + "--cache-registry-username", "$(BUILDAH_CACHE_USERNAME)", + "--cache-registry-password", "$(BUILDAH_CACHE_PASSWORD)", + "--cache-repository", utils.GetImageCachePath(args.AppContainerRegistry, args.AppName), + "--tag", ci.ImagePath, + "--cluster-type-tag", ci.ClusterTypeImagePath, + "--cluster-name-tag", ci.ClusterNameImagePath, + "--secrets-path", "/build-secrets", + "--dockerfile", ci.Dockerfile, + "--context", ci.Context, + "--branch", args.Branch, + "--git-commit-hash", gitCommitHash, + "--git-tags", gitTags, + "--target-environments", ci.EnvName, + } + if useCache { + expectedArgs = append(expectedArgs, "--use-cache") + } + if args.PushImage { + expectedArgs = append(expectedArgs, "--push") + } + for _, secret := range buildSecrets { + expectedArgs = append(expectedArgs, "--secret", secret) + } + if len(args.ExternalContainerRegistryDefaultAuthSecret) > 0 { + expectedArgs = append(expectedArgs, "--auth-file", path.Join("/radix-default-external-registry-auth", corev1.DockerConfigJsonKey)) + } + expectedArgs = append(expectedArgs, "--auth-file", path.Join("/radix-private-image-hubs", corev1.DockerConfigJsonKey)) + assert.Equal(t, expectedArgs, c.Args) + }) + } +} diff --git a/pipeline-runner/internal/jobs/build/common.go b/pipeline-runner/internal/jobs/build/common.go new file mode 100644 index 000000000..58aae63c8 --- /dev/null +++ b/pipeline-runner/internal/jobs/build/common.go @@ -0,0 +1,132 @@ +package build + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/equinor/radix-common/utils/pointers" + "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" + "github.com/equinor/radix-operator/pkg/apis/utils/git" + radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +const ( + volumeDefaultMode = int32(256) +) + +// Common functions - move +func getJobName(timeStamp time.Time, imageTag, hash string) string { + ts := timeStamp.Format("20060102150405") + return fmt.Sprintf("radix-builder-%s-%s-%s", ts, imageTag, hash) +} + +func getCommonJobLabels(appName, pipelineJobName, imageTag string) map[string]string { + return map[string]string{ + kube.RadixJobNameLabel: pipelineJobName, + kube.RadixAppLabel: appName, + kube.RadixImageTagLabel: imageTag, + kube.RadixJobTypeLabel: kube.RadixJobTypeBuild, + } +} + +func getCommonJobAnnotations(branch string, componentImages ...pipeline.BuildComponentImage) map[string]string { + componentImagesAnnotation, _ := json.Marshal(componentImages) + return map[string]string{ + kube.RadixBranchAnnotation: branch, + kube.RadixBuildComponentsAnnotation: string(componentImagesAnnotation), + } +} + +func getCommonPodLabels(pipelineJobName string) map[string]string { + return radixlabels.ForPipelineJobName(pipelineJobName) +} + +func getCommonPodAnnotations() map[string]string { + return radixannotations.ForClusterAutoscalerSafeToEvict(false) +} + +func getCommonPodAffinity(runtime *radixv1.Runtime) *corev1.Affinity { + return utils.GetAffinityForPipelineJob(runtime) +} + +func getCommonPodTolerations() []corev1.Toleration { + return utils.GetPipelineJobPodSpecTolerations() +} + +func getCommonPodInitContainers(cloneURL, branch string, cloneConfig git.CloneConfig) []corev1.Container { + return git.CloneInitContainers(cloneURL, branch, cloneConfig) +} + +func getCommonPodVolumes(componentImages []pipeline.BuildComponentImage) []corev1.Volume { + volumes := []corev1.Volume{ + { + Name: git.BuildContextVolumeName, + }, + { + Name: git.GitSSHKeyVolumeName, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: git.GitSSHKeyVolumeName, + DefaultMode: pointers.Ptr(volumeDefaultMode), + }, + }, + }, + } + + for _, image := range componentImages { + volumes = append(volumes, + corev1.Volume{ + Name: getTmpVolumeNameForContainer(image.ContainerName), + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + SizeLimit: resource.NewScaledQuantity(100, resource.Giga), + }, + }, + }, + corev1.Volume{ + Name: getVarVolumeNameForContainer(image.ContainerName), + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{ + SizeLimit: resource.NewScaledQuantity(100, resource.Giga), + }, + }, + }, + ) + } + + return volumes +} + +func getCommonPodContainerVolumeMounts(componentImage pipeline.BuildComponentImage) []corev1.VolumeMount { + return []corev1.VolumeMount{ + { + Name: git.BuildContextVolumeName, + MountPath: git.Workspace, + }, + { + Name: getTmpVolumeNameForContainer(componentImage.ContainerName), // image-builder creates a script there + MountPath: "/tmp", + ReadOnly: false, + }, + { + Name: getVarVolumeNameForContainer(componentImage.ContainerName), // image-builder creates files there + MountPath: "/var", + ReadOnly: false, + }, + } +} + +func getTmpVolumeNameForContainer(containerName string) string { + return fmt.Sprintf("tmp-%s", containerName) +} + +func getVarVolumeNameForContainer(containerName string) string { + return fmt.Sprintf("var-%s", containerName) +} diff --git a/pipeline-runner/internal/jobs/build/interface.go b/pipeline-runner/internal/jobs/build/interface.go new file mode 100644 index 000000000..f72998153 --- /dev/null +++ b/pipeline-runner/internal/jobs/build/interface.go @@ -0,0 +1,13 @@ +package build + +import ( + "github.com/equinor/radix-operator/pipeline-runner/model" + "github.com/equinor/radix-operator/pkg/apis/pipeline" + batchv1 "k8s.io/api/batch/v1" +) + +// JobsBuilder defines interface for creating pipeline build jobs +type JobsBuilder interface { + // BuildJobs returns a slice of Kubernetes jobs to be used for building container images for Radix components and jobs + BuildJobs(useBuildCache bool, pipelineArgs model.PipelineArguments, cloneURL, gitCommitHash, gitTags string, componentImages []pipeline.BuildComponentImage, buildSecrets []string) []batchv1.Job +} diff --git a/pipeline-runner/internal/jobs/build/internal/kubejob.go b/pipeline-runner/internal/jobs/build/internal/kubejob.go new file mode 100644 index 000000000..0dad4a2ef --- /dev/null +++ b/pipeline-runner/internal/jobs/build/internal/kubejob.go @@ -0,0 +1,52 @@ +package internal + +import ( + "github.com/equinor/radix-common/utils/pointers" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// KubeJobProps defines properties to be used with the BuildKubeJob function. +type KubeJobProps interface { + JobName() string + JobLabels() map[string]string + JobAnnotations() map[string]string + PodLabels() map[string]string + PodAnnotations() map[string]string + PodTolerations() []corev1.Toleration + PodAffinity() *corev1.Affinity + PodSecurityContext() *corev1.PodSecurityContext + PodVolumes() []corev1.Volume + PodInitContainers() []corev1.Container + PodContainers() []corev1.Container +} + +// BuildKubeJob builds a Kubernetes job with properties defined by source argument +func BuildKubeJob(props KubeJobProps) batchv1.Job { + return batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: props.JobName(), + Labels: props.JobLabels(), + Annotations: props.JobAnnotations(), + }, + Spec: batchv1.JobSpec{ + BackoffLimit: pointers.Ptr[int32](0), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: props.PodLabels(), + Annotations: props.PodAnnotations(), + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + Affinity: props.PodAffinity(), + Tolerations: props.PodTolerations(), + SecurityContext: props.PodSecurityContext(), + Volumes: props.PodVolumes(), + InitContainers: props.PodInitContainers(), + Containers: props.PodContainers(), + }, + }, + }, + } +} diff --git a/pipeline-runner/internal/jobs/build/mock/job.go b/pipeline-runner/internal/jobs/build/mock/job.go new file mode 100644 index 000000000..213ab622b --- /dev/null +++ b/pipeline-runner/internal/jobs/build/mock/job.go @@ -0,0 +1,51 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./pipeline-runner/internal/jobs/build/interface.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + model "github.com/equinor/radix-operator/pipeline-runner/model" + pipeline "github.com/equinor/radix-operator/pkg/apis/pipeline" + gomock "github.com/golang/mock/gomock" + v1 "k8s.io/api/batch/v1" +) + +// MockJobsBuilder is a mock of JobsBuilder interface. +type MockJobsBuilder struct { + ctrl *gomock.Controller + recorder *MockJobsBuilderMockRecorder +} + +// MockJobsBuilderMockRecorder is the mock recorder for MockJobsBuilder. +type MockJobsBuilderMockRecorder struct { + mock *MockJobsBuilder +} + +// NewMockJobsBuilder creates a new mock instance. +func NewMockJobsBuilder(ctrl *gomock.Controller) *MockJobsBuilder { + mock := &MockJobsBuilder{ctrl: ctrl} + mock.recorder = &MockJobsBuilderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockJobsBuilder) EXPECT() *MockJobsBuilderMockRecorder { + return m.recorder +} + +// BuildJobs mocks base method. +func (m *MockJobsBuilder) BuildJobs(useBuildCache bool, pipelineArgs model.PipelineArguments, cloneURL, gitCommitHash, gitTags string, componentImages []pipeline.BuildComponentImage, buildSecrets []string) []v1.Job { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildJobs", useBuildCache, pipelineArgs, cloneURL, gitCommitHash, gitTags, componentImages, buildSecrets) + ret0, _ := ret[0].([]v1.Job) + return ret0 +} + +// BuildJobs indicates an expected call of BuildJobs. +func (mr *MockJobsBuilderMockRecorder) BuildJobs(useBuildCache, pipelineArgs, cloneURL, gitCommitHash, gitTags, componentImages, buildSecrets interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildJobs", reflect.TypeOf((*MockJobsBuilder)(nil).BuildJobs), useBuildCache, pipelineArgs, cloneURL, gitCommitHash, gitTags, componentImages, buildSecrets) +} diff --git a/pipeline-runner/internal/watcher/namespace_mock.go b/pipeline-runner/internal/watcher/namespace_mock.go index 46268702f..e3d716ce5 100644 --- a/pipeline-runner/internal/watcher/namespace_mock.go +++ b/pipeline-runner/internal/watcher/namespace_mock.go @@ -1,21 +1,49 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./pipeline-runner/internal/watcher/namespace.go + +// Package watcher is a generated GoMock package. package watcher -import "context" +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockNamespaceWatcher is a mock of NamespaceWatcher interface. +type MockNamespaceWatcher struct { + ctrl *gomock.Controller + recorder *MockNamespaceWatcherMockRecorder +} + +// MockNamespaceWatcherMockRecorder is the mock recorder for MockNamespaceWatcher. +type MockNamespaceWatcherMockRecorder struct { + mock *MockNamespaceWatcher +} -// FakeNamespaceWatcher Unit tests doesn't handle multi-threading well -type FakeNamespaceWatcher struct { +// NewMockNamespaceWatcher creates a new mock instance. +func NewMockNamespaceWatcher(ctrl *gomock.Controller) *MockNamespaceWatcher { + mock := &MockNamespaceWatcher{ctrl: ctrl} + mock.recorder = &MockNamespaceWatcherMockRecorder{mock} + return mock } -// FakeRadixDeploymentWatcher Unit tests doesn't handle multi-threading well -type FakeRadixDeploymentWatcher struct { +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNamespaceWatcher) EXPECT() *MockNamespaceWatcherMockRecorder { + return m.recorder } -// WaitFor Waits for namespace to appear -func (watcher FakeNamespaceWatcher) WaitFor(_ context.Context, _ string) error { - return nil +// WaitFor mocks base method. +func (m *MockNamespaceWatcher) WaitFor(ctx context.Context, namespace string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitFor", ctx, namespace) + ret0, _ := ret[0].(error) + return ret0 } -// WaitFor Waits for radix deployment gets active -func (watcher FakeRadixDeploymentWatcher) WaitForActive(_ context.Context, _, _ string) error { - return nil +// WaitFor indicates an expected call of WaitFor. +func (mr *MockNamespaceWatcherMockRecorder) WaitFor(ctx, namespace interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitFor", reflect.TypeOf((*MockNamespaceWatcher)(nil).WaitFor), ctx, namespace) } diff --git a/pipeline-runner/main.go b/pipeline-runner/main.go index a03d22146..439072363 100755 --- a/pipeline-runner/main.go +++ b/pipeline-runner/main.go @@ -105,7 +105,7 @@ func setPipelineArgsFromArguments(cmd *cobra.Command, pipelineArgs *model.Pipeli cmd.Flags().StringVar(&pipelineArgs.ToEnvironment, defaults.RadixPromoteToEnvironmentEnvironmentVariable, "", "Radix application environment name to promote to") cmd.Flags().StringVar(&pipelineArgs.TektonPipeline, defaults.RadixTektonPipelineImageEnvironmentVariable, "", "Radix Tekton docker image") cmd.Flags().StringVar(&pipelineArgs.ImageBuilder, defaults.RadixImageBuilderEnvironmentVariable, "", "Radix Image Builder docker image") - cmd.Flags().StringVar(&pipelineArgs.BuildKitImageBuilder, defaults.RadixBuildahImageBuilderEnvironmentVariable, "", "Radix Build Kit Image Builder container image") + cmd.Flags().StringVar(&pipelineArgs.BuildKitImageBuilder, defaults.RadixBuildKitImageBuilderEnvironmentVariable, "", "Radix Build Kit Image Builder container image") cmd.Flags().StringVar(&pipelineArgs.SeccompProfileFileName, defaults.SeccompProfileFileNameEnvironmentVariable, "", "Filename of the seccomp profile injected by daemonset, relative to the /var/lib/kubelet/seccomp directory on node") cmd.Flags().StringVar(&pipelineArgs.Clustertype, defaults.RadixClusterTypeEnvironmentVariable, "", "Cluster type") cmd.Flags().StringVar(&pipelineArgs.Clustername, defaults.ClusternameEnvironmentVariable, "", "Cluster name") @@ -119,8 +119,7 @@ func setPipelineArgsFromArguments(cmd *cobra.Command, pipelineArgs *model.Pipeli cmd.Flags().StringVar(&pipelineArgs.Builder.ResourcesLimitsMemory, defaults.OperatorAppBuilderResourcesLimitsMemoryEnvironmentVariable, "2000M", "Image builder resource limit memory") cmd.Flags().StringVar(&pipelineArgs.Builder.ResourcesRequestsCPU, defaults.OperatorAppBuilderResourcesRequestsCPUEnvironmentVariable, "200m", "Image builder resource requests CPU") cmd.Flags().StringVar(&pipelineArgs.Builder.ResourcesRequestsMemory, defaults.OperatorAppBuilderResourcesRequestsMemoryEnvironmentVariable, "500M", "Image builder resource requests memory") - var useCache string - cmd.Flags().StringVar(&useCache, defaults.RadixUseCacheEnvironmentVariable, "0", "Use cache") + cmd.Flags().StringVar(&pipelineArgs.ExternalContainerRegistryDefaultAuthSecret, defaults.RadixExternalRegistryDefaultAuthEnvironmentVariable, "", "Name of secret of type `kubernetes.io/dockerconfigjson` containign default credentials for external container registries") cmd.Flags().Var(&overrideUseBuildCache, defaults.RadixOverrideUseBuildCacheVariable, "Optional. Overrides configured or default useBuildCache option. It is applicable when the useBuildKit option is set as true.") var pushImage string cmd.Flags().StringVar(&pushImage, defaults.RadixPushImageEnvironmentVariable, "0", "Push docker image to a repository") @@ -135,6 +134,9 @@ func setPipelineArgsFromArguments(cmd *cobra.Command, pipelineArgs *model.Pipeli 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") + // 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") + err := cmd.Flags().Parse(arguments) if err != nil { return fmt.Errorf("failed to parse command arguments. Error: %v", err) @@ -147,7 +149,6 @@ func setPipelineArgsFromArguments(cmd *cobra.Command, pipelineArgs *model.Pipeli } pipelineArgs.PushImage, _ = strconv.ParseBool(pushImage) pipelineArgs.PushImage = pipelineArgs.PipelineType == string(v1.BuildDeploy) || pipelineArgs.PushImage // build and deploy require push - pipelineArgs.UseCache, _ = strconv.ParseBool(useCache) pipelineArgs.OverrideUseBuildCache = overrideUseBuildCache.Get() pipelineArgs.Debug, _ = strconv.ParseBool(debug) if len(pipelineArgs.ImageTagNames) > 0 { diff --git a/pipeline-runner/model/pipelineInfo.go b/pipeline-runner/model/pipelineInfo.go index 4141f3649..21b23fa7b 100644 --- a/pipeline-runner/model/pipelineInfo.go +++ b/pipeline-runner/model/pipelineInfo.go @@ -63,7 +63,6 @@ type PipelineArguments struct { // CommitID is sent from GitHub webhook. not to be confused with PipelineInfo.GitCommitHash CommitID string ImageTag string - UseCache bool // OverrideUseBuildCache override default or configured build cache option OverrideUseBuildCache *bool PushImage bool @@ -76,9 +75,9 @@ type PipelineArguments struct { // Images used for copying radix config/building TektonPipeline string - // ImageBuilder Points to the image builder + // ImageBuilder Points to the image builder (repository and tag only) ImageBuilder string - // BuildKitImageBuilder Points to the BuildKit compliant image builder + // BuildKitImageBuilder Points to the BuildKit compliant image builder (repository and tag only) BuildKitImageBuilder string // GitCloneNsLookupImage defines image containing nslookup. // Used as option to the CloneInitContainers function. @@ -112,6 +111,10 @@ type PipelineArguments struct { AppName string Builder Builder DNSConfig *dnsaliasconfig.DNSConfig + + // Name of secret with .dockerconfigjson key containing docker auths. Optional. + // Used to authenticate external container registries when using buildkit to build dockerfiles. + ExternalContainerRegistryDefaultAuthSecret string } // InitPipeline Initialize pipeline with step implementations @@ -196,3 +199,16 @@ func (info *PipelineInfo) IsPipelineType(pipelineType radixv1.RadixPipelineType) func (info *PipelineInfo) IsUsingBuildKit() bool { return info.RadixApplication.Spec.Build != nil && info.RadixApplication.Spec.Build.UseBuildKit != nil && *info.RadixApplication.Spec.Build.UseBuildKit } + +func (info *PipelineInfo) IsUsingBuildCache() bool { + if !info.IsUsingBuildKit() { + return false + } + + useBuildCache := info.RadixApplication.Spec.Build == nil || info.RadixApplication.Spec.Build.UseBuildCache == nil || *info.RadixApplication.Spec.Build.UseBuildCache + if info.PipelineArguments.OverrideUseBuildCache != nil { + useBuildCache = *info.PipelineArguments.OverrideUseBuildCache + } + + return useBuildCache +} diff --git a/pipeline-runner/steps/applyconfig/step.go b/pipeline-runner/steps/applyconfig/step.go index 8a39ea9e1..30d6e52cc 100644 --- a/pipeline-runner/steps/applyconfig/step.go +++ b/pipeline-runner/steps/applyconfig/step.go @@ -329,16 +329,23 @@ func setPipelineBuildComponentImages(pipelineInfo *model.PipelineInfo, component envNameForName := getLengthLimitedName(envName) imageName := fmt.Sprintf("%s-%s", envNameForName, componentName) containerName := fmt.Sprintf("build-%s-%s", componentName, envNameForName) - imagePath := operatorutils.GetImagePath(pipelineInfo.PipelineArguments.ContainerRegistry, pipelineInfo.RadixApplication.GetName(), imageName, pipelineInfo.PipelineArguments.ImageTag) + appName := pipelineInfo.RadixApplication.GetName() + containerRegistry := pipelineInfo.PipelineArguments.ContainerRegistry + imageTag := pipelineInfo.PipelineArguments.ImageTag + imagePath := operatorutils.GetImagePath(containerRegistry, appName, imageName, imageTag) + clusterTypeImagePath := operatorutils.GetImagePath(containerRegistry, appName, imageName, fmt.Sprintf("%s-%s", pipelineInfo.PipelineArguments.Clustertype, imageTag)) + clusterNameImagePath := operatorutils.GetImagePath(containerRegistry, appName, imageName, fmt.Sprintf("%s-%s", pipelineInfo.PipelineArguments.Clustername, imageTag)) buildComponentImages = append(buildComponentImages, pipeline.BuildComponentImage{ - ComponentName: componentName, - EnvName: envName, - ContainerName: containerName, - Context: getContext(imageSource.Source.Folder), - Dockerfile: getDockerfileName(imageSource.Source.DockefileName), - ImageName: imageName, - ImagePath: imagePath, - Runtime: imageSource.Runtime, + ComponentName: componentName, + EnvName: envName, + ContainerName: containerName, + Context: getContext(imageSource.Source.Folder), + Dockerfile: getDockerfileName(imageSource.Source.DockefileName), + ImageName: imageName, + ImagePath: imagePath, + ClusterTypeImagePath: clusterTypeImagePath, + ClusterNameImagePath: clusterNameImagePath, + Runtime: imageSource.Runtime, }) componentImageSourceMap[envName][imageSourceIndex].Image = imagePath } diff --git a/pipeline-runner/steps/applyconfig/step_test.go b/pipeline-runner/steps/applyconfig/step_test.go index 6d0e64bf3..504ec11cd 100644 --- a/pipeline-runner/steps/applyconfig/step_test.go +++ b/pipeline-runner/steps/applyconfig/step_test.go @@ -2,20 +2,28 @@ package applyconfig_test import ( "context" + "encoding/json" + "fmt" "testing" + "time" "github.com/equinor/radix-common/utils/pointers" + "github.com/equinor/radix-common/utils/slice" internaltest "github.com/equinor/radix-operator/pipeline-runner/internal/test" "github.com/equinor/radix-operator/pipeline-runner/model" "github.com/equinor/radix-operator/pipeline-runner/steps/applyconfig" + "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" radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" 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" + "golang.org/x/exp/maps" corev1 "k8s.io/api/core/v1" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubefake "k8s.io/client-go/kubernetes/fake" ) @@ -41,6 +49,266 @@ func (s *applyConfigTestSuite) SetupTest() { s.kubeUtil, _ = kube.New(s.kubeClient, s.radixClient, s.kedaClient, nil) } +func (s *applyConfigTestSuite) SetupSubTest() { + s.SetupTest() +} + +func (s *applyConfigTestSuite) Test_RadixConfigMap_Missing() { + appName := "anyapp" + rr := utils.ARadixRegistration().WithName(appName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + AppName: appName, + }, + RadixConfigMapName: "anyconfigmap", + } + cli := applyconfig.NewApplyConfigStep() + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.True(k8sErrors.IsNotFound(err)) +} + +func (s *applyConfigTestSuite) Test_RadixConfigMap_WithPrepareBuildCtx_Processed() { + appName, radixConfigMapName := "anyapp", "preparecm" + rr := utils.ARadixRegistration().WithName(appName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + expectedRa := utils.ARadixApplication().WithAppName(appName).BuildRA() + expectedPrepareBuildCtx := &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{{Environment: "any", Components: []string{"comp1", "comp2"}}}, + ChangedRadixConfig: true, + EnvironmentSubPipelinesToRun: []model.EnvironmentSubPipelineToRun{{Environment: "any", PipelineFile: "file1"}}, + } + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, radixConfigMapName, appName, expectedRa, expectedPrepareBuildCtx)) + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + AppName: appName, + }, + RadixConfigMapName: radixConfigMapName, + } + cli := applyconfig.NewApplyConfigStep() + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + s.Equal(expectedPrepareBuildCtx, pipelineInfo.PrepareBuildContext) + // We need marshal expected and actual to JSON and compare, since Equal asserts an empty array is different for a nil array + expectedRaJson, _ := json.Marshal(expectedRa) + pipelineRaJson, _ := json.Marshal(pipelineInfo.RadixApplication) + s.Equal(expectedRaJson, pipelineRaJson) + actualRa, err := s.radixClient.RadixV1().RadixApplications(utils.GetAppNamespace(appName)).Get(context.Background(), appName, metav1.GetOptions{}) + s.Require().NoError(err) + actualRaJson, _ := json.Marshal(actualRa) + s.Equal(expectedRaJson, actualRaJson) +} + +func (s *applyConfigTestSuite) Test_RadixConfigMap_WithoutPrepareBuildCtx_Processed() { + appName, radixConfigMapName := "anyapp", "preparecm" + rr := utils.ARadixRegistration().WithName(appName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + expectedRa := utils.ARadixApplication().WithAppName(appName).BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, radixConfigMapName, appName, expectedRa, nil)) + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + AppName: appName, + }, + RadixConfigMapName: radixConfigMapName, + } + cli := applyconfig.NewApplyConfigStep() + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + s.Nil(pipelineInfo.PrepareBuildContext) +} + +func (s *applyConfigTestSuite) Test_GitConfigMap_Processed() { + appName, radixConfigMapName, gitConfigMapName := "anyapp", "preparecm", "gitcm" + expectedGitHash, expectedGitTags := "anygithash", "anygittags" + rr := utils.ARadixRegistration().WithName(appName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + expectedRa := utils.ARadixApplication().WithAppName(appName).BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, radixConfigMapName, appName, expectedRa, nil)) + s.Require().NoError(internaltest.CreateGitInfoConfigMapResponse(s.kubeClient, gitConfigMapName, appName, expectedGitHash, expectedGitTags)) + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + AppName: appName, + PipelineType: string(radixv1.BuildDeploy), + }, + RadixConfigMapName: radixConfigMapName, + GitConfigMapName: gitConfigMapName, + } + cli := applyconfig.NewApplyConfigStep() + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + s.Equal(expectedGitHash, pipelineInfo.GitCommitHash) + s.Equal(expectedGitTags, pipelineInfo.GitTags) +} + +func (s *applyConfigTestSuite) Test_TargetEnvironments_BranchIsNotMapped() { + const ( + anyAppName = "any-app" + mappedBranch = "master" + nonMappedBranch = "feature" + prepareConfigMapName = "preparecm" + ) + + rr := utils.ARadixRegistration().WithName(anyAppName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + ra := utils.NewRadixApplicationBuilder(). + WithAppName(anyAppName). + WithEnvironment("anyenv", mappedBranch). + WithComponents( + utils.AnApplicationComponent(). + WithName("anyname")). + BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, anyAppName, ra, nil)) + + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + AppName: anyAppName, + PipelineType: string(radixv1.BuildDeploy), + Branch: nonMappedBranch, + }, + RadixConfigMapName: prepareConfigMapName, + } + + cli := applyconfig.NewApplyConfigStep() + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + s.Empty(pipelineInfo.TargetEnvironments) +} + +func (s *applyConfigTestSuite) Test_TargetEnvironments_BranchIsMapped() { + const ( + anyAppName = "any-app" + mappedBranch = "master" + nonMappedBranch = "release" + prepareConfigMapName = "preparecm" + ) + + rr := utils.ARadixRegistration().WithName(anyAppName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + + ra := utils.NewRadixApplicationBuilder(). + WithAppName(anyAppName). + WithEnvironment("mappedenv1", mappedBranch). + WithEnvironment("mappedenv2", mappedBranch). + WithEnvironment("nonmappedenv", nonMappedBranch). + WithComponents( + utils.AnApplicationComponent(). + WithName("anyname")). + BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, anyAppName, ra, nil)) + + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + AppName: anyAppName, + PipelineType: string(radixv1.BuildDeploy), + Branch: mappedBranch, + }, + RadixConfigMapName: prepareConfigMapName, + } + + cli := applyconfig.NewApplyConfigStep() + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + s.ElementsMatch([]string{"mappedenv1", "mappedenv2"}, pipelineInfo.TargetEnvironments) +} + +func (s *applyConfigTestSuite) Test_TargetEnvironments_DeployOnly() { + const ( + anyAppName = "any-app" + prepareConfigMapName = "preparecm" + toEnvironment = "anyenv" + ) + + rr := utils.ARadixRegistration().WithName(anyAppName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + ra := utils.NewRadixApplicationBuilder().WithAppName(anyAppName).BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, anyAppName, ra, nil)) + + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + AppName: anyAppName, + PipelineType: string(radixv1.Deploy), + ToEnvironment: toEnvironment, + }, + RadixConfigMapName: prepareConfigMapName, + } + + cli := applyconfig.NewApplyConfigStep() + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + s.ElementsMatch([]string{toEnvironment}, pipelineInfo.TargetEnvironments) +} + +func (s *applyConfigTestSuite) Test_BuildSecrets_SecretMissing() { + const ( + anyAppName = "any-app" + mappedBranch = "master" + nonMappedBranch = "release" + prepareConfigMapName = "preparecm" + ) + + rr := utils.ARadixRegistration().WithName(anyAppName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + + ra := utils.NewRadixApplicationBuilder().WithAppName(anyAppName).WithBuildSecrets("secret1", "secret2").BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, anyAppName, ra, nil)) + + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + AppName: anyAppName, + PipelineType: string(radixv1.BuildDeploy), + Branch: mappedBranch, + }, + RadixConfigMapName: prepareConfigMapName, + } + + cli := applyconfig.NewApplyConfigStep() + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + s.Empty(pipelineInfo.BuildSecret) +} + +func (s *applyConfigTestSuite) Test_BuildSecrets_SecretExist() { + const ( + anyAppName = "any-app" + mappedBranch = "master" + nonMappedBranch = "release" + prepareConfigMapName = "preparecm" + ) + + rr := utils.ARadixRegistration().WithName(anyAppName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + ra := utils.NewRadixApplicationBuilder().WithAppName(anyAppName).WithBuildSecrets("secret1", "secret2").BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, anyAppName, ra, nil)) + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: defaults.BuildSecretsName, Namespace: utils.GetAppNamespace(anyAppName)}, + Data: map[string][]byte{"any": []byte("data")}, + } + secret, _ = s.kubeClient.CoreV1().Secrets(utils.GetAppNamespace(anyAppName)).Create(context.Background(), secret, metav1.CreateOptions{}) + + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + AppName: anyAppName, + PipelineType: string(radixv1.BuildDeploy), + Branch: mappedBranch, + }, + RadixConfigMapName: prepareConfigMapName, + } + + cli := applyconfig.NewApplyConfigStep() + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + s.Equal(secret, pipelineInfo.BuildSecret) +} + func (s *applyConfigTestSuite) Test_Deploy_BuildComponentInDeployPiplineShouldFail() { appName := "anyapp" prepareConfigMapName := "preparecm" @@ -99,46 +367,1071 @@ func (s *applyConfigTestSuite) Test_Deploy_BuildJobInDeployPiplineShouldFail() { s.ErrorIs(err, applyconfig.ErrDeployOnlyPipelineDoesNotSupportBuild) } -func (s *applyConfigTestSuite) Test_ApplyConfig_ShouldNotFail() { - appName := "anyapp" +func (s *applyConfigTestSuite) Test_BuildAndDeployComponentImages() { + appName, envName1, envName2, envName3, envName4, rjName, buildBranch, jobPort := "anyapp", "dev1", "dev2", "dev3", "dev4", "anyrj", "anybranch", pointers.Ptr[int32](9999) prepareConfigMapName := "preparecm" - type scenario struct { - name string - componentBuilder *utils.RadixApplicationComponentBuilder - jobComponentBuilder *utils.RadixApplicationJobComponentBuilder + + rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + ra := utils.NewRadixApplicationBuilder(). + WithAppName(appName). + WithEnvironment(envName1, buildBranch). + WithEnvironment(envName2, buildBranch). + WithEnvironment(envName3, buildBranch). + WithEnvironment(envName4, buildBranch). + WithComponents( + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("component-1").WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"). + WithEnvironmentConfigs( + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName1), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image2:some-tag"), + ), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("component-2").WithDockerfileName("client.Dockerfile"). + WithEnvironmentConfigs( + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName1), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image3:some-tag"), + ), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("component-3").WithSourceFolder("./client/"). + WithEnvironmentConfigs( + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName1), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image4:some-tag"), + ), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("component-4").WithImage("some-image1:some-tag"). + WithEnvironmentConfigs( + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName1), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), + utils.NewComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image5:some-tag"), + ), + ). + WithJobComponents( + utils.NewApplicationJobComponentBuilder().WithPort("any", 8080).WithName("job-1").WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile").WithSchedulerPort(jobPort). + WithEnvironmentConfigs( + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName1), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image2:some-tag"), + ), + utils.NewApplicationJobComponentBuilder().WithPort("any", 8080).WithName("job-2").WithDockerfileName("client.Dockerfile").WithSchedulerPort(jobPort). + WithEnvironmentConfigs( + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName1), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image3:some-tag"), + ), + utils.NewApplicationJobComponentBuilder().WithPort("any", 8080).WithName("job-3").WithSourceFolder("./client/").WithSchedulerPort(jobPort). + WithEnvironmentConfigs( + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName1), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image4:some-tag"), + ), + utils.NewApplicationJobComponentBuilder().WithPort("any", 8080).WithName("job-4").WithImage("some-image1:some-tag").WithSchedulerPort(jobPort). + WithEnvironmentConfigs( + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName1), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), + utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image5:some-tag"), + ), + ). + BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) + pipelineInfo := model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + PipelineType: string(radixv1.BuildDeploy), + Branch: buildBranch, + JobName: rjName, + ImageTag: "imgtag", + ContainerRegistry: "registry", + Clustertype: "clustertype", + Clustername: "clustername", + }, + RadixConfigMapName: prepareConfigMapName, } - scenarios := []scenario{ - {name: "no components"}, - {name: "with a component", componentBuilder: pointers.Ptr(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp1"))}, - {name: "with a job-component", jobComponentBuilder: pointers.Ptr(utils.NewApplicationJobComponentBuilder().WithSchedulerPort(pointers.Ptr[int32](8080)).WithName("job1"))}, + + applyStep := applyconfig.NewApplyConfigStep() + applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + s.Require().NoError(applyStep.Run(context.Background(), &pipelineInfo)) + imageNameFunc := func(env, comp string) string { + return fmt.Sprintf("%s-%s", env, comp) + } + imagePathFunc := func(env, comp string) string { + return fmt.Sprintf("%s/%s-%s:%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(env, comp), pipelineInfo.PipelineArguments.ImageTag) + } + imagePathClusterTypeFunc := func(env, comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(env, comp), pipelineInfo.PipelineArguments.Clustertype, pipelineInfo.PipelineArguments.ImageTag) + } + imagePathClusterNameFunc := func(env, comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(env, comp), pipelineInfo.PipelineArguments.Clustername, pipelineInfo.PipelineArguments.ImageTag) + } + buildComponentImageFunc := func(env, component, context, dockerfile string) pipeline.BuildComponentImage { + return pipeline.BuildComponentImage{ + ComponentName: component, + EnvName: env, + ContainerName: fmt.Sprintf("build-%s-%s", component, env), + Context: context, + Dockerfile: dockerfile, + ImageName: imageNameFunc(env, component), + ImagePath: imagePathFunc(env, component), + ClusterTypeImagePath: imagePathClusterTypeFunc(env, component), + ClusterNameImagePath: imagePathClusterNameFunc(env, component), + } + } + expectedBuildComponentImages := pipeline.EnvironmentBuildComponentImages{ + envName1: []pipeline.BuildComponentImage{ + buildComponentImageFunc(envName1, "component-1", "/workspace/client/", "client.Dockerfile"), + buildComponentImageFunc(envName1, "component-2", "/workspace/", "client.Dockerfile"), + buildComponentImageFunc(envName1, "component-3", "/workspace/client/", "Dockerfile"), + buildComponentImageFunc(envName1, "job-1", "/workspace/client/", "client.Dockerfile"), + buildComponentImageFunc(envName1, "job-2", "/workspace/", "client.Dockerfile"), + buildComponentImageFunc(envName1, "job-3", "/workspace/client/", "Dockerfile"), + }, + envName2: []pipeline.BuildComponentImage{ + buildComponentImageFunc(envName2, "component-1", "/workspace/client2/", "client.Dockerfile"), + buildComponentImageFunc(envName2, "component-2", "/workspace/client2/", "client.Dockerfile"), + buildComponentImageFunc(envName2, "component-3", "/workspace/client2/", "Dockerfile"), + buildComponentImageFunc(envName2, "component-4", "/workspace/client2/", "Dockerfile"), + buildComponentImageFunc(envName2, "job-1", "/workspace/client2/", "client.Dockerfile"), + buildComponentImageFunc(envName2, "job-2", "/workspace/client2/", "client.Dockerfile"), + buildComponentImageFunc(envName2, "job-3", "/workspace/client2/", "Dockerfile"), + buildComponentImageFunc(envName2, "job-4", "/workspace/client2/", "Dockerfile"), + }, + envName3: []pipeline.BuildComponentImage{ + buildComponentImageFunc(envName3, "component-1", "/workspace/client/", "client2.Dockerfile"), + buildComponentImageFunc(envName3, "component-2", "/workspace/", "client2.Dockerfile"), + buildComponentImageFunc(envName3, "component-3", "/workspace/client/", "client2.Dockerfile"), + buildComponentImageFunc(envName3, "component-4", "/workspace/", "client2.Dockerfile"), + buildComponentImageFunc(envName3, "job-1", "/workspace/client/", "client2.Dockerfile"), + buildComponentImageFunc(envName3, "job-2", "/workspace/", "client2.Dockerfile"), + buildComponentImageFunc(envName3, "job-3", "/workspace/client/", "client2.Dockerfile"), + buildComponentImageFunc(envName3, "job-4", "/workspace/", "client2.Dockerfile"), + }, + } + s.ElementsMatch(maps.Keys(expectedBuildComponentImages), maps.Keys(pipelineInfo.BuildComponentImages)) + for env, images := range pipelineInfo.BuildComponentImages { + s.ElementsMatch(expectedBuildComponentImages[env], images) } - for _, ts := range scenarios { - s.T().Run(ts.name, func(t *testing.T) { - s.SetupTest() - rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - raBuilder := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment("dev", "anybranch") - if ts.componentBuilder != nil { - raBuilder = raBuilder.WithComponent(*ts.componentBuilder) + expectedDeployEnvironmentComponentImages := pipeline.DeployEnvironmentComponentImages{ + envName1: pipeline.DeployComponentImages{ + "component-1": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName1, "component-1"), Build: true}, + "component-2": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName1, "component-2"), Build: true}, + "component-3": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName1, "component-3"), Build: true}, + "component-4": pipeline.DeployComponentImage{ImagePath: "some-image1:some-tag", Build: false}, + "job-1": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName1, "job-1"), Build: true}, + "job-2": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName1, "job-2"), Build: true}, + "job-3": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName1, "job-3"), Build: true}, + "job-4": pipeline.DeployComponentImage{ImagePath: "some-image1:some-tag", Build: false}, + }, + envName2: pipeline.DeployComponentImages{ + "component-1": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName2, "component-1"), Build: true}, + "component-2": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName2, "component-2"), Build: true}, + "component-3": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName2, "component-3"), Build: true}, + "component-4": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName2, "component-4"), Build: true}, + "job-1": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName2, "job-1"), Build: true}, + "job-2": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName2, "job-2"), Build: true}, + "job-3": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName2, "job-3"), Build: true}, + "job-4": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName2, "job-4"), Build: true}, + }, + envName3: pipeline.DeployComponentImages{ + "component-1": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName3, "component-1"), Build: true}, + "component-2": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName3, "component-2"), Build: true}, + "component-3": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName3, "component-3"), Build: true}, + "component-4": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName3, "component-4"), Build: true}, + "job-1": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName3, "job-1"), Build: true}, + "job-2": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName3, "job-2"), Build: true}, + "job-3": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName3, "job-3"), Build: true}, + "job-4": pipeline.DeployComponentImage{ImagePath: imagePathFunc(envName3, "job-4"), Build: true}, + }, + envName4: pipeline.DeployComponentImages{ + "component-1": pipeline.DeployComponentImage{ImagePath: "some-image2:some-tag", Build: false}, + "component-2": pipeline.DeployComponentImage{ImagePath: "some-image3:some-tag", Build: false}, + "component-3": pipeline.DeployComponentImage{ImagePath: "some-image4:some-tag", Build: false}, + "component-4": pipeline.DeployComponentImage{ImagePath: "some-image5:some-tag", Build: false}, + "job-1": pipeline.DeployComponentImage{ImagePath: "some-image2:some-tag", Build: false}, + "job-2": pipeline.DeployComponentImage{ImagePath: "some-image3:some-tag", Build: false}, + "job-3": pipeline.DeployComponentImage{ImagePath: "some-image4:some-tag", Build: false}, + "job-4": pipeline.DeployComponentImage{ImagePath: "some-image5:some-tag", Build: false}, + }, + } + s.Equal(expectedDeployEnvironmentComponentImages, pipelineInfo.DeployEnvironmentComponentImages) +} + +func (s *applyConfigTestSuite) Test_BuildAndDeployComponentImages_ExpectedRuntime() { + appName, envName, buildBranch, jobPort := "anyapp", "dev", "anybranch", pointers.Ptr[int32](9999) + prepareConfigMapName := "preparecm" + + rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + ra := utils.NewRadixApplicationBuilder(). + WithAppName(appName). + WithBuildKit(pointers.Ptr(true)). + WithEnvironment(envName, buildBranch). + WithEnvironmentNoBranch("otherenv"). + WithComponents( + utils.NewApplicationComponentBuilder().WithName("comp1-build"), + utils.NewApplicationComponentBuilder().WithName("comp2-build").WithRuntime(&radixv1.Runtime{Architecture: ""}), + utils.NewApplicationComponentBuilder().WithName("comp3-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + utils.NewApplicationComponentBuilder().WithName("comp4-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + utils.NewApplicationComponentBuilder().WithName("comp5-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{})), + utils.NewApplicationComponentBuilder().WithName("comp6-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), + utils.NewApplicationComponentBuilder().WithName("comp7-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment("otherenv").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), + utils.NewApplicationComponentBuilder().WithName("comp1-deploy").WithImage("any"), + utils.NewApplicationComponentBuilder().WithName("comp2-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: ""}), + utils.NewApplicationComponentBuilder().WithName("comp3-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + utils.NewApplicationComponentBuilder().WithName("comp4-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + utils.NewApplicationComponentBuilder().WithName("comp5-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{})), + utils.NewApplicationComponentBuilder().WithName("comp6-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), + utils.NewApplicationComponentBuilder().WithName("comp7-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment("otherenv").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), + ). + WithJobComponents( + utils.NewApplicationJobComponentBuilder().WithName("job1-build").WithSchedulerPort(jobPort), + utils.NewApplicationJobComponentBuilder().WithName("job2-build").WithRuntime(&radixv1.Runtime{Architecture: ""}).WithSchedulerPort(jobPort), + utils.NewApplicationJobComponentBuilder().WithName("job3-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}).WithSchedulerPort(jobPort), + utils.NewApplicationJobComponentBuilder().WithName("job4-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort), + utils.NewApplicationJobComponentBuilder().WithName("job5-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{})), + utils.NewApplicationJobComponentBuilder().WithName("job6-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), + utils.NewApplicationJobComponentBuilder().WithName("job7-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment("otherenv").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), + utils.NewApplicationJobComponentBuilder().WithName("job1-deploy").WithImage("any").WithSchedulerPort(jobPort), + utils.NewApplicationJobComponentBuilder().WithName("job2-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: ""}).WithSchedulerPort(jobPort), + utils.NewApplicationJobComponentBuilder().WithName("job3-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}).WithSchedulerPort(jobPort), + utils.NewApplicationJobComponentBuilder().WithName("job4-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort), + utils.NewApplicationJobComponentBuilder().WithName("job5-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{})), + utils.NewApplicationJobComponentBuilder().WithName("job6-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), + utils.NewApplicationJobComponentBuilder().WithName("job7-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment("otherenv").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), + ). + BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) + pipelineInfo := model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + PipelineType: string(radixv1.BuildDeploy), + Branch: buildBranch, + ImageTag: "anytag", + ContainerRegistry: "anyregistry", + Clustertype: "anyclustertype", + Clustername: "anyclustername", + }, + RadixConfigMapName: prepareConfigMapName, + } + + applyStep := applyconfig.NewApplyConfigStep() + applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + s.Require().NoError(applyStep.Run(context.Background(), &pipelineInfo)) + imageNameFunc := func(comp string) string { + return fmt.Sprintf("%s-%s", envName, comp) + } + imagePathFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(comp), pipelineInfo.PipelineArguments.ImageTag) + } + imagePathClusterTypeFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(comp), pipelineInfo.PipelineArguments.Clustertype, pipelineInfo.PipelineArguments.ImageTag) + } + imagePathClusterNameFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(comp), pipelineInfo.PipelineArguments.Clustername, pipelineInfo.PipelineArguments.ImageTag) + } + buildComponentImageFunc := func(component string, runtime *radixv1.Runtime) pipeline.BuildComponentImage { + return pipeline.BuildComponentImage{ + ComponentName: component, + EnvName: envName, + ContainerName: fmt.Sprintf("build-%s-%s", component, envName), + Context: "/workspace/", + Dockerfile: "Dockerfile", + ImageName: imageNameFunc(component), + ImagePath: imagePathFunc(component), + ClusterTypeImagePath: imagePathClusterTypeFunc(component), + ClusterNameImagePath: imagePathClusterNameFunc(component), + Runtime: runtime, + } + } + expectedBuildComponentImages := pipeline.EnvironmentBuildComponentImages{ + "dev": []pipeline.BuildComponentImage{ + buildComponentImageFunc("comp1-build", nil), + buildComponentImageFunc("comp2-build", nil), + buildComponentImageFunc("comp3-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + buildComponentImageFunc("comp4-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + buildComponentImageFunc("comp5-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + buildComponentImageFunc("comp6-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + buildComponentImageFunc("comp7-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + buildComponentImageFunc("job1-build", nil), + buildComponentImageFunc("job2-build", nil), + buildComponentImageFunc("job3-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + buildComponentImageFunc("job4-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + buildComponentImageFunc("job5-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + buildComponentImageFunc("job6-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + buildComponentImageFunc("job7-build", &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + }, + } + s.ElementsMatch(maps.Keys(expectedBuildComponentImages), maps.Keys(pipelineInfo.BuildComponentImages)) + for env, images := range expectedBuildComponentImages { + s.ElementsMatch(images, pipelineInfo.BuildComponentImages[env]) + } + + expectedDeployEnvironmentComponentImages := pipeline.DeployEnvironmentComponentImages{ + "dev": pipeline.DeployComponentImages{ + "comp1-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp1-build"), ImageTagName: "", Runtime: nil, Build: true}, + "comp2-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp2-build"), ImageTagName: "", Runtime: nil, Build: true}, + "comp3-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp3-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}, Build: true}, + "comp4-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp4-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: true}, + "comp5-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp5-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: true}, + "comp6-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp6-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}, Build: true}, + "comp7-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp7-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: true}, + "comp1-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: nil, Build: false}, + "comp2-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: nil, Build: false}, + "comp3-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}, Build: false}, + "comp4-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: false}, + "comp5-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: false}, + "comp6-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}, Build: false}, + "comp7-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: false}, + "job1-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job1-build"), ImageTagName: "", Runtime: nil, Build: true}, + "job2-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job2-build"), ImageTagName: "", Runtime: nil, Build: true}, + "job3-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job3-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}, Build: true}, + "job4-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job4-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: true}, + "job5-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job5-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: true}, + "job6-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job6-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}, Build: true}, + "job7-build": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job7-build"), ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: true}, + "job1-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: nil, Build: false}, + "job2-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: nil, Build: false}, + "job3-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}, Build: false}, + "job4-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: false}, + "job5-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: false}, + "job6-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}, Build: false}, + "job7-deploy": pipeline.DeployComponentImage{ImagePath: "any", ImageTagName: "", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, Build: false}, + }, + } + s.Equal(expectedDeployEnvironmentComponentImages, pipelineInfo.DeployEnvironmentComponentImages) +} + +func (s *applyConfigTestSuite) Test_BuildAndDeployComponentImages_IgnoreDisabled() { + appName, envName, buildBranch, jobPort := "anyapp", "dev", "anybranch", pointers.Ptr[int32](9999) + prepareConfigMapName := "preparecm" + + rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + ra := utils.NewRadixApplicationBuilder(). + WithAppName(appName). + WithEnvironment(envName, buildBranch). + WithComponents( + utils.NewApplicationComponentBuilder().WithName("client-component-1").WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"), + utils.NewApplicationComponentBuilder().WithName("client-component-2").WithEnabled(true).WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"), + utils.NewApplicationComponentBuilder().WithName("client-component-3").WithEnabled(false).WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"), + utils.NewApplicationComponentBuilder().WithName("client-component-4").WithSourceFolder("./client2/").WithDockerfileName("client.Dockerfile"), + utils.NewApplicationComponentBuilder().WithName("client-component-5").WithEnabled(false).WithSourceFolder("./client2/").WithDockerfileName("client.Dockerfile"), + utils.NewApplicationComponentBuilder().WithName("client-component-6").WithEnabled(false).WithSourceFolder("./client3/").WithDockerfileName("client.Dockerfile"). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(true)), + utils.NewApplicationComponentBuilder().WithName("client-component-7").WithEnabled(true).WithSourceFolder("./client4/").WithDockerfileName("client.Dockerfile"). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(false)), + utils.NewApplicationComponentBuilder().WithName("client-component-8").WithImage("client-8-image"), + utils.NewApplicationComponentBuilder().WithName("client-component-9").WithImage("client-9-image"). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(false)), + utils.NewApplicationComponentBuilder().WithName("client-component-10").WithImage("client-10-image").WithEnabled(false). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(true)), + ). + WithJobComponents( + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-1").WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc/"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-2").WithEnabled(true).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc/"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-3").WithEnabled(false).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc/"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-4").WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc2/"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-5").WithEnabled(false).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc2/"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-6").WithEnabled(false).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc3/"). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(true)), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-7").WithEnabled(true).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc4/"). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(false)), + utils.NewApplicationJobComponentBuilder().WithName("calc-8").WithSchedulerPort(jobPort).WithImage("calc-8-image"), + utils.NewApplicationJobComponentBuilder().WithName("calc-9").WithSchedulerPort(jobPort).WithImage("calc-9-image"). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(false)), + utils.NewApplicationJobComponentBuilder().WithName("calc-10").WithSchedulerPort(jobPort).WithImage("calc-10-image").WithEnabled(false). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(true)), + ). + BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) + pipelineInfo := model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + PipelineType: string(radixv1.BuildDeploy), + Branch: buildBranch, + ImageTag: "imgtag", + ContainerRegistry: "registry", + Clustertype: "clustertype", + Clustername: "clustername", + }, + RadixConfigMapName: prepareConfigMapName, + } + + applyStep := applyconfig.NewApplyConfigStep() + applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + + s.Require().NoError(applyStep.Run(context.Background(), &pipelineInfo)) + + imageNameFunc := func(comp string) string { + return fmt.Sprintf("%s-%s", envName, comp) + } + imagePathFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(comp), pipelineInfo.PipelineArguments.ImageTag) + } + imagePathClusterTypeFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(comp), pipelineInfo.PipelineArguments.Clustertype, pipelineInfo.PipelineArguments.ImageTag) + } + imagePathClusterNameFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(comp), pipelineInfo.PipelineArguments.Clustername, pipelineInfo.PipelineArguments.ImageTag) + } + buildComponentImageFunc := func(component, context, dockerfile string) pipeline.BuildComponentImage { + return pipeline.BuildComponentImage{ + ComponentName: component, + EnvName: envName, + ContainerName: fmt.Sprintf("build-%s-%s", component, envName), + Context: context, + Dockerfile: dockerfile, + ImageName: imageNameFunc(component), + ImagePath: imagePathFunc(component), + ClusterTypeImagePath: imagePathClusterTypeFunc(component), + ClusterNameImagePath: imagePathClusterNameFunc(component), + } + } + expectedBuildComponentImages := pipeline.EnvironmentBuildComponentImages{ + envName: []pipeline.BuildComponentImage{ + buildComponentImageFunc("client-component-1", "/workspace/client/", "client.Dockerfile"), + buildComponentImageFunc("client-component-2", "/workspace/client/", "client.Dockerfile"), + buildComponentImageFunc("client-component-4", "/workspace/client2/", "client.Dockerfile"), + buildComponentImageFunc("client-component-6", "/workspace/client3/", "client.Dockerfile"), + buildComponentImageFunc("calc-1", "/workspace/calc/", "calc.Dockerfile"), + buildComponentImageFunc("calc-2", "/workspace/calc/", "calc.Dockerfile"), + buildComponentImageFunc("calc-4", "/workspace/calc2/", "calc.Dockerfile"), + buildComponentImageFunc("calc-6", "/workspace/calc3/", "calc.Dockerfile"), + }, + } + s.ElementsMatch(maps.Keys(expectedBuildComponentImages), maps.Keys(pipelineInfo.BuildComponentImages)) + for env, images := range pipelineInfo.BuildComponentImages { + s.ElementsMatch(expectedBuildComponentImages[env], images) + } + + expectedDeployEnvironmentComponentImages := pipeline.DeployEnvironmentComponentImages{ + envName: pipeline.DeployComponentImages{ + "client-component-1": pipeline.DeployComponentImage{ImagePath: imagePathFunc("client-component-1"), Build: true}, + "client-component-2": pipeline.DeployComponentImage{ImagePath: imagePathFunc("client-component-2"), Build: true}, + "client-component-4": pipeline.DeployComponentImage{ImagePath: imagePathFunc("client-component-4"), Build: true}, + "client-component-6": pipeline.DeployComponentImage{ImagePath: imagePathFunc("client-component-6"), Build: true}, + "client-component-8": pipeline.DeployComponentImage{ImagePath: "client-8-image", Build: false}, + "client-component-10": pipeline.DeployComponentImage{ImagePath: "client-10-image", Build: false}, + "calc-1": pipeline.DeployComponentImage{ImagePath: imagePathFunc("calc-1"), Build: true}, + "calc-2": pipeline.DeployComponentImage{ImagePath: imagePathFunc("calc-2"), Build: true}, + "calc-4": pipeline.DeployComponentImage{ImagePath: imagePathFunc("calc-4"), Build: true}, + "calc-6": pipeline.DeployComponentImage{ImagePath: imagePathFunc("calc-6"), Build: true}, + "calc-8": pipeline.DeployComponentImage{ImagePath: "calc-8-image", Build: false}, + "calc-10": pipeline.DeployComponentImage{ImagePath: "calc-10-image", Build: false}, + }, + } + s.Equal(expectedDeployEnvironmentComponentImages, pipelineInfo.DeployEnvironmentComponentImages) +} + +func (s *applyConfigTestSuite) Test_BuildAndDeployComponentImages_BuildChangedComponents() { + appName, envName, buildBranch, jobPort := "anyapp", "dev", "anybranch", pointers.Ptr[int32](9999) + prepareConfigMapName := "preparecm" + + rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + ra := utils.NewRadixApplicationBuilder(). + WithAppName(appName). + WithEnvironment(envName, buildBranch). + WithComponents( + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-changed").WithDockerfileName("comp-changed.Dockerfile"), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-new").WithDockerfileName("comp-new.Dockerfile"), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-unchanged").WithDockerfileName("comp-unchanged.Dockerfile"), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-common1-changed").WithDockerfileName("common1.Dockerfile"), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-common2-unchanged").WithDockerfileName("common2.Dockerfile"), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-common3-changed").WithDockerfileName("common3.Dockerfile"), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-deployonly").WithImage("comp-deployonly:anytag"), + ). + WithJobComponents( + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-changed").WithDockerfileName("job-changed.Dockerfile"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-new").WithDockerfileName("job-new.Dockerfile"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-unchanged").WithDockerfileName("job-unchanged.Dockerfile"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-common1-unchanged").WithDockerfileName("common1.Dockerfile"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-common2-changed").WithDockerfileName("common2.Dockerfile"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-common3-changed").WithDockerfileName("common3.Dockerfile"), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-deployonly").WithImage("job-deployonly:anytag"), + ). + BuildRA() + currentRd := utils.NewDeploymentBuilder(). + WithDeploymentName("currentrd"). + WithAppName(appName). + WithEnvironment(envName). + WithAnnotations(map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(ra), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(nil)}). + WithCondition(radixv1.DeploymentActive). + WithComponents( + utils.NewDeployComponentBuilder().WithName("comp-changed").WithImage("dev-comp-changed-current:anytag"), + utils.NewDeployComponentBuilder().WithName("comp-unchanged").WithImage("dev-comp-unchanged-current:anytag"), + utils.NewDeployComponentBuilder().WithName("comp-common1-changed").WithImage("dev-comp-common1-changed:anytag"), + utils.NewDeployComponentBuilder().WithName("comp-common2-unchanged").WithImage("dev-comp-common2-unchanged:anytag"), + ). + WithJobComponents( + utils.NewDeployJobComponentBuilder().WithName("job-changed").WithImage("dev-job-changed-current:anytag"), + utils.NewDeployJobComponentBuilder().WithName("job-unchanged").WithImage("dev-job-unchanged-current:anytag"), + utils.NewDeployJobComponentBuilder().WithName("job-common1-unchanged").WithImage("dev-job-common1-unchanged:anytag"), + utils.NewDeployJobComponentBuilder().WithName("job-common2-changed").WithImage("dev-job-common2-changed:anytag"), + ). + BuildRD() + _, _ = s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).Create(context.Background(), currentRd, metav1.CreateOptions{}) + _, _ = s.kubeClient.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: utils.GetEnvironmentNamespace(appName, envName)}}, metav1.CreateOptions{}) + buildCtx := &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + {Environment: envName, Components: []string{"comp-changed", "comp-common1-changed", "comp-common3-changed", "job-changed", "job-common2-changed", "job-common3-changed"}}, + }, + } + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, buildCtx)) + pipelineInfo := model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + PipelineType: "build-deploy", + Branch: buildBranch, + ImageTag: "imgtag", + Clustertype: "clustertype", + Clustername: "clustername", + ContainerRegistry: "registry", + }, + RadixConfigMapName: prepareConfigMapName, + } + + applyStep := applyconfig.NewApplyConfigStep() + applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + + // Run apply config step + s.Require().NoError(applyStep.Run(context.Background(), &pipelineInfo)) + + imageNameFunc := func(comp string) string { + return fmt.Sprintf("%s-%s", envName, comp) + } + imagePathFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(comp), pipelineInfo.PipelineArguments.ImageTag) + } + imagePathClusterTypeFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(comp), pipelineInfo.PipelineArguments.Clustertype, pipelineInfo.PipelineArguments.ImageTag) + } + imagePathClusterNameFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", pipelineInfo.PipelineArguments.ContainerRegistry, appName, imageNameFunc(comp), pipelineInfo.PipelineArguments.Clustername, pipelineInfo.PipelineArguments.ImageTag) + } + buildComponentImageFunc := func(component, dockerfile string) pipeline.BuildComponentImage { + return pipeline.BuildComponentImage{ + ComponentName: component, + EnvName: envName, + ContainerName: fmt.Sprintf("build-%s-%s", component, envName), + Context: "/workspace/", + Dockerfile: dockerfile, + ImageName: imageNameFunc(component), + ImagePath: imagePathFunc(component), + ClusterTypeImagePath: imagePathClusterTypeFunc(component), + ClusterNameImagePath: imagePathClusterNameFunc(component), + } + } + expectedBuildComponentImages := pipeline.EnvironmentBuildComponentImages{ + envName: []pipeline.BuildComponentImage{ + buildComponentImageFunc("comp-changed", "comp-changed.Dockerfile"), + buildComponentImageFunc("comp-new", "comp-new.Dockerfile"), + buildComponentImageFunc("comp-common1-changed", "common1.Dockerfile"), + buildComponentImageFunc("comp-common3-changed", "common3.Dockerfile"), + buildComponentImageFunc("job-changed", "job-changed.Dockerfile"), + buildComponentImageFunc("job-new", "job-new.Dockerfile"), + buildComponentImageFunc("job-common2-changed", "common2.Dockerfile"), + buildComponentImageFunc("job-common3-changed", "common3.Dockerfile"), + }, + } + s.ElementsMatch(maps.Keys(expectedBuildComponentImages), maps.Keys(pipelineInfo.BuildComponentImages)) + for env, images := range pipelineInfo.BuildComponentImages { + s.ElementsMatch(expectedBuildComponentImages[env], images) + } + + expectedDeployEnvironmentComponentImages := pipeline.DeployEnvironmentComponentImages{ + envName: pipeline.DeployComponentImages{ + "comp-changed": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp-changed"), Build: true}, + "comp-new": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp-new"), Build: true}, + "comp-unchanged": pipeline.DeployComponentImage{ImagePath: "dev-comp-unchanged-current:anytag", Build: false}, + "comp-common1-changed": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp-common1-changed"), Build: true}, + "comp-common2-unchanged": pipeline.DeployComponentImage{ImagePath: "dev-comp-common2-unchanged:anytag", Build: false}, + "comp-common3-changed": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp-common3-changed"), Build: true}, + "comp-deployonly": pipeline.DeployComponentImage{ImagePath: "comp-deployonly:anytag", Build: false}, + "job-changed": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job-changed"), Build: true}, + "job-new": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job-new"), Build: true}, + "job-unchanged": pipeline.DeployComponentImage{ImagePath: "dev-job-unchanged-current:anytag", Build: false}, + "job-common1-unchanged": pipeline.DeployComponentImage{ImagePath: "dev-job-common1-unchanged:anytag", Build: false}, + "job-common2-changed": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job-common2-changed"), Build: true}, + "job-common3-changed": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job-common3-changed"), Build: true}, + "job-deployonly": pipeline.DeployComponentImage{ImagePath: "job-deployonly:anytag", Build: false}, + }, + } + s.Equal(expectedDeployEnvironmentComponentImages, pipelineInfo.DeployEnvironmentComponentImages) +} + +func (s *applyConfigTestSuite) Test_BuildAndDeployComponentImages_DetectComponentsToBuild() { + appName, envName, buildBranch, jobPort, buildSecretName := "anyapp", "dev", "anybranch", pointers.Ptr[int32](9999), "SECRET1" + prepareConfigMapName := "preparecm" + rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() + raBuilder := utils.NewRadixApplicationBuilder(). + WithAppName(appName). + WithEnvironment(envName, buildBranch). + WithComponents( + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp"), + ). + WithJobComponents( + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job"), + ) + defaultRa := raBuilder.WithBuildSecrets(buildSecretName).BuildRA() + raWithoutSecret := raBuilder.WithBuildSecrets().BuildRA() + oldRa := defaultRa.DeepCopy() + oldRa.Spec.Components[0].Variables = radixv1.EnvVarsMap{"anyvar": "anyvalue"} + currentBuildSecret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: defaults.BuildSecretsName}, Data: map[string][]byte{buildSecretName: []byte("anydata")}} + oldBuildSecret := currentBuildSecret.DeepCopy() + oldBuildSecret.Data[buildSecretName] = []byte("newdata") + radixDeploymentFactory := func(annotations map[string]string, condition radixv1.RadixDeployCondition, componentBuilders []utils.DeployComponentBuilder, jobBuilders []utils.DeployJobComponentBuilder) *radixv1.RadixDeployment { + builder := utils.NewDeploymentBuilder(). + WithDeploymentName("currentrd"). + WithAppName(appName). + WithEnvironment(envName). + WithAnnotations(annotations). + WithCondition(condition). + WithActiveFrom(time.Now().Add(-1 * time.Hour)). + WithComponents(componentBuilders...). + WithJobComponents(jobBuilders...) + + if condition == radixv1.DeploymentInactive { + builder = builder.WithActiveTo(time.Now().Add(1 * time.Hour)) + } + + return builder.BuildRD() + } + piplineArgs := model.PipelineArguments{ + PipelineType: string(radixv1.BuildDeploy), + Branch: buildBranch, + ImageTag: "imgtag", + ContainerRegistry: "registry", + Clustertype: "clustertype", + Clustername: "clustername", + } + type testSpec struct { + name string + existingRd *radixv1.RadixDeployment + customRa *radixv1.RadixApplication + prepareBuildCtx *model.PrepareBuildContext + expectedBuildComponentNames []string + expectedDeployComponentImages pipeline.DeployComponentImages + } + imageNameFunc := func(comp string) string { + return fmt.Sprintf("%s-%s", envName, comp) + } + imagePathFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s", piplineArgs.ContainerRegistry, appName, imageNameFunc(comp), piplineArgs.ImageTag) + } + imagePathClusterTypeFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", piplineArgs.ContainerRegistry, appName, imageNameFunc(comp), piplineArgs.Clustertype, piplineArgs.ImageTag) + } + imagePathClusterNameFunc := func(comp string) string { + return fmt.Sprintf("%s/%s-%s:%s-%s", piplineArgs.ContainerRegistry, appName, imageNameFunc(comp), piplineArgs.Clustername, piplineArgs.ImageTag) + } + buildComponentImageFunc := func(component string) pipeline.BuildComponentImage { + return pipeline.BuildComponentImage{ + ComponentName: component, + EnvName: envName, + ContainerName: fmt.Sprintf("build-%s-%s", component, envName), + Context: "/workspace/", + Dockerfile: "Dockerfile", + ImageName: imageNameFunc(component), + ImagePath: imagePathFunc(component), + ClusterTypeImagePath: imagePathClusterTypeFunc(component), + ClusterNameImagePath: imagePathClusterNameFunc(component), + } + } + tests := []testSpec{ + { + name: "radixconfig hash unchanged, buildsecret hash unchanged, component changed, job changed - build all", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-changed-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{"comp", "job"}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret hash unchanged, component changed - build component", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{"comp"}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: "job-current:anytag", Build: false}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret hash unchanged, job changed - build job", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{"job"}, + }, + }, + }, + expectedBuildComponentNames: []string{"job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: "comp-current:anytag", Build: false}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret hash unchanged, component unchanged, job unchanged - no build job", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: "comp-current:anytag", Build: false}, + "job": pipeline.DeployComponentImage{ImagePath: "job-current:anytag", Build: false}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret hash unchanged, component unchanged, job unchanged, existing RD missing - build all", + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret hash unchanged, missing prepare context for environment - build all", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: "otherenv", + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret hash unchanged, component unchanged, job unchanged - no build job", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: "comp-current:anytag", Build: false}, + "job": pipeline.DeployComponentImage{ImagePath: "job-current:anytag", Build: false}, + }, + }, + { + name: "radixconfig hash changed, buildsecret hash unchanged, component unchanged, job unchanged - build all", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(oldRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "radixconfig hash missing, buildsecret hash unchanged, component unchanged, job unchanged - build all", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret hash changed, component unchanged, job unchanged - build all", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(oldBuildSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret magic hash, component unchanged, job unchanged - build all", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(nil)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret magic hash, no build secret, component unchanged, job unchanged - no build job", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(raWithoutSecret), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(nil)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + customRa: raWithoutSecret, + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: "comp-current:anytag", Build: false}, + "job": pipeline.DeployComponentImage{ImagePath: "job-current:anytag", Build: false}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret missing, no build secret, component unchanged, job unchanged - build all", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(raWithoutSecret)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + customRa: raWithoutSecret, + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "radixconfig hash unchanged, buildsecret hash missing, component unchanged, job unchanged - build all", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa)}, + radixv1.DeploymentActive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "missing current RD, component unchanged, job unchanged - build all", + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + { + name: "no current RD, component unchanged, job unchanged - build all", + existingRd: radixDeploymentFactory( + map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, + radixv1.DeploymentInactive, + []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, + []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, + ), + prepareBuildCtx: &model.PrepareBuildContext{ + EnvironmentsToBuild: []model.EnvironmentToBuild{ + { + Environment: envName, + Components: []string{}, + }, + }, + }, + expectedBuildComponentNames: []string{"comp", "job"}, + expectedDeployComponentImages: pipeline.DeployComponentImages{ + "comp": pipeline.DeployComponentImage{ImagePath: imagePathFunc("comp"), Build: true}, + "job": pipeline.DeployComponentImage{ImagePath: imagePathFunc("job"), Build: true}, + }, + }, + } + + for _, test := range tests { + + s.Run(test.name, func() { + ra := defaultRa + if test.customRa != nil { + ra = test.customRa } - if ts.jobComponentBuilder != nil { - raBuilder = raBuilder.WithJobComponent(*ts.jobComponentBuilder) + _, _ = s.kubeClient.CoreV1().Secrets(utils.GetAppNamespace(appName)).Create(context.Background(), currentBuildSecret, metav1.CreateOptions{}) + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + if test.existingRd != nil { + _, _ = s.kubeClient.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: utils.GetEnvironmentNamespace(appName, envName)}}, metav1.CreateOptions{}) + _, _ = s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).Create(context.Background(), test.existingRd, metav1.CreateOptions{}) } - ra := raBuilder.BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{}, + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, test.prepareBuildCtx)) + pipelineInfo := model.PipelineInfo{ + PipelineArguments: piplineArgs, RadixConfigMapName: prepareConfigMapName, } - applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - err := applyStep.Run(context.Background(), &pipeline) - s.NoError(err) + + // Run applyconfig step + s.Require().NoError(applyStep.Run(context.Background(), &pipelineInfo)) + + expectedBuildComponentImages := pipeline.EnvironmentBuildComponentImages{} + if len(test.expectedBuildComponentNames) > 0 { + expectedBuildComponentImages[envName] = slice.Map(test.expectedBuildComponentNames, buildComponentImageFunc) + } + s.ElementsMatch(maps.Keys(expectedBuildComponentImages), maps.Keys(pipelineInfo.BuildComponentImages)) + for env, images := range pipelineInfo.BuildComponentImages { + s.ElementsMatch(expectedBuildComponentImages[env], images) + } + + s.Equal(pipeline.DeployEnvironmentComponentImages{envName: test.expectedDeployComponentImages}, pipelineInfo.DeployEnvironmentComponentImages) }) } } @@ -162,8 +1455,7 @@ func (s *applyConfigTestSuite) Test_Deploy_ComponentImageTagName() { {name: "imageTagName is in a component and in an environment", componentTagName: "some-component-tag", hasEnvironmentConfig: true, environmentTagName: "some-env-tag"}, } for _, ts := range scenarios { - s.SetupTest() - s.T().Run(ts.name, func(t *testing.T) { + s.Run(ts.name, func() { rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) @@ -426,6 +1718,58 @@ func (s *applyConfigTestSuite) Test_Deploy_ComponentsToDeployValidation() { } } +func (s *applyConfigTestSuite) Test_DeployComponentImages_ImageTagNames() { + appName, envName, rjName, buildBranch, jobPort := "anyapp", "dev", "anyrj", "anybranch", pointers.Ptr[int32](9999) + prepareConfigMapName := "preparecm" + + rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() + _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() + _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) + ra := utils.NewRadixApplicationBuilder(). + WithAppName(appName). + WithEnvironment(envName, buildBranch). + WithComponents( + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp1").WithImage("comp1img:{imageTagName}"). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithImageTagName("comp1envtag")), + utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp2").WithImage("comp2img:{imageTagName}"). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithImageTagName("comp2envtag")), + ). + WithJobComponents( + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job1").WithImage("job1img:{imageTagName}"). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithImageTagName("job1envtag")), + utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job2").WithImage("job2img:{imageTagName}"). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithImageTagName("job2envtag")), + ). + BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) + pipelineInfo := model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + PipelineType: "deploy", + ToEnvironment: envName, + JobName: rjName, + ImageTagNames: map[string]string{"comp1": "comp1customtag", "job1": "job1customtag"}, + }, + RadixConfigMapName: prepareConfigMapName, + } + + applyStep := applyconfig.NewApplyConfigStep() + applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + + s.Require().NoError(applyStep.Run(context.Background(), &pipelineInfo)) + + expectedDeployComponentImages := pipeline.DeployEnvironmentComponentImages{ + envName: pipeline.DeployComponentImages{ + "comp1": pipeline.DeployComponentImage{ImagePath: "comp1img:{imageTagName}", ImageTagName: "comp1customtag"}, + "comp2": pipeline.DeployComponentImage{ImagePath: "comp2img:{imageTagName}"}, + "job1": pipeline.DeployComponentImage{ImagePath: "job1img:{imageTagName}", ImageTagName: "job1customtag"}, + "job2": pipeline.DeployComponentImage{ImagePath: "job2img:{imageTagName}"}, + }, + } + + s.Equal(expectedDeployComponentImages, pipelineInfo.DeployEnvironmentComponentImages) +} + func (s *applyConfigTestSuite) Test_BuildDeploy_RuntimeValidation() { appName, branchName, schedulerPort := "anyapp", "anybranch", int32(9999) prepareConfigMapName := "preparecm" @@ -486,7 +1830,6 @@ func (s *applyConfigTestSuite) Test_BuildDeploy_RuntimeValidation() { for name, test := range tests { s.Run(name, func() { - s.SetupTest() rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) ra := utils.NewRadixApplicationBuilder(). @@ -516,5 +1859,4 @@ func (s *applyConfigTestSuite) Test_BuildDeploy_RuntimeValidation() { } }) } - } diff --git a/pipeline-runner/steps/build/build_acr.go b/pipeline-runner/steps/build/build_acr.go deleted file mode 100644 index 674b7cf1e..000000000 --- a/pipeline-runner/steps/build/build_acr.go +++ /dev/null @@ -1,622 +0,0 @@ -package build - -import ( - "context" - "encoding/json" - "fmt" - "path" - "strings" - "time" - - "github.com/equinor/radix-common/utils/pointers" - "github.com/equinor/radix-operator/pipeline-runner/internal/commandbuilder" - internalgit "github.com/equinor/radix-operator/pipeline-runner/internal/git" - "github.com/equinor/radix-operator/pipeline-runner/model" - "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/securitycontext" - "github.com/equinor/radix-operator/pkg/apis/utils" - radixannotations "github.com/equinor/radix-operator/pkg/apis/utils/annotations" - "github.com/equinor/radix-operator/pkg/apis/utils/git" - radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" - "github.com/rs/zerolog/log" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - RadixImageBuilderHomeVolumeName = "radix-image-builder-home" - BuildKitRunVolumeName = "build-kit-run" - BuildKitRootVolumeName = "build-kit-root" - - buildSecretsMountPath = "/build-secrets" - privateImageHubMountPath = "/radix-private-image-hubs" - buildahRegistryAuthFile = "/home/build/auth.json" - azureServicePrincipleContext = "/radix-image-builder/.azure" -) - -func (step *BuildStepImplementation) buildContainerImageBuildingJobs(ctx context.Context, pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar) ([]*batchv1.Job, error) { - rr := step.GetRegistration() - if pipelineInfo.IsUsingBuildKit() { - return step.buildContainerImageBuildingJobsForBuildKit(ctx, rr, pipelineInfo, buildSecrets) - } - return step.buildContainerImageBuildingJobsForACRTasks(ctx, rr, pipelineInfo, buildSecrets) -} - -func (step *BuildStepImplementation) buildContainerImageBuildingJobsForACRTasks(ctx context.Context, rr *radixv1.RadixRegistration, pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar) ([]*batchv1.Job, error) { - var buildComponentImages []pipeline.BuildComponentImage - for _, envComponentImages := range pipelineInfo.BuildComponentImages { - buildComponentImages = append(buildComponentImages, envComponentImages...) - } - - log.Ctx(ctx).Debug().Msg("build a build-job") - hash := strings.ToLower(utils.RandStringStrSeed(5, pipelineInfo.PipelineArguments.JobName)) - job, err := buildContainerImageBuildingJob(ctx, rr, pipelineInfo, buildSecrets, hash, &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}, buildComponentImages...) - if err != nil { - return nil, err - } - return []*batchv1.Job{job}, nil -} - -func (step *BuildStepImplementation) buildContainerImageBuildingJobsForBuildKit(ctx context.Context, rr *radixv1.RadixRegistration, pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar) ([]*batchv1.Job, error) { - var jobs []*batchv1.Job - for envName, buildComponentImages := range pipelineInfo.BuildComponentImages { - log.Ctx(ctx).Debug().Msgf("build a build-kit jobs for the env %s", envName) - for _, componentImage := range buildComponentImages { - log.Ctx(ctx).Debug().Msgf("build a job for the image %s", componentImage.ImageName) - hash := strings.ToLower(utils.RandStringStrSeed(5, fmt.Sprintf("%s-%s-%s", pipelineInfo.PipelineArguments.JobName, envName, componentImage.ComponentName))) - - job, err := buildContainerImageBuildingJob(ctx, rr, pipelineInfo, buildSecrets, hash, componentImage.Runtime, componentImage) - if err != nil { - return nil, err - } - - job.ObjectMeta.Labels[kube.RadixEnvLabel] = envName - job.ObjectMeta.Labels[kube.RadixComponentLabel] = componentImage.ComponentName - jobs = append(jobs, job) - } - } - return jobs, nil -} - -func buildContainerImageBuildingJob(ctx context.Context, rr *radixv1.RadixRegistration, pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar, hash string, jobRuntime *radixv1.Runtime, buildComponentImages ...pipeline.BuildComponentImage) (*batchv1.Job, error) { - appName := rr.Name - branch := pipelineInfo.PipelineArguments.Branch - imageTag := pipelineInfo.PipelineArguments.ImageTag - pipelineJobName := pipelineInfo.PipelineArguments.JobName - initContainers, err := git.CloneInitContainers(rr.Spec.CloneURL, branch, internalgit.CloneConfigFromPipelineArgs(pipelineInfo.PipelineArguments)) - if err != nil { - return nil, err - } - buildContainers := createContainerImageBuildingContainers(appName, pipelineInfo, buildComponentImages, buildSecrets) - timestamp := time.Now().Format("20060102150405") - defaultMode, backOffLimit := int32(256), int32(0) - componentImagesAnnotation, _ := json.Marshal(buildComponentImages) - annotations := radixannotations.ForClusterAutoscalerSafeToEvict(false) - buildPodSecurityContext := getAcrTaskBuildPodSecurityContext() - - if pipelineInfo.IsUsingBuildKit() { - for _, buildContainer := range buildContainers { - annotations[fmt.Sprintf("container.apparmor.security.beta.kubernetes.io/%s", buildContainer.Name)] = "unconfined" - } - buildPodSecurityContext = getBuildKitPodSecurityContext() - } - - buildJobName := fmt.Sprintf("radix-builder-%s-%s-%s", timestamp, imageTag, hash) - log.Ctx(ctx).Debug().Msgf("build a job %s", buildJobName) - job := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: buildJobName, - Labels: map[string]string{ - kube.RadixJobNameLabel: pipelineJobName, - kube.RadixBuildLabel: fmt.Sprintf("%s-%s-%s", appName, imageTag, hash), - kube.RadixAppLabel: appName, - kube.RadixImageTagLabel: imageTag, - kube.RadixJobTypeLabel: kube.RadixJobTypeBuild, - }, - Annotations: map[string]string{ - kube.RadixBranchAnnotation: branch, - kube.RadixBuildComponentsAnnotation: string(componentImagesAnnotation), - }, - }, - Spec: batchv1.JobSpec{ - BackoffLimit: &backOffLimit, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: radixlabels.ForPipelineJobName(pipelineJobName), - Annotations: annotations, - }, - Spec: corev1.PodSpec{ - RestartPolicy: "Never", - InitContainers: initContainers, - Containers: buildContainers, - SecurityContext: buildPodSecurityContext, - Volumes: getContainerImageBuildingJobVolumes(&defaultMode, buildSecrets, pipelineInfo.IsUsingBuildKit(), buildContainers), - Affinity: utils.GetAffinityForPipelineJob(jobRuntime), - Tolerations: utils.GetPipelineJobPodSpecTolerations(), - }, - }, - }, - } - return job, nil -} - -func getContainerImageBuildingJobVolumes(defaultMode *int32, buildSecrets []corev1.EnvVar, isUsingBuildKit bool, containers []corev1.Container) []corev1.Volume { - volumes := []corev1.Volume{ - { - Name: git.BuildContextVolumeName, - }, - { - Name: git.GitSSHKeyVolumeName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: git.GitSSHKeyVolumeName, - DefaultMode: defaultMode, - }, - }, - }, - { - Name: defaults.AzureACRServicePrincipleSecretName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: defaults.AzureACRServicePrincipleSecretName, - }, - }, - }, - { - Name: defaults.PrivateImageHubSecretName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: defaults.PrivateImageHubSecretName, - }, - }, - }, - { - Name: RadixImageBuilderHomeVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - SizeLimit: resource.NewScaledQuantity(5, resource.Mega), - }, - }, - }, - } - - for _, container := range containers { - volumes = append(volumes, []corev1.Volume{ - { - Name: getTmpVolumeNameForContainer(container.Name), - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - SizeLimit: resource.NewScaledQuantity(100, resource.Giga), - }, - }, - }, - { - Name: getVarVolumeNameForContainer(container.Name), - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - SizeLimit: resource.NewScaledQuantity(100, resource.Giga), - }, - }, - }, - }...) - } - - if len(buildSecrets) > 0 { - volumes = append(volumes, - corev1.Volume{ - Name: defaults.BuildSecretsName, - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: defaults.BuildSecretsName, - }, - }, - }) - } - - if isUsingBuildKit { - volumes = append(volumes, - []corev1.Volume{ - { - Name: BuildKitRunVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - SizeLimit: resource.NewScaledQuantity(100, resource.Giga), // buildah puts container overlays there, which can be as large as several gigabytes - }, - }, - }, - { - Name: BuildKitRootVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{ - SizeLimit: resource.NewScaledQuantity(100, resource.Giga), // buildah puts container overlays there, which can be as large as several gigabytes - }, - }, - }, - }...) - } - - return volumes -} - -func createContainerImageBuildingContainers(appName string, pipelineInfo *model.PipelineInfo, buildComponentImages []pipeline.BuildComponentImage, buildSecrets []corev1.EnvVar) []corev1.Container { - var containers []corev1.Container - imageTag := pipelineInfo.PipelineArguments.ImageTag - clusterType := pipelineInfo.PipelineArguments.Clustertype - clusterName := pipelineInfo.PipelineArguments.Clustername - containerRegistry := pipelineInfo.PipelineArguments.ContainerRegistry - - imageBuilder := fmt.Sprintf("%s/%s", containerRegistry, pipelineInfo.PipelineArguments.ImageBuilder) - buildContainerSecContext := getAcrTaskBuildContainerSecurityContext() - var secretMountsArgsString string - if pipelineInfo.IsUsingBuildKit() { - imageBuilder = pipelineInfo.PipelineArguments.BuildKitImageBuilder - buildContainerSecContext = getBuildKitContainerSecurityContext() - secretMountsArgsString = getSecretArgs(buildSecrets) - } - - for _, componentImage := range buildComponentImages { - // For extra meta information about an image - clusterTypeImage := utils.GetImagePath(containerRegistry, appName, componentImage.ImageName, fmt.Sprintf("%s-%s", clusterType, imageTag)) - clusterNameImage := utils.GetImagePath(containerRegistry, appName, componentImage.ImageName, fmt.Sprintf("%s-%s", clusterName, imageTag)) - envVars := getContainerEnvVars(appName, pipelineInfo, componentImage, buildSecrets, clusterTypeImage, clusterNameImage) - command := getContainerCommand(pipelineInfo, containerRegistry, secretMountsArgsString, componentImage, clusterTypeImage, clusterNameImage) - resources := getContainerResources(pipelineInfo) - - container := corev1.Container{ - Name: componentImage.ContainerName, - Image: imageBuilder, - Command: command, - ImagePullPolicy: corev1.PullAlways, - Env: envVars, - VolumeMounts: getContainerImageBuildingJobVolumeMounts(buildSecrets, pipelineInfo.IsUsingBuildKit(), componentImage.ContainerName), - SecurityContext: buildContainerSecContext, - Resources: resources, - } - containers = append(containers, container) - } - return containers -} - -func getContainerEnvVars(appName string, pipelineInfo *model.PipelineInfo, componentImage pipeline.BuildComponentImage, buildSecrets []corev1.EnvVar, clusterTypeImage string, clusterNameImage string) []corev1.EnvVar { - envVars := getStandardEnvVars(appName, pipelineInfo, componentImage, clusterTypeImage, clusterNameImage) - if pipelineInfo.IsUsingBuildKit() { - envVars = append(envVars, getBuildKitEnvVars()...) - } - envVars = append(envVars, buildSecrets...) - return envVars -} - -func getContainerResources(pipelineInfo *model.PipelineInfo) corev1.ResourceRequirements { - var resources corev1.ResourceRequirements - if pipelineInfo.IsUsingBuildKit() { - resources = corev1.ResourceRequirements{ - Requests: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceCPU: resource.MustParse(pipelineInfo.PipelineArguments.Builder.ResourcesRequestsCPU), - corev1.ResourceMemory: resource.MustParse(pipelineInfo.PipelineArguments.Builder.ResourcesRequestsMemory), - }, - Limits: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceMemory: resource.MustParse(pipelineInfo.PipelineArguments.Builder.ResourcesLimitsMemory), - }, - } - } - return resources -} - -func getContainerCommand(pipelineInfo *model.PipelineInfo, containerRegistry string, secretMountsArgsString string, componentImage pipeline.BuildComponentImage, clusterTypeImage, clusterNameImage string) []string { - if !pipelineInfo.IsUsingBuildKit() { - return nil - } - cacheImagePath := utils.GetImageCachePath(pipelineInfo.PipelineArguments.AppContainerRegistry, pipelineInfo.RadixApplication.Name) - useBuildCache := pipelineInfo.RadixApplication.Spec.Build.UseBuildCache == nil || *pipelineInfo.RadixApplication.Spec.Build.UseBuildCache - if pipelineInfo.PipelineArguments.OverrideUseBuildCache != nil { - useBuildCache = *pipelineInfo.PipelineArguments.OverrideUseBuildCache - } - cacheContainerRegistry := pipelineInfo.PipelineArguments.AppContainerRegistry // Store application cache in the App Registry - return getBuildahContainerCommand(containerRegistry, secretMountsArgsString, componentImage, clusterTypeImage, clusterNameImage, cacheContainerRegistry, cacheImagePath, useBuildCache, pipelineInfo.PipelineArguments.PushImage) -} - -func getBuildKitEnvVars() []corev1.EnvVar { - return []corev1.EnvVar{ - { - Name: "BUILDAH_USERNAME", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}, - Key: "username", - }, - }, - }, - { - Name: "BUILDAH_PASSWORD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}, - Key: "password", - }, - }, - }, - { - Name: "BUILDAH_CACHE_USERNAME", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}, - Key: "username", - }, - }, - }, - { - Name: "BUILDAH_CACHE_PASSWORD", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}, - Key: "password", - }, - }, - }, - { - Name: "REGISTRY_AUTH_FILE", - Value: buildahRegistryAuthFile, - }, - } -} - -func getStandardEnvVars(appName string, pipelineInfo *model.PipelineInfo, componentImage pipeline.BuildComponentImage, clusterTypeImage string, clusterNameImage string) []corev1.EnvVar { - var push string - if pipelineInfo.PipelineArguments.PushImage { - push = "--push" - } - var useCache string - if !pipelineInfo.PipelineArguments.UseCache { - useCache = "--no-cache" - } - containerImageRepositoryName := utils.GetRepositoryName(appName, componentImage.ImageName) - subscriptionId := pipelineInfo.PipelineArguments.SubscriptionId - branch := pipelineInfo.PipelineArguments.Branch - targetEnvs := componentImage.EnvName - if len(targetEnvs) == 0 { - targetEnvs = strings.Join(pipelineInfo.TargetEnvironments, ",") - } - containerRegistry := pipelineInfo.PipelineArguments.ContainerRegistry - firstPartContainerRegistry := strings.Split(containerRegistry, ".")[0] - envVars := []corev1.EnvVar{ - { - Name: "DOCKER_FILE_NAME", - Value: componentImage.Dockerfile, - }, - { - Name: "DOCKER_REGISTRY", - Value: firstPartContainerRegistry, - }, - { - Name: "IMAGE", - Value: componentImage.ImagePath, - }, - { - Name: "CONTEXT", - Value: componentImage.Context, - }, - { - Name: "PUSH", - Value: push, - }, - { - Name: "AZURE_CREDENTIALS", - Value: path.Join(azureServicePrincipleContext, "sp_credentials.json"), - }, - { - Name: "SUBSCRIPTION_ID", - Value: subscriptionId, - }, - { - Name: "CLUSTERTYPE_IMAGE", - Value: clusterTypeImage, - }, - { - Name: "CLUSTERNAME_IMAGE", - Value: clusterNameImage, - }, - { - Name: "REPOSITORY_NAME", - Value: containerImageRepositoryName, - }, - { - Name: "CACHE", - Value: useCache, - }, - { - Name: defaults.RadixZoneEnvironmentVariable, - Value: pipelineInfo.PipelineArguments.RadixZone, - }, - // Extra meta information - { - Name: defaults.RadixBranchEnvironmentVariable, - Value: branch, - }, - { - Name: defaults.RadixPipelineTargetEnvironmentsVariable, - Value: targetEnvs, - }, - { - Name: defaults.RadixCommitHashEnvironmentVariable, - Value: pipelineInfo.GitCommitHash, - }, - { - Name: defaults.RadixGitTagsEnvironmentVariable, - Value: pipelineInfo.GitTags, - }, - } - return envVars -} - -func getContainerImageBuildingJobVolumeMounts(buildSecrets []corev1.EnvVar, isUsingBuildKit bool, containerName string) []corev1.VolumeMount { - volumeMounts := []corev1.VolumeMount{ - { - Name: git.BuildContextVolumeName, - MountPath: git.Workspace, - }, - { - Name: defaults.AzureACRServicePrincipleSecretName, - MountPath: azureServicePrincipleContext, - ReadOnly: true, - }, - } - - if isUsingBuildKit { - volumeMounts = append(volumeMounts, []corev1.VolumeMount{ - { - Name: BuildKitRunVolumeName, // buildah creates folder container overlays and secrets there - MountPath: "/run", - ReadOnly: false, - }, - { - Name: BuildKitRootVolumeName, // buildah home folder - MountPath: "/root", - ReadOnly: false, - }, - { - Name: defaults.PrivateImageHubSecretName, - MountPath: privateImageHubMountPath, - ReadOnly: true, - }, - { - Name: RadixImageBuilderHomeVolumeName, // the file /radix-private-image-hubs/.dockerconfigjson is copied to auth.json file in the user home folder - MountPath: "/home/build", - ReadOnly: false, - }, - }...) - } else { - volumeMounts = append(volumeMounts, corev1.VolumeMount{ - Name: RadixImageBuilderHomeVolumeName, // .azure folder is created in the user home folder - MountPath: "/home/radix-image-builder", - ReadOnly: false, - }) - } - volumeMounts = append(volumeMounts, []corev1.VolumeMount{ - { - Name: getTmpVolumeNameForContainer(containerName), // image-builder creates a script there - MountPath: "/tmp", - ReadOnly: false, - }, - { - Name: getVarVolumeNameForContainer(containerName), // image-builder creates files there - MountPath: "/var", - ReadOnly: false, - }, - }...) - if len(buildSecrets) > 0 { - volumeMounts = append(volumeMounts, - corev1.VolumeMount{ - Name: defaults.BuildSecretsName, - MountPath: buildSecretsMountPath, - ReadOnly: true, - }) - } - return volumeMounts -} - -func getTmpVolumeNameForContainer(containerName string) string { - return fmt.Sprintf("tmp-%s", containerName) -} - -func getVarVolumeNameForContainer(containerName string) string { - return fmt.Sprintf("var-%s", containerName) -} - -func getBuildahContainerCommand(containerImageRegistry, secretArgsString string, componentImage pipeline.BuildComponentImage, clusterTypeImageTag, clusterNameImageTag, cacheContainerImageRegistry, cacheImagePath string, useBuildCache, pushImage bool) []string { - commandList := commandbuilder.NewCommandList() - commandList.AddStrCmd("mkdir /var/tmp && cp %s %s", path.Join(privateImageHubMountPath, ".dockerconfigjson"), buildahRegistryAuthFile) - commandList.AddStrCmd("/usr/bin/buildah login --username ${BUILDAH_USERNAME} --password ${BUILDAH_PASSWORD} %s", containerImageRegistry) - if useBuildCache { - commandList.AddStrCmd("/usr/bin/buildah login --username ${BUILDAH_CACHE_USERNAME} --password ${BUILDAH_CACHE_PASSWORD} %s", cacheContainerImageRegistry) - } - buildah := commandbuilder.NewCommand("/usr/bin/buildah build") - commandList.AddCmd(buildah) - - context := componentImage.Context - buildah. - AddArgf("--storage-driver=overlay"). - AddArgf("--isolation=chroot"). - AddArgf("--jobs 0"). - AddArgf("--ulimit nofile=4096:4096"). - AddArg(secretArgsString). - AddArgf("--file %s%s", context, componentImage.Dockerfile). - AddArgf(`--build-arg RADIX_GIT_COMMIT_HASH="${RADIX_GIT_COMMIT_HASH}"`). - AddArgf(`--build-arg RADIX_GIT_TAGS="${RADIX_GIT_TAGS}"`). - AddArgf(`--build-arg BRANCH="${BRANCH}"`). - AddArgf(`--build-arg TARGET_ENVIRONMENTS="${TARGET_ENVIRONMENTS}"`) - - if useBuildCache { - buildah. - AddArgf("--layers"). - AddArgf("--cache-to=%s", cacheImagePath). - AddArgf("--cache-from=%s", cacheImagePath) - } - - imageTag := componentImage.ImagePath - if pushImage { - buildah. - AddArgf("--tag %s", imageTag). - AddArgf("--tag %s", clusterTypeImageTag). - AddArgf("--tag %s", clusterNameImageTag) - } - - buildah.AddArg(context) - - if pushImage { - commandList. - AddStrCmd("/usr/bin/buildah push --storage-driver=overlay %s", imageTag). - AddStrCmd("/usr/bin/buildah push --storage-driver=overlay %s", clusterTypeImageTag). - AddStrCmd("/usr/bin/buildah push --storage-driver=overlay %s", clusterNameImageTag) - } - - return []string{"/bin/bash", "-c", commandList.String()} -} - -func getAcrTaskBuildPodSecurityContext() *corev1.PodSecurityContext { - return securitycontext.Pod( - securitycontext.WithPodFSGroup(1000), - securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault)) -} - -func getBuildKitPodSecurityContext() *corev1.PodSecurityContext { - return securitycontext.Pod( - securitycontext.WithPodFSGroup(1000), - securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault), - securitycontext.WithPodRunAsNonRoot(pointers.Ptr(false))) -} - -func getAcrTaskBuildContainerSecurityContext() *corev1.SecurityContext { - return securitycontext.Container( - securitycontext.WithContainerDropAllCapabilities(), - securitycontext.WithContainerSeccompProfileType(corev1.SeccompProfileTypeRuntimeDefault), - securitycontext.WithContainerRunAsUser(1000), - securitycontext.WithContainerRunAsGroup(1000), - securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), - ) -} - -func getBuildKitContainerSecurityContext() *corev1.SecurityContext { - return securitycontext.Container( - securitycontext.WithContainerDropAllCapabilities(), - securitycontext.WithContainerCapabilities([]corev1.Capability{"SETUID", "SETGID", "SETFCAP"}), - securitycontext.WithContainerSeccompProfile(corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeLocalhost, - LocalhostProfile: utils.StringPtr("allow-buildah.json"), - }), - securitycontext.WithContainerRunAsNonRoot(pointers.Ptr(false)), - securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), - ) -} - -func getSecretArgs(buildSecrets []corev1.EnvVar) string { - var secretArgs []string - for _, envVar := range buildSecrets { - secretArgs = append(secretArgs, fmt.Sprintf("--secret id=%s,src=%s/%s", envVar.ValueFrom.SecretKeyRef.Key, buildSecretsMountPath, envVar.ValueFrom.SecretKeyRef.Key)) - } - return strings.Join(secretArgs, " ") -} diff --git a/pipeline-runner/steps/build/build_secret.go b/pipeline-runner/steps/build/build_secret.go deleted file mode 100644 index 823642577..000000000 --- a/pipeline-runner/steps/build/build_secret.go +++ /dev/null @@ -1,53 +0,0 @@ -package build - -import ( - "errors" - "fmt" - "strings" - - "github.com/equinor/radix-operator/pipeline-runner/model" - "github.com/equinor/radix-operator/pkg/apis/defaults" - corev1 "k8s.io/api/core/v1" -) - -// Will ensure that all build secrets are mounted from build-secrets secret with BUILD_SECRET_ prefix -func getBuildSecretsAsVariables(pipelineInfo *model.PipelineInfo) ([]corev1.EnvVar, error) { - if pipelineInfo.RadixApplication.Spec.Build == nil || len(pipelineInfo.RadixApplication.Spec.Build.Secrets) == 0 { - return nil, nil - } - - if pipelineInfo.BuildSecret == nil { - return nil, errors.New("build secrets has not been set") - } - - var environmentVariables []corev1.EnvVar - for _, secretName := range pipelineInfo.RadixApplication.Spec.Build.Secrets { - if _, ok := pipelineInfo.BuildSecret.Data[secretName]; !ok { - return nil, fmt.Errorf("build secret %s has not been set", secretName) - } - - secretValue := string(pipelineInfo.BuildSecret.Data[secretName]) - if strings.EqualFold(secretValue, defaults.BuildSecretDefaultData) { - return nil, fmt.Errorf("build secret %s has not been set", secretName) - } - - buildSecretName := defaults.BuildSecretPrefix + secretName - - secretKeySelector := corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: defaults.BuildSecretsName, - }, - Key: secretName, - } - envVarSource := corev1.EnvVarSource{ - SecretKeyRef: &secretKeySelector, - } - secretEnvVar := corev1.EnvVar{ - Name: buildSecretName, - ValueFrom: &envVarSource, - } - environmentVariables = append(environmentVariables, secretEnvVar) - } - - return environmentVariables, nil -} diff --git a/pipeline-runner/steps/build/build_test.go b/pipeline-runner/steps/build/build_test.go index 3047c5c10..61db0d9ca 100644 --- a/pipeline-runner/steps/build/build_test.go +++ b/pipeline-runner/steps/build/build_test.go @@ -1,42 +1,45 @@ -package build +package build_test import ( "context" "fmt" - "strings" + "slices" "testing" - "time" - "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" - "github.com/equinor/radix-operator/pipeline-runner/internal/hash" - internaltest "github.com/equinor/radix-operator/pipeline-runner/internal/test" + internalbuild "github.com/equinor/radix-operator/pipeline-runner/internal/jobs/build" + buildjobmock "github.com/equinor/radix-operator/pipeline-runner/internal/jobs/build/mock" internalwait "github.com/equinor/radix-operator/pipeline-runner/internal/wait" - "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/applyconfig" - "github.com/equinor/radix-operator/pipeline-runner/steps/deploy" - application "github.com/equinor/radix-operator/pkg/apis/applicationconfig" + "github.com/equinor/radix-operator/pipeline-runner/steps/build" "github.com/equinor/radix-operator/pkg/apis/defaults" + jobutil "github.com/equinor/radix-operator/pkg/apis/job" "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/pipeline" _ "github.com/equinor/radix-operator/pkg/apis/test" "github.com/equinor/radix-operator/pkg/apis/utils" - "github.com/equinor/radix-operator/pkg/apis/utils/annotations" - "github.com/equinor/radix-operator/pkg/apis/utils/git" - "github.com/equinor/radix-operator/pkg/apis/utils/labels" 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" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubefake "k8s.io/client-go/kubernetes/fake" ) +const ( + buildJobFactoryMockMethodName = "BuildJobFactory" +) + +func createbuildJobFactoryMock(m *mock.Mock) build.BuildJobFactory { + return func(useBuildKit bool) internalbuild.JobsBuilder { + return m.MethodCalled(buildJobFactoryMockMethodName, useBuildKit).Get(0).(internalbuild.JobsBuilder) + } +} + func Test_RunBuildTestSuite(t *testing.T) { suite.Run(t, new(buildTestSuite)) } @@ -73,2239 +76,187 @@ func (s *buildTestSuite) setupTest() { s.ctrl = gomock.NewController(s.T()) } -func (s *buildTestSuite) Test_BranchIsNotMapped_ShouldSkip() { - const ( - anyAppName = "any-app" - anyJobName = "any-job-name" - anyImageTag = "anytag" - anyCommitID = "4faca8595c5283a9d0f17a623b9255a0d9866a2e" - anyBranch = "master" - anyEnvironment = "dev" - anyComponentName = "app" - anyNoMappedBranch = "feature" - ) - - rr := utils.ARadixRegistration(). - WithName(anyAppName). - BuildRR() - - ra := utils.NewRadixApplicationBuilder(). - WithAppName(anyAppName). - WithEnvironment(anyEnvironment, anyBranch). - WithComponents( - utils.AnApplicationComponent(). - WithName(anyComponentName)). - BuildRA() - +func (s *buildTestSuite) Test_TargetEnvironmentsEmpty_ShouldSkip() { + rr := utils.ARadixRegistration().WithName("any").BuildRR() jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(0) - cli := NewBuildStep(jobWaiter) + var m mock.Mock + cli := build.NewBuildStep(jobWaiter, build.WithBuildJobFactory(createbuildJobFactoryMock(&m))) cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - targetEnvs := application.GetTargetEnvironments(anyNoMappedBranch, ra) - pipelineInfo := &model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - JobName: anyJobName, - ImageTag: anyImageTag, - Branch: anyNoMappedBranch, - CommitID: anyCommitID, - }, - TargetEnvironments: targetEnvs, + PipelineArguments: model.PipelineArguments{}, + TargetEnvironments: []string{}, + BuildComponentImages: pipeline.EnvironmentBuildComponentImages{"anyenv": {{ComponentName: "anycomp"}}}, } err := cli.Run(context.Background(), pipelineInfo) s.Require().NoError(err) - radixJobList, err := s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(anyAppName)).List(context.Background(), metav1.ListOptions{}) - s.NoError(err) - s.Empty(radixJobList.Items) + m.AssertNotCalled(s.T(), buildJobFactoryMockMethodName, mock.Anything) } -func (s *buildTestSuite) Test_BuildDeploy_JobSpecAndDeploymentConsistent() { - appName, envName, rjName, compName, cloneURL, buildBranch := "anyapp", "dev", "anyrj", "c1", "git@github.com:anyorg/anyrepo", "anybranch" - prepareConfigMapName := "preparecm" - gitConfigMapName, gitHash, gitTags := "gitcm", "githash", "gittags" - rr := utils.NewRegistrationBuilder().WithCloneURL(cloneURL).WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment(envName, buildBranch). - WithEnvironment("prod", "release"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName)). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - s.Require().NoError(internaltest.CreateGitInfoConfigMapResponse(s.kubeClient, gitConfigMapName, appName, gitHash, gitTags)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - PipelineType: "build-deploy", - Branch: buildBranch, - JobName: rjName, - ImageBuilder: "builder:latest", - CommitID: "commit1234", - ImageTag: "imgtag", - PushImage: false, - UseCache: false, - ContainerRegistry: "registry", - Clustertype: "clustertype", - RadixZone: "radixzone", - Clustername: "clustername", - SubscriptionId: "subscriptionid", - GitCloneGitImage: "anygitimage:latest", - GitCloneNsLookupImage: "any", - GitCloneBashImage: "any", - }, - RadixConfigMapName: prepareConfigMapName, - GitConfigMapName: gitConfigMapName, - } - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) +func (s *buildTestSuite) Test_BuildComponentImagesEmpty_ShouldSkip() { + rr := utils.ARadixRegistration().WithName("any").BuildRR() jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - deployStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - s.Require().NoError(deployStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - expectedPodLabels := map[string]string{kube.RadixJobNameLabel: rjName} - s.Equal(expectedPodLabels, job.Spec.Template.Labels) - expectedPodAnnotations := annotations.ForClusterAutoscalerSafeToEvict(false) - s.Equal(expectedPodAnnotations, job.Spec.Template.Annotations) - expectedVolumes := []corev1.Volume{ - {Name: git.BuildContextVolumeName}, - {Name: git.GitSSHKeyVolumeName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: git.GitSSHKeyVolumeName, DefaultMode: pointers.Ptr[int32](256)}}}, - {Name: defaults.AzureACRServicePrincipleSecretName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: defaults.AzureACRServicePrincipleSecretName}}}, - {Name: defaults.PrivateImageHubSecretName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: defaults.PrivateImageHubSecretName}}}, - {Name: RadixImageBuilderHomeVolumeName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(5, resource.Mega)}}}, - {Name: "tmp-build-c1-dev", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, - {Name: "var-build-c1-dev", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, - } - s.ElementsMatch(expectedVolumes, job.Spec.Template.Spec.Volumes) - expectedAffinity := &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{MatchExpressions: []corev1.NodeSelectorRequirement{ - {Key: kube.RadixJobNodeLabel, Operator: corev1.NodeSelectorOpExists}, - {Key: corev1.LabelOSStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorOS}}, - {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{string(radixv1.RuntimeArchitectureArm64)}}, - }}}}}} - s.Equal(expectedAffinity, job.Spec.Template.Spec.Affinity) + jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(0) + var m mock.Mock + cli := build.NewBuildStep(jobWaiter, build.WithBuildJobFactory(createbuildJobFactoryMock(&m))) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - // Check init containers - s.ElementsMatch([]string{"internal-nslookup", "clone", "internal-chmod"}, slice.Map(job.Spec.Template.Spec.InitContainers, func(c corev1.Container) string { return c.Name })) - cloneContainer, _ := slice.FindFirst(job.Spec.Template.Spec.InitContainers, func(c corev1.Container) bool { return c.Name == "clone" }) - s.Equal(pipeline.PipelineArguments.GitCloneGitImage, cloneContainer.Image) - s.Equal([]string{"git", "clone", "--recurse-submodules", cloneURL, "-b", buildBranch, "--verbose", "--progress", git.Workspace}, cloneContainer.Command) - s.Empty(cloneContainer.Args) - // s.Equal([]string{fmt.Sprintf("git clone --recurse-submodules %s -b %s --verbose --progress /workspace", cloneURL, buildBranch)}, cloneContainer.Args) - expectedCloneVolumeMounts := []corev1.VolumeMount{ - {Name: git.BuildContextVolumeName, MountPath: git.Workspace}, - {Name: git.GitSSHKeyVolumeName, MountPath: "/.ssh", ReadOnly: true}, - } - s.ElementsMatch(expectedCloneVolumeMounts, cloneContainer.VolumeMounts) - // Check containers - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - s.Equal(fmt.Sprintf("build-%s-%s", compName, envName), job.Spec.Template.Spec.Containers[0].Name) - s.Equal("registry/builder:latest", job.Spec.Template.Spec.Containers[0].Image) - s.Len(job.Spec.Template.Spec.Containers[0].Args, 0) - s.Len(job.Spec.Template.Spec.Containers[0].Command, 0) - expectedBuildVolumeMounts := []corev1.VolumeMount{ - {Name: git.BuildContextVolumeName, MountPath: git.Workspace}, - {Name: defaults.AzureACRServicePrincipleSecretName, MountPath: "/radix-image-builder/.azure", ReadOnly: true}, - {Name: RadixImageBuilderHomeVolumeName, MountPath: "/home/radix-image-builder", ReadOnly: false}, - {Name: "tmp-build-c1-dev", MountPath: "/tmp", ReadOnly: false}, - {Name: "var-build-c1-dev", MountPath: "/var", ReadOnly: false}, - } - s.ElementsMatch(expectedBuildVolumeMounts, job.Spec.Template.Spec.Containers[0].VolumeMounts) - expectedEnv := []corev1.EnvVar{ - {Name: "DOCKER_FILE_NAME", Value: "Dockerfile"}, - {Name: "DOCKER_REGISTRY", Value: pipeline.PipelineArguments.ContainerRegistry}, - {Name: "IMAGE", Value: fmt.Sprintf("%s/%s-%s-%s:%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.ImageTag)}, - {Name: "CONTEXT", Value: "/workspace/"}, - {Name: "PUSH", Value: ""}, - {Name: "AZURE_CREDENTIALS", Value: "/radix-image-builder/.azure/sp_credentials.json"}, - {Name: "SUBSCRIPTION_ID", Value: pipeline.PipelineArguments.SubscriptionId}, - {Name: "CLUSTERTYPE_IMAGE", Value: fmt.Sprintf("%s/%s-%s-%s:%s-%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustertype, pipeline.PipelineArguments.ImageTag)}, - {Name: "CLUSTERNAME_IMAGE", Value: fmt.Sprintf("%s/%s-%s-%s:%s-%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustername, pipeline.PipelineArguments.ImageTag)}, - {Name: "REPOSITORY_NAME", Value: fmt.Sprintf("%s-%s-%s", appName, envName, compName)}, - {Name: "CACHE", Value: "--no-cache"}, - {Name: "RADIX_ZONE", Value: pipeline.PipelineArguments.RadixZone}, - {Name: "BRANCH", Value: pipeline.PipelineArguments.Branch}, - {Name: "TARGET_ENVIRONMENTS", Value: "dev"}, - {Name: "RADIX_GIT_COMMIT_HASH", Value: gitHash}, - {Name: "RADIX_GIT_TAGS", Value: gitTags}, + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{}, + TargetEnvironments: []string{"anyenv"}, + BuildComponentImages: pipeline.EnvironmentBuildComponentImages{}, } - s.ElementsMatch(expectedEnv, job.Spec.Template.Spec.Containers[0].Env) - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd := rds.Items[0] - expectedRaHash, err := hash.ToHashString(hash.SHA256, ra.Spec) + err := cli.Run(context.Background(), pipelineInfo) s.Require().NoError(err) - s.Equal(expectedRaHash, rd.GetAnnotations()[kube.RadixConfigHash]) - s.Equal(internaltest.GetBuildSecretHash(nil), rd.GetAnnotations()[kube.RadixBuildSecretHash]) - s.Greater(len(rd.GetAnnotations()[kube.RadixConfigHash]), 0) - s.Require().Len(rd.Spec.Components, 1) - s.Equal(compName, rd.Spec.Components[0].Name) - s.Equal(fmt.Sprintf("%s/%s-%s-%s:%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.ImageTag), rd.Spec.Components[0].Image) -} - -func (s *buildTestSuite) Test_BuildJobSpec_MultipleComponents() { - appName, envName, rjName, buildBranch, jobPort := "anyapp", "dev", "anyrj", "anybranch", pointers.Ptr[int32](9999) - prepareConfigMapName := "preparecm" - - rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment(envName, buildBranch). - WithComponents( - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("client-component-1").WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("client-component-2").WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("server-component-1").WithSourceFolder("./server/").WithDockerfileName("server.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("server-component-2").WithSourceFolder("./server/").WithDockerfileName("server.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("single-component").WithSourceFolder("."), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("public-image-component").WithImage("swaggerapi/swagger-ui"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("private-hub-component").WithImage("radixcanary.azurecr.io/nginx:latest"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("compute-shared-1").WithSourceFolder("./compute/").WithDockerfileName("compute.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("compute-shared-with-different-dockerfile-1").WithSourceFolder("./compute-with-different-dockerfile/").WithDockerfileName("compute-custom1.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("compute-shared-with-different-dockerfile-2").WithSourceFolder("./compute-with-different-dockerfile/").WithDockerfileName("compute-custom2.Dockerfile"), - ). - WithJobComponents( - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("compute-shared-2").WithDockerfileName("compute.Dockerfile").WithSourceFolder("./compute/"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("compute-shared-with-different-dockerfile-3").WithSourceFolder("./compute-with-different-dockerfile/").WithDockerfileName("compute-custom3.Dockerfile"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("single-job").WithDockerfileName("job.Dockerfile").WithSourceFolder("./job/"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-1").WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc/"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-2").WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc/"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("public-job-component").WithImage("job/job:latest"), - ). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - PipelineType: "build-deploy", - Branch: buildBranch, - JobName: rjName, - ImageTag: "imgtag", - ContainerRegistry: "registry", - Clustertype: "clustertype", - Clustername: "clustername", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - }, - RadixConfigMapName: prepareConfigMapName, - } - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - deployStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - s.Require().NoError(deployStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - - // Check build containers - type jobContainerSpec struct { - Name string - Docker string - Image string - Context string - } - imageNameFunc := func(s string) string { - return fmt.Sprintf("%s/%s-%s:%s", pipeline.PipelineArguments.ContainerRegistry, appName, s, pipeline.PipelineArguments.ImageTag) - } - expectedJobContainers := []jobContainerSpec{ - {Name: "build-client-component-1-dev", Docker: "client.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev-client-component-1")}, - {Name: "build-client-component-2-dev", Docker: "client.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev-client-component-2")}, - {Name: "build-server-component-1-dev", Docker: "server.Dockerfile", Context: "/workspace/server/", Image: imageNameFunc("dev-server-component-1")}, - {Name: "build-server-component-2-dev", Docker: "server.Dockerfile", Context: "/workspace/server/", Image: imageNameFunc("dev-server-component-2")}, - {Name: "build-single-component-dev", Docker: "Dockerfile", Context: "/workspace/", Image: imageNameFunc("dev-single-component")}, - {Name: "build-compute-shared-1-dev", Docker: "compute.Dockerfile", Context: "/workspace/compute/", Image: imageNameFunc("dev-compute-shared-1")}, - {Name: "build-compute-shared-with-different-dockerfile-1-dev", Docker: "compute-custom1.Dockerfile", Context: "/workspace/compute-with-different-dockerfile/", Image: imageNameFunc("dev-compute-shared-with-different-dockerfile-1")}, - {Name: "build-compute-shared-with-different-dockerfile-2-dev", Docker: "compute-custom2.Dockerfile", Context: "/workspace/compute-with-different-dockerfile/", Image: imageNameFunc("dev-compute-shared-with-different-dockerfile-2")}, - {Name: "build-compute-shared-2-dev", Docker: "compute.Dockerfile", Context: "/workspace/compute/", Image: imageNameFunc("dev-compute-shared-2")}, - {Name: "build-compute-shared-with-different-dockerfile-3-dev", Docker: "compute-custom3.Dockerfile", Context: "/workspace/compute-with-different-dockerfile/", Image: imageNameFunc("dev-compute-shared-with-different-dockerfile-3")}, - {Name: "build-single-job-dev", Docker: "job.Dockerfile", Context: "/workspace/job/", Image: imageNameFunc("dev-single-job")}, - {Name: "build-calc-1-dev", Docker: "calc.Dockerfile", Context: "/workspace/calc/", Image: imageNameFunc("dev-calc-1")}, - {Name: "build-calc-2-dev", Docker: "calc.Dockerfile", Context: "/workspace/calc/", Image: imageNameFunc("dev-calc-2")}, - } - actualJobContainers := slice.Map(job.Spec.Template.Spec.Containers, func(c corev1.Container) jobContainerSpec { - getEnv := func(env string) string { - if i := slice.FindIndex(c.Env, func(e corev1.EnvVar) bool { return e.Name == env }); i >= 0 { - return c.Env[i].Value - } - return "" - } - return jobContainerSpec{ - Name: c.Name, - Docker: getEnv("DOCKER_FILE_NAME"), - Image: getEnv("IMAGE"), - Context: getEnv("CONTEXT"), - } - }) - s.ElementsMatch(expectedJobContainers, actualJobContainers) - - // Check RadixDeployment component and job images - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd := rds.Items[0] - type deployComponentSpec struct { - Name string - Image string - } - expectedDeployComponents := []deployComponentSpec{ - {Name: "client-component-1", Image: imageNameFunc("dev-client-component-1")}, - {Name: "client-component-2", Image: imageNameFunc("dev-client-component-2")}, - {Name: "server-component-1", Image: imageNameFunc("dev-server-component-1")}, - {Name: "server-component-2", Image: imageNameFunc("dev-server-component-2")}, - {Name: "single-component", Image: imageNameFunc("dev-single-component")}, - {Name: "public-image-component", Image: "swaggerapi/swagger-ui"}, - {Name: "private-hub-component", Image: "radixcanary.azurecr.io/nginx:latest"}, - {Name: "compute-shared-1", Image: imageNameFunc("dev-compute-shared-1")}, - {Name: "compute-shared-with-different-dockerfile-1", Image: imageNameFunc("dev-compute-shared-with-different-dockerfile-1")}, - {Name: "compute-shared-with-different-dockerfile-2", Image: imageNameFunc("dev-compute-shared-with-different-dockerfile-2")}, - } - actualDeployComponents := slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - expectedJobComponents := []deployComponentSpec{ - {Name: "compute-shared-2", Image: imageNameFunc("dev-compute-shared-2")}, - {Name: "compute-shared-with-different-dockerfile-3", Image: imageNameFunc("dev-compute-shared-with-different-dockerfile-3")}, - {Name: "single-job", Image: imageNameFunc("dev-single-job")}, - {Name: "calc-1", Image: imageNameFunc("dev-calc-1")}, - {Name: "calc-2", Image: imageNameFunc("dev-calc-2")}, - {Name: "public-job-component", Image: "job/job:latest"}, - } - actualJobComponents := slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedJobComponents, actualJobComponents) -} - -func (s *buildTestSuite) Test_BuildJobSpec_MultipleComponents_ExpectedRuntime() { - appName, envName, rjName, buildBranch, jobPort := "anyapp", "dev", "anyrj", "anybranch", pointers.Ptr[int32](9999) - prepareConfigMapName := "preparecm" - - rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithBuildKit(pointers.Ptr(true)). - WithEnvironment(envName, buildBranch). - WithEnvironmentNoBranch("otherenv"). - WithComponents( - utils.NewApplicationComponentBuilder().WithName("comp1-build"), - utils.NewApplicationComponentBuilder().WithName("comp2-build").WithRuntime(&radixv1.Runtime{Architecture: ""}), - utils.NewApplicationComponentBuilder().WithName("comp3-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), - utils.NewApplicationComponentBuilder().WithName("comp4-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), - utils.NewApplicationComponentBuilder().WithName("comp5-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{})), - utils.NewApplicationComponentBuilder().WithName("comp6-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), - utils.NewApplicationComponentBuilder().WithName("comp7-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment("otherenv").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), - utils.NewApplicationComponentBuilder().WithName("comp1-deploy"), - utils.NewApplicationComponentBuilder().WithName("comp2-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: ""}), - utils.NewApplicationComponentBuilder().WithName("comp3-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), - utils.NewApplicationComponentBuilder().WithName("comp4-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), - utils.NewApplicationComponentBuilder().WithName("comp5-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{})), - utils.NewApplicationComponentBuilder().WithName("comp6-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), - utils.NewApplicationComponentBuilder().WithName("comp7-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment("otherenv").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), - ). - WithJobComponents( - utils.NewApplicationJobComponentBuilder().WithName("job1-build").WithSchedulerPort(jobPort), - utils.NewApplicationJobComponentBuilder().WithName("job2-build").WithRuntime(&radixv1.Runtime{Architecture: ""}).WithSchedulerPort(jobPort), - utils.NewApplicationJobComponentBuilder().WithName("job3-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}).WithSchedulerPort(jobPort), - utils.NewApplicationJobComponentBuilder().WithName("job4-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort), - utils.NewApplicationJobComponentBuilder().WithName("job5-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{})), - utils.NewApplicationJobComponentBuilder().WithName("job6-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), - utils.NewApplicationJobComponentBuilder().WithName("job7-build").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment("otherenv").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), - utils.NewApplicationJobComponentBuilder().WithName("job1-deploy").WithSchedulerPort(jobPort), - utils.NewApplicationJobComponentBuilder().WithName("job2-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: ""}).WithSchedulerPort(jobPort), - utils.NewApplicationJobComponentBuilder().WithName("job3-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}).WithSchedulerPort(jobPort), - utils.NewApplicationJobComponentBuilder().WithName("job4-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort), - utils.NewApplicationJobComponentBuilder().WithName("job5-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{})), - utils.NewApplicationJobComponentBuilder().WithName("job6-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), - utils.NewApplicationJobComponentBuilder().WithName("job7-deploy").WithImage("any").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(jobPort). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment("otherenv").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64})), - ). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - PipelineType: "build-deploy", - Branch: buildBranch, - JobName: rjName, - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - Builder: model.Builder{ResourcesLimitsMemory: "100M", ResourcesRequestsCPU: "50m", ResourcesRequestsMemory: "50M"}, - }, - RadixConfigMapName: prepareConfigMapName, - } - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).AnyTimes() - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - deployStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - s.Require().NoError(deployStep.Run(context.Background(), &pipeline)) - - // Check RadixDeployment component and job images - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd := rds.Items[0] - type deployComponentSpec struct { - Name string - Runtime *radixv1.Runtime - } - expectedDeployComponents := []deployComponentSpec{ - {Name: "comp1-build", Runtime: nil}, - {Name: "comp2-build", Runtime: nil}, - {Name: "comp3-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}}, - {Name: "comp4-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "comp5-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "comp6-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}}, - {Name: "comp7-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "comp1-deploy", Runtime: nil}, - {Name: "comp2-deploy", Runtime: nil}, - {Name: "comp3-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}}, - {Name: "comp4-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "comp5-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "comp6-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}}, - {Name: "comp7-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - } - actualDeployComponents := slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Runtime: c.Runtime} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - expectedJobComponents := []deployComponentSpec{ - {Name: "job1-build", Runtime: nil}, - {Name: "job2-build", Runtime: nil}, - {Name: "job3-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}}, - {Name: "job4-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "job5-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "job6-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}}, - {Name: "job7-build", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "job1-deploy", Runtime: nil}, - {Name: "job2-deploy", Runtime: nil}, - {Name: "job3-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}}, - {Name: "job4-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "job5-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - {Name: "job6-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}}, - {Name: "job7-deploy", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, - } - actualJobComponents := slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Runtime: c.Runtime} - }) - s.ElementsMatch(expectedJobComponents, actualJobComponents) -} - -func (s *buildTestSuite) Test_BuildJobSpec_MultipleComponents_IgnoreDisabled() { - appName, envName, rjName, buildBranch, jobPort := "anyapp", "dev", "anyrj", "anybranch", pointers.Ptr[int32](9999) - prepareConfigMapName := "preparecm" - - rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment(envName, buildBranch). - WithComponents( - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("client-component-1").WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("client-component-2").WithEnabled(true).WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("client-component-3").WithEnabled(false).WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("client-component-4").WithSourceFolder("./client2/").WithDockerfileName("client.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("client-component-5").WithEnabled(false).WithSourceFolder("./client2/").WithDockerfileName("client.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("client-component-6").WithEnabled(false).WithSourceFolder("./client3/").WithDockerfileName("client.Dockerfile"). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(true)), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("client-component-7").WithEnabled(true).WithSourceFolder("./client4/").WithDockerfileName("client.Dockerfile"). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(false)), - ). - WithJobComponents( - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-1").WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc/"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-2").WithEnabled(true).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc/"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-3").WithEnabled(false).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc/"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-4").WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc2/"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-5").WithEnabled(false).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc2/"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-6").WithEnabled(false).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc3/"). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(true)), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("calc-7").WithEnabled(true).WithDockerfileName("calc.Dockerfile").WithSourceFolder("./calc4/"). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithEnabled(false)), - ). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - PipelineType: "build-deploy", - Branch: buildBranch, - JobName: rjName, - ImageTag: "imgtag", - ContainerRegistry: "registry", - Clustertype: "clustertype", - Clustername: "clustername", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - }, - RadixConfigMapName: prepareConfigMapName, - } - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - deployStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - s.Require().NoError(deployStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - - // Check build containers - type jobContainerSpec struct { - Name string - Docker string - Image string - Context string - } - imageNameFunc := func(s string) string { - return fmt.Sprintf("%s/%s-%s:%s", pipeline.PipelineArguments.ContainerRegistry, appName, s, pipeline.PipelineArguments.ImageTag) - } - expectedJobContainers := []jobContainerSpec{ - {Name: "build-client-component-1-dev", Docker: "client.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev-client-component-1")}, - {Name: "build-client-component-2-dev", Docker: "client.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev-client-component-2")}, - {Name: "build-calc-1-dev", Docker: "calc.Dockerfile", Context: "/workspace/calc/", Image: imageNameFunc("dev-calc-1")}, - {Name: "build-calc-2-dev", Docker: "calc.Dockerfile", Context: "/workspace/calc/", Image: imageNameFunc("dev-calc-2")}, - {Name: "build-client-component-4-dev", Docker: "client.Dockerfile", Context: "/workspace/client2/", Image: imageNameFunc("dev-client-component-4")}, - {Name: "build-client-component-6-dev", Docker: "client.Dockerfile", Context: "/workspace/client3/", Image: imageNameFunc("dev-client-component-6")}, - {Name: "build-calc-4-dev", Docker: "calc.Dockerfile", Context: "/workspace/calc2/", Image: imageNameFunc("dev-calc-4")}, - {Name: "build-calc-6-dev", Docker: "calc.Dockerfile", Context: "/workspace/calc3/", Image: imageNameFunc("dev-calc-6")}, - } - actualJobContainers := slice.Map(job.Spec.Template.Spec.Containers, func(c corev1.Container) jobContainerSpec { - getEnv := func(env string) string { - if i := slice.FindIndex(c.Env, func(e corev1.EnvVar) bool { return e.Name == env }); i >= 0 { - return c.Env[i].Value - } - return "" - } - return jobContainerSpec{ - Name: c.Name, - Docker: getEnv("DOCKER_FILE_NAME"), - Image: getEnv("IMAGE"), - Context: getEnv("CONTEXT"), - } - }) - s.ElementsMatch(expectedJobContainers, actualJobContainers) - - // Check RadixDeployment component and job images - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd := rds.Items[0] - type deployComponentSpec struct { - Name string - Image string - } - expectedDeployComponents := []deployComponentSpec{ - {Name: "client-component-1", Image: imageNameFunc("dev-client-component-1")}, - {Name: "client-component-2", Image: imageNameFunc("dev-client-component-2")}, - {Name: "client-component-4", Image: imageNameFunc("dev-client-component-4")}, - {Name: "client-component-6", Image: imageNameFunc("dev-client-component-6")}, - } - actualDeployComponents := slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - expectedJobComponents := []deployComponentSpec{ - {Name: "calc-1", Image: imageNameFunc("dev-calc-1")}, - {Name: "calc-2", Image: imageNameFunc("dev-calc-2")}, - {Name: "calc-4", Image: imageNameFunc("dev-calc-4")}, - {Name: "calc-6", Image: imageNameFunc("dev-calc-6")}, - } - actualJobComponents := slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedJobComponents, actualJobComponents) -} - -func (s *buildTestSuite) Test_BuildChangedComponents() { - appName, envName, rjName, buildBranch, jobPort := "anyapp", "dev", "anyrj", "anybranch", pointers.Ptr[int32](9999) - prepareConfigMapName := "preparecm" - - rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment(envName, buildBranch). - WithComponents( - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-changed").WithDockerfileName("comp-changed.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-new").WithDockerfileName("comp-new.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-unchanged").WithDockerfileName("comp-unchanged.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-common1-changed").WithDockerfileName("common1.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-common2-unchanged").WithDockerfileName("common2.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-common3-changed").WithDockerfileName("common3.Dockerfile"), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp-deployonly").WithImage("comp-deployonly:anytag"), - ). - WithJobComponents( - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-changed").WithDockerfileName("job-changed.Dockerfile"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-new").WithDockerfileName("job-new.Dockerfile"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-unchanged").WithDockerfileName("job-unchanged.Dockerfile"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-common1-unchanged").WithDockerfileName("common1.Dockerfile"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-common2-changed").WithDockerfileName("common2.Dockerfile"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-common3-changed").WithDockerfileName("common3.Dockerfile"), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job-deployonly").WithImage("job-deployonly:anytag"), - ). - BuildRA() - currentRd := utils.NewDeploymentBuilder(). - WithDeploymentName("currentrd"). - WithAppName(appName). - WithEnvironment(envName). - WithAnnotations(map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(ra), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(nil)}). - WithCondition(radixv1.DeploymentActive). - WithComponents( - utils.NewDeployComponentBuilder().WithName("comp-changed").WithImage("dev-comp-changed-current:anytag"), - utils.NewDeployComponentBuilder().WithName("comp-unchanged").WithImage("dev-comp-unchanged-current:anytag"), - utils.NewDeployComponentBuilder().WithName("comp-common1-changed").WithImage("dev-comp-common1-changed:anytag"), - utils.NewDeployComponentBuilder().WithName("comp-common2-unchanged").WithImage("dev-comp-common2-unchanged:anytag"), - ). - WithJobComponents( - utils.NewDeployJobComponentBuilder().WithName("job-changed").WithImage("dev-job-changed-current:anytag"), - utils.NewDeployJobComponentBuilder().WithName("job-unchanged").WithImage("dev-job-unchanged-current:anytag"), - utils.NewDeployJobComponentBuilder().WithName("job-common1-unchanged").WithImage("dev-job-common1-unchanged:anytag"), - utils.NewDeployJobComponentBuilder().WithName("job-common2-changed").WithImage("dev-job-common2-changed:anytag"), - ). - BuildRD() - _, _ = s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).Create(context.Background(), currentRd, metav1.CreateOptions{}) - _, _ = s.kubeClient.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: utils.GetEnvironmentNamespace(appName, envName)}}, metav1.CreateOptions{}) - buildCtx := &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - {Environment: envName, Components: []string{"comp-changed", "comp-common1-changed", "comp-common3-changed", "job-changed", "job-common2-changed", "job-common3-changed"}}, - }, - } - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, buildCtx)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - PipelineType: "build-deploy", - JobName: rjName, - Branch: buildBranch, - ImageTag: "imgtag", - Clustertype: "clustertype", - Clustername: "clustername", - ContainerRegistry: "registry", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - }, - RadixConfigMapName: prepareConfigMapName, - } - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - deployStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - s.Require().NoError(deployStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - - // Check build containers - imageNameFunc := func(s string) string { - return fmt.Sprintf("%s/%s-%s:%s", pipeline.PipelineArguments.ContainerRegistry, appName, s, pipeline.PipelineArguments.ImageTag) - } - expectedJobContainers := []string{ - "build-comp-changed-dev", - "build-comp-new-dev", - "build-comp-common1-changed-dev", - "build-comp-common3-changed-dev", - "build-job-changed-dev", - "build-job-new-dev", - "build-job-common2-changed-dev", - "build-job-common3-changed-dev", - } - actualJobContainers := slice.Map(job.Spec.Template.Spec.Containers, func(c corev1.Container) string { return c.Name }) - s.ElementsMatch(expectedJobContainers, actualJobContainers) - - // Check RadixDeployment component and job images - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{LabelSelector: labels.ForPipelineJobName(rjName).String()}) - s.Require().Len(rds.Items, 1) - rd := rds.Items[0] - type deployComponentSpec struct { - Name string - Image string - } - expectedDeployComponents := []deployComponentSpec{ - {Name: "comp-changed", Image: imageNameFunc("dev-comp-changed")}, - {Name: "comp-new", Image: imageNameFunc("dev-comp-new")}, - {Name: "comp-unchanged", Image: "dev-comp-unchanged-current:anytag"}, - {Name: "comp-common1-changed", Image: imageNameFunc("dev-comp-common1-changed")}, - {Name: "comp-common2-unchanged", Image: "dev-comp-common2-unchanged:anytag"}, - {Name: "comp-common3-changed", Image: imageNameFunc("dev-comp-common3-changed")}, - {Name: "comp-deployonly", Image: "comp-deployonly:anytag"}, - } - actualDeployComponents := slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - expectedJobComponents := []deployComponentSpec{ - {Name: "job-changed", Image: imageNameFunc("dev-job-changed")}, - {Name: "job-new", Image: imageNameFunc("dev-job-new")}, - {Name: "job-unchanged", Image: "dev-job-unchanged-current:anytag"}, - {Name: "job-common1-unchanged", Image: "dev-job-common1-unchanged:anytag"}, - {Name: "job-common2-changed", Image: imageNameFunc("dev-job-common2-changed")}, - {Name: "job-common3-changed", Image: imageNameFunc("dev-job-common3-changed")}, - {Name: "job-deployonly", Image: "job-deployonly:anytag"}, - } - actualJobComponents := slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedJobComponents, actualJobComponents) -} - -func (s *buildTestSuite) Test_DetectComponentsToBuild() { - appName, envName, rjName, buildBranch, jobPort := "anyapp", "dev", "anyrj", "anybranch", pointers.Ptr[int32](9999) - prepareConfigMapName := "preparecm" - rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - raBuilder := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment(envName, buildBranch). - WithComponents( - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp").WithDockerfileName("comp.Dockerfile"), - ). - WithJobComponents( - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job").WithDockerfileName("job.Dockerfile"), - ) - defaultRa := raBuilder.WithBuildSecrets("SECRET1").BuildRA() - raWithoutSecret := raBuilder.WithBuildSecrets().BuildRA() - oldRa := defaultRa.DeepCopy() - oldRa.Spec.Components[0].Variables = radixv1.EnvVarsMap{"anyvar": "anyvalue"} - currentBuildSecret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: defaults.BuildSecretsName}, Data: map[string][]byte{"SECRET1": []byte("anydata")}} - oldBuildSecret := currentBuildSecret.DeepCopy() - oldBuildSecret.Data["SECRET1"] = []byte("newdata") - radixDeploymentFactory := func(annotations map[string]string, condition radixv1.RadixDeployCondition, componentBuilders []utils.DeployComponentBuilder, jobBuilders []utils.DeployJobComponentBuilder) *radixv1.RadixDeployment { - builder := utils.NewDeploymentBuilder(). - WithDeploymentName("currentrd"). - WithAppName(appName). - WithEnvironment(envName). - WithAnnotations(annotations). - WithCondition(condition). - WithActiveFrom(time.Now().Add(-1 * time.Hour)). - WithComponents(componentBuilders...). - WithJobComponents(jobBuilders...) - - if condition == radixv1.DeploymentInactive { - builder = builder.WithActiveTo(time.Now().Add(1 * time.Hour)) - } - - return builder.BuildRD() - } - piplineArgs := model.PipelineArguments{ - PipelineType: "build-deploy", - Branch: buildBranch, - JobName: rjName, - ImageTag: "imgtag", - ContainerRegistry: "registry", - Clustertype: "clustertype", - Clustername: "clustername", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - } - imageNameFunc := func(s string) string { - return fmt.Sprintf("%s/%s-%s:%s", piplineArgs.ContainerRegistry, appName, s, piplineArgs.ImageTag) - } - type deployComponentSpec struct { - Name string - Image string - } - type testSpec struct { - name string - existingRd *radixv1.RadixDeployment - customRa *radixv1.RadixApplication - prepareBuildCtx *model.PrepareBuildContext - expectedJobContainers []string - expectedDeployComponents []deployComponentSpec - expectedDeployJobs []deployComponentSpec - skipEnvNamespace bool - } - tests := []testSpec{ - { - name: "radixconfig hash unchanged, buildsecret hash unchanged, component changed, job changed - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-changed-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{"comp", "job"}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "radixconfig hash unchanged, buildsecret hash unchanged, component changed - build component", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{"comp"}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: "job-current:anytag"}}, - }, - { - name: "radixconfig hash unchanged, buildsecret hash unchanged, job changed - build job", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{"job"}, - }, - }, - }, - expectedJobContainers: []string{"build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: "comp-current:anytag"}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "radixconfig hash unchanged, buildsecret hash unchanged, component unchanged, job unchanged - no build job", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: "comp-current:anytag"}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: "job-current:anytag"}}, - }, - { - name: "radixconfig hash unchanged, buildsecret hash unchanged, component unchanged, job unchanged, env namespace missing - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - skipEnvNamespace: true, - }, - { - name: "radixconfig hash unchanged, buildsecret hash unchanged, missing prepare context for environment - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: "otherenv", - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "radixconfig hash unchanged, buildsecret hash unchanged, component unchanged, job unchanged - no build job", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: "comp-current:anytag"}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: "job-current:anytag"}}, - }, - { - name: "radixconfig hash changed, buildsecret hash unchanged, component unchanged, job unchanged - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(oldRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "radixconfig hash missing, buildsecret hash unchanged, component unchanged, job unchanged - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "radixconfig hash unchanged, buildsecret hash changed, component unchanged, job unchanged - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(oldBuildSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "radixconfig hash unchanged, buildsecret magic hash, component unchanged, job unchanged - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(nil)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "radixconfig hash unchanged, buildsecret magic hash, no build secret, component unchanged, job unchanged - no build job", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(raWithoutSecret), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(nil)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - customRa: raWithoutSecret, - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: "comp-current:anytag"}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: "job-current:anytag"}}, - }, - { - name: "radixconfig hash unchanged, buildsecret missing, no build secret, component unchanged, job unchanged - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(raWithoutSecret)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - customRa: raWithoutSecret, - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "radixconfig hash unchanged, buildsecret hash missing, component unchanged, job unchanged - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa)}, - radixv1.DeploymentActive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "missing current RD, component unchanged, job unchanged - build all", - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - { - name: "no current RD, component unchanged, job unchanged - build all", - existingRd: radixDeploymentFactory( - map[string]string{kube.RadixConfigHash: internaltest.GetRadixApplicationHash(defaultRa), kube.RadixBuildSecretHash: internaltest.GetBuildSecretHash(currentBuildSecret)}, - radixv1.DeploymentInactive, - []utils.DeployComponentBuilder{utils.NewDeployComponentBuilder().WithName("comp").WithImage("comp-current:anytag")}, - []utils.DeployJobComponentBuilder{utils.NewDeployJobComponentBuilder().WithName("job").WithImage("job-current:anytag")}, - ), - prepareBuildCtx: &model.PrepareBuildContext{ - EnvironmentsToBuild: []model.EnvironmentToBuild{ - { - Environment: envName, - Components: []string{}, - }, - }, - }, - expectedJobContainers: []string{"build-comp-dev", "build-job-dev"}, - expectedDeployComponents: []deployComponentSpec{{Name: "comp", Image: imageNameFunc("dev-comp")}}, - expectedDeployJobs: []deployComponentSpec{{Name: "job", Image: imageNameFunc("dev-job")}}, - }, - } - - for _, test := range tests { - - s.Run(test.name, func() { - ra := defaultRa - if test.customRa != nil { - ra = test.customRa - } - _, _ = s.kubeClient.CoreV1().Secrets(utils.GetAppNamespace(appName)).Create(context.Background(), currentBuildSecret, metav1.CreateOptions{}) - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - if test.existingRd != nil { - _, _ = s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).Create(context.Background(), test.existingRd, metav1.CreateOptions{}) - } - if !test.skipEnvNamespace { - _, _ = s.kubeClient.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: utils.GetEnvironmentNamespace(appName, envName)}}, metav1.CreateOptions{}) - } - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, test.prepareBuildCtx)) - pipeline := model.PipelineInfo{PipelineArguments: piplineArgs, RadixConfigMapName: prepareConfigMapName} - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - if len(test.expectedJobContainers) > 0 { - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - } - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - deployStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - - // Run pipeline steps - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - s.Require().NoError(deployStep.Run(context.Background(), &pipeline)) - - // Check Job containers - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Equal(len(test.expectedJobContainers) > 0, len(jobs.Items) == 1) - if len(test.expectedJobContainers) > 0 { - job := jobs.Items[0] - actualJobContainers := slice.Map(job.Spec.Template.Spec.Containers, func(c corev1.Container) string { return c.Name }) - s.ElementsMatch(test.expectedJobContainers, actualJobContainers) - } - - // Check RadixDeployment component and job images - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{LabelSelector: labels.ForPipelineJobName(rjName).String()}) - s.Require().Len(rds.Items, 1) - rd := rds.Items[0] - actualDeployComponents := slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(test.expectedDeployComponents, actualDeployComponents) - actualJobComponents := slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(test.expectedDeployJobs, actualJobComponents) - }) - } + m.AssertNotCalled(s.T(), buildJobFactoryMockMethodName, mock.Anything) } -func (s *buildTestSuite) Test_BuildJobSpec_ImageTagNames() { - appName, envName, rjName, buildBranch, jobPort := "anyapp", "dev", "anyrj", "anybranch", pointers.Ptr[int32](9999) - prepareConfigMapName := "preparecm" - - rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment(envName, buildBranch). - WithComponents( - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp1").WithImage("comp1img:{imageTagName}"). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithImageTagName("comp1envtag")), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("comp2").WithImage("comp2img:{imageTagName}"). - WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envName).WithImageTagName("comp2envtag")), - ). - WithJobComponents( - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job1").WithImage("job1img:{imageTagName}"). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithImageTagName("job1envtag")), - utils.NewApplicationJobComponentBuilder().WithSchedulerPort(jobPort).WithName("job2").WithImage("job2img:{imageTagName}"). - WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName).WithImageTagName("job2envtag")), - ). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - PipelineType: "deploy", - ToEnvironment: envName, - JobName: rjName, - ImageTagNames: map[string]string{"comp1": "comp1customtag", "job1": "job1customtag"}, - }, - RadixConfigMapName: prepareConfigMapName, - } - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - deployStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(deployStep.Run(context.Background(), &pipeline)) - - // Check RadixDeployment component and job images - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd := rds.Items[0] - type deployComponentSpec struct { - Name string - Image string - } - expectedDeployComponents := []deployComponentSpec{ - {Name: "comp1", Image: "comp1img:comp1customtag"}, - {Name: "comp2", Image: "comp2img:comp2envtag"}, - } - actualDeployComponents := slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - expectedJobComponents := []deployComponentSpec{ - {Name: "job1", Image: "job1img:job1customtag"}, - {Name: "job2", Image: "job2img:job2envtag"}, - } - actualJobComponents := slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedJobComponents, actualJobComponents) -} - -func (s *buildTestSuite) Test_BuildJobSpec_PushImage() { - appName, rjName, compName := "anyapp", "anyrj", "c1" - prepareConfigMapName := "preparecm" - rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment("dev", "main"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName)). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - Branch: "main", - JobName: rjName, - PushImage: true, - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - }, - RadixConfigMapName: prepareConfigMapName, - } - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - expectedEnv := []corev1.EnvVar{ - {Name: "PUSH", Value: "--push"}, - } - s.Subset(job.Spec.Template.Spec.Containers[0].Env, expectedEnv) -} - -func (s *buildTestSuite) Test_BuildJobSpec_UseCache() { - appName, rjName, compName := "anyapp", "anyrj", "c1" - prepareConfigMapName := "preparecm" - rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment("dev", "main"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName)). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - Branch: "main", - JobName: rjName, - UseCache: true, - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - }, - RadixConfigMapName: prepareConfigMapName, - } - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - expectedEnv := []corev1.EnvVar{ - {Name: "CACHE", Value: ""}, - } - s.Subset(job.Spec.Template.Spec.Containers[0].Env, expectedEnv) -} - -func (s *buildTestSuite) Test_BuildJobSpec_WithDockerfileName() { - appName, rjName, compName, dockerFileName := "anyapp", "anyrj", "c1", "anydockerfile" - prepareConfigMapName := "preparecm" +func (s *buildTestSuite) Test_WithBuildSecrets_Validation() { + const ( + appName = "anyapp" + jobName = "anyjob" + secretName = "thesecret" + ) rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() + ra := utils.ARadixApplication().WithBuildSecrets(secretName).BuildRA() + rj := utils.ARadixBuildDeployJob().WithJobName(jobName).WithAppName(appName).BuildRJ() _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment("dev", "main"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName).WithDockerfileName(dockerFileName)). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - Branch: "main", - JobName: rjName, - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - }, - RadixConfigMapName: prepareConfigMapName, - } jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - expectedEnv := []corev1.EnvVar{ - {Name: "DOCKER_FILE_NAME", Value: dockerFileName}, - } - s.Subset(job.Spec.Template.Spec.Containers[0].Env, expectedEnv) -} + jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).AnyTimes() + var m mock.Mock + cli := build.NewBuildStep(jobWaiter, build.WithBuildJobFactory(createbuildJobFactoryMock(&m))) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) -func (s *buildTestSuite) Test_BuildJobSpec_WithSourceFolder() { - appName, rjName, compName := "anyapp", "anyrj", "c1" - prepareConfigMapName := "preparecm" - rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment("dev", "main"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName).WithSourceFolder(".././path/../../subpath")). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ + pipelineInfo := &model.PipelineInfo{ PipelineArguments: model.PipelineArguments{ - Branch: "main", - JobName: rjName, - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", + JobName: jobName, }, - RadixConfigMapName: prepareConfigMapName, + TargetEnvironments: []string{"anyenv"}, + BuildComponentImages: pipeline.EnvironmentBuildComponentImages{"anyenv": {}}, + RadixApplication: ra, } - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - expectedEnv := []corev1.EnvVar{ - {Name: "CONTEXT", Value: "/workspace/subpath/"}, - } - s.Subset(job.Spec.Template.Spec.Containers[0].Env, expectedEnv) + err := cli.Run(context.Background(), pipelineInfo) + s.ErrorContains(err, "build secrets has not been set") + m.AssertNotCalled(s.T(), buildJobFactoryMockMethodName, mock.Anything) + + // secret key missing + pipelineInfo.BuildSecret = &corev1.Secret{Data: map[string][]byte{}} + err = cli.Run(context.Background(), pipelineInfo) + s.ErrorContains(err, fmt.Sprintf("build secret %s has not been set", secretName)) + m.AssertNotCalled(s.T(), buildJobFactoryMockMethodName, mock.Anything) + + // secret set to default value + pipelineInfo.BuildSecret = &corev1.Secret{Data: map[string][]byte{secretName: []byte(defaults.BuildSecretDefaultData)}} + err = cli.Run(context.Background(), pipelineInfo) + s.ErrorContains(err, fmt.Sprintf("build secret %s has not been set", secretName)) + m.AssertNotCalled(s.T(), buildJobFactoryMockMethodName, mock.Anything) + + // secret correctly set + jobBuilder := buildjobmock.NewMockJobsBuilder(gomock.NewController(s.T())) + jobBuilder.EXPECT().BuildJobs(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) + m.On(buildJobFactoryMockMethodName, mock.Anything).Return(jobBuilder) + pipelineInfo.BuildSecret = &corev1.Secret{Data: map[string][]byte{secretName: []byte("anyvalue")}} + err = cli.Run(context.Background(), pipelineInfo) + s.NoError(err) + m.AssertExpectations(s.T()) } -func (s *buildTestSuite) Test_BuildJobSpec_WithBuildSecrets() { - appName, envName, rjName, compName := "anyapp", "dev", "anyrj", "c1" - prepareConfigMapName := "preparecm" +func (s *buildTestSuite) Test_AppWithoutBuildSecrets_Validation() { + const ( + appName = "anyapp" + jobName = "anyjob" + ) rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() + ra := utils.ARadixApplication().WithBuildSecrets().BuildRA() + rj := utils.ARadixBuildDeployJob().WithJobName(jobName).WithAppName(appName).BuildRJ() _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithBuildSecrets("SECRET1", "SECRET2"). - WithEnvironment(envName, "main"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName)). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - s.Require().NoError(internaltest.CreateBuildSecret(s.kubeClient, appName, map[string][]byte{"SECRET1": nil, "SECRET2": nil})) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - Branch: "main", - JobName: rjName, - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - }, - RadixConfigMapName: prepareConfigMapName, - } jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - deployStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - s.Require().NoError(deployStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - s.Len(job.Spec.Template.Spec.Volumes, 8) - expectedVolumes := []corev1.Volume{ - {Name: defaults.BuildSecretsName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: defaults.BuildSecretsName}}}, - } - s.Subset(job.Spec.Template.Spec.Volumes, expectedVolumes) - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - s.Len(job.Spec.Template.Spec.Containers[0].VolumeMounts, 6) - expectedVolumeMounts := []corev1.VolumeMount{ - {Name: defaults.BuildSecretsName, MountPath: "/build-secrets", ReadOnly: true}, - } - s.Subset(job.Spec.Template.Spec.Containers[0].VolumeMounts, expectedVolumeMounts) - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd := rds.Items[0] - s.NotEmpty(rd.GetAnnotations()[kube.RadixBuildSecretHash]) - -} + jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).AnyTimes() + var m mock.Mock + cli := build.NewBuildStep(jobWaiter, build.WithBuildJobFactory(createbuildJobFactoryMock(&m))) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) -func (s *buildTestSuite) Test_BuildJobSpec_BuildKit() { - appName, rjName, compName, sourceFolder, dockerFile, envName := "anyapp", "anyrj", "c1", "../path1/./../../path2", "anydockerfile", "dev" - prepareConfigMapName := "preparecm" - gitConfigMapName, gitHash, gitTags := "gitcm", "githash", "gittags" - rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithBuildKit(pointers.Ptr(true)). - WithEnvironment("dev", "main"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName).WithDockerfileName(dockerFile).WithSourceFolder(sourceFolder)). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - s.Require().NoError(internaltest.CreateGitInfoConfigMapResponse(s.kubeClient, gitConfigMapName, appName, gitHash, gitTags)) - pipeline := model.PipelineInfo{ + pipelineInfo := &model.PipelineInfo{ PipelineArguments: model.PipelineArguments{ - PipelineType: "build-deploy", - Branch: "main", - JobName: rjName, - BuildKitImageBuilder: "anybuildkitimage:tag", - ImageTag: "anyimagetag", - ContainerRegistry: "anyregistry", - AppContainerRegistry: "anyappregistry", - Clustertype: "anyclustertype", - Clustername: "anyclustername", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - Builder: model.Builder{ResourcesLimitsMemory: "100M", ResourcesRequestsCPU: "50m", ResourcesRequestsMemory: "50M"}, - }, - RadixConfigMapName: prepareConfigMapName, - GitConfigMapName: gitConfigMapName, - } - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - s.Equal(pipeline.PipelineArguments.BuildKitImageBuilder, job.Spec.Template.Spec.Containers[0].Image) - expectedBuildCmd := strings.Join( - []string{ - "mkdir /var/tmp && cp /radix-private-image-hubs/.dockerconfigjson /home/build/auth.json && ", - fmt.Sprintf("/usr/bin/buildah login --username ${BUILDAH_USERNAME} --password ${BUILDAH_PASSWORD} %s && ", pipeline.PipelineArguments.ContainerRegistry), - fmt.Sprintf("/usr/bin/buildah login --username ${BUILDAH_CACHE_USERNAME} --password ${BUILDAH_CACHE_PASSWORD} %s && ", pipeline.PipelineArguments.AppContainerRegistry), - "/usr/bin/buildah build --storage-driver=overlay --isolation=chroot --jobs 0 --ulimit nofile=4096:4096 ", - fmt.Sprintf("--file %s%s ", "/workspace/path2/", dockerFile), - "--build-arg RADIX_GIT_COMMIT_HASH=\"${RADIX_GIT_COMMIT_HASH}\" ", - "--build-arg RADIX_GIT_TAGS=\"${RADIX_GIT_TAGS}\" ", - "--build-arg BRANCH=\"${BRANCH}\" ", - "--build-arg TARGET_ENVIRONMENTS=\"${TARGET_ENVIRONMENTS}\" ", - "--layers ", - fmt.Sprintf("--cache-to=%s/%s/cache ", pipeline.PipelineArguments.AppContainerRegistry, appName), - fmt.Sprintf("--cache-from=%s/%s/cache ", pipeline.PipelineArguments.AppContainerRegistry, appName), - "/workspace/path2/", - }, - "", - ) - expectedCommand := []string{"/bin/bash", "-c", expectedBuildCmd} - s.Equal(expectedCommand, job.Spec.Template.Spec.Containers[0].Command) - expectedEnv := []corev1.EnvVar{ - {Name: "DOCKER_FILE_NAME", Value: dockerFile}, - {Name: "DOCKER_REGISTRY", Value: pipeline.PipelineArguments.ContainerRegistry}, - {Name: "IMAGE", Value: fmt.Sprintf("%s/%s-%s-%s:%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.ImageTag)}, - {Name: "CONTEXT", Value: "/workspace/path2/"}, - {Name: "PUSH", Value: ""}, - {Name: "AZURE_CREDENTIALS", Value: "/radix-image-builder/.azure/sp_credentials.json"}, - {Name: "SUBSCRIPTION_ID", Value: pipeline.PipelineArguments.SubscriptionId}, - {Name: "CLUSTERTYPE_IMAGE", Value: fmt.Sprintf("%s/%s-%s-%s:%s-%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustertype, pipeline.PipelineArguments.ImageTag)}, - {Name: "CLUSTERNAME_IMAGE", Value: fmt.Sprintf("%s/%s-%s-%s:%s-%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustername, pipeline.PipelineArguments.ImageTag)}, - {Name: "REPOSITORY_NAME", Value: fmt.Sprintf("%s-%s-%s", appName, envName, compName)}, - {Name: "CACHE", Value: "--no-cache"}, - {Name: "RADIX_ZONE", Value: pipeline.PipelineArguments.RadixZone}, - {Name: "BRANCH", Value: pipeline.PipelineArguments.Branch}, - {Name: "TARGET_ENVIRONMENTS", Value: "dev"}, - {Name: "RADIX_GIT_COMMIT_HASH", Value: gitHash}, - {Name: "RADIX_GIT_TAGS", Value: gitTags}, - {Name: "BUILDAH_USERNAME", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{Key: "username", LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}}}}, - {Name: "BUILDAH_PASSWORD", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{Key: "password", LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}}}}, - {Name: "BUILDAH_CACHE_USERNAME", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{Key: "username", LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}}}}, - {Name: "BUILDAH_CACHE_PASSWORD", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{Key: "password", LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}}}}, - {Name: "REGISTRY_AUTH_FILE", Value: "/home/build/auth.json"}, - } - s.ElementsMatch(expectedEnv, job.Spec.Template.Spec.Containers[0].Env) - expectedVolumeMounts := []corev1.VolumeMount{ - {Name: git.BuildContextVolumeName, MountPath: git.Workspace, ReadOnly: false}, - {Name: defaults.AzureACRServicePrincipleSecretName, MountPath: "/radix-image-builder/.azure", ReadOnly: true}, - {Name: BuildKitRunVolumeName, MountPath: "/run", ReadOnly: false}, - {Name: BuildKitRootVolumeName, MountPath: "/root", ReadOnly: false}, - {Name: defaults.PrivateImageHubSecretName, MountPath: "/radix-private-image-hubs", ReadOnly: true}, - {Name: RadixImageBuilderHomeVolumeName, MountPath: "/home/build", ReadOnly: false}, - {Name: "tmp-build-c1-dev", MountPath: "/tmp", ReadOnly: false}, - {Name: "var-build-c1-dev", MountPath: "/var", ReadOnly: false}, - } - s.ElementsMatch(job.Spec.Template.Spec.Containers[0].VolumeMounts, expectedVolumeMounts) -} - -func (s *buildTestSuite) Test_BuildJobSpec_OverrideUseBuildCacheInBuildKit() { - appName, rjName, compName, sourceFolder, dockerFile, envName := "anyapp", "anyrj", "c1", "../path1/./../../path2", "anydockerfile", "dev" - prepareConfigMapName := "preparecm" - gitConfigMapName, gitHash, gitTags := "gitcm", "githash", "gittags" - type scenario struct { - name string - configUseBuildCache *bool - jobOverrideUseBuildCache *bool - expectedUseBuildCache bool - } - scenarios := []scenario{ - { - name: "configured UseBuildCache true, not overridden", - configUseBuildCache: pointers.Ptr(true), - expectedUseBuildCache: true, - }, - { - name: "configured UseBuildCache false, not overridden", - configUseBuildCache: pointers.Ptr(false), - expectedUseBuildCache: false, - }, - { - name: "configured UseBuildCache true, overridden with false", - configUseBuildCache: pointers.Ptr(true), - jobOverrideUseBuildCache: pointers.Ptr(false), - expectedUseBuildCache: false, - }, - { - name: "configured UseBuildCache false, overridden with true", - configUseBuildCache: pointers.Ptr(false), - jobOverrideUseBuildCache: pointers.Ptr(true), - expectedUseBuildCache: true, - }, - { - name: "not configured UseBuildCache true, overridden with false", - jobOverrideUseBuildCache: pointers.Ptr(false), - expectedUseBuildCache: false, - }, - { - name: "not configured UseBuildCache false, overridden with true", - jobOverrideUseBuildCache: pointers.Ptr(true), - expectedUseBuildCache: true, + JobName: jobName, }, + TargetEnvironments: []string{"anyenv"}, + BuildComponentImages: pipeline.EnvironmentBuildComponentImages{"anyenv": {}}, + RadixApplication: ra, } - for _, ts := range scenarios { - s.T().Run(ts.name, func(t *testing.T) { - s.setupTest() - rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).WithOverrideUseBuildCache(ts.jobOverrideUseBuildCache).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithBuildKit(pointers.Ptr(true)).WithBuildCache(ts.configUseBuildCache). - WithEnvironment("dev", "main"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName).WithDockerfileName(dockerFile).WithSourceFolder(sourceFolder)). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil), "CreatePreparePipelineConfigMapResponse") - s.Require().NoError(internaltest.CreateGitInfoConfigMapResponse(s.kubeClient, gitConfigMapName, appName, gitHash, gitTags), "CreateGitInfoConfigMapResponse") - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - PipelineType: "build-deploy", - Branch: "main", - JobName: rjName, - BuildKitImageBuilder: "anybuildkitimage:tag", - ImageTag: "anyimagetag", - ContainerRegistry: "anyregistry", - AppContainerRegistry: "anyappregistry", - Clustertype: "anyclustertype", - Clustername: "anyclustername", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - Builder: model.Builder{ResourcesLimitsMemory: "100M", ResourcesRequestsCPU: "50m", ResourcesRequestsMemory: "50M"}, - OverrideUseBuildCache: ts.jobOverrideUseBuildCache, - }, - RadixConfigMapName: prepareConfigMapName, - GitConfigMapName: gitConfigMapName, - } - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - s.Equal(pipeline.PipelineArguments.BuildKitImageBuilder, job.Spec.Template.Spec.Containers[0].Image) - expectedCommandElements := []string{ - "mkdir /var/tmp && cp /radix-private-image-hubs/.dockerconfigjson /home/build/auth.json && ", - fmt.Sprintf("/usr/bin/buildah login --username ${BUILDAH_USERNAME} --password ${BUILDAH_PASSWORD} %s && ", pipeline.PipelineArguments.ContainerRegistry), - } - if ts.expectedUseBuildCache { - expectedCommandElements = append(expectedCommandElements, fmt.Sprintf("/usr/bin/buildah login --username ${BUILDAH_CACHE_USERNAME} --password ${BUILDAH_CACHE_PASSWORD} %s && ", pipeline.PipelineArguments.AppContainerRegistry)) - } - expectedCommandElements = append(expectedCommandElements, []string{ - "/usr/bin/buildah build --storage-driver=overlay --isolation=chroot --jobs 0 --ulimit nofile=4096:4096 ", - fmt.Sprintf("--file %s%s ", "/workspace/path2/", dockerFile), - "--build-arg RADIX_GIT_COMMIT_HASH=\"${RADIX_GIT_COMMIT_HASH}\" ", - "--build-arg RADIX_GIT_TAGS=\"${RADIX_GIT_TAGS}\" ", - "--build-arg BRANCH=\"${BRANCH}\" ", - "--build-arg TARGET_ENVIRONMENTS=\"${TARGET_ENVIRONMENTS}\" ", - }...) - if ts.expectedUseBuildCache { - expectedCommandElements = append(expectedCommandElements, "--layers ") - expectedCommandElements = append(expectedCommandElements, fmt.Sprintf("--cache-to=%s/%s/cache ", pipeline.PipelineArguments.AppContainerRegistry, appName)) - expectedCommandElements = append(expectedCommandElements, fmt.Sprintf("--cache-from=%s/%s/cache ", pipeline.PipelineArguments.AppContainerRegistry, appName)) - } - expectedCommandElements = append(expectedCommandElements, "/workspace/path2/") - expectedCommand := []string{"/bin/bash", "-c", strings.Join(expectedCommandElements, "")} - s.Equal(expectedCommand, job.Spec.Template.Spec.Containers[0].Command) - expectedEnv := []corev1.EnvVar{ - {Name: "DOCKER_FILE_NAME", Value: dockerFile}, - {Name: "DOCKER_REGISTRY", Value: pipeline.PipelineArguments.ContainerRegistry}, - {Name: "IMAGE", Value: fmt.Sprintf("%s/%s-%s-%s:%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.ImageTag)}, - {Name: "CONTEXT", Value: "/workspace/path2/"}, - {Name: "PUSH", Value: ""}, - {Name: "AZURE_CREDENTIALS", Value: "/radix-image-builder/.azure/sp_credentials.json"}, - {Name: "SUBSCRIPTION_ID", Value: pipeline.PipelineArguments.SubscriptionId}, - {Name: "CLUSTERTYPE_IMAGE", Value: fmt.Sprintf("%s/%s-%s-%s:%s-%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustertype, pipeline.PipelineArguments.ImageTag)}, - {Name: "CLUSTERNAME_IMAGE", Value: fmt.Sprintf("%s/%s-%s-%s:%s-%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustername, pipeline.PipelineArguments.ImageTag)}, - {Name: "REPOSITORY_NAME", Value: fmt.Sprintf("%s-%s-%s", appName, envName, compName)}, - {Name: "CACHE", Value: "--no-cache"}, - {Name: "RADIX_ZONE", Value: pipeline.PipelineArguments.RadixZone}, - {Name: "BRANCH", Value: pipeline.PipelineArguments.Branch}, - {Name: "TARGET_ENVIRONMENTS", Value: "dev"}, - {Name: "RADIX_GIT_COMMIT_HASH", Value: gitHash}, - {Name: "RADIX_GIT_TAGS", Value: gitTags}, - {Name: "BUILDAH_USERNAME", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{Key: "username", LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}}}}, - {Name: "BUILDAH_PASSWORD", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{Key: "password", LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRServicePrincipleBuildahSecretName}}}}, - {Name: "BUILDAH_CACHE_USERNAME", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{Key: "username", LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}}}}, - {Name: "BUILDAH_CACHE_PASSWORD", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{Key: "password", LocalObjectReference: corev1.LocalObjectReference{Name: defaults.AzureACRTokenPasswordAppRegistrySecretName}}}}, - {Name: "REGISTRY_AUTH_FILE", Value: "/home/build/auth.json"}, - } - s.ElementsMatch(expectedEnv, job.Spec.Template.Spec.Containers[0].Env) - }) - } + jobBuilder := buildjobmock.NewMockJobsBuilder(gomock.NewController(s.T())) + jobBuilder.EXPECT().BuildJobs(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1) + m.On(buildJobFactoryMockMethodName, mock.Anything).Return(jobBuilder) + err := cli.Run(context.Background(), pipelineInfo) + s.NoError(err) + m.AssertExpectations(s.T()) } -func (s *buildTestSuite) Test_BuildJobSpec_BuildKit_PushImage() { - appName, rjName, compName, sourceFolder, dockerFile, envName := "anyapp", "anyrj", "c1", "../path1/./../../path2", "anydockerfile", "dev" - prepareConfigMapName := "preparecm" - rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithBuildKit(pointers.Ptr(true)). - WithEnvironment("dev", "main"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName).WithDockerfileName(dockerFile).WithSourceFolder(sourceFolder)). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - Branch: "main", - JobName: rjName, - BuildKitImageBuilder: "anybuildkitimage:tag", - ImageTag: "anyimagetag", - ContainerRegistry: "anyregistry", - AppContainerRegistry: "anyappregistry", - Clustertype: "anyclustertype", - Clustername: "anyclustername", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - PushImage: true, - Builder: model.Builder{ResourcesLimitsMemory: "100M", ResourcesRequestsCPU: "50m", ResourcesRequestsMemory: "50M"}, - }, - RadixConfigMapName: prepareConfigMapName, - } - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - s.Equal(pipeline.PipelineArguments.BuildKitImageBuilder, job.Spec.Template.Spec.Containers[0].Image) - expectedBuildCmd := strings.Join( - []string{ - "mkdir /var/tmp && cp /radix-private-image-hubs/.dockerconfigjson /home/build/auth.json && ", - fmt.Sprintf("/usr/bin/buildah login --username ${BUILDAH_USERNAME} --password ${BUILDAH_PASSWORD} %s && ", pipeline.PipelineArguments.ContainerRegistry), - fmt.Sprintf("/usr/bin/buildah login --username ${BUILDAH_CACHE_USERNAME} --password ${BUILDAH_CACHE_PASSWORD} %s && ", pipeline.PipelineArguments.AppContainerRegistry), - "/usr/bin/buildah build --storage-driver=overlay --isolation=chroot --jobs 0 --ulimit nofile=4096:4096 ", - fmt.Sprintf("--file %s%s ", "/workspace/path2/", dockerFile), - "--build-arg RADIX_GIT_COMMIT_HASH=\"${RADIX_GIT_COMMIT_HASH}\" ", - "--build-arg RADIX_GIT_TAGS=\"${RADIX_GIT_TAGS}\" ", - "--build-arg BRANCH=\"${BRANCH}\" ", - "--build-arg TARGET_ENVIRONMENTS=\"${TARGET_ENVIRONMENTS}\" ", - "--layers ", - fmt.Sprintf("--cache-to=%s/%s/cache ", pipeline.PipelineArguments.AppContainerRegistry, appName), - fmt.Sprintf("--cache-from=%s/%s/cache ", pipeline.PipelineArguments.AppContainerRegistry, appName), - fmt.Sprintf("--tag %s/%s-%s-%s:%s ", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.ImageTag), - fmt.Sprintf("--tag %s/%s-%s-%s:%s-%s ", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustertype, pipeline.PipelineArguments.ImageTag), - fmt.Sprintf("--tag %s/%s-%s-%s:%s-%s ", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustername, pipeline.PipelineArguments.ImageTag), - "/workspace/path2/ && ", - fmt.Sprintf("/usr/bin/buildah push --storage-driver=overlay %s/%s-%s-%s:%s && ", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.ImageTag), - fmt.Sprintf("/usr/bin/buildah push --storage-driver=overlay %s/%s-%s-%s:%s-%s && ", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustertype, pipeline.PipelineArguments.ImageTag), - fmt.Sprintf("/usr/bin/buildah push --storage-driver=overlay %s/%s-%s-%s:%s-%s", pipeline.PipelineArguments.ContainerRegistry, appName, envName, compName, pipeline.PipelineArguments.Clustername, pipeline.PipelineArguments.ImageTag), - }, - "", +func (s *buildTestSuite) Test_JobsBuilderCalledAndJobsCreated() { + const ( + appName = "anyapp" + jobName = "anyjob" + cloneUrl = "anycloneurl" ) - expectedCommand := []string{"/bin/bash", "-c", expectedBuildCmd} - s.Equal(expectedCommand, job.Spec.Template.Spec.Containers[0].Command) -} - -func (s *buildTestSuite) Test_BuildJobSpec_BuildKit_WithBuildSecrets() { - appName, rjName, compName, sourceFolder, dockerFile := "anyapp", "anyrj", "c1", "../path1/./../../path2", "anydockerfile" - prepareConfigMapName := "preparecm" - rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithBuildKit(pointers.Ptr(true)). - WithBuildSecrets("SECRET1", "SECRET2"). - WithEnvironment("dev", "main"). - WithComponent(utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(compName).WithDockerfileName(dockerFile).WithSourceFolder(sourceFolder)). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - s.Require().NoError(internaltest.CreateBuildSecret(s.kubeClient, appName, map[string][]byte{"SECRET1": nil, "SECRET2": nil})) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - Branch: "main", - JobName: rjName, - BuildKitImageBuilder: "anybuildkitimage:tag", - ImageTag: "anyimagetag", - ContainerRegistry: "anyregistry", - Clustertype: "anyclustertype", - Clustername: "anyclustername", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - Builder: model.Builder{ResourcesLimitsMemory: "100M", ResourcesRequestsCPU: "50m", ResourcesRequestsMemory: "50M"}, - }, - RadixConfigMapName: prepareConfigMapName, - } - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - s.Len(job.Spec.Template.Spec.Volumes, 10) - expectedVolumes := []corev1.Volume{ - {Name: git.BuildContextVolumeName}, - {Name: git.GitSSHKeyVolumeName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: git.GitSSHKeyVolumeName, DefaultMode: pointers.Ptr[int32](256)}}}, - {Name: defaults.AzureACRServicePrincipleSecretName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: defaults.AzureACRServicePrincipleSecretName}}}, - {Name: defaults.PrivateImageHubSecretName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: defaults.PrivateImageHubSecretName}}}, - {Name: RadixImageBuilderHomeVolumeName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(5, resource.Mega)}}}, - {Name: defaults.BuildSecretsName, VolumeSource: corev1.VolumeSource{Secret: &corev1.SecretVolumeSource{SecretName: defaults.BuildSecretsName}}}, - {Name: BuildKitRunVolumeName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, - {Name: BuildKitRootVolumeName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, - {Name: "tmp-build-c1-dev", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, - {Name: "var-build-c1-dev", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, - } - s.ElementsMatch(job.Spec.Template.Spec.Volumes, expectedVolumes) - s.Require().Len(job.Spec.Template.Spec.Containers, 1) - s.Len(job.Spec.Template.Spec.Containers[0].VolumeMounts, 9) - expectedVolumeMounts := []corev1.VolumeMount{ - {Name: git.BuildContextVolumeName, MountPath: git.Workspace, ReadOnly: false}, - {Name: defaults.AzureACRServicePrincipleSecretName, MountPath: "/radix-image-builder/.azure", ReadOnly: true}, - {Name: BuildKitRunVolumeName, MountPath: "/run", ReadOnly: false}, - {Name: BuildKitRootVolumeName, MountPath: "/root", ReadOnly: false}, - {Name: defaults.PrivateImageHubSecretName, MountPath: "/radix-private-image-hubs", ReadOnly: true}, - {Name: RadixImageBuilderHomeVolumeName, MountPath: "/home/build", ReadOnly: false}, - {Name: defaults.BuildSecretsName, MountPath: "/build-secrets", ReadOnly: true}, - {Name: "tmp-build-c1-dev", MountPath: "/tmp", ReadOnly: false}, - {Name: "var-build-c1-dev", MountPath: "/var", ReadOnly: false}, - } - s.ElementsMatch(job.Spec.Template.Spec.Containers[0].VolumeMounts, expectedVolumeMounts) - s.Equal(pipeline.PipelineArguments.BuildKitImageBuilder, job.Spec.Template.Spec.Containers[0].Image) - expectedBuildCmd := strings.Join( - []string{ - "mkdir /var/tmp && cp /radix-private-image-hubs/.dockerconfigjson /home/build/auth.json && ", - fmt.Sprintf("/usr/bin/buildah login --username ${BUILDAH_USERNAME} --password ${BUILDAH_PASSWORD} %s && ", pipeline.PipelineArguments.ContainerRegistry), - fmt.Sprintf("/usr/bin/buildah login --username ${BUILDAH_CACHE_USERNAME} --password ${BUILDAH_CACHE_PASSWORD} %s && ", pipeline.PipelineArguments.AppContainerRegistry), - "/usr/bin/buildah build --storage-driver=overlay --isolation=chroot --jobs 0 --ulimit nofile=4096:4096 ", - "--secret id=SECRET1,src=/build-secrets/SECRET1 --secret id=SECRET2,src=/build-secrets/SECRET2 ", - fmt.Sprintf("--file %s%s ", "/workspace/path2/", dockerFile), - "--build-arg RADIX_GIT_COMMIT_HASH=\"${RADIX_GIT_COMMIT_HASH}\" ", - "--build-arg RADIX_GIT_TAGS=\"${RADIX_GIT_TAGS}\" ", - "--build-arg BRANCH=\"${BRANCH}\" ", - "--build-arg TARGET_ENVIRONMENTS=\"${TARGET_ENVIRONMENTS}\" ", - "--layers ", - fmt.Sprintf("--cache-to=%s/%s/cache ", pipeline.PipelineArguments.AppContainerRegistry, appName), - fmt.Sprintf("--cache-from=%s/%s/cache ", pipeline.PipelineArguments.AppContainerRegistry, appName), - "/workspace/path2/", - }, - "", + var ( + buildSecrets []string = []string{"secret1", "secret2"} + env1Components []pipeline.BuildComponentImage = []pipeline.BuildComponentImage{{ComponentName: "env1comp1"}, {ComponentName: "env1comp2"}} + env2Components []pipeline.BuildComponentImage = []pipeline.BuildComponentImage{{ComponentName: "env2comp1"}, {ComponentName: "env2comp2"}} ) - expectedCommand := []string{"/bin/bash", "-c", expectedBuildCmd} - s.Equal(expectedCommand, job.Spec.Template.Spec.Containers[0].Command) -} - -func (s *buildTestSuite) Test_BuildJobSpec_BuildKit_RuntimeAffinity() { - appName, rjName, comp1Name, comp2Name := "anyapp", "anyrj", "c1", "c2" - prepareConfigMapName := "preparecm" - gitConfigMapName, gitHash, gitTags := "gitcm", "githash", "gittags" - rr := utils.ARadixRegistration().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() + rr := utils.ARadixRegistration().WithName(appName).WithCloneURL(cloneUrl).BuildRR() + ra := utils.ARadixApplication().WithBuildSecrets(buildSecrets...).BuildRA() + rj := utils.ARadixBuildDeployJob().WithJobName(jobName).WithAppName(appName).BuildRJ() _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithBuildKit(pointers.Ptr(true)). - WithEnvironment("dev", "main"). - WithComponents( - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(comp1Name), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName(comp2Name).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), - ). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - s.Require().NoError(internaltest.CreateGitInfoConfigMapResponse(s.kubeClient, gitConfigMapName, appName, gitHash, gitTags)) - pipeline := model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - PipelineType: "build-deploy", - Branch: "main", - JobName: rjName, - BuildKitImageBuilder: "anybuildkitimage:tag", - ImageTag: "anyimagetag", - ContainerRegistry: "anyregistry", - AppContainerRegistry: "anyappregistry", - Clustertype: "anyclustertype", - Clustername: "anyclustername", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - Builder: model.Builder{ResourcesLimitsMemory: "100M", ResourcesRequestsCPU: "50m", ResourcesRequestsMemory: "50M"}, - }, - RadixConfigMapName: prepareConfigMapName, - GitConfigMapName: gitConfigMapName, - } jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(2) - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 2) - - getJobByName := func(jobName string) (batchv1.Job, bool) { - return slice.FindFirst(jobs.Items, func(j batchv1.Job) bool { - return j.Labels[kube.RadixComponentLabel] == jobName - }) - } - - comp1job, ok := getJobByName(comp1Name) - s.True(ok) - expectedAffinity := &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{MatchExpressions: []corev1.NodeSelectorRequirement{ - {Key: kube.RadixJobNodeLabel, Operator: corev1.NodeSelectorOpExists}, - {Key: corev1.LabelOSStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorOS}}, - {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorArchitecture}}, - }}}}}} - s.Equal(expectedAffinity, comp1job.Spec.Template.Spec.Affinity) - - comp2job, ok := getJobByName(comp2Name) - s.True(ok) - expectedAffinity = &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{MatchExpressions: []corev1.NodeSelectorRequirement{ - {Key: kube.RadixJobNodeLabel, Operator: corev1.NodeSelectorOpExists}, - {Key: corev1.LabelOSStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorOS}}, - {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{string(radixv1.RuntimeArchitectureArm64)}}, - }}}}}} - s.Equal(expectedAffinity, comp2job.Spec.Template.Spec.Affinity) -} - -func (s *buildTestSuite) Test_BuildJobSpec_EnvConfigSrcAndImage() { - appName, envName1, envName2, envName3, envName4, rjName, buildBranch, jobPort := "anyapp", "dev1", "dev2", "dev3", "dev4", "anyrj", "anybranch", pointers.Ptr[int32](9999) - prepareConfigMapName := "preparecm" + jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).AnyTimes() + var m mock.Mock + cli := build.NewBuildStep(jobWaiter, build.WithBuildJobFactory(createbuildJobFactoryMock(&m))) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() - _, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - rj := utils.ARadixBuildDeployJob().WithJobName(rjName).WithAppName(appName).BuildRJ() - _, _ = s.radixClient.RadixV1().RadixJobs(utils.GetAppNamespace(appName)).Create(context.Background(), rj, metav1.CreateOptions{}) - ra := utils.NewRadixApplicationBuilder(). - WithAppName(appName). - WithEnvironment(envName1, buildBranch). - WithEnvironment(envName2, buildBranch). - WithEnvironment(envName3, buildBranch). - WithEnvironment(envName4, buildBranch). - WithComponents( - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("component-1").WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile"). - WithEnvironmentConfigs( - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName1), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image2:some-tag"), - ), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("component-2").WithDockerfileName("client.Dockerfile"). - WithEnvironmentConfigs( - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName1), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image3:some-tag"), - ), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("component-3").WithSourceFolder("./client/"). - WithEnvironmentConfigs( - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName1), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image4:some-tag"), - ), - utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("component-4").WithImage("some-image1:some-tag"). - WithEnvironmentConfigs( - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName1), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), - utils.NewComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image5:some-tag"), - ), - ). - WithJobComponents( - utils.NewApplicationJobComponentBuilder().WithPort("any", 8080).WithName("job-1").WithSourceFolder("./client/").WithDockerfileName("client.Dockerfile").WithSchedulerPort(jobPort). - WithEnvironmentConfigs( - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName1), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image2:some-tag"), - ), - utils.NewApplicationJobComponentBuilder().WithPort("any", 8080).WithName("job-2").WithDockerfileName("client.Dockerfile").WithSchedulerPort(jobPort). - WithEnvironmentConfigs( - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName1), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image3:some-tag"), - ), - utils.NewApplicationJobComponentBuilder().WithPort("any", 8080).WithName("job-3").WithSourceFolder("./client/").WithSchedulerPort(jobPort). - WithEnvironmentConfigs( - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName1), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image4:some-tag"), - ), - utils.NewApplicationJobComponentBuilder().WithPort("any", 8080).WithName("job-4").WithImage("some-image1:some-tag").WithSchedulerPort(jobPort). - WithEnvironmentConfigs( - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName1), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName2).WithSourceFolder("./client2/"), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName3).WithDockerfileName("client2.Dockerfile"), - utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envName4).WithImage("some-image5:some-tag"), - ), - ). - BuildRA() - s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) - pipeline := model.PipelineInfo{ + pipelineInfo := &model.PipelineInfo{ PipelineArguments: model.PipelineArguments{ - PipelineType: "build-deploy", - Branch: buildBranch, - JobName: rjName, - ImageTag: "imgtag", - ContainerRegistry: "registry", - Clustertype: "clustertype", - Clustername: "clustername", - GitCloneNsLookupImage: "any", - GitCloneGitImage: "any", - GitCloneBashImage: "any", - }, - RadixConfigMapName: prepareConfigMapName, - } - - applyStep := applyconfig.NewApplyConfigStep() - applyStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) - jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := NewBuildStep(jobWaiter) - buildStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - deployStep.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - - s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) - s.Require().NoError(buildStep.Run(context.Background(), &pipeline)) - s.Require().NoError(deployStep.Run(context.Background(), &pipeline)) - jobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(jobs.Items, 1) - job := jobs.Items[0] - - // Check build containers - type jobContainerSpec struct { - Name string - Docker string - Image string - Context string - } - imageNameFunc := func(s string) string { - return fmt.Sprintf("%s/%s-%s:%s", pipeline.PipelineArguments.ContainerRegistry, appName, s, pipeline.PipelineArguments.ImageTag) - } - expectedJobContainers := []jobContainerSpec{ - {Name: "build-component-1-dev1", Docker: "client.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev1-component-1")}, - {Name: "build-component-2-dev1", Docker: "client.Dockerfile", Context: "/workspace/", Image: imageNameFunc("dev1-component-2")}, - {Name: "build-component-3-dev1", Docker: "Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev1-component-3")}, - {Name: "build-component-1-dev2", Docker: "client.Dockerfile", Context: "/workspace/client2/", Image: imageNameFunc("dev2-component-1")}, - {Name: "build-component-2-dev2", Docker: "client.Dockerfile", Context: "/workspace/client2/", Image: imageNameFunc("dev2-component-2")}, - {Name: "build-component-3-dev2", Docker: "Dockerfile", Context: "/workspace/client2/", Image: imageNameFunc("dev2-component-3")}, - {Name: "build-component-4-dev2", Docker: "Dockerfile", Context: "/workspace/client2/", Image: imageNameFunc("dev2-component-4")}, - {Name: "build-component-1-dev3", Docker: "client2.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev3-component-1")}, - {Name: "build-component-2-dev3", Docker: "client2.Dockerfile", Context: "/workspace/", Image: imageNameFunc("dev3-component-2")}, - {Name: "build-component-3-dev3", Docker: "client2.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev3-component-3")}, - {Name: "build-component-4-dev3", Docker: "client2.Dockerfile", Context: "/workspace/", Image: imageNameFunc("dev3-component-4")}, - {Name: "build-job-1-dev1", Docker: "client.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev1-job-1")}, - {Name: "build-job-2-dev1", Docker: "client.Dockerfile", Context: "/workspace/", Image: imageNameFunc("dev1-job-2")}, - {Name: "build-job-3-dev1", Docker: "Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev1-job-3")}, - {Name: "build-job-1-dev2", Docker: "client.Dockerfile", Context: "/workspace/client2/", Image: imageNameFunc("dev2-job-1")}, - {Name: "build-job-2-dev2", Docker: "client.Dockerfile", Context: "/workspace/client2/", Image: imageNameFunc("dev2-job-2")}, - {Name: "build-job-3-dev2", Docker: "Dockerfile", Context: "/workspace/client2/", Image: imageNameFunc("dev2-job-3")}, - {Name: "build-job-4-dev2", Docker: "Dockerfile", Context: "/workspace/client2/", Image: imageNameFunc("dev2-job-4")}, - {Name: "build-job-1-dev3", Docker: "client2.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev3-job-1")}, - {Name: "build-job-2-dev3", Docker: "client2.Dockerfile", Context: "/workspace/", Image: imageNameFunc("dev3-job-2")}, - {Name: "build-job-3-dev3", Docker: "client2.Dockerfile", Context: "/workspace/client/", Image: imageNameFunc("dev3-job-3")}, - {Name: "build-job-4-dev3", Docker: "client2.Dockerfile", Context: "/workspace/", Image: imageNameFunc("dev3-job-4")}, - } - actualJobContainers := slice.Map(job.Spec.Template.Spec.Containers, func(c corev1.Container) jobContainerSpec { - getEnv := func(env string) string { - if i := slice.FindIndex(c.Env, func(e corev1.EnvVar) bool { return e.Name == env }); i >= 0 { - return c.Env[i].Value - } - return "" - } - return jobContainerSpec{ - Name: c.Name, - Docker: getEnv("DOCKER_FILE_NAME"), - Image: getEnv("IMAGE"), - Context: getEnv("CONTEXT"), - } - }) - s.ElementsMatch(expectedJobContainers, actualJobContainers) - - // Check RadixDeployment component and job images - rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName1)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd := rds.Items[0] - type deployComponentSpec struct { - Name string - Image string - } - expectedDeployComponents := []deployComponentSpec{ - {Name: "component-1", Image: imageNameFunc("dev1-component-1")}, - {Name: "component-2", Image: imageNameFunc("dev1-component-2")}, - {Name: "component-3", Image: imageNameFunc("dev1-component-3")}, - {Name: "component-4", Image: "some-image1:some-tag"}, - } - actualDeployComponents := slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - expectedDeployComponents = []deployComponentSpec{ - {Name: "job-1", Image: imageNameFunc("dev1-job-1")}, - {Name: "job-2", Image: imageNameFunc("dev1-job-2")}, - {Name: "job-3", Image: imageNameFunc("dev1-job-3")}, - {Name: "job-4", Image: "some-image1:some-tag"}, - } - actualDeployComponents = slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - - // Check RadixDeployment component and job images - rds, _ = s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName2)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd = rds.Items[0] - expectedDeployComponents = []deployComponentSpec{ - {Name: "component-1", Image: imageNameFunc("dev2-component-1")}, - {Name: "component-2", Image: imageNameFunc("dev2-component-2")}, - {Name: "component-3", Image: imageNameFunc("dev2-component-3")}, - {Name: "component-4", Image: imageNameFunc("dev2-component-4")}, - } - actualDeployComponents = slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - expectedDeployComponents = []deployComponentSpec{ - {Name: "job-1", Image: imageNameFunc("dev2-job-1")}, - {Name: "job-2", Image: imageNameFunc("dev2-job-2")}, - {Name: "job-3", Image: imageNameFunc("dev2-job-3")}, - {Name: "job-4", Image: imageNameFunc("dev2-job-4")}, - } - actualDeployComponents = slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - - // Check RadixDeployment component and job images - rds, _ = s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName3)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd = rds.Items[0] - expectedDeployComponents = []deployComponentSpec{ - {Name: "component-1", Image: imageNameFunc("dev3-component-1")}, - {Name: "component-2", Image: imageNameFunc("dev3-component-2")}, - {Name: "component-3", Image: imageNameFunc("dev3-component-3")}, - {Name: "component-4", Image: imageNameFunc("dev3-component-4")}, - } - actualDeployComponents = slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - expectedDeployComponents = []deployComponentSpec{ - {Name: "job-1", Image: imageNameFunc("dev3-job-1")}, - {Name: "job-2", Image: imageNameFunc("dev3-job-2")}, - {Name: "job-3", Image: imageNameFunc("dev3-job-3")}, - {Name: "job-4", Image: imageNameFunc("dev3-job-4")}, - } - actualDeployComponents = slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - - // Check RadixDeployment component and job images - rds, _ = s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envName4)).List(context.Background(), metav1.ListOptions{}) - s.Require().Len(rds.Items, 1) - rd = rds.Items[0] - expectedDeployComponents = []deployComponentSpec{ - {Name: "component-1", Image: "some-image2:some-tag"}, - {Name: "component-2", Image: "some-image3:some-tag"}, - {Name: "component-3", Image: "some-image4:some-tag"}, - {Name: "component-4", Image: "some-image5:some-tag"}, - } - actualDeployComponents = slice.Map(rd.Spec.Components, func(c radixv1.RadixDeployComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} - }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) - expectedDeployComponents = []deployComponentSpec{ - {Name: "job-1", Image: "some-image2:some-tag"}, - {Name: "job-2", Image: "some-image3:some-tag"}, - {Name: "job-3", Image: "some-image4:some-tag"}, - {Name: "job-4", Image: "some-image5:some-tag"}, - } - actualDeployComponents = slice.Map(rd.Spec.Jobs, func(c radixv1.RadixDeployJobComponent) deployComponentSpec { - return deployComponentSpec{Name: c.Name, Image: c.Image} + JobName: jobName, + }, + TargetEnvironments: []string{"env1", "env2"}, + BuildComponentImages: pipeline.EnvironmentBuildComponentImages{"env1": env1Components, "env2": env2Components}, + RadixApplication: ra, + GitCommitHash: "anycommithash", + GitTags: "anygittags", + BuildSecret: &corev1.Secret{Data: map[string][]byte{ + buildSecrets[0]: []byte("secretdata"), + buildSecrets[1]: []byte("secretdata"), + }}, + } + + jobBuilder := buildjobmock.NewMockJobsBuilder(gomock.NewController(s.T())) + jobsToReturn := []batchv1.Job{ + {ObjectMeta: metav1.ObjectMeta{Name: "job1", Namespace: utils.GetAppNamespace(appName)}}, + {ObjectMeta: metav1.ObjectMeta{Name: "job2", Namespace: utils.GetAppNamespace(appName)}}, + } + jobBuilder.EXPECT().BuildJobs( + gomock.Any(), + pipelineInfo.PipelineArguments, + cloneUrl, + pipelineInfo.GitCommitHash, + pipelineInfo.GitTags, + gomock.InAnyOrder(slices.Concat(env1Components, env2Components)), + gomock.InAnyOrder(buildSecrets), + ).Return(jobsToReturn).Times(1) + m.On(buildJobFactoryMockMethodName, mock.Anything).Return(jobBuilder) + err := cli.Run(context.Background(), pipelineInfo) + s.NoError(err) + m.AssertExpectations(s.T()) + expectedOwnerRef, _ := jobutil.GetOwnerReferenceOfJob(context.Background(), s.radixClient, utils.GetAppNamespace(appName), pipelineInfo.PipelineArguments.JobName) + expectedJobs := slice.Map(jobsToReturn, func(j batchv1.Job) batchv1.Job { + newjob := j.DeepCopy() + newjob.OwnerReferences = expectedOwnerRef + return *newjob }) - s.ElementsMatch(expectedDeployComponents, actualDeployComponents) + actualJobs, _ := s.kubeClient.BatchV1().Jobs(utils.GetAppNamespace(appName)).List(context.Background(), metav1.ListOptions{}) + s.ElementsMatch(expectedJobs, actualJobs.Items) } diff --git a/pipeline-runner/steps/build/step.go b/pipeline-runner/steps/build/step.go index 33c1dbfbf..a8347ad23 100644 --- a/pipeline-runner/steps/build/step.go +++ b/pipeline-runner/steps/build/step.go @@ -2,12 +2,16 @@ package build import ( "context" + "errors" "fmt" + "slices" "strings" + internalbuild "github.com/equinor/radix-operator/pipeline-runner/internal/jobs/build" internalwait "github.com/equinor/radix-operator/pipeline-runner/internal/wait" "github.com/equinor/radix-operator/pipeline-runner/model" - jobUtil "github.com/equinor/radix-operator/pkg/apis/job" + "github.com/equinor/radix-operator/pkg/apis/defaults" + jobutil "github.com/equinor/radix-operator/pkg/apis/job" "github.com/equinor/radix-operator/pkg/apis/kube" "github.com/equinor/radix-operator/pkg/apis/pipeline" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" @@ -15,25 +19,50 @@ import ( radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" monitoring "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned" "github.com/rs/zerolog/log" + "golang.org/x/exp/maps" "golang.org/x/sync/errgroup" batchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) +type BuildJobFactory func(useBuildKit bool) internalbuild.JobsBuilder + // BuildStepImplementation Step to build docker image type BuildStepImplementation struct { stepType pipeline.StepType model.DefaultStepImplementation - jobWaiter internalwait.JobCompletionWaiter + jobWaiter internalwait.JobCompletionWaiter + buildJobFactory BuildJobFactory +} + +type Option func(step *BuildStepImplementation) + +func WithBuildJobFactory(factory BuildJobFactory) Option { + return func(step *BuildStepImplementation) { + step.buildJobFactory = factory + } +} + +func defaultBuildJobFactory(useBuildKit bool) internalbuild.JobsBuilder { + if useBuildKit { + return internalbuild.NewBuildKit() + } + + return internalbuild.NewACR() } // NewBuildStep Constructor. // jobWaiter is optional and will be set by Init(...) function if nil. -func NewBuildStep(jobWaiter internalwait.JobCompletionWaiter) model.Step { +func NewBuildStep(jobWaiter internalwait.JobCompletionWaiter, options ...Option) model.Step { step := &BuildStepImplementation{ - stepType: pipeline.BuildStep, - jobWaiter: jobWaiter, + stepType: pipeline.BuildStep, + jobWaiter: jobWaiter, + buildJobFactory: defaultBuildJobFactory, + } + + for _, o := range options { + o(step) } return step @@ -78,20 +107,35 @@ func (step *BuildStepImplementation) Run(ctx context.Context, pipelineInfo *mode log.Ctx(ctx).Info().Msgf("Building app %s for branch %s and commit %s", step.GetAppName(), branch, commitID) - namespace := utils.GetAppNamespace(step.GetAppName()) - buildSecrets, err := getBuildSecretsAsVariables(pipelineInfo) - if err != nil { + if err := step.validateBuildSecrets(pipelineInfo); err != nil { return err } - jobs, err := step.buildContainerImageBuildingJobs(ctx, pipelineInfo, buildSecrets) - if err != nil { - return err + jobs := step.getBuildJobs(pipelineInfo) + namespace := utils.GetAppNamespace(step.GetAppName()) + return step.applyBuildJobs(ctx, pipelineInfo, jobs, namespace) +} + +func (step *BuildStepImplementation) getBuildJobs(pipelineInfo *model.PipelineInfo) []batchv1.Job { + rr := step.GetRegistration() + var secrets []string + if pipelineInfo.RadixApplication.Spec.Build != nil { + secrets = pipelineInfo.RadixApplication.Spec.Build.Secrets } - return step.createACRBuildJobs(ctx, pipelineInfo, jobs, namespace) + imagesToBuild := slices.Concat(maps.Values(pipelineInfo.BuildComponentImages)...) + return step.buildJobFactory(pipelineInfo.IsUsingBuildKit()). + BuildJobs( + pipelineInfo.IsUsingBuildCache(), + pipelineInfo.PipelineArguments, + rr.Spec.CloneURL, + pipelineInfo.GitCommitHash, + pipelineInfo.GitTags, + imagesToBuild, + secrets, + ) } -func (step *BuildStepImplementation) createACRBuildJobs(ctx context.Context, pipelineInfo *model.PipelineInfo, jobs []*batchv1.Job, namespace string) error { +func (step *BuildStepImplementation) applyBuildJobs(ctx context.Context, pipelineInfo *model.PipelineInfo, jobs []batchv1.Job, namespace string) error { ownerReference, err := step.getJobOwnerReferences(ctx, pipelineInfo, namespace) if err != nil { return err @@ -99,18 +143,17 @@ func (step *BuildStepImplementation) createACRBuildJobs(ctx context.Context, pip g := errgroup.Group{} for _, job := range jobs { - job := job g.Go(func() error { logger := log.Ctx(ctx).With().Str("job", job.Name).Logger() job.OwnerReferences = ownerReference - jobDescription := step.getJobDescription(job) + jobDescription := step.getJobDescription(&job) logger.Info().Msgf("Apply %s", jobDescription) - job, err = step.GetKubeclient().BatchV1().Jobs(namespace).Create(context.Background(), job, metav1.CreateOptions{}) + createdJob, err := step.GetKubeclient().BatchV1().Jobs(namespace).Create(context.Background(), &job, metav1.CreateOptions{}) if err != nil { logger.Error().Err(err).Msgf("failed %s", jobDescription) return err } - return step.jobWaiter.Wait(job) + return step.jobWaiter.Wait(createdJob) }) } return g.Wait() @@ -121,7 +164,7 @@ func (step *BuildStepImplementation) getJobOwnerReferences(ctx context.Context, if pipelineInfo.PipelineArguments.Debug { return nil, nil } - ownerReference, err := jobUtil.GetOwnerReferenceOfJob(ctx, step.GetRadixclient(), namespace, pipelineInfo.PipelineArguments.JobName) + ownerReference, err := jobutil.GetOwnerReferenceOfJob(ctx, step.GetRadixclient(), namespace, pipelineInfo.PipelineArguments.JobName) if err != nil { return nil, err } @@ -141,3 +184,21 @@ func (step *BuildStepImplementation) getJobDescription(job *batchv1.Job) string } return builder.String() } + +func (*BuildStepImplementation) validateBuildSecrets(pipelineInfo *model.PipelineInfo) error { + if pipelineInfo.RadixApplication.Spec.Build == nil || len(pipelineInfo.RadixApplication.Spec.Build.Secrets) == 0 { + return nil + } + + if pipelineInfo.BuildSecret == nil { + return errors.New("build secrets has not been set") + } + + for _, secretName := range pipelineInfo.RadixApplication.Spec.Build.Secrets { + if secretValue, ok := pipelineInfo.BuildSecret.Data[secretName]; !ok || strings.EqualFold(string(secretValue), defaults.BuildSecretDefaultData) { + return fmt.Errorf("build secret %s has not been set", secretName) + } + } + + return nil +} diff --git a/pipeline-runner/steps/deploy/step.go b/pipeline-runner/steps/deploy/step.go index 73d6c5478..d78ddb3a4 100644 --- a/pipeline-runner/steps/deploy/step.go +++ b/pipeline-runner/steps/deploy/step.go @@ -72,10 +72,7 @@ func (cli *DeployStepImplementation) deploy(ctx context.Context, pipelineInfo *m } func (cli *DeployStepImplementation) deployToEnv(ctx context.Context, appName, envName string, pipelineInfo *model.PipelineInfo) error { - defaultEnvVars, err := getDefaultEnvVars(pipelineInfo) - if err != nil { - return fmt.Errorf("failed to retrieve default env vars for RadixDeployment in app %s. %v", appName, err) - } + 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 @@ -135,7 +132,7 @@ func (cli *DeployStepImplementation) deployToEnv(ctx context.Context, appName, e return nil } -func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) (radixv1.EnvVarsMap, error) { +func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) radixv1.EnvVarsMap { gitCommitHash := pipelineInfo.GitCommitHash gitTags := pipelineInfo.GitTags @@ -143,5 +140,5 @@ func getDefaultEnvVars(pipelineInfo *model.PipelineInfo) (radixv1.EnvVarsMap, er envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = gitCommitHash envVarsMap[defaults.RadixGitTagsEnvironmentVariable] = gitTags - return envVarsMap, nil + return envVarsMap } diff --git a/pipeline-runner/steps/deploy/step_test.go b/pipeline-runner/steps/deploy/step_test.go index 380cb2ace..a84909672 100644 --- a/pipeline-runner/steps/deploy/step_test.go +++ b/pipeline-runner/steps/deploy/step_test.go @@ -8,114 +8,86 @@ import ( "testing" "github.com/equinor/radix-common/utils/pointers" + internaltest "github.com/equinor/radix-operator/pipeline-runner/internal/test" "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/pkg/apis/config/dnsalias" "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" commonTest "github.com/equinor/radix-operator/pkg/apis/test" + "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" - "github.com/stretchr/testify/require" - secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake" - - "github.com/equinor/radix-operator/pipeline-runner/model" - application "github.com/equinor/radix-operator/pkg/apis/applicationconfig" - "github.com/equinor/radix-operator/pkg/apis/kube" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - "github.com/equinor/radix-operator/pkg/apis/utils" monitoring "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned" - "github.com/stretchr/testify/assert" + 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" - kubernetes "k8s.io/client-go/kubernetes/fake" -) - -const ( - anyAppName = "any-app" - anyJobName = "any-job-name" - anyImageTag = "anytag" - anyCommitID = "4faca8595c5283a9d0f17a623b9255a0d9866a2e" - anyGitTags = "some tags go here" + kubefake "k8s.io/client-go/kubernetes/fake" ) -func setupTest(t *testing.T) (*kubernetes.Clientset, *kube.Kube, *radix.Clientset, commonTest.Utils) { - // Setup - kubeclient := kubernetes.NewSimpleClientset() - radixclient := radix.NewSimpleClientset() - kedaClient := kedafake.NewSimpleClientset() - secretproviderclient := secretproviderfake.NewSimpleClientset() - testUtils := commonTest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient) - err := testUtils.CreateClusterPrerequisites("AnyClusterName", "0.0.0.0", "anysubid") - require.NoError(t, err) - kubeUtil, _ := kube.New(kubeclient, radixclient, kedaClient, secretproviderclient) - - return kubeclient, kubeUtil, radixclient, testUtils +func Test_RunDeployTestSuite(t *testing.T) { + suite.Run(t, new(deployTestSuite)) } -func TestDeploy_BranchIsNotMapped_ShouldSkip(t *testing.T) { - kubeclient, kubeUtil, radixclient, _ := setupTest(t) - - anyBranch := "master" - anyEnvironment := "dev" - anyComponentName := "app" - anyNoMappedBranch := "feature" - - rr := utils.ARadixRegistration(). - WithName(anyAppName). - BuildRR() - - ra := utils.NewRadixApplicationBuilder(). - WithAppName(anyAppName). - WithEnvironment(anyEnvironment, anyBranch). - WithComponents( - utils.AnApplicationComponent(). - WithName(anyComponentName)). - BuildRA() - - cli := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - cli.Init(context.Background(), kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) +type deployTestSuite struct { + suite.Suite + kubeClient *kubefake.Clientset + radixClient *radix.Clientset + kedaClient *kedafake.Clientset + promClient *prometheusfake.Clientset + kubeUtil *kube.Kube + ctrl *gomock.Controller +} - targetEnvs := application.GetTargetEnvironments(anyNoMappedBranch, ra) +func (s *deployTestSuite) 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 *deployTestSuite) Test_EmptyTargetEnvironments_SkipDeployment() { + appName := "anyappname" + rr := utils.ARadixRegistration().WithName(appName).BuildRR() pipelineInfo := &model.PipelineInfo{ - PipelineArguments: model.PipelineArguments{ - JobName: anyJobName, - ImageTag: anyImageTag, - Branch: anyNoMappedBranch, - CommitID: anyCommitID, - }, - TargetEnvironments: targetEnvs, + 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) - require.NoError(t, err) - radixJobList, err := radixclient.RadixV1().RadixJobs(utils.GetAppNamespace(anyAppName)).List(context.Background(), metav1.ListOptions{}) - assert.NoError(t, err) - assert.Empty(t, radixJobList.Items) + s.Require().NoError(err) } -func TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists(t *testing.T) { - kubeclient, kubeUtil, radixclient, _ := setupTest(t) - +func (s *deployTestSuite) TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists() { + appName, envName, branch, jobName, imageTag, commitId, gitTags := "anyapp", "dev", "master", "anyjobname", "anyimagetag", "anycommit", "gittags" rr := utils.ARadixRegistration(). - WithName(anyAppName). + WithName(appName). BuildRR() - certificateVerification := v1.VerificationTypeOptional + certificateVerification := radixv1.VerificationTypeOptional ra := utils.NewRadixApplicationBuilder(). - WithAppName(anyAppName). - WithEnvironment("dev", "master"). + WithAppName(appName). + WithEnvironment(envName, branch). WithEnvironment("prod", ""). - WithDNSAppAlias("dev", "app"). + WithDNSAppAlias(envName, "app"). WithComponents( utils.AnApplicationComponent(). WithName("app"). WithPublicPort("http"). WithPort("http", 8080). WithAuthentication( - &v1.Authentication{ - ClientCertificate: &v1.ClientCertificate{ + &radixv1.Authentication{ + ClientCertificate: &radixv1.ClientCertificate{ PassCertificateToUpstream: pointers.Ptr(true), }, }, @@ -125,10 +97,10 @@ func TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists(t WithEnvironment("prod"). WithReplicas(commonTest.IntPtr(4)), utils.AnEnvironmentConfig(). - WithEnvironment("dev"). + WithEnvironment(envName). WithAuthentication( - &v1.Authentication{ - ClientCertificate: &v1.ClientCertificate{ + &radixv1.Authentication{ + ClientCertificate: &radixv1.ClientCertificate{ Verification: &certificateVerification, PassCertificateToUpstream: pointers.Ptr(false), }, @@ -140,15 +112,15 @@ func TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists(t WithPublicPort(""). WithPort("http", 6379). WithAuthentication( - &v1.Authentication{ - ClientCertificate: &v1.ClientCertificate{ + &radixv1.Authentication{ + ClientCertificate: &radixv1.ClientCertificate{ PassCertificateToUpstream: pointers.Ptr(true), }, }, ). WithEnvironmentConfigs( utils.AnEnvironmentConfig(). - WithEnvironment("dev"). + WithEnvironment(envName). WithEnvironmentVariable("DB_HOST", "db-dev"). WithEnvironmentVariable("DB_PORT", "1234"). WithResource(map[string]string{ @@ -175,117 +147,152 @@ func TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists(t WithEnvironmentVariable("DB_PORT", "9876"))). BuildRA() - // Prometheus don´t contain any fake - cli := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - cli.Init(context.Background(), kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) - - dnsConfig := dnsalias.DNSConfig{ - DNSZone: "dev.radix.equinor.com", - ReservedAppDNSAliases: dnsalias.AppReservedDNSAlias{"api": "radix-api"}, - ReservedDNSAliases: []string{"grafana"}, - } - applicationConfig := application.NewApplicationConfig(kubeclient, kubeUtil, radixclient, rr, ra, &dnsConfig) - targetEnvs := application.GetTargetEnvironments("master", ra) - pipelineInfo := &model.PipelineInfo{ PipelineArguments: model.PipelineArguments{ - JobName: anyJobName, - ImageTag: anyImageTag, - Branch: "master", - CommitID: anyCommitID, + JobName: jobName, + ImageTag: imageTag, + Branch: branch, }, - TargetEnvironments: targetEnvs, - GitCommitHash: anyCommitID, - GitTags: anyGitTags, + RadixApplication: ra, + TargetEnvironments: []string{envName}, + GitCommitHash: commitId, + GitTags: gitTags, } - gitCommitHash := pipelineInfo.GitCommitHash - gitTags := pipelineInfo.GitTags - - pipelineInfo.SetApplicationConfig(applicationConfig) - pipelineInfo.SetGitAttributes(gitCommitHash, 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 := deploy.NewDeployStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) err := cli.Run(context.Background(), pipelineInfo) - require.NoError(t, err) - rds, _ := radixclient.RadixV1().RadixDeployments("any-app-dev").List(context.Background(), metav1.ListOptions{}) - - t.Run("validate deploy", func(t *testing.T) { - assert.NoError(t, err) - assert.True(t, len(rds.Items) > 0) + 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"]) }) - rdNameDev := rds.Items[0].Name - - t.Run("validate deployment exist in only the namespace of the modified branch", func(t *testing.T) { - rdDev, err := radixclient.RadixV1().RadixDeployments("any-app-dev").Get(context.Background(), rdNameDev, metav1.GetOptions{}) - assert.NoError(t, err) - assert.NotNil(t, rdDev) - - _, err = radixclient.RadixV1().RadixDeployments("any-app-prod").Get(context.Background(), rdNameDev, metav1.GetOptions{}) - assert.Error(t, err) - }) - - t.Run("validate deployment environment variables", func(t *testing.T) { - rdDev, _ := radixclient.RadixV1().RadixDeployments("any-app-dev").Get(context.Background(), rdNameDev, metav1.GetOptions{}) - assert.Equal(t, 2, len(rdDev.Spec.Components)) - assert.Equal(t, 4, len(rdDev.Spec.Components[1].EnvironmentVariables)) - assert.Equal(t, "db-dev", rdDev.Spec.Components[1].EnvironmentVariables["DB_HOST"]) - assert.Equal(t, "1234", rdDev.Spec.Components[1].EnvironmentVariables["DB_PORT"]) - assert.Equal(t, anyCommitID, rdDev.Spec.Components[1].EnvironmentVariables[defaults.RadixCommitHashEnvironmentVariable]) - assert.Equal(t, anyGitTags, rdDev.Spec.Components[1].EnvironmentVariables[defaults.RadixGitTagsEnvironmentVariable]) - assert.NotEmpty(t, rdDev.Annotations[kube.RadixBranchAnnotation]) - assert.NotEmpty(t, rdDev.Labels[kube.RadixCommitLabel]) - assert.NotEmpty(t, rdDev.Labels["radix-job-name"]) - assert.Equal(t, "master", rdDev.Annotations[kube.RadixBranchAnnotation]) - assert.Equal(t, anyCommitID, rdDev.Labels[kube.RadixCommitLabel]) - assert.Equal(t, anyJobName, rdDev.Labels["radix-job-name"]) - }) - - t.Run("validate authentication variable", func(t *testing.T) { - rdDev, _ := radixclient.RadixV1().RadixDeployments("any-app-dev").Get(context.Background(), rdNameDev, metav1.GetOptions{}) - - x0 := &v1.Authentication{ - ClientCertificate: &v1.ClientCertificate{ + s.Run("validate authentication variable", func() { + x0 := &radixv1.Authentication{ + ClientCertificate: &radixv1.ClientCertificate{ Verification: &certificateVerification, PassCertificateToUpstream: pointers.Ptr(false), }, } - x1 := &v1.Authentication{ - ClientCertificate: &v1.ClientCertificate{ + x1 := &radixv1.Authentication{ + ClientCertificate: &radixv1.ClientCertificate{ PassCertificateToUpstream: pointers.Ptr(true), }, } - assert.NotNil(t, rdDev.Spec.Components[0].Authentication) - assert.NotNil(t, rdDev.Spec.Components[0].Authentication.ClientCertificate) - assert.Equal(t, x0, rdDev.Spec.Components[0].Authentication) + s.NotNil(rdDev.Spec.Components[0].Authentication) + s.NotNil(rdDev.Spec.Components[0].Authentication.ClientCertificate) + s.Equal(x0, rdDev.Spec.Components[0].Authentication) - assert.NotNil(t, rdDev.Spec.Components[1].Authentication) - assert.NotNil(t, rdDev.Spec.Components[1].Authentication.ClientCertificate) - assert.Equal(t, x1, rdDev.Spec.Components[1].Authentication) + s.NotNil(rdDev.Spec.Components[1].Authentication) + s.NotNil(rdDev.Spec.Components[1].Authentication.ClientCertificate) + s.Equal(x1, rdDev.Spec.Components[1].Authentication) }) - t.Run("validate dns app alias", func(t *testing.T) { - rdDev, _ := radixclient.RadixV1().RadixDeployments("any-app-dev").Get(context.Background(), rdNameDev, metav1.GetOptions{}) - assert.True(t, rdDev.Spec.Components[0].DNSAppAlias) - assert.False(t, rdDev.Spec.Components[1].DNSAppAlias) + s.Run("validate dns app alias", func() { + s.True(rdDev.Spec.Components[0].DNSAppAlias) + s.False(rdDev.Spec.Components[1].DNSAppAlias) }) - t.Run("validate resources", func(t *testing.T) { - rdDev, _ := radixclient.RadixV1().RadixDeployments("any-app-dev").Get(context.Background(), rdNameDev, metav1.GetOptions{}) - - fmt.Print(rdDev.Spec.Components[0].Resources) - fmt.Print(rdDev.Spec.Components[1].Resources) - assert.NotNil(t, rdDev.Spec.Components[1].Resources) - assert.Equal(t, "128Mi", rdDev.Spec.Components[1].Resources.Limits["memory"]) - assert.Equal(t, "500m", rdDev.Spec.Components[1].Resources.Limits["cpu"]) + s.Run("validate resources", func() { + s.NotNil(rdDev.Spec.Components[1].Resources) + s.Equal("128Mi", rdDev.Spec.Components[1].Resources.Limits["memory"]) + s.Equal("500m", rdDev.Spec.Components[1].Resources.Limits["cpu"]) }) +} +func (s *deployTestSuite) Test_RadixConfigHashAnnotation() { + anyAppName := "anyapp" + rr := utils.ARadixRegistration(). + WithName(anyAppName). + BuildRR() + + ra := utils.NewRadixApplicationBuilder(). + WithAppName(anyAppName). + WithEnvironment("dev", "master"). + WithComponents(utils.AnApplicationComponent().WithName("app")). + BuildRA() + + 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 := deploy.NewDeployStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, &monitoring.Clientset{}, rr) + + pipelineInfo := &model.PipelineInfo{ + RadixApplication: ra, + TargetEnvironments: []string{"dev"}, + } + + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(anyAppName, "dev")).List(context.Background(), metav1.ListOptions{}) + s.Require().Len(rds.Items, 1) + rd := rds.Items[0] + expectedHash := internaltest.GetRadixApplicationHash(ra) + s.Equal(expectedHash, rd.Annotations[kube.RadixConfigHash]) } -func TestDeploy_SetCommitID_whenSet(t *testing.T) { - kubeclient, kubeUtil, radixclient, _ := setupTest(t) +func (s *deployTestSuite) Test_RadixBuildSecretHashAnnotation_BuildSecretSet() { + anyAppName := "anyapp" + rr := utils.ARadixRegistration(). + WithName(anyAppName). + BuildRR() + + ra := utils.NewRadixApplicationBuilder(). + WithAppName(anyAppName). + WithEnvironment("dev", "master"). + WithComponents(utils.AnApplicationComponent().WithName("app")). + BuildRA() + + secret := &corev1.Secret{Data: map[string][]byte{"anykey": []byte("anydata")}} + + 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 := deploy.NewDeployStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, &monitoring.Clientset{}, rr) + + pipelineInfo := &model.PipelineInfo{ + RadixApplication: ra, + BuildSecret: secret, + TargetEnvironments: []string{"dev"}, + } + + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(anyAppName, "dev")).List(context.Background(), metav1.ListOptions{}) + s.Require().Len(rds.Items, 1) + rd := rds.Items[0] + expectedHash := internaltest.GetBuildSecretHash(secret) + s.Equal(expectedHash, rd.Annotations[kube.RadixBuildSecretHash]) +} +func (s *deployTestSuite) Test_RadixBuildSecretHashAnnotation_BuildSecretNot() { + anyAppName := "anyapp" rr := utils.ARadixRegistration(). WithName(anyAppName). BuildRR() @@ -296,11 +303,46 @@ func TestDeploy_SetCommitID_whenSet(t *testing.T) { WithComponents(utils.AnApplicationComponent().WithName("app")). BuildRA() - // Prometheus don´t contain any fake - cli := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) - cli.Init(context.Background(), kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) + 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 := deploy.NewDeployStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, &monitoring.Clientset{}, rr) + + pipelineInfo := &model.PipelineInfo{ + RadixApplication: ra, + TargetEnvironments: []string{"dev"}, + } + + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(anyAppName, "dev")).List(context.Background(), metav1.ListOptions{}) + s.Require().Len(rds.Items, 1) + rd := rds.Items[0] + expectedHash := internaltest.GetBuildSecretHash(nil) + s.Equal(expectedHash, rd.Annotations[kube.RadixBuildSecretHash]) +} + +func (s *deployTestSuite) TestDeploy_RadixCommitLabel_FromGitCommitHashIfSet() { + anyAppName, anyJobName, anyImageTag := "anyapp", "anyjobname", "anyimagetag" + + rr := utils.ARadixRegistration(). + WithName(anyAppName). + BuildRR() + + ra := utils.NewRadixApplicationBuilder(). + WithAppName(anyAppName). + WithEnvironment("dev", "master"). + WithComponents(utils.AnApplicationComponent().WithName("app")). + BuildRA() - applicationConfig := application.NewApplicationConfig(kubeclient, kubeUtil, radixclient, rr, ra, nil) + 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 := deploy.NewDeployStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, &monitoring.Clientset{}, rr) const commitID = "222ca8595c5283a9d0f17a623b9255a0d9866a2e" @@ -309,29 +351,68 @@ func TestDeploy_SetCommitID_whenSet(t *testing.T) { JobName: anyJobName, ImageTag: anyImageTag, Branch: "master", - CommitID: anyCommitID, + CommitID: "anycommitid", }, - TargetEnvironments: []string{"master"}, + RadixApplication: ra, + TargetEnvironments: []string{"dev"}, GitCommitHash: commitID, GitTags: "", } - gitCommitHash := pipelineInfo.GitCommitHash - gitTags := pipelineInfo.GitTags - - pipelineInfo.SetApplicationConfig(applicationConfig) - pipelineInfo.SetGitAttributes(gitCommitHash, gitTags) err := cli.Run(context.Background(), pipelineInfo) - require.NoError(t, err) - rds, err := radixclient.RadixV1().RadixDeployments("any-app-dev").List(context.Background(), metav1.ListOptions{}) + s.Require().NoError(err) + rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(anyAppName, "dev")).List(context.Background(), metav1.ListOptions{}) + s.Require().Len(rds.Items, 1) + rd := rds.Items[0] + s.Equal(commitID, rd.ObjectMeta.Labels[kube.RadixCommitLabel]) +} + +func (s *deployTestSuite) TestDeploy_RadixCommitLabel_FromCommtIdIfGitCommitHashEmpty() { + anyAppName, anyJobName, anyImageTag := "anyapp", "anyjobname", "anyimagetag" + + rr := utils.ARadixRegistration(). + WithName(anyAppName). + BuildRR() + + ra := utils.NewRadixApplicationBuilder(). + WithAppName(anyAppName). + WithEnvironment("dev", "master"). + WithComponents(utils.AnApplicationComponent().WithName("app")). + BuildRA() + + 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 := deploy.NewDeployStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, &monitoring.Clientset{}, rr) - assert.NoError(t, err) - require.Len(t, rds.Items, 1) + const commitID = "222ca8595c5283a9d0f17a623b9255a0d9866a2e" + + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + JobName: anyJobName, + ImageTag: anyImageTag, + Branch: "master", + CommitID: commitID, + }, + RadixApplication: ra, + TargetEnvironments: []string{"dev"}, + GitCommitHash: "", + GitTags: "", + } + + err := cli.Run(context.Background(), pipelineInfo) + s.Require().NoError(err) + rds, _ := s.radixClient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(anyAppName, "dev")).List(context.Background(), metav1.ListOptions{}) + s.Require().Len(rds.Items, 1) rd := rds.Items[0] - assert.Equal(t, commitID, rd.ObjectMeta.Labels[kube.RadixCommitLabel]) + s.Equal(commitID, rd.ObjectMeta.Labels[kube.RadixCommitLabel]) } -func TestDeploy_WaitActiveDeployment(t *testing.T) { +func (s *deployTestSuite) TestDeploy_WaitActiveDeployment() { + anyAppName, anyJobName, anyImageTag := "anyapp", "anyjobname", "anyimagetag" + rr := utils.ARadixRegistration(). WithName(anyAppName). BuildRR() @@ -353,14 +434,15 @@ func TestDeploy_WaitActiveDeployment(t *testing.T) { {name: "Watch fails", watcherError: errors.New("some error"), expectedRadixDeployments: 0}, } for _, ts := range scenarios { - t.Run(ts.name, func(tt *testing.T) { - kubeclient, kubeUtil, radixClient, _ := setupTest(tt) - ctrl := gomock.NewController(tt) - radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(ctrl) - cli := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, radixDeploymentWatcher) - cli.Init(context.Background(), kubeclient, radixClient, kubeUtil, &monitoring.Clientset{}, rr) - - applicationConfig := application.NewApplicationConfig(kubeclient, kubeUtil, radixClient, rr, ra, nil) + s.Run(ts.name, func() { + s.SetupTest() + namespace := utils.GetEnvironmentNamespace(anyAppName, envName) + namespaceWatcher := watcher.NewMockNamespaceWatcher(s.ctrl) + namespaceWatcher.EXPECT().WaitFor(gomock.Any(), gomock.Any()).Return(nil).Times(1) + radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(s.ctrl) + radixDeploymentWatcher.EXPECT().WaitForActive(context.Background(), namespace, radixDeploymentNameMatcher{envName: envName, imageTag: anyImageTag}).Return(ts.watcherError) + cli := deploy.NewDeployStep(namespaceWatcher, radixDeploymentWatcher) + cli.Init(context.Background(), s.kubeClient, s.radixClient, s.kubeUtil, &monitoring.Clientset{}, rr) pipelineInfo := &model.PipelineInfo{ PipelineArguments: model.PipelineArguments{ @@ -369,22 +451,18 @@ func TestDeploy_WaitActiveDeployment(t *testing.T) { Branch: "master", }, TargetEnvironments: []string{envName}, + RadixApplication: ra, } - pipelineInfo.SetApplicationConfig(applicationConfig) - namespace := utils.GetEnvironmentNamespace(anyAppName, envName) - radixDeploymentWatcher.EXPECT(). - WaitForActive(context.Background(), namespace, radixDeploymentNameMatcher{envName: envName, imageTag: anyImageTag}). - Return(ts.watcherError) err := cli.Run(context.Background(), pipelineInfo) if ts.watcherError == nil { - assert.NoError(tt, err) + s.NoError(err) } else { - assert.EqualError(tt, err, ts.watcherError.Error()) + s.EqualError(err, ts.watcherError.Error()) } - rdList, err := radixClient.RadixV1().RadixDeployments(namespace).List(context.Background(), metav1.ListOptions{}) - assert.NoError(tt, err) - assert.Len(tt, rdList.Items, ts.expectedRadixDeployments, "Invalid expected RadixDeployment-s count") + rdList, err := s.radixClient.RadixV1().RadixDeployments(namespace).List(context.Background(), metav1.ListOptions{}) + s.Require().NoError(err) + s.Len(rdList.Items, ts.expectedRadixDeployments, "Invalid expected RadixDeployment-s count") }) } } diff --git a/pipeline-runner/steps/preparepipeline/step.go b/pipeline-runner/steps/preparepipeline/step.go index a59becb27..06430092e 100644 --- a/pipeline-runner/steps/preparepipeline/step.go +++ b/pipeline-runner/steps/preparepipeline/step.go @@ -83,10 +83,7 @@ func (cli *PreparePipelinesStepImplementation) Run(ctx context.Context, pipeline pipelineInfo.SourceDeploymentGitCommitHash = sourceDeploymentGitCommitHash pipelineInfo.SourceDeploymentGitBranch = sourceDeploymentGitBranch } - job, err := cli.getPreparePipelinesJobConfig(pipelineInfo) - if err != nil { - return err - } + job := cli.getPreparePipelinesJobConfig(pipelineInfo) // When debugging pipeline there will be no RJ if !pipelineInfo.PipelineArguments.Debug { @@ -99,7 +96,7 @@ func (cli *PreparePipelinesStepImplementation) Run(ctx context.Context, pipeline } log.Ctx(ctx).Info().Msgf("Apply job (%s) to copy radixconfig to configmap for app %s and prepare Tekton pipeline", job.Name, appName) - job, err = cli.GetKubeclient().BatchV1().Jobs(namespace).Create(ctx, job, metav1.CreateOptions{}) + job, err := cli.GetKubeclient().BatchV1().Jobs(namespace).Create(ctx, job, metav1.CreateOptions{}) if err != nil { return err } @@ -119,7 +116,7 @@ func logPipelineInfo(ctx context.Context, pipelineType radixv1.RadixPipelineType log.Ctx(ctx).Info().Msg(stringBuilder.String()) } -func (cli *PreparePipelinesStepImplementation) getPreparePipelinesJobConfig(pipelineInfo *model.PipelineInfo) (*batchv1.Job, error) { +func (cli *PreparePipelinesStepImplementation) getPreparePipelinesJobConfig(pipelineInfo *model.PipelineInfo) *batchv1.Job { appName := cli.GetAppName() registration := cli.GetRegistration() configBranch := applicationconfig.GetConfigBranch(registration) @@ -203,13 +200,8 @@ func (cli *PreparePipelinesStepImplementation) getPreparePipelinesJobConfig(pipe Value: strings.Join(pipelineInfo.PipelineArguments.DNSConfig.ReservedDNSAliases, ","), }, } - initContainers, err := git.CloneInitContainersWithContainerName(registration.Spec.CloneURL, configBranch, git.CloneConfigContainerName, internalgit.CloneConfigFromPipelineArgs(pipelineInfo.PipelineArguments)) - if err != nil { - return nil, err - } - - return internaltekton.CreateActionPipelineJob(defaults.RadixPipelineJobPreparePipelinesContainerName, action, pipelineInfo, appName, initContainers, &envVars), nil - + initContainers := git.CloneInitContainersWithContainerName(registration.Spec.CloneURL, configBranch, git.CloneConfigContainerName, internalgit.CloneConfigFromPipelineArgs(pipelineInfo.PipelineArguments)) + return internaltekton.CreateActionPipelineJob(defaults.RadixPipelineJobPreparePipelinesContainerName, action, pipelineInfo, appName, initContainers, &envVars) } func getWebhookCommitID(pipelineInfo *model.PipelineInfo) string { diff --git a/pkg/apis/defaults/environment_variables.go b/pkg/apis/defaults/environment_variables.go index ad8e7a2d2..12ed39158 100644 --- a/pkg/apis/defaults/environment_variables.go +++ b/pkg/apis/defaults/environment_variables.go @@ -132,9 +132,6 @@ const ( // RadixPushImageEnvironmentVariable Push an image for the built component to an ACR RadixPushImageEnvironmentVariable = "PUSH_IMAGE" - // RadixUseCacheEnvironmentVariable Use cache for the built component - RadixUseCacheEnvironmentVariable = "USE_CACHE" - // RadixOverrideUseBuildCacheVariable override default or configured build cache option RadixOverrideUseBuildCacheVariable = "OVERRIDE_USE_BUILD_CACHE" @@ -198,9 +195,13 @@ const ( // SeccompProfileFileNameEnvironmentVariable Filename of the seccomp profile injected by daemonset, relative to the /var/lib/kubelet/seccomp directory SeccompProfileFileNameEnvironmentVariable = "SECCOMP_PROFILE_FILENAME" + // Deprecated: Radix no longer uses the buildah image directly. Use RadixBuildKitImageBuilderEnvironmentVariable // RadixBuildahImageBuilderEnvironmentVariable The container image used for running the buildah engine RadixBuildahImageBuilderEnvironmentVariable = "RADIX_BUILDAH_IMAGE_BUILDER" + // RadixBuildKitImageBuilderEnvironmentVariable Repository and tag for the buildkit image builder + RadixBuildKitImageBuilderEnvironmentVariable = "RADIX_BUILDKIT_IMAGE_BUILDER" + // RadixReservedAppDNSAliasesEnvironmentVariable The list of DNS aliases, reserved for Radix platform Radix application RadixReservedAppDNSAliasesEnvironmentVariable = "RADIX_RESERVED_APP_DNS_ALIASES" diff --git a/pkg/apis/job/job_steps_test.go b/pkg/apis/job/job_steps_test.go index 2fcd81913..9fe8e9c1d 100644 --- a/pkg/apis/job/job_steps_test.go +++ b/pkg/apis/job/job_steps_test.go @@ -3,7 +3,6 @@ package job import ( "context" "encoding/json" - "fmt" "testing" "time" @@ -362,7 +361,6 @@ func (s *RadixJobStepTestSuite) getBuildJob(name, radixJobName, appName, imageTa Namespace: utils.GetAppNamespace(appName), Labels: map[string]string{ kube.RadixJobNameLabel: radixJobName, - kube.RadixBuildLabel: fmt.Sprintf("%s-%s", appName, imageTag), kube.RadixAppLabel: appName, kube.RadixImageTagLabel: imageTag, kube.RadixJobTypeLabel: kube.RadixJobTypeBuild, diff --git a/pkg/apis/job/job_test.go b/pkg/apis/job/job_test.go index 48738c08a..26c7c46c6 100644 --- a/pkg/apis/job/job_test.go +++ b/pkg/apis/job/job_test.go @@ -10,6 +10,7 @@ import ( "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/config" + "github.com/equinor/radix-operator/pkg/apis/config/containerregistry" "github.com/equinor/radix-operator/pkg/apis/config/dnsalias" "github.com/equinor/radix-operator/pkg/apis/config/pipelinejob" "github.com/equinor/radix-operator/pkg/apis/defaults" @@ -42,7 +43,7 @@ type RadixJobTestSuiteBase struct { egressIps string tektonImage string builderImage string - buildahImage string + buildkitImage string buildahSecComp string nslookupImage string gitImage string @@ -61,7 +62,7 @@ func (s *RadixJobTestSuiteBase) SetupSuite() { egressIps string tektonImage string builderImage string - buildahImage string + buildkitImage string buildahSecComp string nslookupImage string gitImage string @@ -76,7 +77,7 @@ func (s *RadixJobTestSuiteBase) SetupSuite() { egressIps: "0.0.0.0", tektonImage: "tekton:any", builderImage: "builder:any", - buildahImage: "buildah:any", + buildkitImage: "buildkit:any", buildahSecComp: "anyseccomp", nslookupImage: "nslookup:any", gitImage: "git:any", @@ -111,7 +112,7 @@ func (s *RadixJobTestSuiteBase) setupTest() { s.T().Setenv(defaults.AppContainerRegistryEnvironmentVariable, s.config.appRegistry) s.T().Setenv(defaults.RadixTektonPipelineImageEnvironmentVariable, s.config.tektonImage) s.T().Setenv(defaults.RadixImageBuilderEnvironmentVariable, s.config.builderImage) - s.T().Setenv(defaults.RadixBuildahImageBuilderEnvironmentVariable, s.config.buildahImage) + s.T().Setenv(defaults.RadixBuildKitImageBuilderEnvironmentVariable, s.config.buildkitImage) s.T().Setenv(defaults.SeccompProfileFileNameEnvironmentVariable, s.config.buildahSecComp) s.T().Setenv(defaults.RadixGitCloneNsLookupImageEnvironmentVariable, s.config.nslookupImage) s.T().Setenv(defaults.RadixGitCloneGitImageEnvironmentVariable, s.config.gitImage) @@ -214,9 +215,10 @@ func (s *RadixJobTestSuite) TestObjectSynced_PipelineJobCreated() { "--RADIXOPERATOR_APP_BUILDER_RESOURCES_REQUESTS_MEMORY=1000Mi", "--RADIXOPERATOR_APP_BUILDER_RESOURCES_REQUESTS_CPU=100m", "--RADIXOPERATOR_APP_BUILDER_RESOURCES_LIMITS_MEMORY=2000Mi", + fmt.Sprintf("--RADIX_EXTERNAL_REGISTRY_DEFAULT_AUTH_SECRET=%s", config.ContainerRegistryConfig.ExternalRegistryAuthSecret), fmt.Sprintf("--RADIX_TEKTON_IMAGE=%s", s.config.tektonImage), fmt.Sprintf("--RADIX_IMAGE_BUILDER=%s", s.config.builderImage), - fmt.Sprintf("--RADIX_BUILDAH_IMAGE_BUILDER=%s", s.config.buildahImage), + fmt.Sprintf("--RADIX_BUILDKIT_IMAGE_BUILDER=%s", s.config.buildkitImage), fmt.Sprintf("--SECCOMP_PROFILE_FILENAME=%s", s.config.buildahSecComp), fmt.Sprintf("--RADIX_CLUSTER_TYPE=%s", s.config.clusterType), fmt.Sprintf("--RADIX_ZONE=%s", s.config.radixZone), @@ -234,7 +236,6 @@ func (s *RadixJobTestSuite) TestObjectSynced_PipelineJobCreated() { fmt.Sprintf("--BRANCH=%s", branch), fmt.Sprintf("--COMMIT_ID=%s", commitID), "--PUSH_IMAGE=1", - "--USE_CACHE=", }, SecurityContext: &corev1.SecurityContext{ Privileged: pointers.Ptr(false), @@ -1391,5 +1392,9 @@ func getConfigWithPipelineJobsHistoryLimit(historyLimit int) *config.Config { AppBuilderResourcesRequestsCPU: pointers.Ptr(resource.MustParse("100m")), AppBuilderResourcesRequestsMemory: pointers.Ptr(resource.MustParse("1000Mi")), AppBuilderResourcesLimitsMemory: pointers.Ptr(resource.MustParse("2000Mi")), - }} + }, + ContainerRegistryConfig: containerregistry.Config{ + ExternalRegistryAuthSecret: "an-external-registry-secret", + }, + } } diff --git a/pkg/apis/job/kubejob.go b/pkg/apis/job/kubejob.go index 1ad2d7831..13fbf7cd2 100644 --- a/pkg/apis/job/kubejob.go +++ b/pkg/apis/job/kubejob.go @@ -119,7 +119,6 @@ func (job *Job) getPipelineJobConfig(ctx context.Context) (*batchv1.Job, error) func (job *Job) getPipelineJobArguments(ctx context.Context, appName, jobName string, jobSpec radixv1.RadixJobSpec, pipeline *pipelineJob.Definition) ([]string, error) { clusterType := os.Getenv(defaults.OperatorClusterTypeEnvironmentVariable) radixZone := os.Getenv(defaults.RadixZoneEnvironmentVariable) - useImageBuilderCache := os.Getenv(defaults.RadixUseCacheEnvironmentVariable) clusterName, err := job.kubeutil.GetClusterName(ctx) if err != nil { @@ -162,11 +161,12 @@ func (job *Job) getPipelineJobArguments(ctx context.Context, appName, jobName st fmt.Sprintf("--%s=%s", defaults.OperatorAppBuilderResourcesRequestsMemoryEnvironmentVariable, job.config.PipelineJobConfig.AppBuilderResourcesRequestsMemory.String()), fmt.Sprintf("--%s=%s", defaults.OperatorAppBuilderResourcesRequestsCPUEnvironmentVariable, job.config.PipelineJobConfig.AppBuilderResourcesRequestsCPU.String()), fmt.Sprintf("--%s=%s", defaults.OperatorAppBuilderResourcesLimitsMemoryEnvironmentVariable, job.config.PipelineJobConfig.AppBuilderResourcesLimitsMemory.String()), + fmt.Sprintf("--%s=%s", defaults.RadixExternalRegistryDefaultAuthEnvironmentVariable, job.config.ContainerRegistryConfig.ExternalRegistryAuthSecret), // Pass tekton and builder images fmt.Sprintf("--%s=%s", defaults.RadixTektonPipelineImageEnvironmentVariable, radixTektonImage), fmt.Sprintf("--%s=%s", defaults.RadixImageBuilderEnvironmentVariable, os.Getenv(defaults.RadixImageBuilderEnvironmentVariable)), - fmt.Sprintf("--%s=%s", defaults.RadixBuildahImageBuilderEnvironmentVariable, os.Getenv(defaults.RadixBuildahImageBuilderEnvironmentVariable)), + fmt.Sprintf("--%s=%s", defaults.RadixBuildKitImageBuilderEnvironmentVariable, os.Getenv(defaults.RadixBuildKitImageBuilderEnvironmentVariable)), fmt.Sprintf("--%s=%s", defaults.SeccompProfileFileNameEnvironmentVariable, os.Getenv(defaults.SeccompProfileFileNameEnvironmentVariable)), // Used for tagging source of image @@ -198,7 +198,6 @@ func (job *Job) getPipelineJobArguments(ctx context.Context, appName, jobName st args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixBranchEnvironmentVariable, jobSpec.Build.Branch)) args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixCommitIdEnvironmentVariable, jobSpec.Build.CommitID)) args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixPushImageEnvironmentVariable, getPushImageTag(jobSpec.Build.PushImage))) - args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixUseCacheEnvironmentVariable, useImageBuilderCache)) if jobSpec.Build.OverrideUseBuildCache != nil { args = append(args, fmt.Sprintf("--%s=%v", defaults.RadixOverrideUseBuildCacheVariable, *jobSpec.Build.OverrideUseBuildCache)) } diff --git a/pkg/apis/kube/kube.go b/pkg/apis/kube/kube.go index 12653a7ca..e4c4cd6c2 100644 --- a/pkg/apis/kube/kube.go +++ b/pkg/apis/kube/kube.go @@ -51,7 +51,6 @@ const ( RadixJobNameLabel = "radix-job-name" RadixAuxiliaryComponentLabel = "radix-aux-component" RadixAuxiliaryComponentTypeLabel = "radix-aux-component-type" - RadixBuildLabel = "radix-build" RadixCommitLabel = "radix-commit" RadixImageTagLabel = "radix-image-tag" RadixJobTypeLabel = "radix-job-type" diff --git a/pkg/apis/pipeline/component_image.go b/pkg/apis/pipeline/component_image.go index f693f5fab..66f2e4849 100644 --- a/pkg/apis/pipeline/component_image.go +++ b/pkg/apis/pipeline/component_image.go @@ -18,14 +18,16 @@ type DeployEnvironmentComponentImages map[string]DeployComponentImages // BuildComponentImage holds info about a build container type BuildComponentImage struct { - ComponentName string - EnvName string - ContainerName string - Context string - Dockerfile string - ImageName string - ImagePath string - Runtime *radixv1.Runtime + ComponentName string + EnvName string + ContainerName string + Context string + Dockerfile string + ImageName string + ImagePath string + ClusterTypeImagePath string + ClusterNameImagePath string + Runtime *radixv1.Runtime } // EnvironmentBuildComponentImages maps component names with build information for environment diff --git a/pkg/apis/utils/git/clone.go b/pkg/apis/utils/git/clone.go index fcade28ac..d7fbf8af0 100644 --- a/pkg/apis/utils/git/clone.go +++ b/pkg/apis/utils/git/clone.go @@ -1,7 +1,6 @@ package git import ( - "errors" "fmt" "path" @@ -40,32 +39,13 @@ type CloneConfig struct { BashImage string } -func (c CloneConfig) Validate() error { - var errs []error - - if len(c.NSlookupImage) == 0 { - errs = append(errs, errors.New("field NSlookupImage not set")) - } - if len(c.GitImage) == 0 { - errs = append(errs, errors.New("field GitImage not set")) - } - if len(c.BashImage) == 0 { - errs = append(errs, errors.New("field BashImage not set")) - } - - return errors.Join(errs...) -} - // CloneInitContainers The sidecars for cloning repo -func CloneInitContainers(sshURL, branch string, config CloneConfig) ([]corev1.Container, error) { +func CloneInitContainers(sshURL, branch string, config CloneConfig) []corev1.Container { return CloneInitContainersWithContainerName(sshURL, branch, CloneContainerName, config) } // CloneInitContainersWithContainerName The sidecars for cloning repo -func CloneInitContainersWithContainerName(sshURL, branch, cloneContainerName string, config CloneConfig) ([]corev1.Container, error) { - if err := config.Validate(); err != nil { - return nil, fmt.Errorf("invalid clone configuration: %w", err) - } +func CloneInitContainersWithContainerName(sshURL, branch, cloneContainerName string, config CloneConfig) []corev1.Container { gitCloneCmd := []string{"git", "clone", "--recurse-submodules", sshURL, "-b", branch, "--verbose", "--progress", Workspace} containers := []corev1.Container{ { @@ -140,5 +120,5 @@ func CloneInitContainersWithContainerName(sshURL, branch, cloneContainerName str }, } - return containers, nil + return containers } diff --git a/pkg/apis/utils/git/clone_test.go b/pkg/apis/utils/git/clone_test.go index 078fe4c02..d0ca70afa 100644 --- a/pkg/apis/utils/git/clone_test.go +++ b/pkg/apis/utils/git/clone_test.go @@ -7,27 +7,16 @@ import ( "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/utils/git" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" ) -func Test_CloneInitContainers_InvalidConfig(t *testing.T) { - _, err := git.CloneInitContainers("anysshurl", "anybranch", git.CloneConfig{NSlookupImage: "any", GitImage: "any"}) - assert.Error(t, err) - _, err = git.CloneInitContainers("anysshurl", "anybranch", git.CloneConfig{NSlookupImage: "any", BashImage: "any"}) - assert.Error(t, err) - _, err = git.CloneInitContainers("anysshurl", "anybranch", git.CloneConfig{GitImage: "any", BashImage: "any"}) - assert.Error(t, err) -} - func Test_CloneInitContainers_CustomImages(t *testing.T) { type containerInfo struct { name string image string } cfg := git.CloneConfig{NSlookupImage: "anynslookup:any", GitImage: "anygit:any", BashImage: "anybash:any"} - containers, err := git.CloneInitContainers("anysshurl", "anybranch", cfg) - require.NoError(t, err) + containers := git.CloneInitContainers("anysshurl", "anybranch", cfg) actual := slice.Map(containers, func(c v1.Container) containerInfo { return containerInfo{name: c.Name, image: c.Image} }) expected := []containerInfo{ {name: fmt.Sprintf("%snslookup", git.InternalContainerPrefix), image: cfg.NSlookupImage}, @@ -37,15 +26,6 @@ func Test_CloneInitContainers_CustomImages(t *testing.T) { assert.Equal(t, expected, actual) } -func Test_CloneInitContainersWithContainerName_DefaultImages(t *testing.T) { - _, err := git.CloneInitContainersWithContainerName("anysshurl", "anybranch", "anycontainername", git.CloneConfig{NSlookupImage: "any", GitImage: "any"}) - assert.Error(t, err) - _, err = git.CloneInitContainersWithContainerName("anysshurl", "anybranch", "anycontainername", git.CloneConfig{NSlookupImage: "any", BashImage: "any"}) - assert.Error(t, err) - _, err = git.CloneInitContainersWithContainerName("anysshurl", "anybranch", "anycontainername", git.CloneConfig{GitImage: "any", BashImage: "any"}) - assert.Error(t, err) -} - func Test_CloneInitContainersWithContainerName_CustomImages(t *testing.T) { type containerInfo struct { name string @@ -53,8 +33,7 @@ func Test_CloneInitContainersWithContainerName_CustomImages(t *testing.T) { } cloneName := "anyclonename" cfg := git.CloneConfig{NSlookupImage: "anynslookup:any", GitImage: "anygit:any", BashImage: "anybash:any"} - containers, err := git.CloneInitContainersWithContainerName("anysshurl", "anybranch", cloneName, cfg) - require.NoError(t, err) + containers := git.CloneInitContainersWithContainerName("anysshurl", "anybranch", cloneName, cfg) actual := slice.Map(containers, func(c v1.Container) containerInfo { return containerInfo{name: c.Name, image: c.Image} }) expected := []containerInfo{ {name: fmt.Sprintf("%snslookup", git.InternalContainerPrefix), image: cfg.NSlookupImage},