diff --git a/.vscode/launch.json b/.vscode/launch.json index 549682e99..cb854d806 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,27 +9,29 @@ "program": "${workspaceFolder}/pipeline-runner/main.go", "env": {}, "args": [ - "--RADIX_APP=radix-job-demo", - "--JOB_NAME=radix-pipeline-20231113133209-sb8w8", + "--RADIX_APP=oauth-demo", + "--JOB_NAME=radix-pipeline-20231113133209-r", "--PIPELINE_TYPE=build-deploy", + "--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", "--SECCOMP_PROFILE_FILENAME=allow-buildah.json", "--RADIX_CLUSTER_TYPE=development", "--RADIX_ZONE=dev", - "--RADIX_CLUSTERNAME=weekly-44", + "--RADIX_CLUSTERNAME=weekly-23", "--RADIX_CONTAINER_REGISTRY=radixdev.azurecr.io", + "--RADIX_APP_CONTAINER_REGISTRY=radixdevapp.azurecr.io", "--AZURE_SUBSCRIPTION_ID=16ede44b-1f74-40a5-b428-46cca9a5741b", - "--IMAGE_TAG=abcde", + "--IMAGE_TAG=abcdw", "--BRANCH=main", - "--COMMIT_ID=1cbb2fb6b8a562d44a27edae9678c86cb7cbda2e", + // "--COMMIT_ID=4069bf49619be55ee7dbdd426194cc14c30fde10", "--PUSH_IMAGE=true", "--USE_CACHE=true", "--RADIX_FILE_NAME=/workspace/radixconfig.yaml", - "--TO_ENVIRONMENT=qa", - "--IMAGE_TAG_NAME=server=1.23-alpine-slim", - "--IMAGE_TAG_NAME=server2=1.22.1-alpine-perl", + "--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" ] @@ -104,7 +106,7 @@ "RADIXOPERATOR_APP_ROLLING_UPDATE_MAX_SURGE": "25%", "RADIXOPERATOR_APP_READINESS_PROBE_INITIAL_DELAY_SECONDS": "5", "RADIXOPERATOR_APP_READINESS_PROBE_PERIOD_SECONDS": "10", - "RADIX_ACTIVE_CLUSTERNAME": "weekly-51", + "RADIX_ACTIVE_CLUSTERNAME": "weekly-23", "RADIX_IMAGE_BUILDER": "radix-image-builder:master-latest", "RADIX_TEKTON_IMAGE": "radix-tekton:main-latest", "RADIXOPERATOR_JOB_SCHEDULER": "radix-job-scheduler:main-latest", diff --git a/charts/radix-operator/Chart.yaml b/charts/radix-operator/Chart.yaml index 0a394f12b..dd73f9ec2 100644 --- a/charts/radix-operator/Chart.yaml +++ b/charts/radix-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: radix-operator -version: 1.35.3 -appVersion: 1.55.3 +version: 1.36.0 +appVersion: 1.56.0 kubeVersion: ">=1.24.0" description: Radix Operator keywords: diff --git a/charts/radix-operator/templates/radixapplication.yaml b/charts/radix-operator/templates/radixapplication.yaml index 52a175ce3..cb527f37b 100644 --- a/charts/radix-operator/templates/radixapplication.yaml +++ b/charts/radix-operator/templates/radixapplication.yaml @@ -730,6 +730,19 @@ spec: that is explicitly specified, otherwise to an implementation-defined value. type: object type: object + runtime: + description: Runtime defines environment specific target + runtime requirements for the component + properties: + architecture: + default: amd64 + description: CPU architecture target for the component + or job. Defaults to amd64. + enum: + - amd64 + - arm64 + type: string + type: object secretRefs: description: |- Environment specific configuration for external secret stores, like Azure KeyVault. @@ -1467,6 +1480,19 @@ spec: that is explicitly specified, otherwise to an implementation-defined value. type: object type: object + runtime: + description: Runtime defines the target runtime requirements + for the component + properties: + architecture: + default: amd64 + description: CPU architecture target for the component or + job. Defaults to amd64. + enum: + - amd64 + - arm64 + type: string + type: object secretRefs: description: |- Configuration for external secret stores, like Azure KeyVault. @@ -2197,6 +2223,19 @@ spec: that is explicitly specified, otherwise to an implementation-defined value. type: object type: object + runtime: + description: Runtime defines environment specific target + runtime requirements for the job + properties: + architecture: + default: amd64 + description: CPU architecture target for the component + or job. Defaults to amd64. + enum: + - amd64 + - arm64 + type: string + type: object secretRefs: description: |- Environment specific configuration for external secret stores, like Azure KeyVault. @@ -2725,6 +2764,19 @@ spec: that is explicitly specified, otherwise to an implementation-defined value. type: object type: object + runtime: + description: Runtime defines target runtime requirements for + the job + properties: + architecture: + default: amd64 + description: CPU architecture target for the component or + job. Defaults to amd64. + enum: + - amd64 + - arm64 + type: string + type: object schedulerPort: description: |- Defines the port number that the job-scheduler API server will listen to. diff --git a/json-schema/radixapplication.json b/json-schema/radixapplication.json index 87a6181b2..cf9ba3c55 100644 --- a/json-schema/radixapplication.json +++ b/json-schema/radixapplication.json @@ -708,6 +708,21 @@ }, "type": "object" }, + "runtime": { + "description": "Runtime defines environment specific target runtime requirements for the component", + "properties": { + "architecture": { + "default": "amd64", + "description": "CPU architecture target for the component or job. Defaults to amd64.", + "enum": [ + "amd64", + "arm64" + ], + "type": "string" + } + }, + "type": "object" + }, "secretRefs": { "description": "Environment specific configuration for external secret stores, like Azure KeyVault.\nMore info: https://www.radix.equinor.com/references/reference-radix-config/#secretrefs", "properties": { @@ -1459,6 +1474,21 @@ }, "type": "object" }, + "runtime": { + "description": "Runtime defines the target runtime requirements for the component", + "properties": { + "architecture": { + "default": "amd64", + "description": "CPU architecture target for the component or job. Defaults to amd64.", + "enum": [ + "amd64", + "arm64" + ], + "type": "string" + } + }, + "type": "object" + }, "secretRefs": { "description": "Configuration for external secret stores, like Azure KeyVault.\nMore info: https://www.radix.equinor.com/references/reference-radix-config/#secretrefs", "properties": { @@ -2213,6 +2243,21 @@ }, "type": "object" }, + "runtime": { + "description": "Runtime defines environment specific target runtime requirements for the job", + "properties": { + "architecture": { + "default": "amd64", + "description": "CPU architecture target for the component or job. Defaults to amd64.", + "enum": [ + "amd64", + "arm64" + ], + "type": "string" + } + }, + "type": "object" + }, "secretRefs": { "description": "Environment specific configuration for external secret stores, like Azure KeyVault.\nMore info: https://www.radix.equinor.com/references/reference-radix-config/#secretrefs", "properties": { @@ -2744,6 +2789,21 @@ }, "type": "object" }, + "runtime": { + "description": "Runtime defines target runtime requirements for the job", + "properties": { + "architecture": { + "default": "amd64", + "description": "CPU architecture target for the component or job. Defaults to amd64.", + "enum": [ + "amd64", + "arm64" + ], + "type": "string" + } + }, + "type": "object" + }, "schedulerPort": { "description": "Defines the port number that the job-scheduler API server will listen to.\nMore info: https://www.radix.equinor.com/references/reference-radix-config/#schedulerport", "format": "int32", diff --git a/operator.Dockerfile b/operator.Dockerfile index a616ae575..c7e39ba1f 100644 --- a/operator.Dockerfile +++ b/operator.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine3.19 as base +FROM golang:1.22-alpine3.20 as base ENV GO111MODULE=on RUN apk update && \ apk add git ca-certificates curl && \ diff --git a/pipeline-runner/internal/tekton/tekton.go b/pipeline-runner/internal/tekton/tekton.go index ae0667bf7..68e929c63 100644 --- a/pipeline-runner/internal/tekton/tekton.go +++ b/pipeline-runner/internal/tekton/tekton.go @@ -5,10 +5,13 @@ import ( "strings" "time" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pipeline-runner/model" pipelineDefaults "github.com/equinor/radix-operator/pipeline-runner/model/defaults" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + "github.com/equinor/radix-operator/pkg/apis/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" @@ -49,21 +52,29 @@ func CreateActionPipelineJob(containerName string, action string, pipelineInfo * }, Spec: corev1.PodSpec{ ServiceAccountName: defaults.RadixTektonServiceAccountName, - SecurityContext: &pipelineInfo.PipelineArguments.PodSecurityContext, - InitContainers: initContainers, + SecurityContext: securitycontext.Pod( + securitycontext.WithPodFSGroup(1000), + securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault)), + InitContainers: initContainers, Containers: []corev1.Container{ { Name: containerName, Image: fmt.Sprintf("%s/%s", pipelineInfo.PipelineArguments.ContainerRegistry, pipelineInfo.PipelineArguments.TektonPipeline), ImagePullPolicy: corev1.PullAlways, VolumeMounts: getJobContainerVolumeMounts(), - SecurityContext: &pipelineInfo.PipelineArguments.ContainerSecurityContext, - Env: *envVars, + SecurityContext: securitycontext.Container( + securitycontext.WithContainerDropAllCapabilities(), + securitycontext.WithContainerRunAsUser(1000), + securitycontext.WithContainerRunAsGroup(1000), + securitycontext.WithContainerSeccompProfileType(corev1.SeccompProfileTypeRuntimeDefault), + securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), + ), + Env: *envVars, }, }, Volumes: getJobVolumes(), RestartPolicy: "Never", - Affinity: utils.GetAffinityForPipelineJob(), + Affinity: utils.GetAffinityForPipelineJob(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), Tolerations: utils.GetPipelineJobPodSpecTolerations(), }, }, diff --git a/pipeline-runner/internal/watcher/namespace.go b/pipeline-runner/internal/watcher/namespace.go new file mode 100644 index 000000000..b7f09b831 --- /dev/null +++ b/pipeline-runner/internal/watcher/namespace.go @@ -0,0 +1,64 @@ +package watcher + +import ( + "context" + "time" + + "github.com/rs/zerolog/log" + corev1 "k8s.io/api/core/v1" + k8errs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" +) + +const waitTimeout = 15 * time.Second + +// NamespaceWatcher Watcher to wait for namespace to be created +type NamespaceWatcher interface { + WaitFor(ctx context.Context, namespace string) error +} + +// NamespaceWatcherImpl Implementation of watcher +type NamespaceWatcherImpl struct { + client kubernetes.Interface +} + +// NewNamespaceWatcherImpl Constructor +func NewNamespaceWatcherImpl(client kubernetes.Interface) NamespaceWatcherImpl { + return NamespaceWatcherImpl{ + client, + } +} + +// WaitFor Waits for namespace to appear +func (watcher NamespaceWatcherImpl) WaitFor(ctx context.Context, namespace string) error { + log.Info().Msgf("Waiting for namespace %s", namespace) + err := waitForNamespace(ctx, watcher.client, namespace) + if err != nil { + return err + } + + log.Info().Msgf("Namespace %s exists and is active", namespace) + return nil + +} + +func waitForNamespace(ctx context.Context, client kubernetes.Interface, namespace string) error { + timoutContext, cancel := context.WithTimeout(ctx, waitTimeout) + defer cancel() + + return wait.PollUntilContextCancel(timoutContext, time.Second, true, func(ctx context.Context) (done bool, err error) { + ns, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) + if err != nil { + if k8errs.IsNotFound(err) || k8errs.IsForbidden(err) { + return false, nil // the environment namespace or the rolebinding for the cluster-role radix-pipeline-env are not yet created + } + return false, err + } + if ns != nil && ns.Status.Phase == corev1.NamespaceActive { + return true, nil + } + return false, nil + }) +} diff --git a/pipeline-runner/internal/watcher/namespace_mock.go b/pipeline-runner/internal/watcher/namespace_mock.go new file mode 100644 index 000000000..8f612375c --- /dev/null +++ b/pipeline-runner/internal/watcher/namespace_mock.go @@ -0,0 +1,21 @@ +package watcher + +import "context" + +// FakeNamespaceWatcher Unit tests doesn't handle multi-threading well +type FakeNamespaceWatcher struct { +} + +// FakeRadixDeploymentWatcher Unit tests doesn't handle multi-threading well +type FakeRadixDeploymentWatcher struct { +} + +// WaitFor Waits for namespace to appear +func (watcher FakeNamespaceWatcher) WaitFor(_ context.Context, _ string) error { + return nil +} + +// WaitFor Waits for radix deployment gets active +func (watcher FakeRadixDeploymentWatcher) WaitForActive(_, _ string) error { + return nil +} diff --git a/pipeline-runner/internal/watcher/radix_deployment_watcher_test.go b/pipeline-runner/internal/watcher/radix_deployment_watcher_test.go index 730575d96..3d58cd09d 100644 --- a/pipeline-runner/internal/watcher/radix_deployment_watcher_test.go +++ b/pipeline-runner/internal/watcher/radix_deployment_watcher_test.go @@ -16,7 +16,7 @@ import ( kubernetes "k8s.io/client-go/kubernetes/fake" ) -func setupTest(t *testing.T) (*radix.Clientset, *kubernetes.Clientset) { +func setupTest() (*radix.Clientset, *kubernetes.Clientset) { radixClient := radix.NewSimpleClientset() kubeClient := kubernetes.NewSimpleClientset() return radixClient, kubeClient @@ -40,7 +40,7 @@ func TestDeploy_WaitActiveDeployment(t *testing.T) { } for _, ts := range scenarios { t.Run(ts.name, func(tt *testing.T) { - radixClient, kubeClient := setupTest(tt) + radixClient, kubeClient := setupTest() require.NoError(t, createNamespace(kubeClient, namespace)) if ts.hasRadixDevelopment { diff --git a/pipeline-runner/model/pipelineInfo.go b/pipeline-runner/model/pipelineInfo.go index aa52adbb5..cbee88e6b 100644 --- a/pipeline-runner/model/pipelineInfo.go +++ b/pipeline-runner/model/pipelineInfo.go @@ -5,16 +5,12 @@ import ( "strings" "time" - "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" application "github.com/equinor/radix-operator/pkg/apis/applicationconfig" dnsaliasconfig "github.com/equinor/radix-operator/pkg/apis/config/dnsalias" - "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" - "github.com/equinor/radix-operator/pkg/apis/utils/conditions" corev1 "k8s.io/api/core/v1" ) @@ -75,12 +71,7 @@ type PipelineArguments struct { ComponentsToDeploy []string RadixConfigFile string - // Security context - PodSecurityContext corev1.PodSecurityContext - // Security context for image builder pods - BuildKitPodSecurityContext corev1.PodSecurityContext - ContainerSecurityContext corev1.SecurityContext // Images used for copying radix config/building TektonPipeline string // ImageBuilder Points to the image builder @@ -121,22 +112,6 @@ func InitPipeline(pipelineType *pipeline.Definition, radixConfigMapName := fmt.Sprintf("radix-config-2-map-%s-%s-%s", timestamp, pipelineArguments.ImageTag, hash) gitConfigFileName := fmt.Sprintf("radix-git-information-%s-%s-%s", timestamp, pipelineArguments.ImageTag, hash) - podSecContext := securitycontext.Pod(securitycontext.WithPodFSGroup(defaults.SecurityContextFsGroup), - securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault)) - buildPodSecContext := securitycontext.Pod( - securitycontext.WithPodFSGroup(defaults.SecurityContextFsGroup), - securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault), - securitycontext.WithPodRunAsNonRoot(conditions.BoolPtr(false))) - containerSecContext := securitycontext.Container(securitycontext.WithContainerDropAllCapabilities(), - securitycontext.WithContainerSeccompProfileType(corev1.SeccompProfileTypeRuntimeDefault), - securitycontext.WithContainerRunAsGroup(defaults.SecurityContextRunAsGroup), - securitycontext.WithContainerRunAsUser(defaults.SecurityContextRunAsUser), - securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true))) - - pipelineArguments.ContainerSecurityContext = *containerSecContext - pipelineArguments.PodSecurityContext = *podSecContext - pipelineArguments.BuildKitPodSecurityContext = *buildPodSecContext - stepImplementationsForType, err := getStepStepImplementationsFromType(pipelineType, stepImplementations...) if err != nil { return nil, err @@ -205,3 +180,7 @@ func (info *PipelineInfo) SetGitAttributes(gitCommitHash, gitTags string) { func (info *PipelineInfo) IsPipelineType(pipelineType radixv1.RadixPipelineType) bool { return info.PipelineArguments.PipelineType == string(pipelineType) } + +func (info *PipelineInfo) IsUsingBuildKit() bool { + return info.RadixApplication.Spec.Build != nil && info.RadixApplication.Spec.Build.UseBuildKit != nil && *info.RadixApplication.Spec.Build.UseBuildKit +} diff --git a/pipeline-runner/pipelines/app.go b/pipeline-runner/pipelines/app.go index 27e738ec9..0021973dc 100644 --- a/pipeline-runner/pipelines/app.go +++ b/pipeline-runner/pipelines/app.go @@ -6,7 +6,12 @@ import ( "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" - "github.com/equinor/radix-operator/pipeline-runner/steps" + "github.com/equinor/radix-operator/pipeline-runner/steps/applyconfig" + "github.com/equinor/radix-operator/pipeline-runner/steps/build" + "github.com/equinor/radix-operator/pipeline-runner/steps/deploy" + "github.com/equinor/radix-operator/pipeline-runner/steps/preparepipeline" + "github.com/equinor/radix-operator/pipeline-runner/steps/promote" + "github.com/equinor/radix-operator/pipeline-runner/steps/runpipeline" jobs "github.com/equinor/radix-operator/pkg/apis/job" "github.com/equinor/radix-operator/pkg/apis/kube" "github.com/equinor/radix-operator/pkg/apis/pipeline" @@ -112,12 +117,12 @@ func (cli *PipelineRunner) TearDown(ctx context.Context) { func (cli *PipelineRunner) initStepImplementations(registration *v1.RadixRegistration) []model.Step { stepImplementations := make([]model.Step, 0) - stepImplementations = append(stepImplementations, steps.NewPreparePipelinesStep(nil)) - stepImplementations = append(stepImplementations, steps.NewApplyConfigStep()) - stepImplementations = append(stepImplementations, steps.NewBuildStep(nil)) - stepImplementations = append(stepImplementations, steps.NewRunPipelinesStep(nil)) - stepImplementations = append(stepImplementations, steps.NewDeployStep(kube.NewNamespaceWatcherImpl(cli.kubeclient), watcher.NewRadixDeploymentWatcher(cli.radixclient, time.Minute*5))) - stepImplementations = append(stepImplementations, steps.NewPromoteStep()) + stepImplementations = append(stepImplementations, preparepipeline.NewPreparePipelinesStep(nil)) + stepImplementations = append(stepImplementations, applyconfig.NewApplyConfigStep()) + stepImplementations = append(stepImplementations, build.NewBuildStep(nil)) + stepImplementations = append(stepImplementations, runpipeline.NewRunPipelinesStep(nil)) + stepImplementations = append(stepImplementations, deploy.NewDeployStep(watcher.NewNamespaceWatcherImpl(cli.kubeclient), watcher.NewRadixDeploymentWatcher(cli.radixclient, time.Minute*5))) + stepImplementations = append(stepImplementations, promote.NewPromoteStep()) for _, stepImplementation := range stepImplementations { stepImplementation. diff --git a/pipeline-runner/steps/applyconfig/errors.go b/pipeline-runner/steps/applyconfig/errors.go new file mode 100644 index 000000000..9c5ee2e06 --- /dev/null +++ b/pipeline-runner/steps/applyconfig/errors.go @@ -0,0 +1,9 @@ +package applyconfig + +import "errors" + +var ( + ErrDeployOnlyPipelineDoesNotSupportBuild = errors.New("deploy pipeline does not support building components and jobs") + ErrMissingRequiredImageTagName = errors.New("missing required imageTagName in a component, an environmentConfig or in a pipeline argument") + ErrBuildNonDefaultRuntimeArchitectureWithoutBuildKitError = errors.New("BuildKit must be enabled to build non-AMD64 container images") +) diff --git a/pipeline-runner/steps/apply_radixconfig.go b/pipeline-runner/steps/applyconfig/step.go similarity index 87% rename from pipeline-runner/steps/apply_radixconfig.go rename to pipeline-runner/steps/applyconfig/step.go index a2be245f2..daf49b8f4 100644 --- a/pipeline-runner/steps/apply_radixconfig.go +++ b/pipeline-runner/steps/applyconfig/step.go @@ -1,19 +1,18 @@ -package steps +package applyconfig import ( "context" - stderrors "errors" + "errors" "fmt" "path/filepath" "strings" - "github.com/equinor/radix-common/utils" + commonutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pipeline-runner/model" pipelineDefaults "github.com/equinor/radix-operator/pipeline-runner/model/defaults" "github.com/equinor/radix-operator/pipeline-runner/steps/internal" application "github.com/equinor/radix-operator/pkg/apis/applicationconfig" - "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" "github.com/equinor/radix-operator/pkg/apis/pipeline" @@ -21,8 +20,6 @@ import ( validate "github.com/equinor/radix-operator/pkg/apis/radixvalidators" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/equinor/radix-operator/pkg/apis/utils/git" - radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" - "github.com/pkg/errors" "github.com/rs/zerolog/log" corev1 "k8s.io/api/core/v1" kubeerrors "k8s.io/apimachinery/pkg/api/errors" @@ -78,7 +75,7 @@ func (cli *ApplyConfigStepImplementation) Run(ctx context.Context, pipelineInfo if !ok { return fmt.Errorf("failed load RadixApplication from ConfigMap") } - ra, err := CreateRadixApplication(ctx, cli.GetRadixclient(), pipelineInfo.PipelineArguments.DNSConfig, configFileContent) + ra, err := internal.CreateRadixApplication(ctx, cli.GetRadixclient(), pipelineInfo.PipelineArguments.DNSConfig, configFileContent) if err != nil { return err } @@ -146,15 +143,37 @@ func (cli *ApplyConfigStepImplementation) setBuildAndDeployImages(ctx context.Co } func (cli *ApplyConfigStepImplementation) validatePipelineInfo(pipelineInfo *model.PipelineInfo) error { - if pipelineInfo.IsPipelineType(radixv1.Deploy) && len(pipelineInfo.BuildComponentImages) > 0 { - return ErrDeployOnlyPipelineDoesNotSupportBuild + + if err := validateBuildComponents(pipelineInfo); err != nil { + return err } + if err := validateDeployComponents(pipelineInfo); err != nil { return err } return validateDeployComponentImages(pipelineInfo.DeployEnvironmentComponentImages, pipelineInfo.RadixApplication) } +func validateBuildComponents(pipelineInfo *model.PipelineInfo) error { + if pipelineInfo.IsPipelineType(radixv1.Deploy) && len(pipelineInfo.BuildComponentImages) > 0 { + return ErrDeployOnlyPipelineDoesNotSupportBuild + } + + if !pipelineInfo.IsUsingBuildKit() { + for _, buildComponents := range pipelineInfo.BuildComponentImages { + if slice.Any(buildComponents, hasNonDefaultRuntimeArchitecture) { + return ErrBuildNonDefaultRuntimeArchitectureWithoutBuildKitError + } + } + } + + return nil +} + +func hasNonDefaultRuntimeArchitecture(c pipeline.BuildComponentImage) bool { + return operatorutils.GetArchitectureFromRuntime(c.Runtime) != defaults.DefaultNodeSelectorArchitecture +} + func validateDeployComponents(pipelineInfo *model.PipelineInfo) error { if len(pipelineInfo.PipelineArguments.ComponentsToDeploy) == 0 { return nil @@ -177,7 +196,7 @@ func validateDeployComponents(pipelineInfo *model.PipelineInfo) error { } } } - return stderrors.Join(errs...) + return errors.Join(errs...) } func getComponentMap(pipelineInfo *model.PipelineInfo) map[string]radixv1.RadixCommonComponent { @@ -201,7 +220,7 @@ func printEnvironmentComponentImageSources(imageSourceMap environmentComponentIm continue } for _, componentSource := range componentImageSources { - log.Info().Msgf(" - %s from %s", componentSource.ComponentName, getImageSourceDescription(componentSource.ImageSource)) + log.Info().Msgf(" - %s (arch: %s) from %s", componentSource.ComponentName, operatorutils.GetArchitectureFromRuntime(componentSource.Runtime), getImageSourceDescription(componentSource.ImageSource)) } } } @@ -225,6 +244,7 @@ type ( ImageSource containerImageSourceEnum Image string Source radixv1.ComponentSource + Runtime *radixv1.Runtime } environmentComponentImageSourceMap map[string][]componentImageSource @@ -267,18 +287,19 @@ func (cli *ApplyConfigStepImplementation) getComponentSources(appComponents []ra componentSource := make([]componentImageSource, 0) componentsEnabledInEnv := slice.FindAll(appComponents, func(rcc radixv1.RadixCommonComponent) bool { return rcc.GetEnabledForEnvironment(envName) }) for _, component := range componentsEnabledInEnv { - imageSource := componentImageSource{ComponentName: component.GetName()} + imageSource := componentImageSource{ComponentName: component.GetName(), Runtime: internal.GetRuntimeForEnvironment(component, envName)} if image := component.GetImageForEnvironment(envName); len(image) > 0 { imageSource.ImageSource = fromImagePath imageSource.Image = image } else { currentlyDeployedComponent := getCurrentlyDeployedComponent(activeRadixDeployment, component.GetName()) - if utils.IsNil(currentlyDeployedComponent) || mustBuildComponent(component) { + if commonutils.IsNil(currentlyDeployedComponent) || mustBuildComponent(component) { imageSource.ImageSource = fromBuild imageSource.Source = component.GetSourceForEnvironment(envName) } else { imageSource.ImageSource = fromDeployment imageSource.Image = currentlyDeployedComponent.GetImage() + imageSource.Runtime = currentlyDeployedComponent.GetRuntime() } } componentSource = append(componentSource, imageSource) @@ -316,6 +337,7 @@ func setPipelineBuildComponentImages(pipelineInfo *model.PipelineInfo, component Dockerfile: getDockerfileName(imageSource.Source.DockefileName), ImageName: imageName, ImagePath: imagePath, + Runtime: imageSource.Runtime, }) componentImageSourceMap[envName][imageSourceIndex].Image = imagePath } @@ -328,7 +350,7 @@ func setPipelineBuildComponentImages(pipelineInfo *model.PipelineInfo, component func getLengthLimitedName(name string) string { validatedName := strings.ToLower(name) if len(validatedName) > 10 { - return fmt.Sprintf("%s-%s", validatedName[:5], strings.ToLower(utils.RandString(4))) + return fmt.Sprintf("%s-%s", validatedName[:5], strings.ToLower(commonutils.RandString(4))) } return validatedName } @@ -341,6 +363,7 @@ func setPipelineDeployEnvironmentComponentImages(pipelineInfo *model.PipelineInf deployComponentImage := pipeline.DeployComponentImage{ ImageTagName: pipelineInfo.PipelineArguments.ImageTagNames[cis.ComponentName], Build: cis.ImageSource == fromBuild, + Runtime: cis.Runtime, } if cis.ImageSource == fromBuild { if buildComponentImages, ok := pipelineInfo.BuildComponentImages[envName]; ok { @@ -370,7 +393,7 @@ func isRadixConfigNewOrModifiedSinceDeployment(rd *radixv1.RadixDeployment, ra * if len(currentRdConfigHash) == 0 { return true, nil } - hashEqual, err := compareRadixApplicationHash(currentRdConfigHash, ra) + hashEqual, err := internal.CompareRadixApplicationHash(currentRdConfigHash, ra) if !hashEqual && err == nil { log.Info().Msgf("RadixApplication updated since last deployment to environment %s", rd.Spec.Environment) } @@ -385,7 +408,7 @@ func isBuildSecretNewOrModifiedSinceDeployment(rd *radixv1.RadixDeployment, buil if len(targetHash) == 0 { return true, nil } - hashEqual, err := compareBuildSecretHash(targetHash, buildSecret) + hashEqual, err := internal.CompareBuildSecretHash(targetHash, buildSecret) if !hashEqual && err == nil { log.Info().Msgf("Build secrets updated since last deployment to environment %s", rd.Spec.Environment) } @@ -420,7 +443,7 @@ func mustBuildComponentForEnvironment(environmentName string, prepareBuildContex return func(comp radixv1.RadixCommonComponent) bool { return slice.Any(envBuildContext.Components, func(s string) bool { return s == comp.GetName() }) || - utils.IsNil(currentRd.GetCommonComponentByName(comp.GetName())) + commonutils.IsNil(currentRd.GetCommonComponentByName(comp.GetName())) }, nil } @@ -508,7 +531,7 @@ func (cli *ApplyConfigStepImplementation) getHashAndTags(ctx context.Context, na } gitCommitHash, commitErr := getValueFromConfigMap(defaults.RadixGitCommitHashKey, gitConfigMap) gitTags, tagsErr := getValueFromConfigMap(defaults.RadixGitTagsKey, gitConfigMap) - err = stderrors.Join(commitErr, tagsErr) + err = errors.Join(commitErr, tagsErr) if err != nil { log.Error().Err(err).Msgf("could not retrieve git values from temporary configmap %s", pipelineInfo.GitConfigMapName) return "", "" @@ -516,56 +539,6 @@ func (cli *ApplyConfigStepImplementation) getHashAndTags(ctx context.Context, na return gitCommitHash, gitTags } -// CreateRadixApplication Create RadixApplication from radixconfig.yaml content -func CreateRadixApplication(ctx context.Context, radixClient radixclient.Interface, dnsConfig *dnsalias.DNSConfig, configFileContent string) (*radixv1.RadixApplication, error) { - ra := &radixv1.RadixApplication{} - - // Important: Must use sigs.k8s.io/yaml decoder to correctly unmarshal Kubernetes objects. - // This package supports encoding and decoding of yaml for CRD struct types using the json tag. - // The gopkg.in/yaml.v3 package requires the yaml tag. - if err := yaml.Unmarshal([]byte(configFileContent), ra); err != nil { - return nil, err - } - correctRadixApplication(ra) - - // Validate RA - if validate.RAContainsOldPublic(ra) { - log.Warn().Msg("component.public is deprecated, please use component.publicPort instead") - } - if err := validate.CanRadixApplicationBeInserted(ctx, radixClient, ra, dnsConfig); err != nil { - log.Error().Msg("Radix config not valid") - return nil, err - } - return ra, nil -} - -func correctRadixApplication(ra *radixv1.RadixApplication) { - if isAppNameLowercase, err := validate.IsApplicationNameLowercase(ra.Name); !isAppNameLowercase { - log.Warn().Err(err).Msg("%s Converting name to lowercase") - ra.Name = strings.ToLower(ra.Name) - } - for i := 0; i < len(ra.Spec.Components); i++ { - ra.Spec.Components[i].Resources = buildResource(&ra.Spec.Components[i]) - } - for i := 0; i < len(ra.Spec.Jobs); i++ { - ra.Spec.Jobs[i].Resources = buildResource(&ra.Spec.Jobs[i]) - } -} - -func buildResource(component radixv1.RadixCommonComponent) radixv1.ResourceRequirements { - memoryReqName := corev1.ResourceMemory.String() - resources := component.GetResources() - delete(resources.Limits, memoryReqName) - - if requestsMemory, ok := resources.Requests[memoryReqName]; ok { - if resources.Limits == nil { - resources.Limits = radixv1.ResourceList{} - } - resources.Limits[memoryReqName] = requestsMemory - } - return resources -} - func getValueFromConfigMap(key string, configMap *corev1.ConfigMap) (string, error) { value, ok := configMap.Data[key] if !ok { @@ -586,14 +559,14 @@ func validateDeployComponentImages(deployComponentImages pipeline.DeployEnvironm component := ra.GetCommonComponentByName(componentName) env := component.GetEnvironmentConfigByName(envName) - if len(component.GetImageTagName()) > 0 || (!utils.IsNil(env) && len(env.GetImageTagName()) > 0) { + if len(component.GetImageTagName()) > 0 || (!commonutils.IsNil(env) && len(env.GetImageTagName()) > 0) { continue } - errs = append(errs, errors.WithMessagef(ErrMissingRequiredImageTagName, "component %s in environment %s", componentName, envName)) + errs = append(errs, fmt.Errorf("component %s in environment %s: %w", componentName, envName, ErrMissingRequiredImageTagName)) } } } - return stderrors.Join(errs...) + return errors.Join(errs...) } diff --git a/pipeline-runner/steps/apply_radixconfig_test.go b/pipeline-runner/steps/applyconfig/step_test.go similarity index 77% rename from pipeline-runner/steps/apply_radixconfig_test.go rename to pipeline-runner/steps/applyconfig/step_test.go index 32f585eac..513b37cd5 100644 --- a/pipeline-runner/steps/apply_radixconfig_test.go +++ b/pipeline-runner/steps/applyconfig/step_test.go @@ -1,15 +1,13 @@ -package steps_test +package applyconfig_test import ( "context" - "os" "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/model" - "github.com/equinor/radix-operator/pipeline-runner/steps" - "github.com/equinor/radix-operator/pkg/apis/config/dnsalias" + "github.com/equinor/radix-operator/pipeline-runner/steps/applyconfig" "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" @@ -22,10 +20,6 @@ import ( kubefake "k8s.io/client-go/kubernetes/fake" ) -const ( - sampleApp = "./testdata/radixconfig.yaml" -) - func Test_RunApplyConfigTestSuite(t *testing.T) { suite.Run(t, new(applyConfigTestSuite)) } @@ -70,10 +64,10 @@ func (s *applyConfigTestSuite) Test_Deploy_BuildComponentInDeployPiplineShouldFa RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) err := applyStep.Run(context.Background(), &pipeline) - s.ErrorIs(err, steps.ErrDeployOnlyPipelineDoesNotSupportBuild) + s.ErrorIs(err, applyconfig.ErrDeployOnlyPipelineDoesNotSupportBuild) } func (s *applyConfigTestSuite) Test_Deploy_BuildJobInDeployPiplineShouldFail() { @@ -99,10 +93,10 @@ func (s *applyConfigTestSuite) Test_Deploy_BuildJobInDeployPiplineShouldFail() { RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) err := applyStep.Run(context.Background(), &pipeline) - s.ErrorIs(err, steps.ErrDeployOnlyPipelineDoesNotSupportBuild) + s.ErrorIs(err, applyconfig.ErrDeployOnlyPipelineDoesNotSupportBuild) } func (s *applyConfigTestSuite) Test_ApplyConfig_ShouldNotFail() { @@ -141,7 +135,7 @@ func (s *applyConfigTestSuite) Test_ApplyConfig_ShouldNotFail() { RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) err := applyStep.Run(context.Background(), &pipeline) s.NoError(err) @@ -160,9 +154,9 @@ func (s *applyConfigTestSuite) Test_Deploy_ComponentImageTagName() { expectedError error } scenarios := []scenario{ - {name: "no imageTagName in a component or an environment", expectedError: steps.ErrMissingRequiredImageTagName}, + {name: "no imageTagName in a component or an environment", expectedError: applyconfig.ErrMissingRequiredImageTagName}, {name: "imageTagName is in a component", componentTagName: "some-component-tag"}, - {name: "imageTagName is not set in an environment", hasEnvironmentConfig: true, expectedError: steps.ErrMissingRequiredImageTagName}, + {name: "imageTagName is not set in an environment", hasEnvironmentConfig: true, expectedError: applyconfig.ErrMissingRequiredImageTagName}, {name: "imageTagName is in an environment", hasEnvironmentConfig: true, environmentTagName: "some-env-tag"}, {name: "imageTagName is in a component, not in an environment", componentTagName: "some-component-tag", hasEnvironmentConfig: true}, {name: "imageTagName is in a component and in an environment", componentTagName: "some-component-tag", hasEnvironmentConfig: true, environmentTagName: "some-env-tag"}, @@ -191,13 +185,13 @@ func (s *applyConfigTestSuite) Test_Deploy_ComponentImageTagName() { RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) err := applyStep.Run(context.Background(), &pipeline) if ts.expectedError == nil { s.NoError(err) } else { - s.ErrorIs(err, steps.ErrMissingRequiredImageTagName) + s.ErrorIs(err, applyconfig.ErrMissingRequiredImageTagName) } }) } @@ -226,7 +220,7 @@ func (s *applyConfigTestSuite) Test_Deploy_ComponentWithImageTagNameInRAShouldSu RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.NoError(applyStep.Run(context.Background(), &pipeline)) } @@ -254,7 +248,7 @@ func (s *applyConfigTestSuite) Test_Deploy_ComponentWithImageTagNameInPipelineAr RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.NoError(applyStep.Run(context.Background(), &pipeline)) } @@ -281,10 +275,10 @@ func (s *applyConfigTestSuite) Test_Deploy_JobWithMissingImageTagNameShouldFail( RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) err := applyStep.Run(context.Background(), &pipeline) - s.ErrorIs(err, steps.ErrMissingRequiredImageTagName) + s.ErrorIs(err, applyconfig.ErrMissingRequiredImageTagName) s.ErrorContains(err, "deployjob") s.ErrorContains(err, "dev") } @@ -312,7 +306,7 @@ func (s *applyConfigTestSuite) Test_Deploy_JobWithImageTagNameInRAShouldSucceed( RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.NoError(applyStep.Run(context.Background(), &pipeline)) } @@ -340,25 +334,11 @@ func (s *applyConfigTestSuite) Test_DeployComponentWitImageTagNameInPipelineArgS RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.NoError(applyStep.Run(context.Background(), &pipeline)) } -func (s *applyConfigTestSuite) Test_CreateRadixApplication_LimitMemoryIsTakenFromRequestsMemory() { - rr := utils.NewRegistrationBuilder().WithName("testapp").BuildRR() - _, err := s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) - s.Require().NoError(err) - configFileContent, err := os.ReadFile(sampleApp) - s.Require().NoError(err) - ra, err := steps.CreateRadixApplication(context.Background(), s.radixClient, &dnsalias.DNSConfig{}, string(configFileContent)) - s.Require().NoError(err) - s.Equal("100Mi", ra.Spec.Components[0].Resources.Limits["memory"], "server1 invalid resource limits memory") - s.Equal("100Mi", ra.Spec.Components[1].Resources.Limits["memory"], "server2 invalid resource limits memory") - s.Empty(ra.Spec.Components[2].Resources.Limits["memory"], "server3 not expected resource limits memory") - s.Empty(ra.Spec.Components[3].Resources.Limits["memory"], "server4 not expected resource limits memory") -} - func (s *applyConfigTestSuite) Test_Deploy_ComponentsToDeployValidation() { schedulerPort := pointers.Ptr(int32(8080)) raBuilder := utils.ARadixApplication(). @@ -434,7 +414,7 @@ func (s *applyConfigTestSuite) Test_Deploy_ComponentsToDeployValidation() { pipeline.PipelineArguments.DeploymentName = "depl" } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) err = applyStep.Run(context.Background(), &pipeline) if len(ts.expectedError) > 0 { @@ -445,3 +425,96 @@ func (s *applyConfigTestSuite) Test_Deploy_ComponentsToDeployValidation() { }) } } + +func (s *applyConfigTestSuite) Test_BuildDeploy_RuntimeValidation() { + appName, branchName, schedulerPort := "anyapp", "anybranch", int32(9999) + prepareConfigMapName := "preparecm" + + tests := map[string]struct { + useBuildKit bool + components []utils.RadixApplicationComponentBuilder + jobs []utils.RadixApplicationJobComponentBuilder + expectError bool + }{ + "buildkit: support non-amd64 build architectures": { + components: []utils.RadixApplicationComponentBuilder{ + utils.NewApplicationComponentBuilder().WithName("comp1").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + utils.NewApplicationComponentBuilder().WithName("comp2").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + }, + jobs: []utils.RadixApplicationJobComponentBuilder{ + utils.NewApplicationJobComponentBuilder().WithName("job1").WithSchedulerPort(&schedulerPort).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + utils.NewApplicationJobComponentBuilder().WithName("job2").WithSchedulerPort(&schedulerPort).WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + }, + useBuildKit: true, + expectError: false, + }, + "non-buildkit: succeed if all components are amd64": { + components: []utils.RadixApplicationComponentBuilder{ + utils.NewApplicationComponentBuilder().WithName("comp1").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + utils.NewApplicationComponentBuilder().WithName("comp2").WithRuntime(&radixv1.Runtime{Architecture: ""}), + utils.NewApplicationComponentBuilder().WithName("comp3"), + }, + useBuildKit: false, + expectError: false, + }, + "non-buildkit: fail if any component is non-amd64": { + components: []utils.RadixApplicationComponentBuilder{ + utils.NewApplicationComponentBuilder().WithName("comp1").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}), + utils.NewApplicationComponentBuilder().WithName("comp2").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), + }, + useBuildKit: false, + expectError: true, + }, + "non-buildkit: succeed if all jobs are amd64": { + jobs: []utils.RadixApplicationJobComponentBuilder{ + utils.NewApplicationJobComponentBuilder().WithName("job1").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}).WithSchedulerPort(&schedulerPort), + utils.NewApplicationJobComponentBuilder().WithName("job2").WithRuntime(&radixv1.Runtime{Architecture: ""}).WithSchedulerPort(&schedulerPort), + utils.NewApplicationJobComponentBuilder().WithName("job3").WithSchedulerPort(&schedulerPort), + }, + useBuildKit: false, + expectError: false, + }, + "non-buildkit: fail if any job is non-amd64": { + jobs: []utils.RadixApplicationJobComponentBuilder{ + utils.NewApplicationJobComponentBuilder().WithName("job1").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}).WithSchedulerPort(&schedulerPort), + utils.NewApplicationJobComponentBuilder().WithName("job2").WithRuntime(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}).WithSchedulerPort(&schedulerPort), + }, + useBuildKit: false, + expectError: true, + }, + } + + 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(). + WithAppName(appName). + WithBuildKit(&test.useBuildKit). + WithEnvironment("dev", branchName). + WithComponents(test.components...). + WithJobComponents(test.jobs...). + BuildRA() + s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil)) + + pipeline := model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + PipelineType: string(radixv1.BuildDeploy), + Branch: branchName, + }, + RadixConfigMapName: prepareConfigMapName, + } + + applyStep := applyconfig.NewApplyConfigStep() + applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + err := applyStep.Run(context.Background(), &pipeline) + if test.expectError { + s.ErrorIs(err, applyconfig.ErrBuildNonDefaultRuntimeArchitectureWithoutBuildKitError) + } else { + s.NoError(err) + } + }) + } + +} diff --git a/pipeline-runner/steps/build_acr.go b/pipeline-runner/steps/build/build_acr.go similarity index 87% rename from pipeline-runner/steps/build_acr.go rename to pipeline-runner/steps/build/build_acr.go index f933cd226..ec1c5fb92 100644 --- a/pipeline-runner/steps/build_acr.go +++ b/pipeline-runner/steps/build/build_acr.go @@ -1,4 +1,4 @@ -package steps +package build import ( "encoding/json" @@ -7,12 +7,13 @@ import ( "strings" "time" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pipeline-runner/internal/commandbuilder" "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" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + 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" @@ -26,24 +27,25 @@ import ( ) const ( - buildSecretsMountPath = "/build-secrets" - privateImageHubMountPath = "/radix-private-image-hubs" - buildahRegistryAuthFile = "/home/build/auth.json" - azureServicePrincipleContext = "/radix-image-builder/.azure" 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(pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar) ([]*batchv1.Job, error) { rr := step.GetRegistration() - if isUsingBuildKit(pipelineInfo) { + if pipelineInfo.IsUsingBuildKit() { return step.buildContainerImageBuildingJobsForBuildKit(rr, pipelineInfo, buildSecrets) } return step.buildContainerImageBuildingJobsForACRTasks(rr, pipelineInfo, buildSecrets) } -func (step *BuildStepImplementation) buildContainerImageBuildingJobsForACRTasks(rr *v1.RadixRegistration, pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar) ([]*batchv1.Job, error) { +func (step *BuildStepImplementation) buildContainerImageBuildingJobsForACRTasks(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...) @@ -51,11 +53,11 @@ func (step *BuildStepImplementation) buildContainerImageBuildingJobsForACRTasks( log.Debug().Msg("build a build-job") hash := strings.ToLower(utils.RandStringStrSeed(5, pipelineInfo.PipelineArguments.JobName)) - job := buildContainerImageBuildingJob(rr, pipelineInfo, buildSecrets, hash, buildComponentImages...) + job := buildContainerImageBuildingJob(rr, pipelineInfo, buildSecrets, hash, &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}, buildComponentImages...) return []*batchv1.Job{job}, nil } -func (step *BuildStepImplementation) buildContainerImageBuildingJobsForBuildKit(rr *v1.RadixRegistration, pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar) ([]*batchv1.Job, error) { +func (step *BuildStepImplementation) buildContainerImageBuildingJobsForBuildKit(rr *radixv1.RadixRegistration, pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar) ([]*batchv1.Job, error) { var jobs []*batchv1.Job for envName, buildComponentImages := range pipelineInfo.BuildComponentImages { log.Debug().Msgf("build a build-kit jobs for the env %s", envName) @@ -63,7 +65,7 @@ func (step *BuildStepImplementation) buildContainerImageBuildingJobsForBuildKit( log.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 := buildContainerImageBuildingJob(rr, pipelineInfo, buildSecrets, hash, componentImage) + job := buildContainerImageBuildingJob(rr, pipelineInfo, buildSecrets, hash, componentImage.Runtime, componentImage) job.ObjectMeta.Labels[kube.RadixEnvLabel] = envName job.ObjectMeta.Labels[kube.RadixComponentLabel] = componentImage.ComponentName @@ -73,24 +75,24 @@ func (step *BuildStepImplementation) buildContainerImageBuildingJobsForBuildKit( return jobs, nil } -func buildContainerImageBuildingJob(rr *v1.RadixRegistration, pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar, hash string, buildComponentImages ...pipeline.BuildComponentImage) *batchv1.Job { +func buildContainerImageBuildingJob(rr *radixv1.RadixRegistration, pipelineInfo *model.PipelineInfo, buildSecrets []corev1.EnvVar, hash string, jobRuntime *radixv1.Runtime, buildComponentImages ...pipeline.BuildComponentImage) *batchv1.Job { appName := rr.Name branch := pipelineInfo.PipelineArguments.Branch imageTag := pipelineInfo.PipelineArguments.ImageTag pipelineJobName := pipelineInfo.PipelineArguments.JobName - initContainers := git.CloneInitContainers(rr.Spec.CloneURL, branch, pipelineInfo.PipelineArguments.ContainerSecurityContext) + initContainers := git.CloneInitContainers(rr.Spec.CloneURL, branch) 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 := &pipelineInfo.PipelineArguments.PodSecurityContext - if isUsingBuildKit(pipelineInfo) { + buildPodSecurityContext := getAcrTaskBuildPodSecurityContext() + + if pipelineInfo.IsUsingBuildKit() { for _, buildContainer := range buildContainers { annotations[fmt.Sprintf("container.apparmor.security.beta.kubernetes.io/%s", buildContainer.Name)] = "unconfined" } - buildPodSecurityContext = &pipelineInfo.PipelineArguments.BuildKitPodSecurityContext + buildPodSecurityContext = getBuildKitPodSecurityContext() } buildJobName := fmt.Sprintf("radix-builder-%s-%s-%s", timestamp, imageTag, hash) @@ -122,8 +124,8 @@ func buildContainerImageBuildingJob(rr *v1.RadixRegistration, pipelineInfo *mode InitContainers: initContainers, Containers: buildContainers, SecurityContext: buildPodSecurityContext, - Volumes: getContainerImageBuildingJobVolumes(&defaultMode, buildSecrets, isUsingBuildKit(pipelineInfo), buildContainers), - Affinity: utils.GetAffinityForPipelineJob(), + Volumes: getContainerImageBuildingJobVolumes(&defaultMode, buildSecrets, pipelineInfo.IsUsingBuildKit(), buildContainers), + Affinity: utils.GetAffinityForPipelineJob(jobRuntime), Tolerations: utils.GetPipelineJobPodSpecTolerations(), }, }, @@ -238,11 +240,11 @@ func createContainerImageBuildingContainers(appName string, pipelineInfo *model. containerRegistry := pipelineInfo.PipelineArguments.ContainerRegistry imageBuilder := fmt.Sprintf("%s/%s", containerRegistry, pipelineInfo.PipelineArguments.ImageBuilder) - buildContainerSecContext := &pipelineInfo.PipelineArguments.ContainerSecurityContext + buildContainerSecContext := getAcrTaskBuildContainerSecurityContext() var secretMountsArgsString string - if isUsingBuildKit(pipelineInfo) { + if pipelineInfo.IsUsingBuildKit() { imageBuilder = pipelineInfo.PipelineArguments.BuildKitImageBuilder - buildContainerSecContext = getBuildContainerSecContext() + buildContainerSecContext = getBuildKitContainerSecurityContext() secretMountsArgsString = getSecretArgs(buildSecrets) } @@ -260,7 +262,7 @@ func createContainerImageBuildingContainers(appName string, pipelineInfo *model. Command: command, ImagePullPolicy: corev1.PullAlways, Env: envVars, - VolumeMounts: getContainerImageBuildingJobVolumeMounts(buildSecrets, isUsingBuildKit(pipelineInfo), componentImage.ContainerName), + VolumeMounts: getContainerImageBuildingJobVolumeMounts(buildSecrets, pipelineInfo.IsUsingBuildKit(), componentImage.ContainerName), SecurityContext: buildContainerSecContext, Resources: resources, } @@ -271,7 +273,7 @@ func createContainerImageBuildingContainers(appName string, pipelineInfo *model. 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 isUsingBuildKit(pipelineInfo) { + if pipelineInfo.IsUsingBuildKit() { envVars = append(envVars, getBuildKitEnvVars()...) } envVars = append(envVars, buildSecrets...) @@ -280,7 +282,7 @@ func getContainerEnvVars(appName string, pipelineInfo *model.PipelineInfo, compo func getContainerResources(pipelineInfo *model.PipelineInfo) corev1.ResourceRequirements { var resources corev1.ResourceRequirements - if isUsingBuildKit(pipelineInfo) { + if pipelineInfo.IsUsingBuildKit() { resources = corev1.ResourceRequirements{ Requests: map[corev1.ResourceName]resource.Quantity{ corev1.ResourceCPU: resource.MustParse(pipelineInfo.PipelineArguments.Builder.ResourcesRequestsCPU), @@ -295,7 +297,7 @@ func getContainerResources(pipelineInfo *model.PipelineInfo) corev1.ResourceRequ } func getContainerCommand(pipelineInfo *model.PipelineInfo, containerRegistry string, secretMountsArgsString string, componentImage pipeline.BuildComponentImage, clusterTypeImage, clusterNameImage string) []string { - if !isUsingBuildKit(pipelineInfo) { + if !pipelineInfo.IsUsingBuildKit() { return nil } cacheImagePath := utils.GetImageCachePath(pipelineInfo.PipelineArguments.AppContainerRegistry, pipelineInfo.RadixApplication.Name) @@ -561,11 +563,30 @@ func getBuildahContainerCommand(containerImageRegistry, secretArgsString string, return []string{"/bin/bash", "-c", commandList.String()} } -func isUsingBuildKit(pipelineInfo *model.PipelineInfo) bool { - return pipelineInfo.RadixApplication.Spec.Build != nil && pipelineInfo.RadixApplication.Spec.Build.UseBuildKit != nil && *pipelineInfo.RadixApplication.Spec.Build.UseBuildKit +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 getBuildContainerSecContext() *corev1.SecurityContext { +func getBuildKitContainerSecurityContext() *corev1.SecurityContext { return securitycontext.Container( securitycontext.WithContainerDropAllCapabilities(), securitycontext.WithContainerCapabilities([]corev1.Capability{"SETUID", "SETGID", "SETFCAP"}), @@ -573,8 +594,8 @@ func getBuildContainerSecContext() *corev1.SecurityContext { Type: corev1.SeccompProfileTypeLocalhost, LocalhostProfile: utils.StringPtr("allow-buildah.json"), }), - securitycontext.WithContainerRunAsNonRoot(utils.BoolPtr(false)), - securitycontext.WithReadOnlyRootFileSystem(utils.BoolPtr(true)), + securitycontext.WithContainerRunAsNonRoot(pointers.Ptr(false)), + securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), ) } diff --git a/pipeline-runner/steps/build_secret.go b/pipeline-runner/steps/build/build_secret.go similarity index 99% rename from pipeline-runner/steps/build_secret.go rename to pipeline-runner/steps/build/build_secret.go index 1460ef4c5..823642577 100644 --- a/pipeline-runner/steps/build_secret.go +++ b/pipeline-runner/steps/build/build_secret.go @@ -1,4 +1,4 @@ -package steps +package build import ( "errors" diff --git a/pipeline-runner/steps/build.go b/pipeline-runner/steps/build/step.go similarity index 99% rename from pipeline-runner/steps/build.go rename to pipeline-runner/steps/build/step.go index 4617150fa..c5a5804f9 100644 --- a/pipeline-runner/steps/build.go +++ b/pipeline-runner/steps/build/step.go @@ -1,4 +1,4 @@ -package steps +package build import ( "context" diff --git a/pipeline-runner/steps/build_test.go b/pipeline-runner/steps/build_test.go index c5bc4eb9e..f042705be 100644 --- a/pipeline-runner/steps/build_test.go +++ b/pipeline-runner/steps/build_test.go @@ -12,8 +12,11 @@ import ( "github.com/equinor/radix-operator/pipeline-runner/internal/hash" internaltest "github.com/equinor/radix-operator/pipeline-runner/internal/test" 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" + "github.com/equinor/radix-operator/pipeline-runner/steps/applyconfig" + "github.com/equinor/radix-operator/pipeline-runner/steps/build" + "github.com/equinor/radix-operator/pipeline-runner/steps/deploy" application "github.com/equinor/radix-operator/pkg/apis/applicationconfig" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" @@ -28,6 +31,7 @@ import ( kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" prometheusfake "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" "github.com/stretchr/testify/suite" + 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" @@ -67,10 +71,17 @@ func (s *buildTestSuite) SetupSubTest() { } func (s *buildTestSuite) Test_BranchIsNotMapped_ShouldSkip() { - anyBranch := "master" - anyEnvironment := "dev" - anyComponentName := "app" - anyNoMappedBranch := "feature" + const ( + anyAppName = "any-app" + anyJobName = "any-job-name" + anyImageTag = "anytag" + anyCommitID = "4faca8595c5283a9d0f17a623b9255a0d9866a2e" + anyGitTags = "some tags go here" + anyBranch = "master" + anyEnvironment = "dev" + anyComponentName = "app" + anyNoMappedBranch = "feature" + ) rr := utils.ARadixRegistration(). WithName(anyAppName). @@ -86,7 +97,7 @@ func (s *buildTestSuite) Test_BranchIsNotMapped_ShouldSkip() { jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(0) - cli := steps.NewBuildStep(jobWaiter) + cli := build.NewBuildStep(jobWaiter) cli.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) targetEnvs := application.GetTargetEnvironments(anyNoMappedBranch, ra) @@ -144,13 +155,13 @@ func (s *buildTestSuite) Test_BuildDeploy_JobSpecAndDeploymentConsistent() { GitConfigMapName: gitConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) deployStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) @@ -168,7 +179,7 @@ func (s *buildTestSuite) Test_BuildDeploy_JobSpecAndDeploymentConsistent() { {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: steps.RadixImageBuilderHomeVolumeName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(5, resource.Mega)}}}, + {Name: build.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)}}}, } @@ -181,15 +192,15 @@ func (s *buildTestSuite) Test_BuildDeploy_JobSpecAndDeploymentConsistent() { s.Equal(expectedAffinity, job.Spec.Template.Spec.Affinity) // Check init containers - s.Len(job.Spec.Template.Spec.InitContainers, 2) - s.ElementsMatch([]string{"internal-nslookup", "clone"}, slice.Map(job.Spec.Template.Spec.InitContainers, func(c corev1.Container) string { return c.Name })) + 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("alpine/git:user", cloneContainer.Image) - s.Equal([]string{"/bin/sh", "-c"}, cloneContainer.Command) - s.Equal([]string{fmt.Sprintf("git clone --recurse-submodules %s -b %s --verbose --progress /workspace", cloneURL, buildBranch)}, cloneContainer.Args) + s.Equal("alpine/git:2.45.1", 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: "/home/git-user/.ssh", ReadOnly: true}, + {Name: git.GitSSHKeyVolumeName, MountPath: "/.ssh", ReadOnly: true}, } s.ElementsMatch(expectedCloneVolumeMounts, cloneContainer.VolumeMounts) // Check containers @@ -201,7 +212,7 @@ func (s *buildTestSuite) Test_BuildDeploy_JobSpecAndDeploymentConsistent() { expectedBuildVolumeMounts := []corev1.VolumeMount{ {Name: git.BuildContextVolumeName, MountPath: git.Workspace}, {Name: defaults.AzureACRServicePrincipleSecretName, MountPath: "/radix-image-builder/.azure", ReadOnly: true}, - {Name: steps.RadixImageBuilderHomeVolumeName, MountPath: "/home/radix-image-builder", ReadOnly: false}, + {Name: build.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}, } @@ -285,13 +296,13 @@ func (s *buildTestSuite) Test_BuildJobSpec_MultipleComponents() { RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) deployStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) @@ -380,6 +391,138 @@ func (s *buildTestSuite) Test_BuildJobSpec_MultipleComponents() { 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, + Builder: model.Builder{ResourcesLimitsMemory: "100M", ResourcesRequestsCPU: "50m", ResourcesRequestsMemory: "50M"}, + }, + RadixConfigMapName: prepareConfigMapName, + } + + applyStep := applyconfig.NewApplyConfigStep() + applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) + jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).AnyTimes() + buildStep := build.NewBuildStep(jobWaiter) + buildStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) + deployStep.Init(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" @@ -428,13 +571,13 @@ func (s *buildTestSuite) Test_BuildJobSpec_MultipleComponents_IgnoreDisabled() { RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) deployStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) @@ -580,13 +723,13 @@ func (s *buildTestSuite) Test_BuildChangedComponents() { RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) deployStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) @@ -1051,15 +1194,15 @@ func (s *buildTestSuite) Test_DetectComponentsToBuild() { } s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, test.prepareBuildCtx)) pipeline := model.PipelineInfo{PipelineArguments: piplineArgs, RadixConfigMapName: prepareConfigMapName} - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(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 := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) deployStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) // Run pipeline steps @@ -1127,9 +1270,9 @@ func (s *buildTestSuite) Test_BuildJobSpec_ImageTagNames() { RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) deployStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) @@ -1185,9 +1328,9 @@ func (s *buildTestSuite) Test_BuildJobSpec_PushImage() { jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(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)) @@ -1225,9 +1368,9 @@ func (s *buildTestSuite) Test_BuildJobSpec_UseCache() { jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(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)) @@ -1264,9 +1407,9 @@ func (s *buildTestSuite) Test_BuildJobSpec_WithDockerfileName() { jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(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)) @@ -1303,9 +1446,9 @@ func (s *buildTestSuite) Test_BuildJobSpec_WithSourceFolder() { jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(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)) @@ -1344,11 +1487,11 @@ func (s *buildTestSuite) Test_BuildJobSpec_WithBuildSecrets() { jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) deployStep.Init(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)) @@ -1409,9 +1552,9 @@ func (s *buildTestSuite) Test_BuildJobSpec_BuildKit() { jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(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)) @@ -1467,20 +1610,14 @@ func (s *buildTestSuite) Test_BuildJobSpec_BuildKit() { expectedVolumeMounts := []corev1.VolumeMount{ {Name: git.BuildContextVolumeName, MountPath: git.Workspace, ReadOnly: false}, {Name: defaults.AzureACRServicePrincipleSecretName, MountPath: "/radix-image-builder/.azure", ReadOnly: true}, - {Name: steps.BuildKitRunVolumeName, MountPath: "/run", ReadOnly: false}, - {Name: steps.BuildKitRootVolumeName, MountPath: "/root", ReadOnly: false}, + {Name: build.BuildKitRunVolumeName, MountPath: "/run", ReadOnly: false}, + {Name: build.BuildKitRootVolumeName, MountPath: "/root", ReadOnly: false}, {Name: defaults.PrivateImageHubSecretName, MountPath: "/radix-private-image-hubs", ReadOnly: true}, - {Name: steps.RadixImageBuilderHomeVolumeName, MountPath: "/home/build", ReadOnly: false}, + {Name: build.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) - 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, job.Spec.Template.Spec.Affinity) } func (s *buildTestSuite) Test_BuildJobSpec_BuildKit_PushImage() { @@ -1515,9 +1652,9 @@ func (s *buildTestSuite) Test_BuildJobSpec_BuildKit_PushImage() { jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(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)) @@ -1586,9 +1723,9 @@ func (s *buildTestSuite) Test_BuildJobSpec_BuildKit_WithBuildSecrets() { jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(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)) @@ -1601,10 +1738,10 @@ func (s *buildTestSuite) Test_BuildJobSpec_BuildKit_WithBuildSecrets() { {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: steps.RadixImageBuilderHomeVolumeName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(5, resource.Mega)}}}, + {Name: build.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: steps.BuildKitRunVolumeName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, - {Name: steps.BuildKitRootVolumeName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, + {Name: build.BuildKitRunVolumeName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{SizeLimit: resource.NewScaledQuantity(100, resource.Giga)}}}, + {Name: build.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)}}}, } @@ -1614,10 +1751,10 @@ func (s *buildTestSuite) Test_BuildJobSpec_BuildKit_WithBuildSecrets() { expectedVolumeMounts := []corev1.VolumeMount{ {Name: git.BuildContextVolumeName, MountPath: git.Workspace, ReadOnly: false}, {Name: defaults.AzureACRServicePrincipleSecretName, MountPath: "/radix-image-builder/.azure", ReadOnly: true}, - {Name: steps.BuildKitRunVolumeName, MountPath: "/run", ReadOnly: false}, - {Name: steps.BuildKitRootVolumeName, MountPath: "/root", ReadOnly: false}, + {Name: build.BuildKitRunVolumeName, MountPath: "/run", ReadOnly: false}, + {Name: build.BuildKitRootVolumeName, MountPath: "/root", ReadOnly: false}, {Name: defaults.PrivateImageHubSecretName, MountPath: "/radix-private-image-hubs", ReadOnly: true}, - {Name: steps.RadixImageBuilderHomeVolumeName, MountPath: "/home/build", ReadOnly: false}, + {Name: build.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}, @@ -1647,6 +1784,78 @@ func (s *buildTestSuite) Test_BuildJobSpec_BuildKit_WithBuildSecrets() { 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() + _, _ = 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", + 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(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) + buildStep := build.NewBuildStep(jobWaiter) + buildStep.Init(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" @@ -1736,13 +1945,13 @@ func (s *buildTestSuite) Test_BuildJobSpec_EnvConfigSrcAndImage() { RadixConfigMapName: prepareConfigMapName, } - applyStep := steps.NewApplyConfigStep() + applyStep := applyconfig.NewApplyConfigStep() applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) jobWaiter := internalwait.NewMockJobCompletionWaiter(s.ctrl) jobWaiter.EXPECT().Wait(gomock.Any()).Return(nil).Times(1) - buildStep := steps.NewBuildStep(jobWaiter) + buildStep := build.NewBuildStep(jobWaiter) buildStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) - deployStep := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + deployStep := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) deployStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr) s.Require().NoError(applyStep.Run(context.Background(), &pipeline)) diff --git a/pipeline-runner/steps/deploy.go b/pipeline-runner/steps/deploy/step.go similarity index 89% rename from pipeline-runner/steps/deploy.go rename to pipeline-runner/steps/deploy/step.go index eacd8df96..17b2d5e66 100644 --- a/pipeline-runner/steps/deploy.go +++ b/pipeline-runner/steps/deploy/step.go @@ -1,14 +1,13 @@ -package steps +package deploy import ( "context" "fmt" - pipelineRunnerInternal "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" + "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" "github.com/equinor/radix-operator/pipeline-runner/model" "github.com/equinor/radix-operator/pipeline-runner/steps/internal" "github.com/equinor/radix-operator/pkg/apis/defaults" - "github.com/equinor/radix-operator/pkg/apis/kube" "github.com/equinor/radix-operator/pkg/apis/pipeline" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" @@ -19,13 +18,13 @@ import ( // DeployStepImplementation Step to deploy RD into environment type DeployStepImplementation struct { stepType pipeline.StepType - namespaceWatcher kube.NamespaceWatcher - radixDeploymentWatcher pipelineRunnerInternal.RadixDeploymentWatcher + namespaceWatcher watcher.NamespaceWatcher + radixDeploymentWatcher watcher.RadixDeploymentWatcher model.DefaultStepImplementation } // NewDeployStep Constructor -func NewDeployStep(namespaceWatcher kube.NamespaceWatcher, radixDeploymentWatcher pipelineRunnerInternal.RadixDeploymentWatcher) model.Step { +func NewDeployStep(namespaceWatcher watcher.NamespaceWatcher, radixDeploymentWatcher watcher.RadixDeploymentWatcher) model.Step { return &DeployStepImplementation{ stepType: pipeline.DeployStep, namespaceWatcher: namespaceWatcher, @@ -82,12 +81,12 @@ func (cli *DeployStepImplementation) deployToEnv(ctx context.Context, appName, e defaultEnvVars[defaults.RadixCommitHashEnvironmentVariable] = pipelineInfo.PipelineArguments.CommitID // Commit ID specified by job arguments } - radixApplicationHash, err := createRadixApplicationHash(pipelineInfo.RadixApplication) + radixApplicationHash, err := internal.CreateRadixApplicationHash(pipelineInfo.RadixApplication) if err != nil { return err } - buildSecretHash, err := createBuildSecretHash(pipelineInfo.BuildSecret) + buildSecretHash, err := internal.CreateBuildSecretHash(pipelineInfo.BuildSecret) if err != nil { return err } diff --git a/pipeline-runner/steps/deploy_test.go b/pipeline-runner/steps/deploy/step_test.go similarity index 92% rename from pipeline-runner/steps/deploy_test.go rename to pipeline-runner/steps/deploy/step_test.go index 0f328f3fc..56b4741fc 100644 --- a/pipeline-runner/steps/deploy_test.go +++ b/pipeline-runner/steps/deploy/step_test.go @@ -1,4 +1,4 @@ -package steps_test +package deploy_test import ( "context" @@ -7,7 +7,9 @@ import ( "strings" "testing" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pipeline-runner/internal/watcher" + "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" commonTest "github.com/equinor/radix-operator/pkg/apis/test" @@ -18,7 +20,6 @@ import ( secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake" "github.com/equinor/radix-operator/pipeline-runner/model" - "github.com/equinor/radix-operator/pipeline-runner/steps" 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" @@ -51,24 +52,6 @@ func setupTest(t *testing.T) (*kubernetes.Clientset, *kube.Kube, *radix.Clientse return kubeclient, kubeUtil, radixclient, testUtils } -// FakeNamespaceWatcher Unit tests doesn't handle multi-threading well -type FakeNamespaceWatcher struct { -} - -// FakeRadixDeploymentWatcher Unit tests doesn't handle multi-threading well -type FakeRadixDeploymentWatcher struct { -} - -// WaitFor Waits for namespace to appear -func (watcher FakeNamespaceWatcher) WaitFor(_ context.Context, _ string) error { - return nil -} - -// WaitFor Waits for radix deployment gets active -func (watcher FakeRadixDeploymentWatcher) WaitForActive(_, _ string) error { - return nil -} - func TestDeploy_BranchIsNotMapped_ShouldSkip(t *testing.T) { kubeclient, kubeUtil, radixclient, _ := setupTest(t) @@ -89,7 +72,7 @@ func TestDeploy_BranchIsNotMapped_ShouldSkip(t *testing.T) { WithName(anyComponentName)). BuildRA() - cli := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + cli := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) targetEnvs := application.GetTargetEnvironments(anyNoMappedBranch, ra) @@ -133,7 +116,7 @@ func TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists(t WithAuthentication( &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ - PassCertificateToUpstream: utils.BoolPtr(true), + PassCertificateToUpstream: pointers.Ptr(true), }, }, ). @@ -147,7 +130,7 @@ func TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists(t &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ Verification: &certificateVerification, - PassCertificateToUpstream: utils.BoolPtr(false), + PassCertificateToUpstream: pointers.Ptr(false), }, }, ). @@ -159,7 +142,7 @@ func TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists(t WithAuthentication( &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ - PassCertificateToUpstream: utils.BoolPtr(true), + PassCertificateToUpstream: pointers.Ptr(true), }, }, ). @@ -193,7 +176,7 @@ func TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists(t BuildRA() // Prometheus don´t contain any fake - cli := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + cli := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) dnsConfig := dnsalias.DNSConfig{ @@ -262,13 +245,13 @@ func TestDeploy_PromotionSetup_ShouldCreateNamespacesForAllBranchesIfNotExists(t x0 := &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ Verification: &certificateVerification, - PassCertificateToUpstream: utils.BoolPtr(false), + PassCertificateToUpstream: pointers.Ptr(false), }, } x1 := &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ - PassCertificateToUpstream: utils.BoolPtr(true), + PassCertificateToUpstream: pointers.Ptr(true), }, } @@ -313,7 +296,7 @@ func TestDeploy_SetCommitID_whenSet(t *testing.T) { BuildRA() // Prometheus don´t contain any fake - cli := steps.NewDeployStep(FakeNamespaceWatcher{}, FakeRadixDeploymentWatcher{}) + cli := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, watcher.FakeRadixDeploymentWatcher{}) cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) applicationConfig := application.NewApplicationConfig(kubeclient, kubeUtil, radixclient, rr, ra, nil) @@ -373,7 +356,7 @@ func TestDeploy_WaitActiveDeployment(t *testing.T) { kubeclient, kubeUtil, radixClient, _ := setupTest(tt) ctrl := gomock.NewController(tt) radixDeploymentWatcher := watcher.NewMockRadixDeploymentWatcher(ctrl) - cli := steps.NewDeployStep(FakeNamespaceWatcher{}, radixDeploymentWatcher) + cli := deploy.NewDeployStep(watcher.FakeNamespaceWatcher{}, radixDeploymentWatcher) cli.Init(kubeclient, radixClient, kubeUtil, &monitoring.Clientset{}, rr) applicationConfig := application.NewApplicationConfig(kubeclient, kubeUtil, radixClient, rr, ra, nil) diff --git a/pipeline-runner/steps/errors.go b/pipeline-runner/steps/errors.go deleted file mode 100644 index 0888493a8..000000000 --- a/pipeline-runner/steps/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package steps - -import ( - "github.com/pkg/errors" -) - -var ( - ErrDeployOnlyPipelineDoesNotSupportBuild = errors.New("deploy pipeline does not support building components and jobs") - ErrMissingRequiredImageTagName = errors.New("missing required imageTagName in a component, an environmentConfig or in a pipeline argument") -) diff --git a/pipeline-runner/steps/internal/application.go b/pipeline-runner/steps/internal/application.go new file mode 100644 index 000000000..84f70bebb --- /dev/null +++ b/pipeline-runner/steps/internal/application.go @@ -0,0 +1,64 @@ +package internal + +import ( + "context" + "strings" + + "github.com/equinor/radix-operator/pkg/apis/config/dnsalias" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + validate "github.com/equinor/radix-operator/pkg/apis/radixvalidators" + radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" + "github.com/rs/zerolog/log" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" +) + +// CreateRadixApplication Create RadixApplication from radixconfig.yaml content +func CreateRadixApplication(ctx context.Context, radixClient radixclient.Interface, dnsConfig *dnsalias.DNSConfig, configFileContent string) (*radixv1.RadixApplication, error) { + ra := &radixv1.RadixApplication{} + + // Important: Must use sigs.k8s.io/yaml decoder to correctly unmarshal Kubernetes objects. + // This package supports encoding and decoding of yaml for CRD struct types using the json tag. + // The gopkg.in/yaml.v3 package requires the yaml tag. + if err := yaml.Unmarshal([]byte(configFileContent), ra); err != nil { + return nil, err + } + correctRadixApplication(ra) + + // Validate RA + if validate.RAContainsOldPublic(ra) { + log.Warn().Msg("component.public is deprecated, please use component.publicPort instead") + } + if err := validate.CanRadixApplicationBeInserted(ctx, radixClient, ra, dnsConfig); err != nil { + log.Error().Msg("Radix config not valid") + return nil, err + } + return ra, nil +} + +func correctRadixApplication(ra *radixv1.RadixApplication) { + if isAppNameLowercase, err := validate.IsApplicationNameLowercase(ra.Name); !isAppNameLowercase { + log.Warn().Err(err).Msg("%s Converting name to lowercase") + ra.Name = strings.ToLower(ra.Name) + } + for i := 0; i < len(ra.Spec.Components); i++ { + ra.Spec.Components[i].Resources = buildResource(&ra.Spec.Components[i]) + } + for i := 0; i < len(ra.Spec.Jobs); i++ { + ra.Spec.Jobs[i].Resources = buildResource(&ra.Spec.Jobs[i]) + } +} + +func buildResource(component radixv1.RadixCommonComponent) radixv1.ResourceRequirements { + memoryReqName := corev1.ResourceMemory.String() + resources := component.GetResources() + delete(resources.Limits, memoryReqName) + + if requestsMemory, ok := resources.Requests[memoryReqName]; ok { + if resources.Limits == nil { + resources.Limits = radixv1.ResourceList{} + } + resources.Limits[memoryReqName] = requestsMemory + } + return resources +} diff --git a/pipeline-runner/steps/internal/application_test.go b/pipeline-runner/steps/internal/application_test.go new file mode 100644 index 000000000..d677ace2d --- /dev/null +++ b/pipeline-runner/steps/internal/application_test.go @@ -0,0 +1,34 @@ +package internal_test + +import ( + "context" + "os" + "testing" + + "github.com/equinor/radix-operator/pipeline-runner/steps/internal" + "github.com/equinor/radix-operator/pkg/apis/config/dnsalias" + "github.com/equinor/radix-operator/pkg/apis/utils" + radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_CreateRadixApplication_LimitMemoryIsTakenFromRequestsMemory(t *testing.T) { + const ( + sampleApp = "./testdata/radixconfig.yaml" + ) + + radixClient := radixfake.NewSimpleClientset() + rr := utils.NewRegistrationBuilder().WithName("testapp").BuildRR() + _, err := radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{}) + require.NoError(t, err) + configFileContent, err := os.ReadFile(sampleApp) + require.NoError(t, err) + ra, err := internal.CreateRadixApplication(context.Background(), radixClient, &dnsalias.DNSConfig{}, string(configFileContent)) + require.NoError(t, err) + assert.Equal(t, "100Mi", ra.Spec.Components[0].Resources.Limits["memory"], "server1 invalid resource limits memory") + assert.Equal(t, "100Mi", ra.Spec.Components[1].Resources.Limits["memory"], "server2 invalid resource limits memory") + assert.Empty(t, ra.Spec.Components[2].Resources.Limits["memory"], "server3 not expected resource limits memory") + assert.Empty(t, ra.Spec.Components[3].Resources.Limits["memory"], "server4 not expected resource limits memory") +} diff --git a/pipeline-runner/steps/internal/deployment.go b/pipeline-runner/steps/internal/deployment.go index 2812438ae..22e38a2d8 100644 --- a/pipeline-runner/steps/internal/deployment.go +++ b/pipeline-runner/steps/internal/deployment.go @@ -45,6 +45,16 @@ func ConstructForTargetEnvironment(config *radixv1.RadixApplication, activeRadix return radixDeployment, nil } +func GetGitCommitHashFromDeployment(radixDeployment *radixv1.RadixDeployment) string { + if gitCommitHash, ok := radixDeployment.Annotations[kube.RadixCommitAnnotation]; ok { + return gitCommitHash + } + if gitCommitHash, ok := radixDeployment.Labels[kube.RadixCommitLabel]; ok { + return gitCommitHash + } + return "" +} + func constructRadixDeployment(radixApplication *radixv1.RadixApplication, env, jobName, imageTag, branch, commitID, gitTags string, components []radixv1.RadixDeployComponent, jobs []radixv1.RadixDeployJobComponent, radixConfigHash, buildSecretHash string) *radixv1.RadixDeployment { appName := radixApplication.GetName() deployName := utils.GetDeploymentName(appName, env, imageTag) diff --git a/pipeline-runner/steps/hash.go b/pipeline-runner/steps/internal/hash.go similarity index 80% rename from pipeline-runner/steps/hash.go rename to pipeline-runner/steps/internal/hash.go index 2e69c1b9c..071d0b05e 100644 --- a/pipeline-runner/steps/hash.go +++ b/pipeline-runner/steps/internal/hash.go @@ -1,4 +1,4 @@ -package steps +package internal import ( "github.com/equinor/radix-operator/pipeline-runner/internal/hash" @@ -12,14 +12,22 @@ const ( magicValueForNilBuildSecretData = "34Wd68DsJRUzrHp2f63o3U5hUD6zl8Tj" ) -func createRadixApplicationHash(ra *radixv1.RadixApplication) (string, error) { +func CreateRadixApplicationHash(ra *radixv1.RadixApplication) (string, error) { return hash.ToHashString(hash.SHA256, getRadixApplicationOrMagicValue(ra)) } -func compareRadixApplicationHash(targetHash string, ra *radixv1.RadixApplication) (bool, error) { +func CompareRadixApplicationHash(targetHash string, ra *radixv1.RadixApplication) (bool, error) { return hash.CompareWithHashString(getRadixApplicationOrMagicValue(ra), targetHash) } +func CreateBuildSecretHash(secret *corev1.Secret) (string, error) { + return hash.ToHashString(hash.SHA256, getBuildSecretOrMagicValue(secret)) +} + +func CompareBuildSecretHash(targetHash string, secret *corev1.Secret) (bool, error) { + return hash.CompareWithHashString(getBuildSecretOrMagicValue(secret), targetHash) +} + func getRadixApplicationOrMagicValue(ra *radixv1.RadixApplication) any { if ra == nil { return magicValueForNilRadixApplication @@ -27,14 +35,6 @@ func getRadixApplicationOrMagicValue(ra *radixv1.RadixApplication) any { return ra.Spec } -func createBuildSecretHash(secret *corev1.Secret) (string, error) { - return hash.ToHashString(hash.SHA256, getBuildSecretOrMagicValue(secret)) -} - -func compareBuildSecretHash(targetHash string, secret *corev1.Secret) (bool, error) { - return hash.CompareWithHashString(getBuildSecretOrMagicValue(secret), targetHash) -} - func getBuildSecretOrMagicValue(secret *corev1.Secret) any { if secret == nil || len(secret.Data) == 0 { return magicValueForNilBuildSecretData diff --git a/pipeline-runner/steps/internal/runtime.go b/pipeline-runner/steps/internal/runtime.go new file mode 100644 index 000000000..9179bf46e --- /dev/null +++ b/pipeline-runner/steps/internal/runtime.go @@ -0,0 +1,31 @@ +package internal + +import ( + "reflect" + + commonutils "github.com/equinor/radix-common/utils" + v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" +) + +// GetRuntimeForEnvironment Returns Runtime configuration by combining Runtime from the +// specified environment with Runtime configuration on the component level. +func GetRuntimeForEnvironment(radixComponent v1.RadixCommonComponent, environment string) *v1.Runtime { + var finalRuntime v1.Runtime + + if rt := radixComponent.GetRuntime(); rt != nil { + finalRuntime.Architecture = rt.Architecture + } + + environmentSpecificConfig := radixComponent.GetEnvironmentConfigByName(environment) + + if !commonutils.IsNil(environmentSpecificConfig) && environmentSpecificConfig.GetRuntime() != nil { + if arch := environmentSpecificConfig.GetRuntime().Architecture; len(arch) > 0 { + finalRuntime.Architecture = arch + } + } + + if reflect.ValueOf(finalRuntime).IsZero() { + return nil + } + return &finalRuntime +} diff --git a/pipeline-runner/steps/internal/runtime_test.go b/pipeline-runner/steps/internal/runtime_test.go new file mode 100644 index 000000000..1345d9ba7 --- /dev/null +++ b/pipeline-runner/steps/internal/runtime_test.go @@ -0,0 +1,80 @@ +package internal_test + +import ( + "testing" + + "github.com/equinor/radix-operator/pipeline-runner/steps/internal" + v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + "github.com/stretchr/testify/assert" +) + +func Test_GetRuntimeForEnvironment(t *testing.T) { + tests := map[string]struct { + runtime *v1.Runtime + env []v1.RadixEnvironmentConfig + getEnv string + expected *v1.Runtime + }{ + "all nil": { + runtime: nil, + env: nil, + getEnv: "dev", + expected: nil, + }, + "common empty, env nil": { + runtime: &v1.Runtime{Architecture: ""}, + env: nil, + getEnv: "dev", + expected: nil, + }, + "common set, env nil": { + runtime: &v1.Runtime{Architecture: "commonarch"}, + env: nil, + getEnv: "dev", + expected: &v1.Runtime{Architecture: "commonarch"}, + }, + "common set, env empty": { + runtime: &v1.Runtime{Architecture: "commonarch"}, + env: []v1.RadixEnvironmentConfig{{Environment: "dev", Runtime: &v1.Runtime{Architecture: ""}}}, + getEnv: "dev", + expected: &v1.Runtime{Architecture: "commonarch"}, + }, + "common nil, env set": { + runtime: nil, + env: []v1.RadixEnvironmentConfig{{Environment: "dev", Runtime: &v1.Runtime{Architecture: "devarch"}}}, + getEnv: "dev", + expected: &v1.Runtime{Architecture: "devarch"}, + }, + "common empty, env set": { + runtime: &v1.Runtime{Architecture: ""}, + env: []v1.RadixEnvironmentConfig{{Environment: "dev", Runtime: &v1.Runtime{Architecture: "devarch"}}}, + getEnv: "dev", + expected: &v1.Runtime{Architecture: "devarch"}, + }, + "common set, env set": { + runtime: &v1.Runtime{Architecture: "commonarch"}, + env: []v1.RadixEnvironmentConfig{{Environment: "dev", Runtime: &v1.Runtime{Architecture: "devarch"}}}, + getEnv: "dev", + expected: &v1.Runtime{Architecture: "devarch"}, + }, + "common set, other env set": { + runtime: &v1.Runtime{Architecture: "commonarch"}, + env: []v1.RadixEnvironmentConfig{{Environment: "other", Runtime: &v1.Runtime{Architecture: "devarch"}}}, + getEnv: "dev", + expected: &v1.Runtime{Architecture: "commonarch"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + comp := v1.RadixComponent{Runtime: test.runtime, EnvironmentConfig: test.env} + actual := internal.GetRuntimeForEnvironment(&comp, test.getEnv) + if test.expected == nil { + assert.Nil(t, actual) + } else { + assert.Equal(t, test.expected, actual) + } + }) + } + +} diff --git a/pipeline-runner/steps/testdata/radixconfig.yaml b/pipeline-runner/steps/internal/testdata/radixconfig.yaml similarity index 100% rename from pipeline-runner/steps/testdata/radixconfig.yaml rename to pipeline-runner/steps/internal/testdata/radixconfig.yaml diff --git a/pipeline-runner/steps/prepare_pipelines.go b/pipeline-runner/steps/preparepipeline/step.go similarity index 96% rename from pipeline-runner/steps/prepare_pipelines.go rename to pipeline-runner/steps/preparepipeline/step.go index 6f6e41c35..cf59ce4b1 100644 --- a/pipeline-runner/steps/prepare_pipelines.go +++ b/pipeline-runner/steps/preparepipeline/step.go @@ -1,4 +1,4 @@ -package steps +package preparepipeline import ( "context" @@ -10,6 +10,7 @@ import ( internalwait "github.com/equinor/radix-operator/pipeline-runner/internal/wait" "github.com/equinor/radix-operator/pipeline-runner/model" pipelineDefaults "github.com/equinor/radix-operator/pipeline-runner/model/defaults" + "github.com/equinor/radix-operator/pipeline-runner/steps/internal" "github.com/equinor/radix-operator/pkg/apis/applicationconfig" "github.com/equinor/radix-operator/pkg/apis/defaults" jobUtil "github.com/equinor/radix-operator/pkg/apis/job" @@ -199,7 +200,7 @@ func (cli *PreparePipelinesStepImplementation) getPreparePipelinesJobConfig(pipe }, } sshURL := registration.Spec.CloneURL - initContainers := cli.getInitContainerCloningRepo(pipelineInfo, configBranch, sshURL) + initContainers := cli.getInitContainerCloningRepo(configBranch, sshURL) return internaltekton.CreateActionPipelineJob(defaults.RadixPipelineJobPreparePipelinesContainerName, action, pipelineInfo, appName, initContainers, &envVars) @@ -212,9 +213,8 @@ func getWebhookCommitID(pipelineInfo *model.PipelineInfo) string { return "" } -func (cli *PreparePipelinesStepImplementation) getInitContainerCloningRepo(pipelineInfo *model.PipelineInfo, configBranch, sshURL string) []corev1.Container { - return git.CloneInitContainersWithContainerName(sshURL, configBranch, git.CloneConfigContainerName, - pipelineInfo.PipelineArguments.ContainerSecurityContext) +func (cli *PreparePipelinesStepImplementation) getInitContainerCloningRepo(configBranch, sshURL string) []corev1.Container { + return git.CloneInitContainersWithContainerName(sshURL, configBranch, git.CloneConfigContainerName) } func (cli *PreparePipelinesStepImplementation) getSourceDeploymentGitInfo(ctx context.Context, appName, sourceEnvName, sourceDeploymentName string) (string, string, error) { @@ -223,7 +223,7 @@ func (cli *PreparePipelinesStepImplementation) getSourceDeploymentGitInfo(ctx co if err != nil { return "", "", err } - gitHash := getGitCommitHashFromDeployment(rd) + gitHash := internal.GetGitCommitHashFromDeployment(rd) gitBranch := rd.Annotations[kube.RadixBranchAnnotation] return gitHash, gitBranch, err } diff --git a/pipeline-runner/steps/promote/errors.go b/pipeline-runner/steps/promote/errors.go new file mode 100644 index 000000000..030360a56 --- /dev/null +++ b/pipeline-runner/steps/promote/errors.go @@ -0,0 +1,28 @@ +package promote + +import "fmt" + +// EmptyArgument Argument by name cannot be empty +func EmptyArgument(argumentName string) error { + return fmt.Errorf("%s cannot be empty", argumentName) +} + +// NonExistingFromEnvironment From environment does not exist +func NonExistingFromEnvironment(environment string) error { + return fmt.Errorf("non existing from environment %s", environment) +} + +// NonExistingToEnvironment To environment does not exist +func NonExistingToEnvironment(environment string) error { + return fmt.Errorf("non existing to environment %s", environment) +} + +// NonExistingDeployment Deployment wasn't found +func NonExistingDeployment(deploymentName string) error { + return fmt.Errorf("non existing deployment %s", deploymentName) +} + +// NonExistingComponentName Component by name was not found +func NonExistingComponentName(appName, componentName string) error { + return fmt.Errorf("unable to get application component %s for app %s", componentName, appName) +} diff --git a/pipeline-runner/steps/promotion.go b/pipeline-runner/steps/promote/step.go similarity index 85% rename from pipeline-runner/steps/promotion.go rename to pipeline-runner/steps/promote/step.go index d5e40dcf6..979020628 100644 --- a/pipeline-runner/steps/promotion.go +++ b/pipeline-runner/steps/promote/step.go @@ -1,4 +1,4 @@ -package steps +package promote import ( "context" @@ -8,6 +8,7 @@ import ( "github.com/rs/zerolog/log" "github.com/equinor/radix-operator/pipeline-runner/model" + "github.com/equinor/radix-operator/pipeline-runner/steps/internal" "github.com/equinor/radix-operator/pkg/apis/deployment" "github.com/equinor/radix-operator/pkg/apis/kube" "github.com/equinor/radix-operator/pkg/apis/pipeline" @@ -17,31 +18,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// EmptyArgument Argument by name cannot be empty -func EmptyArgument(argumentName string) error { - return fmt.Errorf("%s cannot be empty", argumentName) -} - -// NonExistingFromEnvironment From environment does not exist -func NonExistingFromEnvironment(environment string) error { - return fmt.Errorf("non existing from environment %s", environment) -} - -// NonExistingToEnvironment From environment does not exist -func NonExistingToEnvironment(environment string) error { - return fmt.Errorf("non existing to environment %s", environment) -} - -// NonExistingDeployment Deployment wasn't found -func NonExistingDeployment(deploymentName string) error { - return fmt.Errorf("non existing deployment %s", deploymentName) -} - -// NonExistingComponentName Component by name was not found -func NonExistingComponentName(appName, componentName string) error { - return fmt.Errorf("unable to get application component %s for app %s", componentName, appName) -} - // PromoteStepImplementation Step to promote deployment to another environment, // or inside environment type PromoteStepImplementation struct { @@ -198,6 +174,7 @@ func mergeJobComponentsWithRadixApplication(radixConfig *v1.RadixApplication, ra // Environment variables, SecretRefs are taken from current configuration newEnvJob.Secrets = job.Secrets newEnvJob.Image = job.Image + newEnvJob.Runtime = job.Runtime radixDeployment.Spec.Jobs[idx] = newEnvJob } @@ -223,6 +200,7 @@ func mergeComponentsWithRadixApplication(radixConfig *v1.RadixApplication, radix // Environment variables, SecretRefs are taken from current configuration newEnvComponent.Secrets = component.Secrets newEnvComponent.Image = component.Image + newEnvComponent.Runtime = component.Runtime radixDeployment.Spec.Components[idx] = newEnvComponent } @@ -231,7 +209,7 @@ func mergeComponentsWithRadixApplication(radixConfig *v1.RadixApplication, radix func getDefaultEnvVarsFromRadixDeployment(radixDeployment *v1.RadixDeployment) v1.EnvVarsMap { envVarsMap := make(v1.EnvVarsMap) - gitCommitHash := getGitCommitHashFromDeployment(radixDeployment) + gitCommitHash := internal.GetGitCommitHashFromDeployment(radixDeployment) if gitCommitHash != "" { envVarsMap[defaults.RadixCommitHashEnvironmentVariable] = gitCommitHash } @@ -240,13 +218,3 @@ func getDefaultEnvVarsFromRadixDeployment(radixDeployment *v1.RadixDeployment) v } return envVarsMap } - -func getGitCommitHashFromDeployment(radixDeployment *v1.RadixDeployment) string { - if gitCommitHash, ok := radixDeployment.Annotations[kube.RadixCommitAnnotation]; ok { - return gitCommitHash - } - if gitCommitHash, ok := radixDeployment.Labels[kube.RadixCommitLabel]; ok { - return gitCommitHash - } - return "" -} diff --git a/pipeline-runner/steps/promotion_test.go b/pipeline-runner/steps/promote/step_test.go similarity index 84% rename from pipeline-runner/steps/promotion_test.go rename to pipeline-runner/steps/promote/step_test.go index 93f976696..96122d886 100644 --- a/pipeline-runner/steps/promotion_test.go +++ b/pipeline-runner/steps/promote/step_test.go @@ -1,4 +1,4 @@ -package steps_test +package promote_test import ( "context" @@ -6,21 +6,48 @@ import ( "strings" "testing" + "github.com/equinor/radix-common/utils/pointers" + commonslice "github.com/equinor/radix-common/utils/slice" + "github.com/equinor/radix-operator/pipeline-runner/model" + "github.com/equinor/radix-operator/pipeline-runner/steps/promote" 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" + commonTest "github.com/equinor/radix-operator/pkg/apis/test" "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/equinor/radix-operator/pkg/apis/utils/numbers" + radix "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + kedafake "github.com/kedacore/keda/v2/pkg/generated/clientset/versioned/fake" monitoring "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubernetes "k8s.io/client-go/kubernetes/fake" + secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake" +) - "github.com/equinor/radix-operator/pipeline-runner/model" - "github.com/equinor/radix-operator/pipeline-runner/steps" - "github.com/equinor/radix-operator/pkg/apis/test" +const ( + anyAppName = "any-app" + anyJobName = "any-job-name" + anyImageTag = "anytag" + anyCommitID = "4faca8595c5283a9d0f17a623b9255a0d9866a2e" + anyGitTags = "some tags go here" ) +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 TestPromote_ErrorScenarios_ErrorIsReturned(t *testing.T) { anyApp1 := "any-app-1" anyApp2 := "any-app-2" @@ -97,7 +124,7 @@ func TestPromote_ErrorScenarios_ErrorIsReturned(t *testing.T) { WithName(nonExistingJobComponent))) require.NoError(t, err) - test.CreateEnvNamespace(kubeclient, anyApp2, anyProdEnvironment) + commonTest.CreateEnvNamespace(kubeclient, anyApp2, anyProdEnvironment) var testScenarios = []struct { name string @@ -109,23 +136,23 @@ func TestPromote_ErrorScenarios_ErrorIsReturned(t *testing.T) { deploymentName string expectedError error }{ - {"empty from environment", anyApp1, "", anyImageTag, anyJobName, anyProdEnvironment, anyDeployment2, steps.EmptyArgument("From environment")}, - {"empty to environment", anyApp1, anyDevEnvironment, anyImageTag, anyJobName, "", anyDeployment2, steps.EmptyArgument("To environment")}, - {"empty image tag", anyApp1, anyDevEnvironment, "", anyJobName, anyProdEnvironment, anyDeployment2, steps.EmptyArgument("Image tag")}, - {"empty job name", anyApp1, anyDevEnvironment, anyImageTag, "", anyProdEnvironment, anyDeployment2, steps.EmptyArgument("Job name")}, - {"empty deployment name", anyApp1, anyDevEnvironment, anyImageTag, anyJobName, anyProdEnvironment, "", steps.EmptyArgument("Deployment name")}, - {"promote from non-existing environment", anyApp1, anyQAEnvironment, anyImageTag, anyJobName, anyProdEnvironment, anyDeployment2, steps.NonExistingFromEnvironment(anyQAEnvironment)}, - {"promote to non-existing environment", anyApp1, anyDevEnvironment, anyImageTag, anyJobName, anyQAEnvironment, anyDeployment2, steps.NonExistingToEnvironment(anyQAEnvironment)}, - {"promote non-existing deployment", anyApp2, anyDevEnvironment, "nopqrst", anyJobName, anyProdEnvironment, "non-existing", steps.NonExistingDeployment("non-existing")}, - {"promote deployment with non-existing component", anyApp4, anyDevEnvironment, anyImageTag, anyJobName, anyDevEnvironment, anyDeployment4, steps.NonExistingComponentName(anyApp4, nonExistingComponent)}, - {"promote deployment with non-existing job component", anyApp5, anyDevEnvironment, anyImageTag, anyJobName, anyDevEnvironment, anyDeployment5, steps.NonExistingComponentName(anyApp5, nonExistingJobComponent)}, + {"empty from environment", anyApp1, "", anyImageTag, anyJobName, anyProdEnvironment, anyDeployment2, promote.EmptyArgument("From environment")}, + {"empty to environment", anyApp1, anyDevEnvironment, anyImageTag, anyJobName, "", anyDeployment2, promote.EmptyArgument("To environment")}, + {"empty image tag", anyApp1, anyDevEnvironment, "", anyJobName, anyProdEnvironment, anyDeployment2, promote.EmptyArgument("Image tag")}, + {"empty job name", anyApp1, anyDevEnvironment, anyImageTag, "", anyProdEnvironment, anyDeployment2, promote.EmptyArgument("Job name")}, + {"empty deployment name", anyApp1, anyDevEnvironment, anyImageTag, anyJobName, anyProdEnvironment, "", promote.EmptyArgument("Deployment name")}, + {"promote from non-existing environment", anyApp1, anyQAEnvironment, anyImageTag, anyJobName, anyProdEnvironment, anyDeployment2, promote.NonExistingFromEnvironment(anyQAEnvironment)}, + {"promote to non-existing environment", anyApp1, anyDevEnvironment, anyImageTag, anyJobName, anyQAEnvironment, anyDeployment2, promote.NonExistingToEnvironment(anyQAEnvironment)}, + {"promote non-existing deployment", anyApp2, anyDevEnvironment, "nopqrst", anyJobName, anyProdEnvironment, "non-existing", promote.NonExistingDeployment("non-existing")}, + {"promote deployment with non-existing component", anyApp4, anyDevEnvironment, anyImageTag, anyJobName, anyDevEnvironment, anyDeployment4, promote.NonExistingComponentName(anyApp4, nonExistingComponent)}, + {"promote deployment with non-existing job component", anyApp5, anyDevEnvironment, anyImageTag, anyJobName, anyDevEnvironment, anyDeployment5, promote.NonExistingComponentName(anyApp5, nonExistingJobComponent)}, } for _, scenario := range testScenarios { t.Run(scenario.name, func(t *testing.T) { rr, _ := radixclient.RadixV1().RadixRegistrations().Get(context.Background(), scenario.appName, metav1.GetOptions{}) - cli := steps.NewPromoteStep() + cli := promote.NewPromoteStep() cli.Init(kubeclient, radixclient, kube, &monitoring.Clientset{}, rr) pipelineInfo := &model.PipelineInfo{ @@ -227,12 +254,12 @@ func TestPromote_PromoteToOtherEnvironment_NewStateIsExpected(t *testing.T) { WithEnvironmentConfigs( utils.AnEnvironmentConfig(). WithEnvironment(anyDevEnvironment). - WithReplicas(test.IntPtr(2)). + WithReplicas(commonTest.IntPtr(2)). WithEnvironmentVariable("DB_HOST", "db-dev"). WithEnvironmentVariable("DB_PORT", "1234"), utils.AnEnvironmentConfig(). WithEnvironment(anyProdEnvironment). - WithReplicas(test.IntPtr(4)). + WithReplicas(commonTest.IntPtr(4)). WithEnvironmentVariable("DB_HOST", "db-prod"). WithEnvironmentVariable("DB_PORT", "5678"). WithEnvironmentVariable("DB_NAME", "my-db-prod"). @@ -279,12 +306,12 @@ func TestPromote_PromoteToOtherEnvironment_NewStateIsExpected(t *testing.T) { require.NoError(t, err) // Create prod environment without any deployments - test.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) + commonTest.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) rr, _ := radixclient.RadixV1().RadixRegistrations().Get(context.Background(), anyApp, metav1.GetOptions{}) ra, _ := radixclient.RadixV1().RadixApplications(utils.GetAppNamespace(anyApp)).Get(context.Background(), anyApp, metav1.GetOptions{}) - cli := steps.NewPromoteStep() + cli := promote.NewPromoteStep() cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) pipelineInfo := &model.PipelineInfo{ @@ -405,12 +432,12 @@ func TestPromote_PromoteToOtherEnvironment_Resources_NoOverride(t *testing.T) { require.NoError(t, err) // Create prod environment without any deployments - test.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) + commonTest.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) rr, _ := radixclient.RadixV1().RadixRegistrations().Get(context.Background(), anyApp, metav1.GetOptions{}) ra, _ := radixclient.RadixV1().RadixApplications(utils.GetAppNamespace(anyApp)).Get(context.Background(), anyApp, metav1.GetOptions{}) - cli := steps.NewPromoteStep() + cli := promote.NewPromoteStep() cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) pipelineInfo := &model.PipelineInfo{ @@ -482,7 +509,7 @@ func TestPromote_PromoteToOtherEnvironment_Authentication(t *testing.T) { WithAuthentication( &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ - PassCertificateToUpstream: utils.BoolPtr(true), + PassCertificateToUpstream: pointers.Ptr(true), }, }, ). @@ -499,13 +526,13 @@ func TestPromote_PromoteToOtherEnvironment_Authentication(t *testing.T) { require.NoError(t, err) // Create environments - test.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) - test.CreateEnvNamespace(kubeclient, anyApp, anyDevEnvironment) + commonTest.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) + commonTest.CreateEnvNamespace(kubeclient, anyApp, anyDevEnvironment) rr, _ := radixclient.RadixV1().RadixRegistrations().Get(context.Background(), anyApp, metav1.GetOptions{}) ra, _ := radixclient.RadixV1().RadixApplications(utils.GetAppNamespace(anyApp)).Get(context.Background(), anyApp, metav1.GetOptions{}) - cli := steps.NewPromoteStep() + cli := promote.NewPromoteStep() cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) pipelineInfo := &model.PipelineInfo{ @@ -533,7 +560,7 @@ func TestPromote_PromoteToOtherEnvironment_Authentication(t *testing.T) { x0 := &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ Verification: &verification, - PassCertificateToUpstream: utils.BoolPtr(true), + PassCertificateToUpstream: pointers.Ptr(true), }, } assert.NotNil(t, rds.Items[0].Spec.Components[0].Authentication) @@ -617,12 +644,12 @@ func TestPromote_PromoteToOtherEnvironment_Resources_WithOverride(t *testing.T) require.NoError(t, err) // Create prod environment without any deployments - test.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) + commonTest.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) rr, _ := radixclient.RadixV1().RadixRegistrations().Get(context.Background(), anyApp, metav1.GetOptions{}) ra, _ := radixclient.RadixV1().RadixApplications(utils.GetAppNamespace(anyApp)).Get(context.Background(), anyApp, metav1.GetOptions{}) - cli := steps.NewPromoteStep() + cli := promote.NewPromoteStep() cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) pipelineInfo := &model.PipelineInfo{ @@ -684,7 +711,7 @@ func TestPromote_PromoteToSameEnvironment_NewStateIsExpected(t *testing.T) { rr, _ := radixclient.RadixV1().RadixRegistrations().Get(context.Background(), anyApp, metav1.GetOptions{}) ra, _ := radixclient.RadixV1().RadixApplications(utils.GetAppNamespace(anyApp)).Get(context.Background(), anyApp, metav1.GetOptions{}) - cli := steps.NewPromoteStep() + cli := promote.NewPromoteStep() cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) pipelineInfo := &model.PipelineInfo{ @@ -798,12 +825,12 @@ func TestPromote_PromoteToOtherEnvironment_Identity(t *testing.T) { require.NoError(t, err) // Create prod environment without any deployments - test.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) + commonTest.CreateEnvNamespace(kubeclient, anyApp, anyProdEnvironment) rr, _ := radixclient.RadixV1().RadixRegistrations().Get(context.Background(), anyApp, metav1.GetOptions{}) ra, _ := radixclient.RadixV1().RadixApplications(utils.GetAppNamespace(anyApp)).Get(context.Background(), anyApp, metav1.GetOptions{}) - cli := steps.NewPromoteStep() + cli := promote.NewPromoteStep() cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) pipelineInfo := &model.PipelineInfo{ @@ -865,7 +892,7 @@ func TestPromote_AnnotatedBySourceDeploymentAttributes(t *testing.T) { rr, _ := radixclient.RadixV1().RadixRegistrations().Get(context.Background(), anyAppName, metav1.GetOptions{}) ra, _ := radixclient.RadixV1().RadixApplications(utils.GetAppNamespace(anyAppName)).Get(context.Background(), anyAppName, metav1.GetOptions{}) - cli := steps.NewPromoteStep() + cli := promote.NewPromoteStep() cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr) pipelineInfo := &model.PipelineInfo{ @@ -896,3 +923,83 @@ func TestPromote_AnnotatedBySourceDeploymentAttributes(t *testing.T) { assert.Equal(t, srcRadixConfigHash, promotedRD.GetAnnotations()[kube.RadixConfigHash]) assert.Equal(t, srcDeploymentCommitID, promotedRD.GetLabels()[kube.RadixCommitLabel]) } + +func TestPromote_Runtime_KeepFromSourceRD(t *testing.T) { + var ( + appName string = "anyapp" + comp1, comp2 string = "comp1", "comp2" + job1, job2 string = "job1", "job2" + envDev, envProd string = "dev", "prod" + rdSource string = "rdsource" + schedulerPort int32 = 9999 + ) + + // Setup + kubeclient, kubeUtil, radixclient, commonTestUtils := setupTest(t) + rr := utils.NewRegistrationBuilder().WithName(appName) + ra := utils.NewRadixApplicationBuilder().WithRadixRegistration(rr).WithAppName(appName). + WithEnvironmentNoBranch(envDev).WithEnvironmentNoBranch(envProd). + WithComponents( + utils.NewApplicationComponentBuilder().WithName(comp1).WithRuntime(&v1.Runtime{Architecture: "commonarch"}), + utils.NewApplicationComponentBuilder().WithName(comp2).WithRuntime(&v1.Runtime{Architecture: "commonarch"}).WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder().WithEnvironment(envProd).WithRuntime(&v1.Runtime{Architecture: "prodarch"})), + ). + WithJobComponents( + utils.NewApplicationJobComponentBuilder().WithName(job1).WithSchedulerPort(&schedulerPort).WithRuntime(&v1.Runtime{Architecture: "commonarch"}), + utils.NewApplicationJobComponentBuilder().WithName(job2).WithSchedulerPort(&schedulerPort).WithRuntime(&v1.Runtime{Architecture: "commonarch"}).WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder().WithEnvironment(envProd).WithRuntime(&v1.Runtime{Architecture: "prodarch"})), + ) + + _, err := commonTestUtils.ApplyApplication(ra) + require.NoError(t, err) + commonTest.CreateEnvNamespace(kubeclient, appName, envDev) + commonTest.CreateEnvNamespace(kubeclient, appName, envProd) + + rd := utils.NewDeploymentBuilder(). + WithDeploymentName(rdSource). + WithAppName(appName). + WithEnvironment(envDev). + WithComponents( + utils.NewDeployComponentBuilder().WithName(comp1).WithRuntime(nil), + utils.NewDeployComponentBuilder().WithName(comp2).WithRuntime(&v1.Runtime{Architecture: "comp2arch"}), + ). + WithJobComponents( + utils.NewDeployJobComponentBuilder().WithName(job1).WithRuntime(nil), + utils.NewDeployJobComponentBuilder().WithName(job2).WithRuntime(&v1.Runtime{Architecture: "job2arch"}), + ) + + _, err = commonTestUtils.ApplyDeployment(context.Background(), rd) + require.NoError(t, err) + + cli := promote.NewPromoteStep() + cli.Init(kubeclient, radixclient, kubeUtil, &monitoring.Clientset{}, rr.BuildRR()) + + pipelineInfo := &model.PipelineInfo{ + PipelineArguments: model.PipelineArguments{ + DeploymentName: rdSource, + FromEnvironment: envDev, + ToEnvironment: envProd, + JobName: "ajob", + ImageTag: "atag", + }, + } + + err = cli.Run(context.Background(), pipelineInfo) + require.NoError(t, err) + + rdListProd, err := radixclient.RadixV1().RadixDeployments(utils.GetEnvironmentNamespace(appName, envProd)).List(context.Background(), metav1.ListOptions{}) + require.NoError(t, err) + require.Len(t, rdListProd.Items, 1) + rdProd := rdListProd.Items[0] + comp1Prod, found := commonslice.FindFirst(rdProd.Spec.Components, func(c v1.RadixDeployComponent) bool { return c.Name == comp1 }) + require.True(t, found) + comp2Prod, found := commonslice.FindFirst(rdProd.Spec.Components, func(c v1.RadixDeployComponent) bool { return c.Name == comp2 }) + require.True(t, found) + job1Prod, found := commonslice.FindFirst(rdProd.Spec.Jobs, func(c v1.RadixDeployJobComponent) bool { return c.Name == job1 }) + require.True(t, found) + job2Prod, found := commonslice.FindFirst(rdProd.Spec.Jobs, func(c v1.RadixDeployJobComponent) bool { return c.Name == job2 }) + require.True(t, found) + assert.Nil(t, comp1Prod.Runtime, "%s should use Runtime from source RD", comp1Prod.Name) + assert.Equal(t, &v1.Runtime{Architecture: "comp2arch"}, comp2Prod.Runtime, "%s should use Runtime from source RD", comp2Prod.Name) + assert.Nil(t, job1Prod.Runtime, "%s should use Runtime from source RD", job1Prod.Name) + assert.Equal(t, &v1.Runtime{Architecture: "job2arch"}, job2Prod.Runtime, "%s should use Runtime from source RD", job2Prod.Name) + +} diff --git a/pipeline-runner/steps/run_pipelines.go b/pipeline-runner/steps/runpipeline/step.go similarity index 99% rename from pipeline-runner/steps/run_pipelines.go rename to pipeline-runner/steps/runpipeline/step.go index 2545da112..9494934e6 100644 --- a/pipeline-runner/steps/run_pipelines.go +++ b/pipeline-runner/steps/runpipeline/step.go @@ -1,4 +1,4 @@ -package steps +package runpipeline import ( "context" diff --git a/pipeline.Dockerfile b/pipeline.Dockerfile index cbdc69591..c348cec38 100644 --- a/pipeline.Dockerfile +++ b/pipeline.Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22-alpine3.19 as base +FROM golang:1.22-alpine3.20 as base RUN apk update && \ apk add ca-certificates curl git && \ diff --git a/pkg/apis/applicationconfig/applicationconfig_test.go b/pkg/apis/applicationconfig/applicationconfig_test.go index 5dff4ed7f..dda82c5df 100644 --- a/pkg/apis/applicationconfig/applicationconfig_test.go +++ b/pkg/apis/applicationconfig/applicationconfig_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/applicationconfig" "github.com/equinor/radix-operator/pkg/apis/defaults" @@ -602,13 +603,13 @@ func Test_UseBuildKit(t *testing.T) { }, { appName: "any-app2", - useBuildKit: utils.BoolPtr(false), - expectedUseBuildKit: utils.BoolPtr(false), + useBuildKit: pointers.Ptr(false), + expectedUseBuildKit: pointers.Ptr(false), }, { appName: "any-app3", - useBuildKit: utils.BoolPtr(true), - expectedUseBuildKit: utils.BoolPtr(true), + useBuildKit: pointers.Ptr(true), + expectedUseBuildKit: pointers.Ptr(true), }, } tu, client, kubeUtil, radixClient := setupTest(t) diff --git a/pkg/apis/batch/kubejob.go b/pkg/apis/batch/kubejob.go index 564837341..2f31784e2 100644 --- a/pkg/apis/batch/kubejob.go +++ b/pkg/apis/batch/kubejob.go @@ -180,7 +180,7 @@ func (s *syncer) buildJob(ctx context.Context, batchJob *radixv1.RadixBatchJob, SecurityContext: securitycontext.Pod(securitycontext.WithPodSeccompProfile(corev1.SeccompProfileTypeRuntimeDefault)), RestartPolicy: corev1.RestartPolicyNever, ImagePullSecrets: rd.Spec.ImagePullSecrets, - Affinity: operatorUtils.GetAffinityForBatchJob(node), + Affinity: operatorUtils.GetAffinityForBatchJob(jobComponent, node), Tolerations: operatorUtils.GetScheduledJobPodSpecTolerations(node), ActiveDeadlineSeconds: timeLimitSeconds, ServiceAccountName: serviceAccountSpec.ServiceAccountName(), diff --git a/pkg/apis/batch/syncer_test.go b/pkg/apis/batch/syncer_test.go index 9f0f65236..c34110a60 100644 --- a/pkg/apis/batch/syncer_test.go +++ b/pkg/apis/batch/syncer_test.go @@ -516,7 +516,7 @@ func (s *syncerTestSuite) Test_BatchStaticConfiguration() { })) s.Equal(corev1.PullAlways, kubejob.Spec.Template.Spec.Containers[0].ImagePullPolicy) s.Equal("default", kubejob.Spec.Template.Spec.ServiceAccountName) - s.Equal(utils.BoolPtr(false), kubejob.Spec.Template.Spec.AutomountServiceAccountToken) + s.Equal(pointers.Ptr(false), kubejob.Spec.Template.Spec.AutomountServiceAccountToken) 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}}, @@ -534,6 +534,60 @@ func (s *syncerTestSuite) Test_BatchStaticConfiguration() { } } +func (s *syncerTestSuite) Test_Batch_AffinityFromRuntime() { + appName, batchName, componentName, namespace, rdName, imageName := "any-app", "any-batch", "compute", "any-ns", "any-rd", "any-image" + jobName, runtimeArch := "job1", "customarch" + batch := &radixv1.RadixBatch{ + ObjectMeta: metav1.ObjectMeta{Name: batchName}, + Spec: radixv1.RadixBatchSpec{ + RadixDeploymentJobRef: radixv1.RadixDeploymentJobComponentSelector{ + LocalObjectReference: radixv1.LocalObjectReference{Name: rdName}, + Job: componentName, + }, + Jobs: []radixv1.RadixBatchJob{ + {Name: jobName}, + }, + }, + } + rd := &radixv1.RadixDeployment{ + ObjectMeta: metav1.ObjectMeta{Name: rdName}, + Spec: radixv1.RadixDeploymentSpec{ + AppName: appName, + Jobs: []radixv1.RadixDeployJobComponent{ + { + Name: componentName, + Image: imageName, + EnvironmentVariables: radixv1.EnvVarsMap{"VAR1": "any-val", "VAR2": "any-val"}, + Secrets: []string{"SECRET1", "SECRET2"}, + Runtime: &radixv1.Runtime{ + Architecture: radixv1.RuntimeArchitecture(runtimeArch), + }, + }, + }, + }, + } + batch, err := s.radixClient.RadixV1().RadixBatches(namespace).Create(context.Background(), batch, metav1.CreateOptions{}) + s.Require().NoError(err) + _, err = s.radixClient.RadixV1().RadixDeployments(namespace).Create(context.Background(), rd, metav1.CreateOptions{}) + s.Require().NoError(err) + + sut := s.createSyncer(batch) + s.Require().NoError(sut.OnSync(context.Background())) + + allJobs, _ := s.kubeClient.BatchV1().Jobs(namespace).List(context.Background(), metav1.ListOptions{}) + s.Require().Len(allJobs.Items, 1) + kubejob := allJobs.Items[0] + 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{runtimeArch}}, + }}}}}} + s.Equal(expectedAffinity, kubejob.Spec.Template.Spec.Affinity, "affinity should use arch from runtime") + s.Len(kubejob.Spec.Template.Spec.Tolerations, 1) + expectedTolerations := []corev1.Toleration{{Key: kube.NodeTaintJobsKey, Operator: corev1.TolerationOpExists, Effect: corev1.TaintEffectNoSchedule}} + s.ElementsMatch(expectedTolerations, kubejob.Spec.Template.Spec.Tolerations) +} + func (s *syncerTestSuite) Test_JobNotCreatedForJobWithPhaseDone() { appName, batchName, componentName, namespace, rdName := "any-app", "any-batch", "compute", "any-ns", "any-rd" batch := &radixv1.RadixBatch{ @@ -745,7 +799,7 @@ func (s *syncerTestSuite) Test_JobWithIdentity() { expectedPodLabels := map[string]string{kube.RadixAppLabel: appName, kube.RadixComponentLabel: componentName, kube.RadixJobTypeLabel: kube.RadixJobTypeJobSchedule, kube.RadixBatchNameLabel: batchName, kube.RadixBatchJobNameLabel: jobName, "azure.workload.identity/use": "true"} s.Equal(expectedPodLabels, jobs.Items[0].Spec.Template.Labels) s.Equal(utils.GetComponentServiceAccountName(componentName), jobs.Items[0].Spec.Template.Spec.ServiceAccountName) - s.Equal(utils.BoolPtr(false), jobs.Items[0].Spec.Template.Spec.AutomountServiceAccountToken) + s.Equal(pointers.Ptr(false), jobs.Items[0].Spec.Template.Spec.AutomountServiceAccountToken) } func (s *syncerTestSuite) Test_JobWithPayload() { @@ -1075,6 +1129,7 @@ func (s *syncerTestSuite) Test_JobWithAzureSecretRefs() { func (s *syncerTestSuite) Test_JobWithGpuNode() { appName, batchName, jobComponentName, namespace, rdName := "any-app", "any-job", "compute", "any-ns", "any-rd" job1Name, job2Name := "job1", "job2" + arch := "customarch" batch := &radixv1.RadixBatch{ ObjectMeta: metav1.ObjectMeta{Name: batchName}, Spec: radixv1.RadixBatchSpec{ @@ -1096,6 +1151,9 @@ func (s *syncerTestSuite) Test_JobWithGpuNode() { { Name: jobComponentName, Node: radixv1.RadixNode{Gpu: " gpu1, gpu2", GpuCount: "4"}, + Runtime: &radixv1.Runtime{ + Architecture: radixv1.RuntimeArchitecture(arch), + }, }, }, }, @@ -1111,31 +1169,23 @@ func (s *syncerTestSuite) Test_JobWithGpuNode() { s.Require().Len(jobs.Items, 2) job1 := slice.FindAll(jobs.Items, func(job batchv1.Job) bool { return job.GetName() == getKubeJobName(batchName, job1Name) })[0] - job1NodeSelectorTerms := job1.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms - s.Require().Len(job1NodeSelectorTerms, 1) - s.Equal(corev1.NodeSelectorRequirement{ - Key: kube.RadixJobNodeLabel, - Operator: corev1.NodeSelectorOpExists, - Values: nil, - }, job1NodeSelectorTerms[0].MatchExpressions[0]) + 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{arch}}, + }}}}}} + s.Equal(expectedAffinity, job1.Spec.Template.Spec.Affinity) tolerations := job1.Spec.Template.Spec.Tolerations s.Require().Len(tolerations, 1) s.Equal(corev1.Toleration{Key: kube.NodeTaintJobsKey, Operator: corev1.TolerationOpExists, Effect: corev1.TaintEffectNoSchedule}, tolerations[0]) job2 := slice.FindAll(jobs.Items, func(job batchv1.Job) bool { return job.GetName() == getKubeJobName(batchName, job2Name) })[0] - job2NodeSelectorTerms := job2.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms - s.Require().Len(job2NodeSelectorTerms, 1) - s.Equal(corev1.NodeSelectorRequirement{ - Key: kube.RadixGpuCountLabel, - Operator: corev1.NodeSelectorOpGt, - Values: []string{"7"}, - }, job2NodeSelectorTerms[0].MatchExpressions[0]) - s.Equal(corev1.NodeSelectorRequirement{ - Key: kube.RadixGpuLabel, - Operator: corev1.NodeSelectorOpIn, - Values: []string{"gpu3", "gpu4"}, - }, job2NodeSelectorTerms[0].MatchExpressions[1]) + expectedAffinity = &corev1.Affinity{NodeAffinity: &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{MatchExpressions: []corev1.NodeSelectorRequirement{ + {Key: kube.RadixGpuCountLabel, Operator: corev1.NodeSelectorOpGt, Values: []string{"7"}}, + {Key: kube.RadixGpuLabel, Operator: corev1.NodeSelectorOpIn, Values: []string{"gpu3", "gpu4"}}, + }}}}}} + s.Equal(expectedAffinity, job2.Spec.Template.Spec.Affinity, "job with gpu should ignore runtime architecture") tolerations = job2.Spec.Template.Spec.Tolerations s.Require().Len(tolerations, 1) s.Equal(corev1.Toleration{Key: kube.RadixGpuCountLabel, Operator: corev1.TolerationOpExists, Effect: corev1.TaintEffectNoSchedule}, tolerations[0]) @@ -1177,7 +1227,7 @@ func (s *syncerTestSuite) Test_StopJob() { allJobs, _ := s.kubeClient.BatchV1().Jobs(namespace).List(context.Background(), metav1.ListOptions{}) s.Require().Len(allJobs.Items, 2) - batch.Spec.Jobs[0].Stop = utils.BoolPtr(true) + batch.Spec.Jobs[0].Stop = pointers.Ptr(true) sut = s.createSyncer(batch) s.Require().NoError(sut.OnSync(context.Background())) allJobs, _ = s.kubeClient.BatchV1().Jobs(namespace).List(context.Background(), metav1.ListOptions{}) @@ -1418,7 +1468,7 @@ func (s *syncerTestSuite) Test_BatchStatusCondition() { s.Nil(batch.Status.Condition.CompletionTime) // Set job3 to stopped => batch condition is Running - batch.Spec.Jobs[2].Stop = utils.BoolPtr(true) + batch.Spec.Jobs[2].Stop = pointers.Ptr(true) sut = s.createSyncer(batch) s.Require().NoError(sut.OnSync(context.Background())) batch, err = s.radixClient.RadixV1().RadixBatches(namespace).Get(context.Background(), batch.GetName(), metav1.GetOptions{}) @@ -1715,7 +1765,7 @@ func (s *syncerTestSuite) Test_BatchJobStatusWaitingToStopped() { s.Nil(batch.Status.JobStatuses[0].EndTime) // Set job status.conditions to failed => phase is Failed - batch.Spec.Jobs[0].Stop = utils.BoolPtr(true) + batch.Spec.Jobs[0].Stop = pointers.Ptr(true) sut = s.createSyncer(batch) s.Require().NoError(sut.OnSync(context.Background())) batch, err = s.radixClient.RadixV1().RadixBatches(namespace).Get(context.Background(), batch.GetName(), metav1.GetOptions{}) diff --git a/pkg/apis/defaults/pipelinejobs.go b/pkg/apis/defaults/pipelinejobs.go index 033a7b748..7fefb3376 100644 --- a/pkg/apis/defaults/pipelinejobs.go +++ b/pkg/apis/defaults/pipelinejobs.go @@ -14,13 +14,3 @@ const RadixCacheLayerNamePrefix = "radix-cache" // DefaultRadixConfigFileName Default name for the radix configuration file const DefaultRadixConfigFileName = "radixconfig.yaml" - -// SecurityContextRunAsUser The user ID to run the container as -const SecurityContextRunAsUser = 1000 - -// SecurityContextRunAsGroup A group ID which the user running the container is member of -const SecurityContextRunAsGroup = 1000 - -// SecurityContextFsGroup A group ID which the user running the container is member of. This is also the group ID of -// files in any mounted volume -const SecurityContextFsGroup = 1000 diff --git a/pkg/apis/deployment/deployment_test.go b/pkg/apis/deployment/deployment_test.go index a7d5c628f..035044b78 100644 --- a/pkg/apis/deployment/deployment_test.go +++ b/pkg/apis/deployment/deployment_test.go @@ -440,6 +440,67 @@ func TestObjectSynced_MultiComponent_ContainsAllElements(t *testing.T) { } } +func TestObjectSynced_Components_AffinityAccordingToSpec(t *testing.T) { + var ( + appName = "anyapp" + environment = "anyenv" + comp1, comp2, comp3 = "comp1", "comp2", "comp3" + ) + tu, kubeclient, kubeUtil, radixclient, kedaClient, prometheusclient, _, certClient := SetupTest(t) + defer TeardownTest() + + rrBuilder := utils.ARadixRegistration() + raBuilder := utils.ARadixApplication().WithRadixRegistration(rrBuilder) + rdBuilder := utils.ARadixDeployment(). + WithRadixApplication(raBuilder). + WithAppName(appName). + WithEnvironment(environment). + WithJobComponents(). + WithComponents( + utils.NewDeployComponentBuilder(). + WithName(comp1). + WithRuntime(nil), + utils.NewDeployComponentBuilder(). + WithName(comp2). + WithRuntime(&radixv1.Runtime{Architecture: ""}), + utils.NewDeployComponentBuilder(). + WithName(comp3). + WithRuntime(&radixv1.Runtime{Architecture: "customarch"})) + + _, err := ApplyDeploymentWithSync(tu, kubeclient, kubeUtil, radixclient, kedaClient, prometheusclient, certClient, rdBuilder) + require.NoError(t, err) + + deployments, err := kubeclient.AppsV1().Deployments(utils.GetEnvironmentNamespace(appName, environment)).List(context.Background(), metav1.ListOptions{}) + require.NoError(t, err) + + // Check affinity for comp1 + deployment := getDeploymentByName(comp1, deployments.Items) + require.NotNil(t, deployment) + expectedAffinity := &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{MatchExpressions: []corev1.NodeSelectorRequirement{ + {Key: corev1.LabelOSStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorOS}}, + {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorArchitecture}}, + }}}}} + assert.Equal(t, expectedAffinity, deployment.Spec.Template.Spec.Affinity.NodeAffinity) + + // Check affinity for comp2 + deployment = getDeploymentByName(comp2, deployments.Items) + require.NotNil(t, deployment) + expectedAffinity = &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{MatchExpressions: []corev1.NodeSelectorRequirement{ + {Key: corev1.LabelOSStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorOS}}, + {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorArchitecture}}, + }}}}} + assert.Equal(t, expectedAffinity, deployment.Spec.Template.Spec.Affinity.NodeAffinity) + + // Check affinity for comp3 + deployment = getDeploymentByName(comp3, deployments.Items) + require.NotNil(t, deployment) + expectedAffinity = &corev1.NodeAffinity{RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{NodeSelectorTerms: []corev1.NodeSelectorTerm{{MatchExpressions: []corev1.NodeSelectorRequirement{ + {Key: corev1.LabelOSStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorOS}}, + {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{"customarch"}}, + }}}}} + assert.Equal(t, expectedAffinity, deployment.Spec.Template.Spec.Affinity.NodeAffinity) +} + func TestObjectSynced_MultiJob_ContainsAllElements(t *testing.T) { const jobSchedulerImage = "radix-job-scheduler:latest" defer TeardownTest() @@ -495,7 +556,8 @@ func TestObjectSynced_MultiJob_ContainsAllElements(t *testing.T) { WithSchedulerPort(&schedulerPortUpdate). WithPayloadPath(&payloadPath). WithSecrets([]string{remainingSecret, addingSecret}). - WithAlwaysPullImageOnDeploy(false), + WithAlwaysPullImageOnDeploy(false). + WithRuntime(&radixv1.Runtime{Architecture: "customarch"}), ). WithComponents() _, err := ApplyDeploymentWithSync(tu, kubeclient, kubeUtil, radixclient, kedaClient, prometheusclient, certClient, existingRadixDeploymentBuilder) @@ -539,9 +601,10 @@ func TestObjectSynced_MultiJob_ContainsAllElements(t *testing.T) { WithSchedulerPort(&schedulerPortCreate). WithPayloadPath(&payloadPath). WithSecrets([]string{outdatedSecret, remainingSecret}). - WithAlwaysPullImageOnDeploy(false), + WithAlwaysPullImageOnDeploy(false). + WithRuntime(&radixv1.Runtime{Architecture: "customarch"}), utils.NewDeployJobComponentBuilder(). - WithName(jobName2).WithSchedulerPort(&schedulerPortCreate), + WithName(jobName2).WithSchedulerPort(&schedulerPortCreate).WithRuntime(&radixv1.Runtime{Architecture: "customarch"}), ). WithComponents() @@ -572,7 +635,7 @@ func TestObjectSynced_MultiJob_ContainsAllElements(t *testing.T) { }}}}}, } for _, deployment := range jobAuxDeployments { - assert.Equal(t, expectedAuxAffinity, deployment.Spec.Template.Spec.Affinity) + assert.Equal(t, expectedAuxAffinity, deployment.Spec.Template.Spec.Affinity, "job aux component must not use job's runtime config") } assert.Equal(t, jobName, getDeploymentByName(jobName, deployments).Name, "app deployment not there") @@ -626,7 +689,7 @@ func TestObjectSynced_MultiJob_ContainsAllElements(t *testing.T) { {Key: kube.RadixComponentLabel, Operator: metav1.LabelSelectorOpIn, Values: []string{job}}, }}}}}}, } - assert.Equal(t, expectedAffinity, deploy.Spec.Template.Spec.Affinity) + assert.Equal(t, expectedAffinity, deploy.Spec.Template.Spec.Affinity, "job api server must not use job's runtime config") } }) @@ -796,8 +859,8 @@ func TestObjectSynced_ReadOnlyFileSystem(t *testing.T) { tests := map[string]scenarioSpec{ "notSet": {readOnlyFileSystem: nil, expectedReadOnlyFileSystem: nil}, - "false": {readOnlyFileSystem: utils.BoolPtr(false), expectedReadOnlyFileSystem: utils.BoolPtr(false)}, - "true": {readOnlyFileSystem: utils.BoolPtr(true), expectedReadOnlyFileSystem: utils.BoolPtr(true)}, + "false": {readOnlyFileSystem: pointers.Ptr(false), expectedReadOnlyFileSystem: pointers.Ptr(false)}, + "true": {readOnlyFileSystem: pointers.Ptr(true), expectedReadOnlyFileSystem: pointers.Ptr(true)}, } for name, test := range tests { @@ -926,7 +989,7 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { assert.Equal(t, 0, len(serviceAccounts.Items), "Number of service accounts was not expected") deployments, _ := client.AppsV1().Deployments(utils.GetEnvironmentNamespace("any-other-app", "test")).List(context.Background(), metav1.ListOptions{}) expectedDeployments := getDeploymentsForRadixComponents(deployments.Items) - assert.Equal(t, utils.BoolPtr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, defaultServiceAccountName, expectedDeployments[0].Spec.Template.Spec.ServiceAccountName) }) @@ -954,7 +1017,7 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { deployments, _ := client.AppsV1().Deployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) expectedDeployments := getDeploymentsForRadixComponents(deployments.Items) - assert.Equal(t, utils.BoolPtr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, utils.GetComponentServiceAccountName(componentName), expectedDeployments[0].Spec.Template.Spec.ServiceAccountName) assert.Equal(t, "true", expectedDeployments[0].Spec.Template.Labels["azure.workload.identity/use"]) @@ -978,7 +1041,7 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { deployments, _ = client.AppsV1().Deployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) expectedDeployments = getDeploymentsForRadixComponents(deployments.Items) - assert.Equal(t, utils.BoolPtr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, utils.GetComponentServiceAccountName(componentName), expectedDeployments[0].Spec.Template.Spec.ServiceAccountName) assert.Equal(t, "true", expectedDeployments[0].Spec.Template.Labels["azure.workload.identity/use"]) @@ -994,7 +1057,7 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { deployments, _ = client.AppsV1().Deployments(utils.GetEnvironmentNamespace(appName, envName)).List(context.Background(), metav1.ListOptions{}) expectedDeployments = getDeploymentsForRadixComponents(deployments.Items) - assert.Equal(t, utils.BoolPtr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, defaultServiceAccountName, expectedDeployments[0].Spec.Template.Spec.ServiceAccountName) _, hasLabel := expectedDeployments[0].Spec.Template.Labels["azure.workload.identity/use"] assert.False(t, hasLabel) @@ -1101,7 +1164,7 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { assert.Equal(t, 1, len(serviceAccounts.Items), "Number of service accounts was not expected") deployments, _ := client.AppsV1().Deployments(utils.GetEnvironmentNamespace("any-other-app", "test")).List(context.Background(), metav1.ListOptions{}) expectedDeployments := getDeploymentsForRadixComponents(deployments.Items) - assert.Equal(t, utils.BoolPtr(true), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(true), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, defaults.RadixJobSchedulerServiceName, expectedDeployments[0].Spec.Template.Spec.ServiceAccountName) }) @@ -1122,7 +1185,7 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { assert.Equal(t, 0, len(serviceAccounts.Items), "Number of service accounts was not expected") allDeployments, _ := client.AppsV1().Deployments(utils.GetEnvironmentNamespace("any-other-app", "test")).List(context.Background(), metav1.ListOptions{}) expectedDeployments := getDeploymentsForRadixComponents(allDeployments.Items) - assert.Equal(t, utils.BoolPtr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, defaultServiceAccountName, expectedDeployments[0].Spec.Template.Spec.ServiceAccountName) // Change app to be a job @@ -1139,11 +1202,11 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { metav1.ListOptions{}) expectedJobDeployments := getDeploymentsForRadixComponents(allDeployments.Items) assert.Equal(t, 1, len(expectedJobDeployments)) - assert.Equal(t, utils.BoolPtr(true), expectedJobDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(true), expectedJobDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, defaults.RadixJobSchedulerServiceName, expectedJobDeployments[0].Spec.Template.Spec.ServiceAccountName) expectedJobAuxDeployments := getDeploymentsForRadixJobAux(allDeployments.Items) assert.Equal(t, 1, len(expectedJobAuxDeployments)) - assert.Equal(t, utils.BoolPtr(false), expectedJobAuxDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(false), expectedJobAuxDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, defaultServiceAccountName, expectedJobAuxDeployments[0].Spec.Template.Spec.ServiceAccountName) // And change app back to a component @@ -1159,7 +1222,7 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { allDeployments, _ = client.AppsV1().Deployments(utils.GetEnvironmentNamespace("any-other-app", "test")).List(context.Background(), metav1.ListOptions{}) expectedDeployments = getDeploymentsForRadixComponents(allDeployments.Items) assert.Equal(t, 1, len(expectedDeployments)) - assert.Equal(t, utils.BoolPtr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(false), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, defaultServiceAccountName, expectedDeployments[0].Spec.Template.Spec.ServiceAccountName) }) @@ -1174,7 +1237,7 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { assert.Equal(t, 1, len(serviceAccounts.Items), "Number of service accounts was not expected") deployments, _ := client.AppsV1().Deployments(utils.GetEnvironmentNamespace("radix-github-webhook", "test")).List(context.Background(), metav1.ListOptions{}) expectedDeployments := getDeploymentsForRadixComponents(deployments.Items) - assert.Equal(t, utils.BoolPtr(true), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(true), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, defaults.RadixGithubWebhookServiceAccountName, expectedDeployments[0].Spec.Template.Spec.ServiceAccountName) }) @@ -1190,7 +1253,7 @@ func TestObjectSynced_ServiceAccountSettingsAndRbac(t *testing.T) { assert.Equal(t, 1, len(serviceAccounts.Items), "Number of service accounts was not expected") deployments, _ := client.AppsV1().Deployments(utils.GetEnvironmentNamespace("radix-api", "test")).List(context.Background(), metav1.ListOptions{}) expectedDeployments := getDeploymentsForRadixComponents(deployments.Items) - assert.Equal(t, utils.BoolPtr(true), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) + assert.Equal(t, pointers.Ptr(true), expectedDeployments[0].Spec.Template.Spec.AutomountServiceAccountToken) assert.Equal(t, defaults.RadixAPIServiceAccountName, expectedDeployments[0].Spec.Template.Spec.ServiceAccountName) }) } diff --git a/pkg/apis/deployment/jobschedulercomponent.go b/pkg/apis/deployment/jobschedulercomponent.go index 469b89402..bd705c50c 100644 --- a/pkg/apis/deployment/jobschedulercomponent.go +++ b/pkg/apis/deployment/jobschedulercomponent.go @@ -6,15 +6,15 @@ import ( "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" ) type jobSchedulerComponent struct { - *v1.RadixDeployJobComponent - radixDeployment *v1.RadixDeployment + *radixv1.RadixDeployJobComponent + radixDeployment *radixv1.RadixDeployment } -func newJobSchedulerComponent(jobComponent *v1.RadixDeployJobComponent, rd *v1.RadixDeployment) v1.RadixCommonDeployComponent { +func newJobSchedulerComponent(jobComponent *radixv1.RadixDeployJobComponent, rd *radixv1.RadixDeployment) radixv1.RadixCommonDeployComponent { return &jobSchedulerComponent{ jobComponent, rd, @@ -28,12 +28,12 @@ func (js *jobSchedulerComponent) GetImage() string { return radixJobSchedulerImageUrl } -func (js *jobSchedulerComponent) GetPorts() []v1.ComponentPort { +func (js *jobSchedulerComponent) GetPorts() []radixv1.ComponentPort { if js.RadixDeployJobComponent.SchedulerPort == nil { return nil } - return []v1.ComponentPort{ + return []radixv1.ComponentPort{ { Name: defaults.RadixJobSchedulerPortName, Port: *js.RadixDeployJobComponent.SchedulerPort, @@ -41,10 +41,10 @@ func (js *jobSchedulerComponent) GetPorts() []v1.ComponentPort { } } -func (js *jobSchedulerComponent) GetEnvironmentVariables() v1.EnvVarsMap { +func (js *jobSchedulerComponent) GetEnvironmentVariables() radixv1.EnvVarsMap { envVarsMap := js.EnvironmentVariables.DeepCopy() if envVarsMap == nil { - envVarsMap = v1.EnvVarsMap{} + envVarsMap = radixv1.EnvVarsMap{} } envVarsMap[defaults.RadixDeploymentEnvironmentVariable] = js.radixDeployment.Name envVarsMap[defaults.OperatorEnvLimitDefaultMemoryEnvironmentVariable] = os.Getenv(defaults.OperatorEnvLimitDefaultMemoryEnvironmentVariable) @@ -59,8 +59,8 @@ func (js *jobSchedulerComponent) GetMonitoring() bool { return false } -func (js *jobSchedulerComponent) GetResources() *v1.ResourceRequirements { - return &v1.ResourceRequirements{ +func (js *jobSchedulerComponent) GetResources() *radixv1.ResourceRequirements { + return &radixv1.ResourceRequirements{ Limits: map[string]string{ "memory": "500M", }, @@ -79,13 +79,17 @@ func (js *jobSchedulerComponent) IsAlwaysPullImageOnDeploy() bool { return true } -func (js *jobSchedulerComponent) GetNode() *v1.RadixNode { +func (js *jobSchedulerComponent) GetNode() *radixv1.RadixNode { // Job configuration in radixconfig.yaml contains section "node", which supposed to configure scheduled jobs by RadixDeployment // "node" section settings should not be applied to the JobScheduler component itself return nil } -func isDeployComponentJobSchedulerDeployment(deployComponent v1.RadixCommonDeployComponent) bool { +func (js *jobSchedulerComponent) GetRuntime() *radixv1.Runtime { + return &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64} +} + +func isDeployComponentJobSchedulerDeployment(deployComponent radixv1.RadixCommonDeployComponent) bool { _, isJobScheduler := interface{}(deployComponent).(*jobSchedulerComponent) return isJobScheduler } diff --git a/pkg/apis/deployment/kubedeployment.go b/pkg/apis/deployment/kubedeployment.go index 825550bd1..5895b431d 100644 --- a/pkg/apis/deployment/kubedeployment.go +++ b/pkg/apis/deployment/kubedeployment.go @@ -262,7 +262,7 @@ func (deploy *Deployment) setDesiredDeploymentProperties(ctx context.Context, de spec := NewServiceAccountSpec(deploy.radixDeployment, deployComponent) desiredDeployment.Spec.Template.Spec.AutomountServiceAccountToken = spec.AutomountServiceAccountToken() desiredDeployment.Spec.Template.Spec.ServiceAccountName = spec.ServiceAccountName() - desiredDeployment.Spec.Template.Spec.Affinity = utils.GetAffinityForDeployComponent(deployComponent.GetNode(), appName, componentName) + desiredDeployment.Spec.Template.Spec.Affinity = utils.GetAffinityForDeployComponent(deployComponent, appName, componentName) desiredDeployment.Spec.Template.Spec.Tolerations = utils.GetDeploymentPodSpecTolerations(deployComponent.GetNode()) volumes, err := deploy.GetVolumesForComponent(ctx, deployComponent) diff --git a/pkg/apis/deployment/oauthproxyresourcemanager_test.go b/pkg/apis/deployment/oauthproxyresourcemanager_test.go index 5220076a4..f1cd7d86c 100644 --- a/pkg/apis/deployment/oauthproxyresourcemanager_test.go +++ b/pkg/apis/deployment/oauthproxyresourcemanager_test.go @@ -260,8 +260,8 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyDeploymentCreat returnOAuth := &v1.OAuth2{ ClientID: commonUtils.RandString(20), Scope: commonUtils.RandString(20), - SetXAuthRequestHeaders: commonUtils.BoolPtr(true), - SetAuthorizationHeader: commonUtils.BoolPtr(false), + SetXAuthRequestHeaders: pointers.Ptr(true), + SetAuthorizationHeader: pointers.Ptr(false), ProxyPrefix: commonUtils.RandString(20), LoginURL: commonUtils.RandString(20), RedeemURL: commonUtils.RandString(20), @@ -273,7 +273,7 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyDeploymentCreat SameSite: v1.CookieSameSiteType(commonUtils.RandString(20)), }, CookieStore: &v1.OAuth2CookieStore{ - Minimal: utils.BoolPtr(true), + Minimal: pointers.Ptr(true), }, RedisStore: &v1.OAuth2RedisStore{ ConnectionURL: commonUtils.RandString(20), @@ -281,8 +281,8 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyDeploymentCreat OIDC: &v1.OAuth2OIDC{ IssuerURL: commonUtils.RandString(20), JWKSURL: commonUtils.RandString(20), - SkipDiscovery: commonUtils.BoolPtr(true), - InsecureSkipVerifyNonce: commonUtils.BoolPtr(false), + SkipDiscovery: pointers.Ptr(true), + InsecureSkipVerifyNonce: pointers.Ptr(false), }, } s.oauth2Config.EXPECT().MergeWith(inputOAuth).Times(1).Return(returnOAuth, nil) @@ -291,7 +291,7 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyDeploymentCreat rd := utils.NewDeploymentBuilder(). WithAppName(appName). WithEnvironment(envName). - WithComponent(utils.NewDeployComponentBuilder().WithName(componentName).WithPublicPort("http").WithAuthentication(&v1.Authentication{OAuth2: inputOAuth})). + WithComponent(utils.NewDeployComponentBuilder().WithName(componentName).WithPublicPort("http").WithAuthentication(&v1.Authentication{OAuth2: inputOAuth}).WithRuntime(&v1.Runtime{Architecture: "customarch"})). BuildRD() sut := &oauthProxyResourceManager{rd, rr, s.kubeUtil, []ingress.AnnotationProvider{}, s.oauth2Config, "", zerolog.Nop()} err := sut.Sync(context.Background()) @@ -324,7 +324,7 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyDeploymentCreat {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorArchitecture}}, }}}}}, } - s.Equal(expectedAffinity, actualDeploy.Spec.Template.Spec.Affinity) + s.Equal(expectedAffinity, actualDeploy.Spec.Template.Spec.Affinity, "oauth2 aux deployment must not use component's runtime config") s.Len(defaultContainer.Env, 30) s.Equal("oidc", s.getEnvVarValueByName("OAUTH2_PROXY_PROVIDER", defaultContainer.Env)) @@ -830,7 +830,7 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyUninstall() { func (s *OAuthProxyResourceManagerTestSuite) Test_GetOwnerReferenceOfIngress() { actualOwnerReferences := ingress.GetOwnerReferenceOfIngress(&networkingv1.Ingress{ObjectMeta: metav1.ObjectMeta{Name: "anyingress", UID: "anyuid"}}) - s.ElementsMatch([]metav1.OwnerReference{{APIVersion: networkingv1.SchemeGroupVersion.Identifier(), Kind: k8s.KindIngress, Name: "anyingress", UID: "anyuid", Controller: utils.BoolPtr(true)}}, actualOwnerReferences) + s.ElementsMatch([]metav1.OwnerReference{{APIVersion: networkingv1.SchemeGroupVersion.Identifier(), Kind: k8s.KindIngress, Name: "anyingress", UID: "anyuid", Controller: pointers.Ptr(true)}}, actualOwnerReferences) } func (s *OAuthProxyResourceManagerTestSuite) Test_GetIngressName() { diff --git a/pkg/apis/deployment/radixcomponent.go b/pkg/apis/deployment/radixcomponent.go index 580cae183..8c0cfc3bf 100644 --- a/pkg/apis/deployment/radixcomponent.go +++ b/pkg/apis/deployment/radixcomponent.go @@ -72,9 +72,8 @@ func GetRadixComponentsForEnv(radixApplication *radixv1.RadixApplication, env st deployComponent.Identity = identity deployComponent.ReadOnlyFileSystem = getRadixCommonComponentReadOnlyFileSystem(&radixComponent, environmentSpecificConfig) deployComponent.Monitoring = getRadixCommonComponentMonitoring(&radixComponent, environmentSpecificConfig) - if deployComponent.HorizontalScaling, err = getRadixCommonComponentHorizontalScaling(&radixComponent, environmentSpecificConfig); err != nil { - return nil, err - } + deployComponent.HorizontalScaling = getRadixCommonComponentHorizontalScaling(&radixComponent, environmentSpecificConfig) + deployComponent.Runtime = componentImage.Runtime if deployComponent.VolumeMounts, err = getRadixCommonComponentVolumeMounts(&radixComponent, environmentSpecificConfig); err != nil { return nil, err } @@ -99,14 +98,14 @@ func getRadixCommonComponentMonitoring(radixComponent radixv1.RadixCommonCompone return !commonutils.IsNil(monitoring) && *monitoring } -func getRadixCommonComponentHorizontalScaling(radixComponent radixv1.RadixCommonComponent, environmentSpecificConfig radixv1.RadixCommonEnvironmentConfig) (*radixv1.RadixHorizontalScaling, error) { +func getRadixCommonComponentHorizontalScaling(radixComponent radixv1.RadixCommonComponent, environmentSpecificConfig radixv1.RadixCommonEnvironmentConfig) *radixv1.RadixHorizontalScaling { if commonutils.IsNil(environmentSpecificConfig) || environmentSpecificConfig.GetHorizontalScaling() == nil { - return radixComponent.GetHorizontalScaling().NormalizeConfig(), nil + return radixComponent.GetHorizontalScaling().NormalizeConfig() } environmentHorizontalScaling := environmentSpecificConfig.GetHorizontalScaling() if radixComponent.GetHorizontalScaling() == nil { - return environmentHorizontalScaling.NormalizeConfig(), nil + return environmentHorizontalScaling.NormalizeConfig() } finalHorizontalScaling := radixComponent.GetHorizontalScaling().NormalizeConfig() @@ -129,7 +128,7 @@ func getRadixCommonComponentHorizontalScaling(radixComponent radixv1.RadixCommon if len(environmentHorizontalScaling.Triggers) > 0 || environmentHorizontalScaling.RadixHorizontalScalingResources != nil { //nolint:staticcheck // backward compatibility support finalHorizontalScaling.Triggers = environmentHorizontalScaling.NormalizeConfig().Triggers } - return finalHorizontalScaling, nil + return finalHorizontalScaling } diff --git a/pkg/apis/deployment/radixcomponent_test.go b/pkg/apis/deployment/radixcomponent_test.go index b1762bd94..9a927e790 100644 --- a/pkg/apis/deployment/radixcomponent_test.go +++ b/pkg/apis/deployment/radixcomponent_test.go @@ -1400,6 +1400,72 @@ func Test_GetRadixComponents_VolumeMounts_MultipleEnvs(t *testing.T) { } } +func Test_GetRadixComponentsForEnv_Runtime_AlwaysUseFromDeployComponentImages(t *testing.T) { + componentBuilder := utils.NewApplicationComponentBuilder(). + WithName("anycomp"). + WithRuntime(&radixv1.Runtime{Architecture: "commonarch"}). + WithEnvironmentConfig(utils.NewComponentEnvironmentBuilder(). + WithEnvironment("dev"). + WithRuntime(&radixv1.Runtime{Architecture: "devarch"})) + + ra := utils.ARadixApplication(). + WithEnvironmentNoBranch("dev"). + WithEnvironmentNoBranch("prod"). + WithComponents(componentBuilder).BuildRA() + + tests := map[string]struct { + env string + deployImages pipeline.DeployComponentImages + expectedRuntime *radixv1.Runtime + }{ + "dev:nil when deployImages is nil": { + env: "dev", + deployImages: nil, + expectedRuntime: nil, + }, + "dev:nil when comp not defined in deployImages": { + env: "dev", + deployImages: pipeline.DeployComponentImages{"othercomp": {Runtime: &radixv1.Runtime{Architecture: "othercomparch"}}}, + expectedRuntime: nil, + }, + "dev:runtime from deployImage comp when defined": { + env: "dev", + deployImages: pipeline.DeployComponentImages{"anycomp": {Runtime: &radixv1.Runtime{Architecture: "anycomparch"}}}, + expectedRuntime: &radixv1.Runtime{Architecture: "anycomparch"}, + }, + "prod:nil when deployImages is nil": { + env: "prod", + deployImages: nil, + expectedRuntime: nil, + }, + "prod:nil when comp not defined in deployImages": { + env: "prod", + deployImages: pipeline.DeployComponentImages{"othercomp": {Runtime: &radixv1.Runtime{Architecture: "othercomparch"}}}, + expectedRuntime: nil, + }, + "prod:runtime from deployImage comp when defined": { + env: "prod", + deployImages: pipeline.DeployComponentImages{"anycomp": {Runtime: &radixv1.Runtime{Architecture: "anycomparch"}}}, + expectedRuntime: &radixv1.Runtime{Architecture: "anycomparch"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + deployComponents, err := GetRadixComponentsForEnv(ra, test.env, test.deployImages, make(radixv1.EnvVarsMap), nil) + require.NoError(t, err) + require.Len(t, deployComponents, 1) + deployComponent := deployComponents[0] + actualRuntime := deployComponent.Runtime + if test.expectedRuntime == nil { + assert.Nil(t, actualRuntime) + } else { + assert.Equal(t, test.expectedRuntime, actualRuntime) + } + }) + } +} + func convertRadixDeployComponentToNameSet(deployComponents []radixv1.RadixDeployComponent) map[string]bool { set := make(map[string]bool) for _, deployComponent := range deployComponents { diff --git a/pkg/apis/deployment/radixjobcomponent.go b/pkg/apis/deployment/radixjobcomponent.go index 28177a28c..060ec0ef5 100644 --- a/pkg/apis/deployment/radixjobcomponent.go +++ b/pkg/apis/deployment/radixjobcomponent.go @@ -117,6 +117,7 @@ func (c *jobComponentsBuilder) buildJobComponent(radixJobComponent v1.RadixJobCo Notifications: notifications, ReadOnlyFileSystem: getRadixCommonComponentReadOnlyFileSystem(&radixJobComponent, environmentSpecificConfig), VolumeMounts: volumeMounts, + Runtime: componentImage.Runtime, } return &deployJob, nil } diff --git a/pkg/apis/deployment/radixjobcomponent_test.go b/pkg/apis/deployment/radixjobcomponent_test.go index beb01a8a1..bd80a3899 100644 --- a/pkg/apis/deployment/radixjobcomponent_test.go +++ b/pkg/apis/deployment/radixjobcomponent_test.go @@ -658,14 +658,14 @@ func TestGetRadixJobComponentsForEnv_ReadOnlyFileSystem(t *testing.T) { expectedReadOnlyFile *bool }{ {"No configuration set", nil, nil, nil}, - {"Env controls when readOnlyFileSystem is nil, set to true", nil, utils.BoolPtr(true), utils.BoolPtr(true)}, - {"Env controls when readOnlyFileSystem is nil, set to false", nil, utils.BoolPtr(false), utils.BoolPtr(false)}, - {"readOnlyFileSystem set to true, no env config", utils.BoolPtr(true), nil, utils.BoolPtr(true)}, - {"Both readOnlyFileSystem and monitoringEnv set to true", utils.BoolPtr(true), utils.BoolPtr(true), utils.BoolPtr(true)}, - {"Env overrides to false when both is set", utils.BoolPtr(true), utils.BoolPtr(false), utils.BoolPtr(false)}, - {"readOnlyFileSystem set to false, no env config", utils.BoolPtr(false), nil, utils.BoolPtr(false)}, - {"Env overrides to true when both is set", utils.BoolPtr(false), utils.BoolPtr(true), utils.BoolPtr(true)}, - {"Both readOnlyFileSystem and monitoringEnv set to false", utils.BoolPtr(false), utils.BoolPtr(false), utils.BoolPtr(false)}, + {"Env controls when readOnlyFileSystem is nil, set to true", nil, pointers.Ptr(true), pointers.Ptr(true)}, + {"Env controls when readOnlyFileSystem is nil, set to false", nil, pointers.Ptr(false), pointers.Ptr(false)}, + {"readOnlyFileSystem set to true, no env config", pointers.Ptr(true), nil, pointers.Ptr(true)}, + {"Both readOnlyFileSystem and monitoringEnv set to true", pointers.Ptr(true), pointers.Ptr(true), pointers.Ptr(true)}, + {"Env overrides to false when both is set", pointers.Ptr(true), pointers.Ptr(false), pointers.Ptr(false)}, + {"readOnlyFileSystem set to false, no env config", pointers.Ptr(false), nil, pointers.Ptr(false)}, + {"Env overrides to true when both is set", pointers.Ptr(false), pointers.Ptr(true), pointers.Ptr(true)}, + {"Both readOnlyFileSystem and monitoringEnv set to false", pointers.Ptr(false), pointers.Ptr(false), pointers.Ptr(false)}, } for _, ts := range testCases { @@ -1049,3 +1049,69 @@ func Test_GetRadixJobComponents_VolumeMounts_MultipleEnvs(t *testing.T) { }) } } + +func Test_JobCompopnentBuilder_Runtime(t *testing.T) { + jobComponentBuilder := utils.NewApplicationJobComponentBuilder(). + WithName("anyjob"). + WithRuntime(&radixv1.Runtime{Architecture: "commonarch"}). + WithEnvironmentConfig(utils.NewJobComponentEnvironmentBuilder(). + WithEnvironment("dev"). + WithRuntime(&radixv1.Runtime{Architecture: "devarch"})) + + ra := utils.ARadixApplication(). + WithEnvironmentNoBranch("dev"). + WithEnvironmentNoBranch("prod"). + WithJobComponents(jobComponentBuilder).BuildRA() + + tests := map[string]struct { + env string + deployImages pipeline.DeployComponentImages + expectedRuntime *radixv1.Runtime + }{ + "dev:nil when deployImages is nil": { + env: "dev", + deployImages: nil, + expectedRuntime: nil, + }, + "dev:nil when job not defined in deployImages": { + env: "dev", + deployImages: pipeline.DeployComponentImages{"otherjob": {Runtime: &radixv1.Runtime{Architecture: "otherjobarch"}}}, + expectedRuntime: nil, + }, + "dev:runtime from deployImage job when defined": { + env: "dev", + deployImages: pipeline.DeployComponentImages{"anyjob": {Runtime: &radixv1.Runtime{Architecture: "anjobarch"}}}, + expectedRuntime: &radixv1.Runtime{Architecture: "anjobarch"}, + }, + "prod:nil when deployImages is nil": { + env: "prod", + deployImages: nil, + expectedRuntime: nil, + }, + "prod:nil when job not defined in deployImages": { + env: "prod", + deployImages: pipeline.DeployComponentImages{"otherjob": {Runtime: &radixv1.Runtime{Architecture: "otherjobarch"}}}, + expectedRuntime: nil, + }, + "prod:runtime from deployImage job when defined": { + env: "prod", + deployImages: pipeline.DeployComponentImages{"anyjob": {Runtime: &radixv1.Runtime{Architecture: "anjobarch"}}}, + expectedRuntime: &radixv1.Runtime{Architecture: "anjobarch"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + deployJobComponents, err := NewJobComponentsBuilder(ra, test.env, test.deployImages, make(radixv1.EnvVarsMap), nil).JobComponents() + require.NoError(t, err) + require.Len(t, deployJobComponents, 1) + deployJobComponent := deployJobComponents[0] + actualRuntime := deployJobComponent.Runtime + if test.expectedRuntime == nil { + assert.Nil(t, actualRuntime) + } else { + assert.Equal(t, test.expectedRuntime, actualRuntime) + } + }) + } +} diff --git a/pkg/apis/deployment/secrets_test.go b/pkg/apis/deployment/secrets_test.go index 6d8d905f1..ea8c6afba 100644 --- a/pkg/apis/deployment/secrets_test.go +++ b/pkg/apis/deployment/secrets_test.go @@ -6,6 +6,7 @@ import ( "testing" certfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" @@ -71,7 +72,7 @@ func TestSecretDeployed_ClientCertificateSecretGetsSet(t *testing.T) { &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ Verification: &verificationOn, - PassCertificateToUpstream: utils.BoolPtr(true), + PassCertificateToUpstream: pointers.Ptr(true), }, }, ), @@ -83,7 +84,7 @@ func TestSecretDeployed_ClientCertificateSecretGetsSet(t *testing.T) { &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ Verification: &verificationOff, - PassCertificateToUpstream: utils.BoolPtr(false), + PassCertificateToUpstream: pointers.Ptr(false), }, }, ), @@ -94,7 +95,7 @@ func TestSecretDeployed_ClientCertificateSecretGetsSet(t *testing.T) { &v1.Authentication{ ClientCertificate: &v1.ClientCertificate{ Verification: &verificationOn, - PassCertificateToUpstream: utils.BoolPtr(true), + PassCertificateToUpstream: pointers.Ptr(true), }, }, ), diff --git a/pkg/apis/deployment/serviceaccountspec.go b/pkg/apis/deployment/serviceaccountspec.go index 3924a64b6..508c254ac 100644 --- a/pkg/apis/deployment/serviceaccountspec.go +++ b/pkg/apis/deployment/serviceaccountspec.go @@ -1,6 +1,7 @@ package deployment import ( + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" @@ -22,7 +23,7 @@ func (spec *radixAPIServiceAccountSpec) ServiceAccountName() string { } func (spec *radixAPIServiceAccountSpec) AutomountServiceAccountToken() *bool { - return utils.BoolPtr(true) + return pointers.Ptr(true) } // Service account spec for Radix GitHub Webhook deployment @@ -33,7 +34,7 @@ func (spec *radixWebhookServiceAccountSpec) ServiceAccountName() string { } func (spec *radixWebhookServiceAccountSpec) AutomountServiceAccountToken() *bool { - return utils.BoolPtr(true) + return pointers.Ptr(true) } // Service account spec for Radix job scheduler deployment @@ -44,7 +45,7 @@ func (spec *jobSchedulerServiceAccountSpec) ServiceAccountName() string { } func (spec *jobSchedulerServiceAccountSpec) AutomountServiceAccountToken() *bool { - return utils.BoolPtr(true) + return pointers.Ptr(true) } // Service account spec for Radix component deployments @@ -60,7 +61,7 @@ func (spec *radixComponentServiceAccountSpec) ServiceAccountName() string { } func (spec *radixComponentServiceAccountSpec) AutomountServiceAccountToken() *bool { - return utils.BoolPtr(false) + return pointers.Ptr(false) } // NewServiceAccountSpec Create ServiceAccountSpec based on RadixDeployment and RadixCommonDeployComponent diff --git a/pkg/apis/deployment/serviceaccountspec_test.go b/pkg/apis/deployment/serviceaccountspec_test.go index 1a1ab2c3d..66520f7a4 100644 --- a/pkg/apis/deployment/serviceaccountspec_test.go +++ b/pkg/apis/deployment/serviceaccountspec_test.go @@ -3,6 +3,7 @@ package deployment import ( "testing" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" @@ -32,27 +33,27 @@ func Test_ServiceAccountSpec(t *testing.T) { BuildRD() spec := NewServiceAccountSpec(rd, &rd.Spec.Components[0]) - assert.Equal(t, utils.BoolPtr(false), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(false), spec.AutomountServiceAccountToken()) assert.Equal(t, defaultServiceAccountName, spec.ServiceAccountName()) spec = NewServiceAccountSpec(rd, &rd.Spec.Components[1]) - assert.Equal(t, utils.BoolPtr(false), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(false), spec.AutomountServiceAccountToken()) assert.Equal(t, utils.GetComponentServiceAccountName(rd.Spec.Components[1].Name), spec.ServiceAccountName()) spec = NewServiceAccountSpec(rd, newJobSchedulerComponent(&rd.Spec.Jobs[0], rd)) - assert.Equal(t, utils.BoolPtr(true), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(true), spec.AutomountServiceAccountToken()) assert.Equal(t, defaults.RadixJobSchedulerServiceName, spec.ServiceAccountName()) spec = NewServiceAccountSpec(rd, &rd.Spec.Jobs[0]) - assert.Equal(t, utils.BoolPtr(false), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(false), spec.AutomountServiceAccountToken()) assert.Equal(t, defaultServiceAccountName, spec.ServiceAccountName()) spec = NewServiceAccountSpec(rd, newJobSchedulerComponent(&rd.Spec.Jobs[1], rd)) - assert.Equal(t, utils.BoolPtr(true), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(true), spec.AutomountServiceAccountToken()) assert.Equal(t, defaults.RadixJobSchedulerServiceName, spec.ServiceAccountName()) spec = NewServiceAccountSpec(rd, &rd.Spec.Jobs[1]) - assert.Equal(t, utils.BoolPtr(false), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(false), spec.AutomountServiceAccountToken()) assert.Equal(t, utils.GetComponentServiceAccountName(rd.Spec.Jobs[1].Name), spec.ServiceAccountName()) }) @@ -67,15 +68,15 @@ func Test_ServiceAccountSpec(t *testing.T) { BuildRD() spec := NewServiceAccountSpec(rd, &rd.Spec.Components[0]) - assert.Equal(t, utils.BoolPtr(true), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(true), spec.AutomountServiceAccountToken()) assert.Equal(t, defaults.RadixAPIServiceAccountName, spec.ServiceAccountName()) spec = NewServiceAccountSpec(rd, newJobSchedulerComponent(&rd.Spec.Jobs[0], rd)) - assert.Equal(t, utils.BoolPtr(true), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(true), spec.AutomountServiceAccountToken()) assert.Equal(t, defaults.RadixJobSchedulerServiceName, spec.ServiceAccountName()) spec = NewServiceAccountSpec(rd, &rd.Spec.Jobs[0]) - assert.Equal(t, utils.BoolPtr(false), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(false), spec.AutomountServiceAccountToken()) assert.Equal(t, defaultServiceAccountName, spec.ServiceAccountName()) }) @@ -91,15 +92,15 @@ func Test_ServiceAccountSpec(t *testing.T) { BuildRD() spec := NewServiceAccountSpec(rd, &rd.Spec.Components[0]) - assert.Equal(t, utils.BoolPtr(true), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(true), spec.AutomountServiceAccountToken()) assert.Equal(t, defaults.RadixGithubWebhookServiceAccountName, spec.ServiceAccountName()) spec = NewServiceAccountSpec(rd, newJobSchedulerComponent(&rd.Spec.Jobs[0], rd)) - assert.Equal(t, utils.BoolPtr(true), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(true), spec.AutomountServiceAccountToken()) assert.Equal(t, defaults.RadixJobSchedulerServiceName, spec.ServiceAccountName()) spec = NewServiceAccountSpec(rd, &rd.Spec.Jobs[0]) - assert.Equal(t, utils.BoolPtr(false), spec.AutomountServiceAccountToken()) + assert.Equal(t, pointers.Ptr(false), spec.AutomountServiceAccountToken()) assert.Equal(t, defaultServiceAccountName, spec.ServiceAccountName()) }) diff --git a/pkg/apis/ingress/ingress.go b/pkg/apis/ingress/ingress.go index 3489ae1aa..c2514b487 100644 --- a/pkg/apis/ingress/ingress.go +++ b/pkg/apis/ingress/ingress.go @@ -2,10 +2,10 @@ package ingress import ( "github.com/equinor/radix-common/utils/maps" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - "github.com/equinor/radix-operator/pkg/apis/utils" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -67,7 +67,7 @@ func ParseClientCertificateConfiguration(clientCertificate radixv1.ClientCertifi verification := radixv1.VerificationTypeOff certificate = radixv1.ClientCertificate{ Verification: &verification, - PassCertificateToUpstream: utils.BoolPtr(false), + PassCertificateToUpstream: pointers.Ptr(false), } if passUpstream := clientCertificate.PassCertificateToUpstream; passUpstream != nil { diff --git a/pkg/apis/ingress/ingressannotationprovider_test.go b/pkg/apis/ingress/ingressannotationprovider_test.go index 25b9443ec..8d36d2744 100644 --- a/pkg/apis/ingress/ingressannotationprovider_test.go +++ b/pkg/apis/ingress/ingressannotationprovider_test.go @@ -5,6 +5,7 @@ import ( "testing" maputils "github.com/equinor/radix-common/utils/maps" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" @@ -94,13 +95,13 @@ func Test_ClientCertificateAnnotations(t *testing.T) { config1 := &radixv1.Authentication{ ClientCertificate: &radixv1.ClientCertificate{ - PassCertificateToUpstream: utils.BoolPtr(true), + PassCertificateToUpstream: pointers.Ptr(true), }, } config2 := &radixv1.Authentication{ ClientCertificate: &radixv1.ClientCertificate{ - PassCertificateToUpstream: utils.BoolPtr(false), + PassCertificateToUpstream: pointers.Ptr(false), }, } @@ -184,7 +185,7 @@ func (s *OAuth2AnnotationsTestSuite) Test_AuthSigninAndUrlAnnotations() { } func (s *OAuth2AnnotationsTestSuite) Test_AuthResponseHeaderAnnotations_All() { - s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&radixv1.OAuth2{SetXAuthRequestHeaders: utils.BoolPtr(true), SetAuthorizationHeader: utils.BoolPtr(true)}, nil) + s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&radixv1.OAuth2{SetXAuthRequestHeaders: pointers.Ptr(true), SetAuthorizationHeader: pointers.Ptr(true)}, nil) sut := oauth2AnnotationProvider{oauth2DefaultConfig: s.oauth2Config} actual, err := sut.GetAnnotations(&radixv1.RadixDeployComponent{PublicPort: "http", Authentication: &radixv1.Authentication{OAuth2: &radixv1.OAuth2{}}}, "unused-namespace") s.Nil(err) @@ -192,7 +193,7 @@ func (s *OAuth2AnnotationsTestSuite) Test_AuthResponseHeaderAnnotations_All() { } func (s *OAuth2AnnotationsTestSuite) Test_AuthResponseHeaderAnnotations_XAuthHeadersOnly() { - s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&radixv1.OAuth2{SetXAuthRequestHeaders: utils.BoolPtr(true), SetAuthorizationHeader: utils.BoolPtr(false)}, nil) + s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&radixv1.OAuth2{SetXAuthRequestHeaders: pointers.Ptr(true), SetAuthorizationHeader: pointers.Ptr(false)}, nil) sut := oauth2AnnotationProvider{oauth2DefaultConfig: s.oauth2Config} actual, err := sut.GetAnnotations(&radixv1.RadixDeployComponent{PublicPort: "http", Authentication: &radixv1.Authentication{OAuth2: &radixv1.OAuth2{}}}, "unused-namespace") s.Nil(err) @@ -200,7 +201,7 @@ func (s *OAuth2AnnotationsTestSuite) Test_AuthResponseHeaderAnnotations_XAuthHea } func (s *OAuth2AnnotationsTestSuite) Test_AuthResponseHeaderAnnotations_AuthorizationHeaderOnly() { - s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&radixv1.OAuth2{SetXAuthRequestHeaders: utils.BoolPtr(false), SetAuthorizationHeader: utils.BoolPtr(true)}, nil) + s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&radixv1.OAuth2{SetXAuthRequestHeaders: pointers.Ptr(false), SetAuthorizationHeader: pointers.Ptr(true)}, nil) sut := oauth2AnnotationProvider{oauth2DefaultConfig: s.oauth2Config} actual, err := sut.GetAnnotations(&radixv1.RadixDeployComponent{PublicPort: "http", Authentication: &radixv1.Authentication{OAuth2: &radixv1.OAuth2{}}}, "unused-namespace") s.Nil(err) @@ -208,7 +209,7 @@ func (s *OAuth2AnnotationsTestSuite) Test_AuthResponseHeaderAnnotations_Authoriz } func (s *OAuth2AnnotationsTestSuite) Test_OAuthConfig_ApplyTo_ReturnError() { - s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&radixv1.OAuth2{SetXAuthRequestHeaders: utils.BoolPtr(false), SetAuthorizationHeader: utils.BoolPtr(true)}, errors.New("any error")) + s.oauth2Config.EXPECT().MergeWith(gomock.Any()).Times(1).Return(&radixv1.OAuth2{SetXAuthRequestHeaders: pointers.Ptr(false), SetAuthorizationHeader: pointers.Ptr(true)}, errors.New("any error")) sut := oauth2AnnotationProvider{oauth2DefaultConfig: s.oauth2Config} actual, err := sut.GetAnnotations(&radixv1.RadixDeployComponent{PublicPort: "http", Authentication: &radixv1.Authentication{OAuth2: &radixv1.OAuth2{}}}, "unused-namespace") s.Error(err) diff --git a/pkg/apis/ingress/ownerreferences.go b/pkg/apis/ingress/ownerreferences.go index 3476ba389..d20d95be7 100644 --- a/pkg/apis/ingress/ownerreferences.go +++ b/pkg/apis/ingress/ownerreferences.go @@ -1,8 +1,8 @@ package ingress import ( + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults/k8s" - "github.com/equinor/radix-operator/pkg/apis/utils" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -15,7 +15,7 @@ func GetOwnerReferenceOfIngress(ingress *networkingv1.Ingress) []metav1.OwnerRef Kind: k8s.KindIngress, Name: ingress.Name, UID: ingress.UID, - Controller: utils.BoolPtr(true), + Controller: pointers.Ptr(true), }, } } diff --git a/pkg/apis/job/kubejob.go b/pkg/apis/job/kubejob.go index d772e84a9..667077730 100644 --- a/pkg/apis/job/kubejob.go +++ b/pkg/apis/job/kubejob.go @@ -11,7 +11,7 @@ import ( "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" pipelineJob "github.com/equinor/radix-operator/pkg/apis/pipeline" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + 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" @@ -109,7 +109,7 @@ func (job *Job) getPipelineJobConfig(ctx context.Context) (*batchv1.Job, error) }, }, RestartPolicy: "Never", - Affinity: utils.GetAffinityForPipelineJob(), + Affinity: utils.GetAffinityForPipelineJob(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), Tolerations: utils.GetPipelineJobPodSpecTolerations(), }, }, @@ -119,7 +119,7 @@ func (job *Job) getPipelineJobConfig(ctx context.Context) (*batchv1.Job, error) return &jobCfg, nil } -func (job *Job) getPipelineJobArguments(ctx context.Context, appName, jobName string, jobSpec v1.RadixJobSpec, pipeline *pipelineJob.Definition) ([]string, 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) @@ -185,17 +185,17 @@ func (job *Job) getPipelineJobArguments(ctx context.Context, appName, jobName st } switch pipeline.Type { - case v1.BuildDeploy, v1.Build: + case radixv1.BuildDeploy, radixv1.Build: args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixImageTagEnvironmentVariable, jobSpec.Build.ImageTag)) 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)) - case v1.Promote: + case radixv1.Promote: args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixPromoteDeploymentEnvironmentVariable, jobSpec.Promote.DeploymentName)) args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixPromoteFromEnvironmentEnvironmentVariable, jobSpec.Promote.FromEnvironment)) args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixPromoteToEnvironmentEnvironmentVariable, jobSpec.Promote.ToEnvironment)) - case v1.Deploy: + case radixv1.Deploy: args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixPromoteToEnvironmentEnvironmentVariable, jobSpec.Deploy.ToEnvironment)) args = append(args, fmt.Sprintf("--%s=%s", defaults.RadixCommitIdEnvironmentVariable, jobSpec.Deploy.CommitID)) for componentName, imageTagName := range jobSpec.Deploy.ImageTagNames { @@ -207,7 +207,7 @@ func (job *Job) getPipelineJobArguments(ctx context.Context, appName, jobName st return args, nil } -func getPipelineJobLabels(appName, jobName string, jobSpec v1.RadixJobSpec, pipeline *pipelineJob.Definition) map[string]string { +func getPipelineJobLabels(appName, jobName string, jobSpec radixv1.RadixJobSpec, pipeline *pipelineJob.Definition) map[string]string { // Base labels for all types of pipeline labels := radixlabels.Merge( radixlabels.ForApplicationName(appName), @@ -217,7 +217,7 @@ func getPipelineJobLabels(appName, jobName string, jobSpec v1.RadixJobSpec, pipe ) switch pipeline.Type { - case v1.BuildDeploy, v1.Build: + case radixv1.BuildDeploy, radixv1.Build: labels = radixlabels.Merge( labels, radixlabels.ForCommitId(jobSpec.Build.CommitID), @@ -236,28 +236,28 @@ func getPushImageTag(pushImage bool) string { return "0" } -func (job *Job) getJobConditionFromJobStatus(ctx context.Context, jobStatus batchv1.JobStatus) (v1.RadixJobCondition, error) { +func (job *Job) getJobConditionFromJobStatus(ctx context.Context, jobStatus batchv1.JobStatus) (radixv1.RadixJobCondition, error) { if jobStatus.Failed > 0 { - return v1.JobFailed, nil + return radixv1.JobFailed, nil } if jobStatus.Active > 0 { - return v1.JobRunning, nil + return radixv1.JobRunning, nil } if jobStatus.Succeeded > 0 { jobResult, err := job.getRadixJobResult(ctx) if err != nil { - return v1.JobSucceeded, err + return radixv1.JobSucceeded, err } - if jobResult.Result == v1.RadixJobResultStoppedNoChanges || job.radixJob.Status.Condition == v1.JobStoppedNoChanges { - return v1.JobStoppedNoChanges, nil + if jobResult.Result == radixv1.RadixJobResultStoppedNoChanges || job.radixJob.Status.Condition == radixv1.JobStoppedNoChanges { + return radixv1.JobStoppedNoChanges, nil } - return v1.JobSucceeded, nil + return radixv1.JobSucceeded, nil } - return v1.JobWaiting, nil + return radixv1.JobWaiting, nil } -func (job *Job) getRadixJobResult(ctx context.Context) (*v1.RadixJobResult, error) { +func (job *Job) getRadixJobResult(ctx context.Context) (*radixv1.RadixJobResult, error) { namespace := job.radixJob.GetNamespace() jobName := job.radixJob.GetName() configMaps, err := job.kubeutil.ListConfigMapsWithSelector(ctx, namespace, getRadixPipelineJobResultConfigMapSelector(jobName)) @@ -267,7 +267,7 @@ func (job *Job) getRadixJobResult(ctx context.Context) (*v1.RadixJobResult, erro if len(configMaps) > 1 { return nil, fmt.Errorf("unexpected multiple Radix pipeline result ConfigMaps for the job %s in %s", jobName, job.radixJob.GetNamespace()) } - radixJobResult := &v1.RadixJobResult{} + radixJobResult := &radixv1.RadixJobResult{} if len(configMaps) == 0 { return radixJobResult, nil } diff --git a/pkg/apis/kube/namespaces.go b/pkg/apis/kube/namespaces.go index ff2844c86..00086bbbe 100644 --- a/pkg/apis/kube/namespaces.go +++ b/pkg/apis/kube/namespaces.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "time" "github.com/rs/zerolog/log" corev1 "k8s.io/api/core/v1" @@ -12,12 +11,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/kubernetes" ) -const waitTimeout = 15 * time.Second - // ApplyNamespace Creates a new namespace, if not exists already func (kubeutil *Kube) ApplyNamespace(ctx context.Context, name string, labels map[string]string, ownerRefs []metav1.OwnerReference) error { log.Debug().Msgf("Create namespace: %s", name) @@ -88,52 +83,3 @@ func (kubeutil *Kube) getNamespace(ctx context.Context, name string) (*corev1.Na return namespace, nil } - -// NamespaceWatcher Watcher to wait for namespace to be created -type NamespaceWatcher interface { - WaitFor(ctx context.Context, namespace string) error -} - -// NamespaceWatcherImpl Implementation of watcher -type NamespaceWatcherImpl struct { - client kubernetes.Interface -} - -// NewNamespaceWatcherImpl Constructor -func NewNamespaceWatcherImpl(client kubernetes.Interface) NamespaceWatcherImpl { - return NamespaceWatcherImpl{ - client, - } -} - -// WaitFor Waits for namespace to appear -func (watcher NamespaceWatcherImpl) WaitFor(ctx context.Context, namespace string) error { - log.Info().Msgf("Waiting for namespace %s", namespace) - err := waitForNamespace(ctx, watcher.client, namespace) - if err != nil { - return err - } - - log.Info().Msgf("Namespace %s exists and is active", namespace) - return nil - -} - -func waitForNamespace(ctx context.Context, client kubernetes.Interface, namespace string) error { - timoutContext, cancel := context.WithTimeout(ctx, waitTimeout) - defer cancel() - - return wait.PollUntilContextCancel(timoutContext, time.Second, true, func(ctx context.Context) (done bool, err error) { - ns, err := client.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) - if err != nil { - if k8errs.IsNotFound(err) || k8errs.IsForbidden(err) { - return false, nil // the environment namespace or the rolebinding for the cluster-role radix-pipeline-env are not yet created - } - return false, err - } - if ns != nil && ns.Status.Phase == corev1.NamespaceActive { - return true, nil - } - return false, nil - }) -} diff --git a/pkg/apis/pipeline/component_image.go b/pkg/apis/pipeline/component_image.go index b6b6a5bfd..f693f5fab 100644 --- a/pkg/apis/pipeline/component_image.go +++ b/pkg/apis/pipeline/component_image.go @@ -1,9 +1,12 @@ package pipeline +import radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + // DeployComponentImage Holds info about the image associated with a component type DeployComponentImage struct { ImagePath string ImageTagName string + Runtime *radixv1.Runtime Build bool } @@ -22,6 +25,7 @@ type BuildComponentImage struct { Dockerfile string ImageName string ImagePath string + Runtime *radixv1.Runtime } // EnvironmentBuildComponentImages maps component names with build information for environment diff --git a/pkg/apis/pipeline/componentimagesource.go b/pkg/apis/pipeline/componentimagesource.go deleted file mode 100644 index bef371eca..000000000 --- a/pkg/apis/pipeline/componentimagesource.go +++ /dev/null @@ -1,96 +0,0 @@ -package pipeline - -import v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - -// ComponentImageSource holds information about the container image source for a Radix component or job -type ComponentImageSource struct { - Name string - SourceFolder string - DockerfileName string - Image string -} - -// SourceFunc defines a function that can be used as input argument to WithSourceFunc -type SourceFunc func(*componentImageSourceBuilder) - -// RadixComponentSource returns a SourceFunc that sets properties from the RadixComponent -func RadixComponentSource(c v1.RadixComponent) SourceFunc { - return func(b *componentImageSourceBuilder) { - b.WithName(c.Name). - WithSourceFolder(c.SourceFolder). - WithDockerfileName(c.DockerfileName). - WithImage(c.Image) - } -} - -// RadixJobComponentSource returns a SourceFunc that sets properties from the RadixJobComponent -func RadixJobComponentSource(c v1.RadixJobComponent) SourceFunc { - return func(b *componentImageSourceBuilder) { - b.WithName(c.Name). - WithSourceFolder(c.SourceFolder). - WithDockerfileName(c.DockerfileName). - WithImage(c.Image) - } -} - -// ComponentImageSourceBuilder handles construction of ComponentImageSource -type ComponentImageSourceBuilder interface { - WithName(name string) ComponentImageSourceBuilder - WithSourceFolder(sourceFolder string) ComponentImageSourceBuilder - WithDockerfileName(dockerfileName string) ComponentImageSourceBuilder - WithImage(image string) ComponentImageSourceBuilder - WithSourceFunc(f SourceFunc) ComponentImageSourceBuilder - Build() ComponentImageSource -} - -type componentImageSourceBuilder struct { - name string - sourceFolder string - dockerfileName string - image string -} - -// NewComponentImageSourceBuilder Constructor for the ComponentImageSourceBuilder -func NewComponentImageSourceBuilder() ComponentImageSourceBuilder { - return &componentImageSourceBuilder{} -} - -// WithName sets the name of the component image -func (b *componentImageSourceBuilder) WithName(name string) ComponentImageSourceBuilder { - b.name = name - return b -} - -// WithSourceFolder sets the name of the directory where the docker file exists -func (b *componentImageSourceBuilder) WithSourceFolder(sourceFolder string) ComponentImageSourceBuilder { - b.sourceFolder = sourceFolder - return b -} - -// WithDockerfileName sets the name of the docker file -func (b *componentImageSourceBuilder) WithDockerfileName(dockerfileName string) ComponentImageSourceBuilder { - b.dockerfileName = dockerfileName - return b -} - -// WithImage set the image to use as source for the component -func (b *componentImageSourceBuilder) WithImage(image string) ComponentImageSourceBuilder { - b.image = image - return b -} - -// WithSourceFunc calls the SourceFunc to set properties from a source object -func (b *componentImageSourceBuilder) WithSourceFunc(f SourceFunc) ComponentImageSourceBuilder { - f(b) - return b -} - -// Build builds the ComponentImageSource -func (b *componentImageSourceBuilder) Build() ComponentImageSource { - return ComponentImageSource{ - Name: b.name, - SourceFolder: b.sourceFolder, - DockerfileName: b.dockerfileName, - Image: b.image, - } -} diff --git a/pkg/apis/pipeline/componentimagesource_test.go b/pkg/apis/pipeline/componentimagesource_test.go deleted file mode 100644 index 7b2aae96a..000000000 --- a/pkg/apis/pipeline/componentimagesource_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package pipeline - -import ( - "testing" - - _ "github.com/equinor/radix-operator/pkg/apis/test" - "github.com/equinor/radix-operator/pkg/apis/utils" - "github.com/stretchr/testify/assert" -) - -func Test_RadixComponentSourceBuilder_SinglePropFunc(t *testing.T) { - // comp := utils.AnApplicationComponent(). - // WithName("app"). - // WithSourceFolder("/dir"). - // WithDockerfileName("dockerfile"). - // WithImage("img:latest") - - source := NewComponentImageSourceBuilder(). - WithName("app"). - WithSourceFolder("folder"). - WithDockerfileName("file"). - WithImage("image"). - Build() - - assert.Equal(t, "app", source.Name) - assert.Equal(t, "folder", source.SourceFolder) - assert.Equal(t, "file", source.DockerfileName) - assert.Equal(t, "image", source.Image) -} - -func Test_RadixComponentSourceBuilder_RadixComponentSource(t *testing.T) { - comp := utils.AnApplicationComponent(). - WithName("app"). - WithSourceFolder("folder"). - WithDockerfileName("file"). - WithImage("image"). - BuildComponent() - - source := NewComponentImageSourceBuilder(). - WithSourceFunc(RadixComponentSource(comp)). - Build() - - assert.Equal(t, "app", source.Name) - assert.Equal(t, "folder", source.SourceFolder) - assert.Equal(t, "file", source.DockerfileName) - assert.Equal(t, "image", source.Image) -} diff --git a/pkg/apis/pipeline/container_output_name.go b/pkg/apis/pipeline/container_output_name.go deleted file mode 100644 index fb661c71e..000000000 --- a/pkg/apis/pipeline/container_output_name.go +++ /dev/null @@ -1,6 +0,0 @@ -package pipeline - -// ContainerOutputName holds information about the configmap a container writes output to. -// Key is the name of the container and value is the name of the configmap. -// The configmap must exist in the same namespace as the container Pod. -type ContainerOutputName map[string]string diff --git a/pkg/apis/radix/v1/radixapptypes.go b/pkg/apis/radix/v1/radixapptypes.go index 2d75e588e..5b76d0375 100644 --- a/pkg/apis/radix/v1/radixapptypes.go +++ b/pkg/apis/radix/v1/radixapptypes.go @@ -456,6 +456,10 @@ type RadixComponent struct { // More info: https://www.radix.equinor.com/references/reference-radix-config/#volumemounts // +optional VolumeMounts []RadixVolumeMount `json:"volumeMounts,omitempty"` + + // Runtime defines the target runtime requirements for the component + // +optional + Runtime *Runtime `json:"runtime,omitempty"` } // RadixEnvironmentConfig defines environment specific settings for component. @@ -550,6 +554,10 @@ type RadixEnvironmentConfig struct { // Controls if the filesystem shall be read-only. // +optional ReadOnlyFileSystem *bool `json:"readOnlyFileSystem,omitempty"` + + // Runtime defines environment specific target runtime requirements for the component + // +optional + Runtime *Runtime `json:"runtime,omitempty"` } // RadixJobComponent defines a single job component within a RadixApplication @@ -675,6 +683,10 @@ type RadixJobComponent struct { // Controls if the filesystem shall be read-only. // +optional ReadOnlyFileSystem *bool `json:"readOnlyFileSystem,omitempty"` + + // Runtime defines target runtime requirements for the job + // +optional + Runtime *Runtime `json:"runtime,omitempty"` } // RadixJobComponentEnvironmentConfig defines environment specific settings @@ -763,6 +775,10 @@ type RadixJobComponentEnvironmentConfig struct { // Controls if the filesystem shall be read-only. // +optional ReadOnlyFileSystem *bool `json:"readOnlyFileSystem,omitempty"` + + // Runtime defines environment specific target runtime requirements for the job + // +optional + Runtime *Runtime `json:"runtime,omitempty"` } // RadixJobComponentPayload defines the path and where the payload received @@ -1366,6 +1382,22 @@ type ComponentSource struct { DockefileName string } +type RuntimeArchitecture string + +const ( + RuntimeArchitectureAmd64 RuntimeArchitecture = "amd64" + RuntimeArchitectureArm64 RuntimeArchitecture = "arm64" +) + +// Runtime defines the component or job's target runtime requirements +type Runtime struct { + // CPU architecture target for the component or job. Defaults to amd64. + // +kubebuilder:validation:Enum=amd64;arm64 + // +kubebuilder:default:=amd64 + // +optional + Architecture RuntimeArchitecture `json:"architecture,omitempty"` +} + // RadixCommonComponent defines a common component interface for Radix components type RadixCommonComponent interface { // GetName Gets component name @@ -1418,6 +1450,8 @@ type RadixCommonComponent interface { GetVolumeMounts() []RadixVolumeMount // GetImageTagName Is a dynamic image tag for the component image GetImageTagName() string + // GetRuntime Gets target runtime requirements + GetRuntime() *Runtime } func (component *RadixComponent) GetName() string { @@ -1488,6 +1522,10 @@ func (component *RadixComponent) GetIdentity() *Identity { return component.Identity } +func (component *RadixComponent) GetRuntime() *Runtime { + return component.Runtime +} + func (component *RadixComponent) getEnabled() bool { return component.Enabled == nil || *component.Enabled } @@ -1613,6 +1651,10 @@ func (component *RadixJobComponent) GetIdentity() *Identity { return component.Identity } +func (component *RadixJobComponent) GetRuntime() *Runtime { + return component.Runtime +} + // GetNotifications Get job component notifications func (component *RadixJobComponent) GetNotifications() *Notifications { return component.Notifications diff --git a/pkg/apis/radix/v1/radixdeploytypes.go b/pkg/apis/radix/v1/radixdeploytypes.go index ee72bfb6d..c4995e324 100644 --- a/pkg/apis/radix/v1/radixdeploytypes.go +++ b/pkg/apis/radix/v1/radixdeploytypes.go @@ -126,6 +126,7 @@ type RadixDeployComponent struct { Authentication *Authentication `json:"authentication,omitempty"` Identity *Identity `json:"identity,omitempty"` ReadOnlyFileSystem *bool `json:"readOnlyFileSystem,omitempty"` + Runtime *Runtime `json:"runtime,omitempty"` } func (deployComponent *RadixDeployComponent) GetName() string { @@ -225,6 +226,10 @@ func (deployComponent *RadixDeployComponent) GetReadOnlyFileSystem() *bool { return deployComponent.ReadOnlyFileSystem } +func (deployComponent *RadixDeployComponent) GetRuntime() *Runtime { + return deployComponent.Runtime +} + func (deployComponent *RadixDeployComponent) SetName(name string) { deployComponent.Name = name } @@ -337,6 +342,10 @@ func (deployJobComponent *RadixDeployJobComponent) GetReadOnlyFileSystem() *bool return deployJobComponent.ReadOnlyFileSystem } +func (deployJobComponent *RadixDeployJobComponent) GetRuntime() *Runtime { + return deployJobComponent.Runtime +} + func (deployJobComponent *RadixDeployJobComponent) SetName(name string) { deployJobComponent.Name = name } @@ -383,6 +392,7 @@ type RadixDeployJobComponent struct { Identity *Identity `json:"identity,omitempty"` Notifications *Notifications `json:"notifications,omitempty"` ReadOnlyFileSystem *bool `json:"readOnlyFileSystem,omitempty"` + Runtime *Runtime `json:"runtime,omitempty"` } type RadixComponentType string @@ -420,6 +430,7 @@ type RadixCommonDeployComponent interface { SetVolumeMounts(mounts []RadixVolumeMount) GetIdentity() *Identity GetReadOnlyFileSystem() *bool + GetRuntime() *Runtime } // RadixCommonDeployComponentFactory defines a common component factory diff --git a/pkg/apis/radix/v1/radixenvironmentconfigtypes.go b/pkg/apis/radix/v1/radixenvironmentconfigtypes.go index 3e5cbb34e..483dbd211 100644 --- a/pkg/apis/radix/v1/radixenvironmentconfigtypes.go +++ b/pkg/apis/radix/v1/radixenvironmentconfigtypes.go @@ -24,6 +24,8 @@ type RadixCommonEnvironmentConfig interface { GetMonitoring() *bool // GetVolumeMounts Get volume mounts configurations GetVolumeMounts() []RadixVolumeMount + // GetRuntime Gets environment specific target runtime requirements + GetRuntime() *Runtime getEnabled() *bool } @@ -87,6 +89,10 @@ func (config *RadixEnvironmentConfig) GetVolumeMounts() []RadixVolumeMount { return config.VolumeMounts } +func (config *RadixEnvironmentConfig) GetRuntime() *Runtime { + return config.Runtime +} + func (config *RadixEnvironmentConfig) getEnabled() *bool { return config.Enabled } @@ -156,6 +162,10 @@ func (config *RadixJobComponentEnvironmentConfig) GetVolumeMounts() []RadixVolum return config.VolumeMounts } +func (config *RadixJobComponentEnvironmentConfig) GetRuntime() *Runtime { + return config.Runtime +} + func (config *RadixJobComponentEnvironmentConfig) getEnabled() *bool { return config.Enabled } diff --git a/pkg/apis/radix/v1/zz_generated.deepcopy.go b/pkg/apis/radix/v1/zz_generated.deepcopy.go index 5feb382a1..709068e1c 100644 --- a/pkg/apis/radix/v1/zz_generated.deepcopy.go +++ b/pkg/apis/radix/v1/zz_generated.deepcopy.go @@ -1318,6 +1318,11 @@ func (in *RadixComponent) DeepCopyInto(out *RadixComponent) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Runtime != nil { + in, out := &in.Runtime, &out.Runtime + *out = new(Runtime) + **out = **in + } return } @@ -1499,6 +1504,11 @@ func (in *RadixDeployComponent) DeepCopyInto(out *RadixDeployComponent) { *out = new(bool) **out = **in } + if in.Runtime != nil { + in, out := &in.Runtime, &out.Runtime + *out = new(Runtime) + **out = **in + } return } @@ -1610,6 +1620,11 @@ func (in *RadixDeployJobComponent) DeepCopyInto(out *RadixDeployJobComponent) { *out = new(bool) **out = **in } + if in.Runtime != nil { + in, out := &in.Runtime, &out.Runtime + *out = new(Runtime) + **out = **in + } return } @@ -1904,6 +1919,11 @@ func (in *RadixEnvironmentConfig) DeepCopyInto(out *RadixEnvironmentConfig) { *out = new(bool) **out = **in } + if in.Runtime != nil { + in, out := &in.Runtime, &out.Runtime + *out = new(Runtime) + **out = **in + } return } @@ -2330,6 +2350,11 @@ func (in *RadixJobComponent) DeepCopyInto(out *RadixJobComponent) { *out = new(bool) **out = **in } + if in.Runtime != nil { + in, out := &in.Runtime, &out.Runtime + *out = new(Runtime) + **out = **in + } return } @@ -2398,6 +2423,11 @@ func (in *RadixJobComponentEnvironmentConfig) DeepCopyInto(out *RadixJobComponen *out = new(bool) **out = **in } + if in.Runtime != nil { + in, out := &in.Runtime, &out.Runtime + *out = new(Runtime) + **out = **in + } return } @@ -2907,6 +2937,22 @@ func (in *ResourceRequirements) DeepCopy() *ResourceRequirements { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Runtime) DeepCopyInto(out *Runtime) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Runtime. +func (in *Runtime) DeepCopy() *Runtime { + if in == nil { + return nil + } + out := new(Runtime) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SlackConfig) DeepCopyInto(out *SlackConfig) { *out = *in diff --git a/pkg/apis/radixvalidators/errors.go b/pkg/apis/radixvalidators/errors.go index ed3769e38..620f99370 100644 --- a/pkg/apis/radixvalidators/errors.go +++ b/pkg/apis/radixvalidators/errors.go @@ -106,6 +106,7 @@ var ( ErrInvalidPortInWebhookUrl = errors.Wrap(ErrWebhook, "invalid port in webhook") ErrInvalidUseOfPublicPortInWebhookUrl = errors.New("invalid use of public port in webhook") ErrMissingAzureIdentity = errors.New("missing identity") + ErrInvalidRuntimeArchitecture = errors.New("invalid runtime architecture") ) // DuplicateAliasForDNSAliasError Error when aliases are duplicate diff --git a/pkg/apis/radixvalidators/testdata/radixconfig.yaml b/pkg/apis/radixvalidators/testdata/radixconfig.yaml index f95278c85..6e9dc5568 100644 --- a/pkg/apis/radixvalidators/testdata/radixconfig.yaml +++ b/pkg/apis/radixvalidators/testdata/radixconfig.yaml @@ -29,6 +29,8 @@ spec: identity: azure: clientId: 11111111-2222-BBBB-cccc-555555555555 + runtime: + architecture: arm64 environmentConfig: - environment: prod replicas: 4 @@ -61,6 +63,8 @@ spec: name: blobvol container: blobcontainer path: /path/to/mount + runtime: + architecture: amd64 - name: redis src: redis/ ports: @@ -69,6 +73,7 @@ spec: publicPort: "" variables: DB_NAME: "my-db" + runtime: {} environmentConfig: - environment: dev variables: @@ -130,6 +135,8 @@ spec: identity: azure: clientId: 11111111-2222-3333-4444-555555555555 + runtime: + architecture: arm64 environmentConfig: - environment: dev variables: @@ -149,9 +156,12 @@ spec: name: blobvol container: blobcontainer path: /path/to/mount + runtime: + architecture: amd64 - name: job2 src: job2/ schedulerPort: 8888 + runtime: {} - name: job3 src: job3/ schedulerPort: 8888 diff --git a/pkg/apis/radixvalidators/validate_ra.go b/pkg/apis/radixvalidators/validate_ra.go index aeaa2bd11..5098b5ad5 100644 --- a/pkg/apis/radixvalidators/validate_ra.go +++ b/pkg/apis/radixvalidators/validate_ra.go @@ -316,6 +316,10 @@ func validateComponents(app *radixv1.RadixApplication) error { errs = append(errs, err) } + if err := validateRuntime(component.Runtime); err != nil { + errs = append(errs, err) + } + for _, environment := range component.EnvironmentConfig { if !doesEnvExist(app, environment.Environment) { err = EnvironmentReferencedByComponentDoesNotExistErrorWithMessage(environment.Environment, component.Name) @@ -342,6 +346,9 @@ func validateComponents(app *radixv1.RadixApplication) error { errs = append(errs, err) } + if err := validateRuntime(environment.Runtime); err != nil { + errs = append(errs, err) + } } } @@ -391,6 +398,10 @@ func validateJobComponents(app *radixv1.RadixApplication) error { errs = append(errs, err) } + if err := validateRuntime(job.Runtime); err != nil { + errs = append(errs, err) + } + for _, environment := range job.EnvironmentConfig { if !doesEnvExist(app, environment.Environment) { err = EnvironmentReferencedByComponentDoesNotExistErrorWithMessage(environment.Environment, job.Name) @@ -411,6 +422,10 @@ func validateJobComponents(app *radixv1.RadixApplication) error { if err != nil { errs = append(errs, err) } + + if err := validateRuntime(environment.Runtime); err != nil { + errs = append(errs, err) + } } } @@ -1545,6 +1560,18 @@ func validateExpectedAzureIdentity(azureIdentity radixv1.AzureIdentity) error { return nil } +func validateRuntime(runtime *radixv1.Runtime) error { + if runtime == nil { + return nil + } + + if !slices.Contains([]radixv1.RuntimeArchitecture{radixv1.RuntimeArchitectureAmd64, radixv1.RuntimeArchitectureArm64, ""}, runtime.Architecture) { + return ErrInvalidRuntimeArchitecture + } + + return nil +} + func doesComponentExistInEnvironment(app *radixv1.RadixApplication, componentName string, environment string) bool { for _, component := range app.Spec.Components { if component.Name == componentName { diff --git a/pkg/apis/radixvalidators/validate_ra_test.go b/pkg/apis/radixvalidators/validate_ra_test.go index 5f0dd6bed..4796ca2d9 100644 --- a/pkg/apis/radixvalidators/validate_ra_test.go +++ b/pkg/apis/radixvalidators/validate_ra_test.go @@ -641,6 +641,18 @@ func Test_invalid_ra(t *testing.T) { {"invalid identity.azure.clientId for job environment config", radixvalidators.InvalidUUIDErrorWithMessage("identity.azure.clientId", "1111-22-33-44"), func(ra *radixv1.RadixApplication) { ra.Spec.Jobs[0].EnvironmentConfig[0].Identity.Azure.ClientId = "1111-22-33-44" }}, + {"invalid runtime architecture for component", radixvalidators.ErrInvalidRuntimeArchitecture, func(ra *radixv1.RadixApplication) { + ra.Spec.Components[0].Runtime.Architecture = "anyarch" + }}, + {"invalid runtime architecture for component environment config", radixvalidators.ErrInvalidRuntimeArchitecture, func(ra *radixv1.RadixApplication) { + ra.Spec.Components[0].EnvironmentConfig[0].Runtime.Architecture = "anyarch" + }}, + {"invalid runtime architecture for job", radixvalidators.ErrInvalidRuntimeArchitecture, func(ra *radixv1.RadixApplication) { + ra.Spec.Jobs[0].Runtime.Architecture = "anyarch" + }}, + {"invalid runtime architecture for job environment config", radixvalidators.ErrInvalidRuntimeArchitecture, func(ra *radixv1.RadixApplication) { + ra.Spec.Jobs[0].EnvironmentConfig[0].Runtime.Architecture = "anyarch" + }}, } _, client := validRASetup() diff --git a/pkg/apis/utils/affinity.go b/pkg/apis/utils/affinity.go index cf83da227..3f6ded364 100644 --- a/pkg/apis/utils/affinity.go +++ b/pkg/apis/utils/affinity.go @@ -8,31 +8,31 @@ import ( "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/rs/zerolog/log" corev1 "k8s.io/api/core/v1" ) // GetAffinityForDeployComponent Gets component pod specific affinity -func GetAffinityForDeployComponent(node *v1.RadixNode, appName string, componentName string) *corev1.Affinity { +func GetAffinityForDeployComponent(component radixv1.RadixCommonDeployComponent, appName string, componentName string) *corev1.Affinity { return &corev1.Affinity{ PodAntiAffinity: getPodAntiAffinity(appName, componentName), - NodeAffinity: getNodeAffinityForDeployComponent(node), + NodeAffinity: getNodeAffinityForDeployComponent(component), } } // GetAffinityForBatchJob Gets batch job pod specific affinity -func GetAffinityForBatchJob(node *v1.RadixNode) *corev1.Affinity { +func GetAffinityForBatchJob(job *radixv1.RadixDeployJobComponent, node *radixv1.RadixNode) *corev1.Affinity { return &corev1.Affinity{ - NodeAffinity: getNodeAffinityForBatchJob(node), + NodeAffinity: getNodeAffinityForBatchJob(job, node), } } // GetAffinityForPipelineJob Gets pipeline job pod specific affinity -func GetAffinityForPipelineJob() *corev1.Affinity { +func GetAffinityForPipelineJob(runtime *radixv1.Runtime) *corev1.Affinity { return &corev1.Affinity{ - NodeAffinity: getNodeAffinityForPipelineJob(), + NodeAffinity: getNodeAffinityForPipelineJob(runtime), } } @@ -42,7 +42,7 @@ func GetAffinityForOAuthAuxComponent() *corev1.Affinity { RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ NodeSelectorTerms: []corev1.NodeSelectorTerm{ { - MatchExpressions: getNodeSelectorRequirementsForRuntimeEnvironment(), + MatchExpressions: getNodeSelectorRequirementsForRuntimeEnvironment(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), }, }, }, @@ -56,7 +56,7 @@ func GetAffinityForJobAPIAuxComponent() *corev1.Affinity { RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ NodeSelectorTerms: []corev1.NodeSelectorTerm{ { - MatchExpressions: getNodeSelectorRequirementsForRuntimeEnvironment(), + MatchExpressions: getNodeSelectorRequirementsForRuntimeEnvironment(&radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureAmd64}), }, }, }, @@ -64,22 +64,22 @@ func GetAffinityForJobAPIAuxComponent() *corev1.Affinity { } } -func getNodeAffinityForDeployComponent(node *v1.RadixNode) *corev1.NodeAffinity { - if affinity := getNodeAffinityForGPUNode(node); affinity != nil { +func getNodeAffinityForDeployComponent(component radixv1.RadixCommonDeployComponent) *corev1.NodeAffinity { + if affinity := getNodeAffinityForGPUNode(component.GetNode()); affinity != nil { return affinity } return &corev1.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ NodeSelectorTerms: []corev1.NodeSelectorTerm{ { - MatchExpressions: getNodeSelectorRequirementsForRuntimeEnvironment(), + MatchExpressions: getNodeSelectorRequirementsForRuntimeEnvironment(component.GetRuntime()), }, }, }, } } -func getNodeAffinityForBatchJob(node *v1.RadixNode) *corev1.NodeAffinity { +func getNodeAffinityForBatchJob(job *radixv1.RadixDeployJobComponent, node *radixv1.RadixNode) *corev1.NodeAffinity { if affinity := getNodeAffinityForGPUNode(node); affinity != nil { return affinity } @@ -87,19 +87,19 @@ func getNodeAffinityForBatchJob(node *v1.RadixNode) *corev1.NodeAffinity { RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ NodeSelectorTerms: []corev1.NodeSelectorTerm{ { - MatchExpressions: slices.Concat(getNodeSelectorRequirementsForJobNodePool(), getNodeSelectorRequirementsForRuntimeEnvironment()), + MatchExpressions: slices.Concat(getNodeSelectorRequirementsForJobNodePool(), getNodeSelectorRequirementsForRuntimeEnvironment(job.Runtime)), }, }, }, } } -func getNodeAffinityForPipelineJob() *corev1.NodeAffinity { +func getNodeAffinityForPipelineJob(runtime *radixv1.Runtime) *corev1.NodeAffinity { return &corev1.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ NodeSelectorTerms: []corev1.NodeSelectorTerm{ { - MatchExpressions: slices.Concat(getNodeSelectorRequirementsForJobNodePool(), getNodeSelectorRequirementsForRuntimeEnvironment()), + MatchExpressions: slices.Concat(getNodeSelectorRequirementsForJobNodePool(), getNodeSelectorRequirementsForRuntimeEnvironment(runtime)), }, }, }, @@ -140,7 +140,7 @@ func getPodAffinityTerm(appName string, componentName string) corev1.PodAffinity } } -func getNodeAffinityForGPUNode(radixNode *v1.RadixNode) *corev1.NodeAffinity { +func getNodeAffinityForGPUNode(radixNode *radixv1.RadixNode) *corev1.NodeAffinity { if !UseGPUNode(radixNode) { return nil } @@ -188,10 +188,10 @@ func addNodeSelectorRequirement(nodeSelectorTerm *corev1.NodeSelectorTerm, key s return true } -func getNodeSelectorRequirementsForRuntimeEnvironment() []corev1.NodeSelectorRequirement { +func getNodeSelectorRequirementsForRuntimeEnvironment(runtime *radixv1.Runtime) []corev1.NodeSelectorRequirement { return []corev1.NodeSelectorRequirement{ {Key: corev1.LabelOSStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorOS}}, - {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{defaults.DefaultNodeSelectorArchitecture}}, + {Key: corev1.LabelArchStable, Operator: corev1.NodeSelectorOpIn, Values: []string{GetArchitectureFromRuntime(runtime)}}, } } diff --git a/pkg/apis/utils/applicationcomponent_builder.go b/pkg/apis/utils/applicationcomponent_builder.go index b5a6570ec..1ce1ff46a 100644 --- a/pkg/apis/utils/applicationcomponent_builder.go +++ b/pkg/apis/utils/applicationcomponent_builder.go @@ -32,6 +32,7 @@ type RadixApplicationComponentBuilder interface { WithIdentity(*v1.Identity) RadixApplicationComponentBuilder WithReadOnlyFileSystem(*bool) RadixApplicationComponentBuilder WithHorizontalScaling(scaling *v1.RadixHorizontalScaling) RadixApplicationComponentBuilder + WithRuntime(runtime *v1.Runtime) RadixApplicationComponentBuilder BuildComponent() v1.RadixComponent } @@ -60,6 +61,7 @@ type radixApplicationComponentBuilder struct { monitoring *bool imageTagName string horizontalScaling *v1.RadixHorizontalScaling + runtime *v1.Runtime } func (rcb *radixApplicationComponentBuilder) WithName(name string) RadixApplicationComponentBuilder { @@ -206,6 +208,11 @@ func (rcb *radixApplicationComponentBuilder) WithHorizontalScaling(scaling *v1.R return rcb } +func (rcb *radixApplicationComponentBuilder) WithRuntime(runtime *v1.Runtime) RadixApplicationComponentBuilder { + rcb.runtime = runtime + return rcb +} + func (rcb *radixApplicationComponentBuilder) BuildComponent() v1.RadixComponent { var environmentConfig = make([]v1.RadixEnvironmentConfig, 0) for _, env := range rcb.environmentConfig { @@ -237,6 +244,7 @@ func (rcb *radixApplicationComponentBuilder) BuildComponent() v1.RadixComponent ImageTagName: rcb.imageTagName, HorizontalScaling: rcb.horizontalScaling, VolumeMounts: rcb.volumeMounts, + Runtime: rcb.runtime, } } diff --git a/pkg/apis/utils/applicationjobcomponent_builder.go b/pkg/apis/utils/applicationjobcomponent_builder.go index f78949bb8..8679d05d8 100644 --- a/pkg/apis/utils/applicationjobcomponent_builder.go +++ b/pkg/apis/utils/applicationjobcomponent_builder.go @@ -28,6 +28,7 @@ type RadixApplicationJobComponentBuilder interface { WithIdentity(*v1.Identity) RadixApplicationJobComponentBuilder WithNotifications(*v1.Notifications) RadixApplicationJobComponentBuilder WithReadOnlyFileSystem(*bool) RadixApplicationJobComponentBuilder + WithRuntime(*v1.Runtime) RadixApplicationJobComponentBuilder BuildJobComponent() v1.RadixJobComponent } @@ -55,6 +56,7 @@ type radixApplicationJobComponentBuilder struct { readOnlyFileSystem *bool monitoring *bool imageTagName string + runtime *v1.Runtime } func (rcb *radixApplicationJobComponentBuilder) WithTimeLimitSeconds(timeLimitSeconds *int64) RadixApplicationJobComponentBuilder { @@ -189,10 +191,17 @@ func (rcb *radixApplicationJobComponentBuilder) WithNotifications(notifications rcb.notifications = notifications return rcb } + func (rcb *radixApplicationJobComponentBuilder) WithReadOnlyFileSystem(readOnlyFileSystem *bool) RadixApplicationJobComponentBuilder { rcb.readOnlyFileSystem = readOnlyFileSystem return rcb } + +func (rcb *radixApplicationJobComponentBuilder) WithRuntime(runtime *v1.Runtime) RadixApplicationJobComponentBuilder { + rcb.runtime = runtime + return rcb +} + func (rcb *radixApplicationJobComponentBuilder) BuildJobComponent() v1.RadixJobComponent { var environmentConfig = make([]v1.RadixJobComponentEnvironmentConfig, 0) for _, env := range rcb.environmentConfig { @@ -228,6 +237,7 @@ func (rcb *radixApplicationJobComponentBuilder) BuildJobComponent() v1.RadixJobC Monitoring: rcb.monitoring, ImageTagName: rcb.imageTagName, VolumeMounts: rcb.volumes, + Runtime: rcb.runtime, } } diff --git a/pkg/apis/utils/bools.go b/pkg/apis/utils/bools.go deleted file mode 100644 index f87e42590..000000000 --- a/pkg/apis/utils/bools.go +++ /dev/null @@ -1,6 +0,0 @@ -package utils - -// BoolPtr returns a pointer to the passed bool. -func BoolPtr(value bool) *bool { - return &value -} diff --git a/pkg/apis/utils/componentenvironment_builder.go b/pkg/apis/utils/componentenvironment_builder.go index f021be8e4..09517d36f 100644 --- a/pkg/apis/utils/componentenvironment_builder.go +++ b/pkg/apis/utils/componentenvironment_builder.go @@ -25,6 +25,7 @@ type RadixEnvironmentConfigBuilder interface { WithImageTagName(string) RadixEnvironmentConfigBuilder WithHorizontalScaling(scaling *v1.RadixHorizontalScaling) RadixEnvironmentConfigBuilder WithReadOnlyFileSystem(*bool) RadixEnvironmentConfigBuilder + WithRuntime(*v1.Runtime) RadixEnvironmentConfigBuilder } type radixEnvironmentConfigBuilder struct { @@ -46,6 +47,7 @@ type radixEnvironmentConfigBuilder struct { imageTagName string horizontalScaling *v1.RadixHorizontalScaling readOnlyFileSystem *bool + runtime *v1.Runtime } func (ceb *radixEnvironmentConfigBuilder) WithHorizontalScaling(scaling *v1.RadixHorizontalScaling) RadixEnvironmentConfigBuilder { @@ -139,11 +141,17 @@ func (ceb *radixEnvironmentConfigBuilder) WithImageTagName(imageTagName string) ceb.imageTagName = imageTagName return ceb } + func (ceb *radixEnvironmentConfigBuilder) WithReadOnlyFileSystem(readOnlyFileSystem *bool) RadixEnvironmentConfigBuilder { ceb.readOnlyFileSystem = readOnlyFileSystem return ceb } +func (ceb *radixEnvironmentConfigBuilder) WithRuntime(runtime *v1.Runtime) RadixEnvironmentConfigBuilder { + ceb.runtime = runtime + return ceb +} + func (ceb *radixEnvironmentConfigBuilder) BuildEnvironmentConfig() v1.RadixEnvironmentConfig { return v1.RadixEnvironmentConfig{ Environment: ceb.environment, @@ -164,6 +172,7 @@ func (ceb *radixEnvironmentConfigBuilder) BuildEnvironmentConfig() v1.RadixEnvir ImageTagName: ceb.imageTagName, HorizontalScaling: ceb.horizontalScaling, ReadOnlyFileSystem: ceb.readOnlyFileSystem, + Runtime: ceb.runtime, } } diff --git a/pkg/apis/utils/conditions/bool_utils.go b/pkg/apis/utils/conditions/bool_utils.go deleted file mode 100644 index b5f7ab5fa..000000000 --- a/pkg/apis/utils/conditions/bool_utils.go +++ /dev/null @@ -1,6 +0,0 @@ -package conditions - -// BoolPtr converts a bool to *bool -func BoolPtr(b bool) *bool { - return &b -} diff --git a/pkg/apis/utils/deploymentcomponent_builder.go b/pkg/apis/utils/deploymentcomponent_builder.go index 01f64f357..bd3956713 100644 --- a/pkg/apis/utils/deploymentcomponent_builder.go +++ b/pkg/apis/utils/deploymentcomponent_builder.go @@ -36,6 +36,7 @@ type DeployComponentBuilder interface { WithAuthentication(*v1.Authentication) DeployComponentBuilder WithIdentity(*v1.Identity) DeployComponentBuilder WithReadOnlyFileSystem(*bool) DeployComponentBuilder + WithRuntime(*v1.Runtime) DeployComponentBuilder BuildComponent() v1.RadixDeployComponent } @@ -66,6 +67,7 @@ type deployComponentBuilder struct { authentication *v1.Authentication identity *v1.Identity readOnlyFileSystem *bool + runtime *v1.Runtime } func (dcb *deployComponentBuilder) WithVolumeMounts(volumeMounts ...v1.RadixVolumeMount) DeployComponentBuilder { @@ -224,6 +226,11 @@ func (dcb *deployComponentBuilder) WithReadOnlyFileSystem(readOnlyFileSystem *bo return dcb } +func (dcb *deployComponentBuilder) WithRuntime(runtime *v1.Runtime) DeployComponentBuilder { + dcb.runtime = runtime + return dcb +} + func (dcb *deployComponentBuilder) BuildComponent() v1.RadixDeployComponent { return v1.RadixDeployComponent{ Image: dcb.image, @@ -249,6 +256,7 @@ func (dcb *deployComponentBuilder) BuildComponent() v1.RadixDeployComponent { Authentication: dcb.authentication, Identity: dcb.identity, ReadOnlyFileSystem: dcb.readOnlyFileSystem, + Runtime: dcb.runtime, } } diff --git a/pkg/apis/utils/deploymentjobcomponent_builder.go b/pkg/apis/utils/deploymentjobcomponent_builder.go index 3161625a0..5d651c761 100644 --- a/pkg/apis/utils/deploymentjobcomponent_builder.go +++ b/pkg/apis/utils/deploymentjobcomponent_builder.go @@ -27,6 +27,7 @@ type DeployJobComponentBuilder interface { WithTimeLimitSeconds(*int64) DeployJobComponentBuilder WithIdentity(*v1.Identity) DeployJobComponentBuilder WithNotifications(*v1.Notifications) DeployJobComponentBuilder + WithRuntime(*v1.Runtime) DeployJobComponentBuilder BuildJobComponent() v1.RadixDeployJobComponent } @@ -48,6 +49,7 @@ type deployJobComponentBuilder struct { timeLimitSeconds *int64 identity *v1.Identity notifications *v1.Notifications + runtime *v1.Runtime } func (dcb *deployJobComponentBuilder) WithVolumeMounts(volumeMounts ...v1.RadixVolumeMount) DeployJobComponentBuilder { @@ -170,6 +172,11 @@ func (dcb *deployJobComponentBuilder) WithNotifications(notifications *v1.Notifi return dcb } +func (dcb *deployJobComponentBuilder) WithRuntime(runtime *v1.Runtime) DeployJobComponentBuilder { + dcb.runtime = runtime + return dcb +} + func (dcb *deployJobComponentBuilder) BuildJobComponent() v1.RadixDeployJobComponent { var payload *v1.RadixJobComponentPayload if dcb.payloadPath != nil { @@ -194,6 +201,7 @@ func (dcb *deployJobComponentBuilder) BuildJobComponent() v1.RadixDeployJobCompo TimeLimitSeconds: dcb.timeLimitSeconds, Identity: dcb.identity, Notifications: dcb.notifications, + Runtime: dcb.runtime, } } diff --git a/pkg/apis/utils/git/clone.go b/pkg/apis/utils/git/clone.go index dce18e928..ab0c0d39e 100644 --- a/pkg/apis/utils/git/clone.go +++ b/pkg/apis/utils/git/clone.go @@ -2,8 +2,12 @@ package git import ( "fmt" + "path" + "github.com/equinor/radix-common/utils/pointers" + "github.com/equinor/radix-operator/pkg/apis/securitycontext" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" ) const ( @@ -30,28 +34,39 @@ const ( ) // CloneInitContainers The sidecars for cloning repo -func CloneInitContainers(sshURL, branch string, containerSecContext corev1.SecurityContext) []corev1.Container { - return CloneInitContainersWithContainerName(sshURL, branch, CloneContainerName, containerSecContext) +func CloneInitContainers(sshURL, branch string) []corev1.Container { + return CloneInitContainersWithContainerName(sshURL, branch, CloneContainerName) } // CloneInitContainersWithContainerName The sidecars for cloning repo -func CloneInitContainersWithContainerName(sshURL, branch, cloneContainerName string, containerSecContext corev1.SecurityContext) []corev1.Container { - gitCloneCmd := fmt.Sprintf("git clone --recurse-submodules %s -b %s --verbose --progress %s", sshURL, branch, Workspace) +func CloneInitContainersWithContainerName(sshURL, branch, cloneContainerName string) []corev1.Container { + gitCloneCmd := []string{"git", "clone", "--recurse-submodules", sshURL, "-b", branch, "--verbose", "--progress", Workspace} containers := []corev1.Container{ { Name: fmt.Sprintf("%snslookup", InternalContainerPrefix), Image: "alpine", + ImagePullPolicy: corev1.PullAlways, Args: []string{waitForGithubToRespond}, Command: []string{"/bin/sh", "-c"}, - ImagePullPolicy: "Always", - SecurityContext: &containerSecContext, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: *resource.NewScaledQuantity(10, resource.Milli), + corev1.ResourceMemory: *resource.NewScaledQuantity(1, resource.Mega), + }, + }, + SecurityContext: securitycontext.Container( + securitycontext.WithContainerRunAsUser(1000), // Any user will probably do + securitycontext.WithContainerRunAsGroup(1000), + securitycontext.WithContainerDropAllCapabilities(), + securitycontext.WithContainerSeccompProfileType(corev1.SeccompProfileTypeRuntimeDefault), + securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), + ), }, { Name: cloneContainerName, - Image: "alpine/git:user", - ImagePullPolicy: "IfNotPresent", - Command: []string{"/bin/sh", "-c"}, - Args: []string{gitCloneCmd}, + Image: "alpine/git:2.45.1", + ImagePullPolicy: corev1.PullIfNotPresent, + Command: gitCloneCmd, VolumeMounts: []corev1.VolumeMount{ { Name: BuildContextVolumeName, @@ -59,11 +74,43 @@ func CloneInitContainersWithContainerName(sshURL, branch, cloneContainerName str }, { Name: GitSSHKeyVolumeName, - MountPath: "/home/git-user/.ssh", + MountPath: "/.ssh", ReadOnly: true, }, }, - SecurityContext: &containerSecContext, + SecurityContext: securitycontext.Container( + securitycontext.WithContainerRunAsUser(65534), // Must be this user when running as non root + securitycontext.WithContainerRunAsGroup(1000), + securitycontext.WithContainerDropAllCapabilities(), + securitycontext.WithContainerSeccompProfileType(corev1.SeccompProfileTypeRuntimeDefault), + securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), + ), + }, + { + Name: fmt.Sprintf("%schmod", InternalContainerPrefix), + Image: "bash", + ImagePullPolicy: corev1.PullAlways, + Command: []string{"/usr/local/bin/bash", "-O", "dotglob", "-c"}, + Args: []string{fmt.Sprintf("chmod -R g+rw %s", path.Join(Workspace, "*"))}, + VolumeMounts: []corev1.VolumeMount{ + { + Name: BuildContextVolumeName, + MountPath: Workspace, + }, + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: *resource.NewScaledQuantity(10, resource.Milli), + corev1.ResourceMemory: *resource.NewScaledQuantity(1, resource.Mega), + }, + }, + SecurityContext: securitycontext.Container( + securitycontext.WithContainerRunAsUser(65534), // Must be this user when running as non root + securitycontext.WithContainerRunAsGroup(1000), + securitycontext.WithContainerDropAllCapabilities(), + securitycontext.WithContainerSeccompProfileType(corev1.SeccompProfileTypeRuntimeDefault), + securitycontext.WithReadOnlyRootFileSystem(pointers.Ptr(true)), + ), }, } diff --git a/pkg/apis/utils/jobcomponentenvironment_builder.go b/pkg/apis/utils/jobcomponentenvironment_builder.go index 396d240c2..63ea397c1 100644 --- a/pkg/apis/utils/jobcomponentenvironment_builder.go +++ b/pkg/apis/utils/jobcomponentenvironment_builder.go @@ -24,6 +24,7 @@ type RadixJobComponentEnvironmentConfigBuilder interface { WithIdentity(*v1.Identity) RadixJobComponentEnvironmentConfigBuilder WithNotifications(*v1.Notifications) RadixJobComponentEnvironmentConfigBuilder WithReadOnlyFileSystem(*bool) RadixJobComponentEnvironmentConfigBuilder + WithRuntime(*v1.Runtime) RadixJobComponentEnvironmentConfigBuilder BuildEnvironmentConfig() v1.RadixJobComponentEnvironmentConfig } @@ -46,6 +47,7 @@ type radixJobComponentEnvironmentConfigBuilder struct { identity *v1.Identity notifications *v1.Notifications readOnlyFileSystem *bool + runtime *v1.Runtime } func (ceb *radixJobComponentEnvironmentConfigBuilder) WithTimeLimitSeconds(timeLimitSeconds *int64) RadixJobComponentEnvironmentConfigBuilder { @@ -138,10 +140,17 @@ func (ceb *radixJobComponentEnvironmentConfigBuilder) WithNotifications(notifica ceb.notifications = notifications return ceb } + func (ceb *radixJobComponentEnvironmentConfigBuilder) WithReadOnlyFileSystem(readOnlyFileSystem *bool) RadixJobComponentEnvironmentConfigBuilder { ceb.readOnlyFileSystem = readOnlyFileSystem return ceb } + +func (ceb *radixJobComponentEnvironmentConfigBuilder) WithRuntime(runtime *v1.Runtime) RadixJobComponentEnvironmentConfigBuilder { + ceb.runtime = runtime + return ceb +} + func (ceb *radixJobComponentEnvironmentConfigBuilder) BuildEnvironmentConfig() v1.RadixJobComponentEnvironmentConfig { return v1.RadixJobComponentEnvironmentConfig{ Environment: ceb.environment, @@ -161,6 +170,7 @@ func (ceb *radixJobComponentEnvironmentConfigBuilder) BuildEnvironmentConfig() v Identity: ceb.identity, Notifications: ceb.notifications, ReadOnlyFileSystem: ceb.readOnlyFileSystem, + Runtime: ceb.runtime, } } diff --git a/pkg/apis/utils/runtime.go b/pkg/apis/utils/runtime.go new file mode 100644 index 000000000..0984d0b20 --- /dev/null +++ b/pkg/apis/utils/runtime.go @@ -0,0 +1,15 @@ +package utils + +import ( + "github.com/equinor/radix-operator/pkg/apis/defaults" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" +) + +// GetArchitectureFromRuntime returns architecture from Runtime. +// If Runtime is nil or Runtime.Architecture is empty then defaults.DefaultNodeSelectorArchitecture is returned +func GetArchitectureFromRuntime(runtime *radixv1.Runtime) string { + if runtime != nil && len(runtime.Architecture) > 0 { + return string(runtime.Architecture) + } + return defaults.DefaultNodeSelectorArchitecture +} diff --git a/pkg/apis/utils/runtime_test.go b/pkg/apis/utils/runtime_test.go new file mode 100644 index 000000000..7c60bb185 --- /dev/null +++ b/pkg/apis/utils/runtime_test.go @@ -0,0 +1,16 @@ +package utils_test + +import ( + "testing" + + "github.com/equinor/radix-operator/pkg/apis/defaults" + v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/stretchr/testify/assert" +) + +func Test_GetArchitectureFromRuntime(t *testing.T) { + assert.Equal(t, defaults.DefaultNodeSelectorArchitecture, utils.GetArchitectureFromRuntime(nil), "use default architecture when runtime is nil") + assert.Equal(t, defaults.DefaultNodeSelectorArchitecture, utils.GetArchitectureFromRuntime(&v1.Runtime{Architecture: ""}), "use default architecture when runtime.architecture is empty") + assert.Equal(t, "customarch", utils.GetArchitectureFromRuntime(&v1.Runtime{Architecture: "customarch"}), "use when runtime.architecture set") +}