diff --git a/charts/radix-operator/Chart.yaml b/charts/radix-operator/Chart.yaml index 0edb0ab5d..59bd2aa86 100644 --- a/charts/radix-operator/Chart.yaml +++ b/charts/radix-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: radix-operator -version: 1.25.3 -appVersion: 1.45.3 +version: 1.25.4 +appVersion: 1.45.4 kubeVersion: ">=1.24.0" description: Radix Operator keywords: diff --git a/pkg/apis/deployment/kubedeployment.go b/pkg/apis/deployment/kubedeployment.go index c5f86d124..02b9db1b1 100644 --- a/pkg/apis/deployment/kubedeployment.go +++ b/pkg/apis/deployment/kubedeployment.go @@ -99,7 +99,7 @@ func (deploy *Deployment) getDesiredCreatedDeploymentConfig(deployComponent v1.R desiredDeployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{Labels: make(map[string]string), Annotations: make(map[string]string)}, Spec: appsv1.DeploymentSpec{ - Replicas: int32Ptr(DefaultReplicas), + Replicas: pointers.Ptr[int32](DefaultReplicas), Selector: &metav1.LabelSelector{MatchLabels: make(map[string]string)}, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Labels: make(map[string]string), Annotations: make(map[string]string)}, @@ -122,7 +122,7 @@ func (deploy *Deployment) createJobAuxDeployment(deployComponent v1.RadixCommonD Annotations: make(map[string]string), }, Spec: appsv1.DeploymentSpec{ - Replicas: int32Ptr(1), + Replicas: pointers.Ptr[int32](1), Selector: &metav1.LabelSelector{MatchLabels: radixlabels.ForJobAuxObject(jobName)}, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Labels: make(map[string]string), Annotations: make(map[string]string)}, @@ -474,7 +474,3 @@ func getContainerPorts(deployComponent v1.RadixCommonDeployComponent) []corev1.C } return ports } - -func int32Ptr(i int32) *int32 { - return &i -} diff --git a/pkg/apis/deployment/oauthproxyresourcemanager.go b/pkg/apis/deployment/oauthproxyresourcemanager.go index a4a227b5b..2fbb6da6f 100644 --- a/pkg/apis/deployment/oauthproxyresourcemanager.go +++ b/pkg/apis/deployment/oauthproxyresourcemanager.go @@ -8,6 +8,7 @@ import ( commonutils "github.com/equinor/radix-common/utils" radixmaps "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" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" @@ -669,6 +670,11 @@ func (o *oauthProxyResourceManager) getDesiredDeployment(component v1.RadixCommo return nil, err } + var replicas int32 = 1 + if isComponentStopped(component) { + replicas = 0 + } + // Spec.Strategy defaults to RollingUpdate, ref https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy desiredDeployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ @@ -677,7 +683,7 @@ func (o *oauthProxyResourceManager) getDesiredDeployment(component v1.RadixCommo OwnerReferences: []metav1.OwnerReference{getOwnerReferenceOfDeployment(o.rd)}, }, Spec: appsv1.DeploymentSpec{ - Replicas: int32Ptr(DefaultReplicas), + Replicas: pointers.Ptr(replicas), Selector: &metav1.LabelSelector{ MatchLabels: o.getLabelsForAuxComponent(component), }, @@ -728,7 +734,12 @@ func (o *oauthProxyResourceManager) getEnvVars(component v1.RadixCommonDeployCom } } - // Add fixed envvars + // Radix envvars + if v, ok := component.GetEnvironmentVariables()[defaults.RadixRestartEnvironmentVariable]; ok { + envVars = append(envVars, corev1.EnvVar{Name: defaults.RadixRestartEnvironmentVariable, Value: v}) + } + + // oauth2-proxy envvars envVars = append(envVars, corev1.EnvVar{Name: "OAUTH2_PROXY_PROVIDER", Value: "oidc"}) envVars = append(envVars, corev1.EnvVar{Name: "OAUTH2_PROXY_COOKIE_HTTPONLY", Value: "true"}) envVars = append(envVars, corev1.EnvVar{Name: "OAUTH2_PROXY_COOKIE_SECURE", Value: "true"}) diff --git a/pkg/apis/deployment/oauthproxyresourcemanager_test.go b/pkg/apis/deployment/oauthproxyresourcemanager_test.go index f6a3c3962..67dab84dd 100644 --- a/pkg/apis/deployment/oauthproxyresourcemanager_test.go +++ b/pkg/apis/deployment/oauthproxyresourcemanager_test.go @@ -3,11 +3,12 @@ package deployment import ( "context" "fmt" - "os" "reflect" "testing" commonUtils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/pointers" + "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/defaults/k8s" "github.com/equinor/radix-operator/pkg/apis/kube" @@ -47,32 +48,18 @@ func TestOAuthProxyResourceManagerTestSuite(t *testing.T) { suite.Run(t, new(OAuthProxyResourceManagerTestSuite)) } -func (*OAuthProxyResourceManagerTestSuite) SetupSuite() { - os.Setenv(defaults.OperatorDefaultUserGroupEnvironmentVariable, "1234-5678-91011") - os.Setenv(defaults.OperatorDNSZoneEnvironmentVariable, dnsZone) - os.Setenv(defaults.OperatorAppAliasBaseURLEnvironmentVariable, "app.dev.radix.equinor.com") - os.Setenv(defaults.OperatorEnvLimitDefaultMemoryEnvironmentVariable, "300M") - os.Setenv(defaults.OperatorRollingUpdateMaxUnavailable, "25%") - os.Setenv(defaults.OperatorRollingUpdateMaxSurge, "25%") - os.Setenv(defaults.OperatorReadinessProbeInitialDelaySeconds, "5") - os.Setenv(defaults.OperatorReadinessProbePeriodSeconds, "10") - os.Setenv(defaults.OperatorRadixJobSchedulerEnvironmentVariable, "radix-job-scheduler:main-latest") - os.Setenv(defaults.OperatorClusterTypeEnvironmentVariable, "development") - os.Setenv(defaults.RadixOAuthProxyDefaultOIDCIssuerURLEnvironmentVariable, "oidc_issuer_url") -} - -func (*OAuthProxyResourceManagerTestSuite) TearDownSuite() { - os.Unsetenv(defaults.OperatorDefaultUserGroupEnvironmentVariable) - os.Unsetenv(defaults.OperatorDNSZoneEnvironmentVariable) - os.Unsetenv(defaults.OperatorAppAliasBaseURLEnvironmentVariable) - os.Unsetenv(defaults.OperatorEnvLimitDefaultMemoryEnvironmentVariable) - os.Unsetenv(defaults.OperatorRollingUpdateMaxUnavailable) - os.Unsetenv(defaults.OperatorRollingUpdateMaxSurge) - os.Unsetenv(defaults.OperatorReadinessProbeInitialDelaySeconds) - os.Unsetenv(defaults.OperatorReadinessProbePeriodSeconds) - os.Unsetenv(defaults.OperatorRadixJobSchedulerEnvironmentVariable) - os.Unsetenv(defaults.OperatorClusterTypeEnvironmentVariable) - os.Unsetenv(defaults.RadixOAuthProxyDefaultOIDCIssuerURLEnvironmentVariable) +func (s *OAuthProxyResourceManagerTestSuite) SetupSuite() { + s.T().Setenv(defaults.OperatorDefaultUserGroupEnvironmentVariable, "1234-5678-91011") + s.T().Setenv(defaults.OperatorDNSZoneEnvironmentVariable, dnsZone) + s.T().Setenv(defaults.OperatorAppAliasBaseURLEnvironmentVariable, "app.dev.radix.equinor.com") + s.T().Setenv(defaults.OperatorEnvLimitDefaultMemoryEnvironmentVariable, "300M") + s.T().Setenv(defaults.OperatorRollingUpdateMaxUnavailable, "25%") + s.T().Setenv(defaults.OperatorRollingUpdateMaxSurge, "25%") + s.T().Setenv(defaults.OperatorReadinessProbeInitialDelaySeconds, "5") + s.T().Setenv(defaults.OperatorReadinessProbePeriodSeconds, "10") + s.T().Setenv(defaults.OperatorRadixJobSchedulerEnvironmentVariable, "radix-job-scheduler:main-latest") + s.T().Setenv(defaults.OperatorClusterTypeEnvironmentVariable, "development") + s.T().Setenv(defaults.RadixOAuthProxyDefaultOIDCIssuerURLEnvironmentVariable, "oidc_issuer_url") } func (s *OAuthProxyResourceManagerTestSuite) SetupTest() { @@ -108,6 +95,47 @@ func (s *OAuthProxyResourceManagerTestSuite) TestNewOAuthProxyResourceManager() s.Equal("proxy:123", sut.oauth2ProxyDockerImage) } +func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_ComponentRestartEnvVar() { + auth := &v1.Authentication{OAuth2: &v1.OAuth2{ClientID: "1234"}} + appName := "anyapp" + rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() + baseComp := func() utils.DeployComponentBuilder { + return utils.NewDeployComponentBuilder().WithName("comp").WithPublicPort("http").WithAuthentication(auth) + } + type testSpec struct { + name string + rd *v1.RadixDeployment + expectRestartEnvVar bool + } + tests := []testSpec{ + { + name: "component default config", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp().WithEnvironmentVariable(defaults.RadixRestartEnvironmentVariable, "anyval")). + BuildRD(), + expectRestartEnvVar: true, + }, + { + name: "component replicas set to 1", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp().WithReplicas(pointers.Ptr(1))). + BuildRD(), + expectRestartEnvVar: false, + }, + } + s.oauth2Config.EXPECT().MergeWith(gomock.Any()).AnyTimes().Return(&v1.OAuth2{}, nil) + for _, test := range tests { + s.Run(test.name, func() { + sut := &oauthProxyResourceManager{test.rd, rr, s.kubeUtil, []IngressAnnotationProvider{}, s.oauth2Config, ""} + err := sut.Sync() + s.Nil(err) + deploys, _ := s.kubeClient.AppsV1().Deployments(corev1.NamespaceAll).List(context.Background(), metav1.ListOptions{LabelSelector: s.getAppNameSelector(appName)}) + envVarExist := slice.Any(deploys.Items[0].Spec.Template.Spec.Containers[0].Env, func(ev corev1.EnvVar) bool { return ev.Name == defaults.RadixRestartEnvironmentVariable }) + s.Equal(test.expectRestartEnvVar, envVarExist) + }) + } +} + func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_NotPublicOrNoOAuth() { type scenarioDef struct{ rd *v1.RadixDeployment } scenarios := []scenarioDef{ @@ -136,6 +164,89 @@ func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_NotPublicOrNoOAuth() { } } +func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OauthDeploymentReplicas() { + auth := &v1.Authentication{OAuth2: &v1.OAuth2{ClientID: "1234"}} + appName := "anyapp" + rr := utils.NewRegistrationBuilder().WithName(appName).BuildRR() + baseComp := func() utils.DeployComponentBuilder { + return utils.NewDeployComponentBuilder().WithName("comp").WithPublicPort("http").WithAuthentication(auth) + } + type testSpec struct { + name string + rd *v1.RadixDeployment + expectedReplicas int32 + } + tests := []testSpec{ + { + name: "component default config", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp()). + BuildRD(), + expectedReplicas: 1, + }, + { + name: "component replicas set to 1", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp().WithReplicas(pointers.Ptr(1))). + BuildRD(), + expectedReplicas: 1, + }, + { + name: "component replicas set to 2", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp().WithReplicas(pointers.Ptr(2))). + BuildRD(), + expectedReplicas: 1, + }, + { + name: "component replicas set to 0", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp().WithReplicas(pointers.Ptr(0))). + BuildRD(), + expectedReplicas: 0, + }, + { + name: "component with hpa and default config", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp().WithHorizontalScaling(pointers.Ptr[int32](3), 4, pointers.Ptr[int32](1), pointers.Ptr[int32](1))). + BuildRD(), + expectedReplicas: 1, + }, + { + name: "component with hpa and replicas set to 1", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp().WithReplicas(pointers.Ptr(1)).WithHorizontalScaling(pointers.Ptr[int32](3), 4, pointers.Ptr[int32](1), pointers.Ptr[int32](1))). + BuildRD(), + expectedReplicas: 1, + }, + { + name: "component with hpa and replicas set to 2", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp().WithReplicas(pointers.Ptr(2)).WithHorizontalScaling(pointers.Ptr[int32](3), 4, pointers.Ptr[int32](1), pointers.Ptr[int32](1))). + BuildRD(), + expectedReplicas: 1, + }, + { + + name: "component with hpa and replicas set to 0", + rd: utils.NewDeploymentBuilder().WithAppName(appName).WithEnvironment("qa"). + WithComponent(baseComp().WithReplicas(pointers.Ptr(0)).WithHorizontalScaling(pointers.Ptr[int32](3), 4, pointers.Ptr[int32](1), pointers.Ptr[int32](1))). + BuildRD(), + expectedReplicas: 0, + }, + } + s.oauth2Config.EXPECT().MergeWith(gomock.Any()).AnyTimes().Return(&v1.OAuth2{}, nil) + for _, test := range tests { + s.Run(test.name, func() { + sut := &oauthProxyResourceManager{test.rd, rr, s.kubeUtil, []IngressAnnotationProvider{}, s.oauth2Config, ""} + err := sut.Sync() + s.Nil(err) + deploys, _ := s.kubeClient.AppsV1().Deployments(corev1.NamespaceAll).List(context.Background(), metav1.ListOptions{LabelSelector: s.getAppNameSelector(appName)}) + s.Equal(test.expectedReplicas, *deploys.Items[0].Spec.Replicas) + }) + } +} + func (s *OAuthProxyResourceManagerTestSuite) Test_Sync_OAuthProxyDeploymentCreated() { appName, envName, componentName := "anyapp", "qa", "server" envNs := utils.GetEnvironmentNamespace(appName, envName) diff --git a/pkg/apis/deployment/pdb.go b/pkg/apis/deployment/pdb.go index f5f12639f..ae888c515 100644 --- a/pkg/apis/deployment/pdb.go +++ b/pkg/apis/deployment/pdb.go @@ -3,6 +3,7 @@ package deployment import ( "context" "fmt" + "github.com/equinor/radix-common/utils/errors" "github.com/equinor/radix-operator/pkg/apis/kube" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" diff --git a/pkg/apis/deployment/radixjobcomponent_test.go b/pkg/apis/deployment/radixjobcomponent_test.go index 66cf2182e..2624fa544 100644 --- a/pkg/apis/deployment/radixjobcomponent_test.go +++ b/pkg/apis/deployment/radixjobcomponent_test.go @@ -22,7 +22,7 @@ func Test_GetRadixJobComponents_BuildAllJobComponents(t *testing.T) { WithJobComponents( utils.AnApplicationJobComponent(). WithName("job1"). - WithSchedulerPort(int32Ptr(8888)). + WithSchedulerPort(pointers.Ptr[int32](8888)). WithPayloadPath(utils.StringPtr("/path/to/payload")), utils.AnApplicationJobComponent(). WithName("job2"), @@ -37,7 +37,7 @@ func Test_GetRadixJobComponents_BuildAllJobComponents(t *testing.T) { require.NoError(t, err) assert.Len(t, jobs, 2) assert.Equal(t, "job1", jobs[0].Name) - assert.Equal(t, int32Ptr(8888), jobs[0].SchedulerPort) + assert.Equal(t, pointers.Ptr[int32](8888), jobs[0].SchedulerPort) assert.Equal(t, "/path/to/payload", jobs[0].Payload.Path) assert.Equal(t, "job2", jobs[1].Name) assert.Nil(t, jobs[1].SchedulerPort) @@ -51,7 +51,7 @@ func Test_GetRadixJobComponentsWithNode_BuildAllJobComponents(t *testing.T) { WithJobComponents( utils.AnApplicationJobComponent(). WithName("job1"). - WithSchedulerPort(int32Ptr(8888)). + WithSchedulerPort(pointers.Ptr[int32](8888)). WithPayloadPath(utils.StringPtr("/path/to/payload")). WithNode(v1.RadixNode{Gpu: gpu, GpuCount: gpuCount}), utils.AnApplicationJobComponent(). diff --git a/pkg/apis/deployment/volumemount_test.go b/pkg/apis/deployment/volumemount_test.go index b0b46b6b1..4f6671023 100644 --- a/pkg/apis/deployment/volumemount_test.go +++ b/pkg/apis/deployment/volumemount_test.go @@ -1526,7 +1526,7 @@ func getDesiredDeployment(componentName string, volumes []corev1.Volume) *appsv1 Annotations: make(map[string]string), }, Spec: appsv1.DeploymentSpec{ - Replicas: int32Ptr(DefaultReplicas), + Replicas: pointers.Ptr[int32](DefaultReplicas), Selector: &metav1.LabelSelector{MatchLabels: make(map[string]string)}, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Labels: make(map[string]string), Annotations: make(map[string]string)},