Skip to content

Commit

Permalink
Add component level radix config properties (#1081)
Browse files Browse the repository at this point in the history
* Added properties to radixapplication

* Configured properties to radix components

* Fixed issues

* Updated files

* Extended RA validation

* Corrected volume mount and hpa logic

* Corrected volume mount and hpa logic

* Corrected volume mount and hpa logic

* Set version

* Added ImageTagName to job-component

* Added unit-tests

* Added unit-tests

* Added unit-tests

* Added unit-tests

* Added unit-tests

* Added unit-tests

* Added unit-tests

* Added unit-tests

* Extended unit-tests

* Extended unit-tests
  • Loading branch information
satr authored Apr 11, 2024
1 parent 10a2f8b commit efb9e6e
Show file tree
Hide file tree
Showing 22 changed files with 2,573 additions and 230 deletions.
4 changes: 2 additions & 2 deletions charts/radix-operator/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: v2
name: radix-operator
version: 1.30.9
appVersion: 1.50.9
version: 1.31.0
appVersion: 1.51.0
kubeVersion: ">=1.24.0"
description: Radix Operator
keywords:
Expand Down
560 changes: 560 additions & 0 deletions charts/radix-operator/templates/radixapplication.yaml

Large diffs are not rendered by default.

579 changes: 579 additions & 0 deletions json-schema/radixapplication.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions pipeline-runner/steps/apply_radixconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,8 +584,9 @@ func validateDeployComponentImages(deployComponentImages pipeline.DeployEnvironm
continue
}

env := ra.GetCommonComponentByName(componentName).GetEnvironmentConfigByName(envName)
if !utils.IsNil(env) && len(env.GetImageTagName()) > 0 {
component := ra.GetCommonComponentByName(componentName)
env := component.GetEnvironmentConfigByName(envName)
if len(component.GetImageTagName()) > 0 || (!utils.IsNil(env) && len(env.GetImageTagName()) > 0) {
continue
}

Expand Down
72 changes: 48 additions & 24 deletions pipeline-runner/steps/apply_radixconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,34 +102,58 @@ func (s *applyConfigTestSuite) Test_Deploy_BuildJobInDeployPiplineShouldFail() {
s.ErrorIs(err, steps.ErrDeployOnlyPipelineDoesNotSupportBuild)
}

func (s *applyConfigTestSuite) Test_Deploy_ComponentWithMissingImageTagNameShouldFail() {
func (s *applyConfigTestSuite) Test_Deploy_ComponentImageTagName() {
appName := "anyapp"
prepareConfigMapName := "preparecm"
rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR()
_, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{})
ra := utils.NewRadixApplicationBuilder().
WithAppName(appName).
WithEnvironment("dev", "anybranch").
WithComponents(
utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("deploycomp").WithImage("any:{imageTagName}"),
).
BuildRA()
s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil))

pipeline := model.PipelineInfo{
PipelineArguments: model.PipelineArguments{
PipelineType: string(radixv1.Deploy),
ToEnvironment: "dev",
},
RadixConfigMapName: prepareConfigMapName,
type scenario struct {
name string
componentTagName string
hasEnvironmentConfig bool
environmentTagName string
expectedError error
}
scenarios := []scenario{
{name: "no imageTagName in a component or an environment", expectedError: steps.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 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"},
}
for _, ts := range scenarios {
s.SetupTest()
s.T().Run(ts.name, func(t *testing.T) {
rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR()
_, _ = s.radixClient.RadixV1().RadixRegistrations().Create(context.Background(), rr, metav1.CreateOptions{})

applyStep := steps.NewApplyConfigStep()
applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr)
err := applyStep.Run(&pipeline)
s.ErrorIs(err, steps.ErrMissingRequiredImageTagName)
s.ErrorContains(err, "deploycomp")
s.ErrorContains(err, "dev")
componentBuilder := utils.NewApplicationComponentBuilder().WithPort("any", 8080).WithName("deploycomp").WithImage("any:{imageTagName}").WithImageTagName(ts.componentTagName)
if ts.hasEnvironmentConfig || ts.environmentTagName != "" {
componentBuilder = componentBuilder.WithEnvironmentConfig(utils.AnEnvironmentConfig().WithEnvironment("dev").WithImageTagName(ts.environmentTagName))
}
ra := utils.NewRadixApplicationBuilder().
WithAppName(appName).
WithEnvironment("dev", "anybranch").
WithComponents(componentBuilder).BuildRA()
s.Require().NoError(internaltest.CreatePreparePipelineConfigMapResponse(s.kubeClient, prepareConfigMapName, appName, ra, nil))

pipeline := model.PipelineInfo{
PipelineArguments: model.PipelineArguments{
PipelineType: string(radixv1.Deploy),
ToEnvironment: "dev",
},
RadixConfigMapName: prepareConfigMapName,
}

applyStep := steps.NewApplyConfigStep()
applyStep.Init(s.kubeClient, s.radixClient, s.kubeUtil, s.promClient, rr)
err := applyStep.Run(&pipeline)
if ts.expectedError == nil {
s.NoError(err)
} else {
s.ErrorIs(err, steps.ErrMissingRequiredImageTagName)
}
})
}
}

func (s *applyConfigTestSuite) Test_Deploy_ComponentWithImageTagNameInRAShouldSucceed() {
Expand Down
2 changes: 1 addition & 1 deletion pipeline-runner/steps/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import (

var (
ErrDeployOnlyPipelineDoesNotSupportBuild = errors.New("deploy pipeline does not support building components and jobs")
ErrMissingRequiredImageTagName = errors.New("missing required imageTagName in environmentConfig or pipeline argument")
ErrMissingRequiredImageTagName = errors.New("missing required imageTagName in a component, an environmentConfig or in a pipeline argument")
)
15 changes: 9 additions & 6 deletions pkg/apis/deployment/radixcommoncomponent.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ func getRadixCommonComponentNode(radixComponent v1.RadixCommonComponent, environ
return node
}

func getImagePath(componentName string, componentImage pipeline.DeployComponentImage, environmentSpecificConfig v1.RadixCommonEnvironmentConfig) (string, error) {
func getImagePath(componentName string, componentImage pipeline.DeployComponentImage, componentImageTagName string, environmentSpecificConfig v1.RadixCommonEnvironmentConfig) (string, error) {
image := componentImage.ImagePath
if componentImage.Build {
return image, nil
}

imageTagName := getImageTagName(componentImage, environmentSpecificConfig)
imageTagName := getImageTagName(componentImage, componentImageTagName, environmentSpecificConfig)
if strings.HasSuffix(image, v1.DynamicTagNameInEnvironmentConfig) {
if len(imageTagName) == 0 {
return "", errorMissingExpectedDynamicImageTagName(componentName)
Expand All @@ -99,14 +99,17 @@ func errorMissingExpectedDynamicImageTagName(componentName string) error {
return fmt.Errorf(fmt.Sprintf("component %s is missing an expected dynamic imageTagName for its image", componentName))
}

func getImageTagName(componentImage pipeline.DeployComponentImage, environmentSpecificConfig v1.RadixCommonEnvironmentConfig) string {
func getImageTagName(componentImage pipeline.DeployComponentImage, componentImageTagName string, environmentSpecificConfig v1.RadixCommonEnvironmentConfig) string {
if componentImage.ImageTagName != "" {
return componentImage.ImageTagName // provided via radix-api build request
}
if !commonUtils.IsNil(environmentSpecificConfig) {
return environmentSpecificConfig.GetImageTagName()
if commonUtils.IsNil(environmentSpecificConfig) {
return componentImageTagName
}
if environmentImageTagName := environmentSpecificConfig.GetImageTagName(); environmentImageTagName != "" {
return environmentImageTagName
}
return ""
return componentImageTagName
}

func getRadixCommonComponentRadixSecretRefs(component v1.RadixCommonComponent, environmentSpecificConfig v1.RadixCommonEnvironmentConfig) v1.RadixSecretRefs {
Expand Down
88 changes: 80 additions & 8 deletions pkg/apis/deployment/radixcomponent.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package deployment

import (
"errors"
"fmt"

"dario.cat/mergo"
commonutils "github.com/equinor/radix-common/utils"
mergoutils "github.com/equinor/radix-common/utils/mergo"
Expand All @@ -10,7 +13,7 @@ import (
)

var (
authTransformer mergo.Transformers = mergoutils.CombinedTransformer{Transformers: []mergo.Transformers{mergoutils.BoolPtrTransformer{}}}
booleanPointerTransformer mergo.Transformers = mergoutils.CombinedTransformer{Transformers: []mergo.Transformers{mergoutils.BoolPtrTransformer{}}}
)

func GetRadixComponentsForEnv(radixApplication *radixv1.RadixApplication, env string, componentImages pipeline.DeployComponentImages, defaultEnvVars radixv1.EnvVarsMap, preservingDeployComponents []radixv1.RadixDeployComponent) ([]radixv1.RadixDeployComponent, error) {
Expand Down Expand Up @@ -38,13 +41,9 @@ func GetRadixComponentsForEnv(radixApplication *radixv1.RadixApplication, env st
MonitoringConfig: radixComponent.MonitoringConfig,
Secrets: radixComponent.Secrets,
DNSAppAlias: IsDNSAppAlias(env, componentName, dnsAppAlias),
Monitoring: false,
}
if environmentSpecificConfig != nil {
deployComponent.Replicas = environmentSpecificConfig.Replicas
deployComponent.Monitoring = environmentSpecificConfig.Monitoring
deployComponent.HorizontalScaling = environmentSpecificConfig.HorizontalScaling
deployComponent.VolumeMounts = environmentSpecificConfig.VolumeMounts
}

auth, err := getRadixComponentAuthentication(&radixComponent, environmentSpecificConfig)
Expand All @@ -58,7 +57,7 @@ func GetRadixComponentsForEnv(radixApplication *radixv1.RadixApplication, env st
}

componentImage := componentImages[componentName]
deployComponent.Image, err = getImagePath(componentName, componentImage, environmentSpecificConfig)
deployComponent.Image, err = getImagePath(componentName, componentImage, radixComponent.ImageTagName, environmentSpecificConfig)
if err != nil {
return nil, err
}
Expand All @@ -72,7 +71,13 @@ func GetRadixComponentsForEnv(radixApplication *radixv1.RadixApplication, env st
deployComponent.Authentication = auth
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
}
if deployComponent.VolumeMounts, err = getRadixCommonComponentVolumeMounts(&radixComponent, environmentSpecificConfig); err != nil {
return nil, err
}
deployComponents = append(deployComponents, deployComponent)
}

Expand All @@ -86,6 +91,73 @@ func getRadixCommonComponentReadOnlyFileSystem(radixComponent radixv1.RadixCommo
return radixComponent.GetReadOnlyFileSystem()
}

func getRadixCommonComponentMonitoring(radixComponent radixv1.RadixCommonComponent, environmentSpecificConfig radixv1.RadixCommonEnvironmentConfig) bool {
if !commonutils.IsNil(environmentSpecificConfig) && environmentSpecificConfig.GetMonitoring() != nil {
return *environmentSpecificConfig.GetMonitoring()
}
monitoring := radixComponent.GetMonitoring()
return !commonutils.IsNil(monitoring) && *monitoring
}

func getRadixCommonComponentHorizontalScaling(radixComponent radixv1.RadixCommonComponent, environmentSpecificConfig radixv1.RadixCommonEnvironmentConfig) (*radixv1.RadixHorizontalScaling, error) {
if commonutils.IsNil(environmentSpecificConfig) || environmentSpecificConfig.GetHorizontalScaling() == nil {
return radixComponent.GetHorizontalScaling(), nil
}
environmentHorizontalScaling := environmentSpecificConfig.GetHorizontalScaling()
if radixComponent.GetHorizontalScaling() == nil {
return environmentHorizontalScaling, nil
}
finalHorizontalScaling := radixComponent.GetHorizontalScaling()
if environmentHorizontalScaling.MinReplicas != nil {
finalHorizontalScaling.MinReplicas = environmentHorizontalScaling.MinReplicas
}
if environmentHorizontalScaling.MaxReplicas > 0 && (finalHorizontalScaling.MinReplicas == nil || *finalHorizontalScaling.MinReplicas < environmentHorizontalScaling.MaxReplicas) {
finalHorizontalScaling.MaxReplicas = environmentHorizontalScaling.MaxReplicas
}

if err := mergo.Merge(finalHorizontalScaling.RadixHorizontalScalingResources, environmentHorizontalScaling.RadixHorizontalScalingResources, mergo.WithOverride); err != nil {
return nil, err
}
return finalHorizontalScaling, nil
}

func getRadixCommonComponentVolumeMounts(radixComponent radixv1.RadixCommonComponent, environmentSpecificConfig radixv1.RadixCommonEnvironmentConfig) ([]radixv1.RadixVolumeMount, error) {
componentVolumeMounts := radixComponent.GetVolumeMounts()
if commonutils.IsNil(environmentSpecificConfig) || environmentSpecificConfig.GetVolumeMounts() == nil {
return componentVolumeMounts, nil
}
environmentVolumeMounts := environmentSpecificConfig.GetVolumeMounts()
if componentVolumeMounts == nil {
return environmentVolumeMounts, nil
}
environmentVolumeMountMap := slice.Reduce(environmentVolumeMounts, make(map[string]radixv1.RadixVolumeMount), func(acc map[string]radixv1.RadixVolumeMount, volumeMount radixv1.RadixVolumeMount) map[string]radixv1.RadixVolumeMount {
environmentVolumeMount := volumeMount
acc[volumeMount.Name] = environmentVolumeMount
return acc
})
var errs []error
var finalVolumeMounts []radixv1.RadixVolumeMount
for _, componentVolumeMount := range componentVolumeMounts {
finalVolumeMount := componentVolumeMount
volumeMountName := componentVolumeMount.Name
if envVolumeMount, ok := environmentVolumeMountMap[volumeMountName]; ok {
if err := mergo.Merge(&finalVolumeMount, envVolumeMount, mergo.WithOverride, mergo.WithTransformers(booleanPointerTransformer)); err != nil {
errs = append(errs, fmt.Errorf("failed to merge component and environment volume-mounts %s: %w", volumeMountName, err))
}
delete(environmentVolumeMountMap, volumeMountName)
}
finalVolumeMounts = append(finalVolumeMounts, finalVolumeMount)
}
for _, environmentVolumeMount := range environmentVolumeMountMap {
volumeMount := environmentVolumeMount
finalVolumeMounts = append(finalVolumeMounts, volumeMount)
}
if len(errs) > 0 {
return nil, errors.Join(errs...)
}
return finalVolumeMounts, nil
}

func getRadixComponentAlwaysPullImageOnDeployFlag(radixComponent *radixv1.RadixComponent, environmentSpecificConfig *radixv1.RadixEnvironmentConfig) bool {
if environmentSpecificConfig != nil {
return coalesceBool(environmentSpecificConfig.AlwaysPullImageOnDeploy, radixComponent.AlwaysPullImageOnDeploy, false)
Expand Down Expand Up @@ -127,7 +199,7 @@ func GetAuthenticationForComponent(componentAuthentication *radixv1.Authenticati
authEnv = environmentAuthentication.DeepCopy()
}

if err := mergo.Merge(authBase, authEnv, mergo.WithOverride, mergo.WithTransformers(authTransformer)); err != nil {
if err := mergo.Merge(authBase, authEnv, mergo.WithOverride, mergo.WithTransformers(booleanPointerTransformer)); err != nil {
return nil, err
}
return authBase, nil
Expand Down
Loading

0 comments on commit efb9e6e

Please sign in to comment.