Skip to content

Commit

Permalink
[v0.36.x] codegen: support pod security context and deployment strate…
Browse files Browse the repository at this point in the history
…gy (#553)

* codegen: allow setting Strategy and PodSecurityContext (#538)

* codegen: support for conditional deployment strategy (#549)

* codegen: fix to prevent null strategy (#550)
  • Loading branch information
Samu committed Apr 30, 2024
1 parent aaa4fae commit 063b531
Show file tree
Hide file tree
Showing 31 changed files with 1,247 additions and 5 deletions.
7 changes: 7 additions & 0 deletions changelog/v0.36.6/codegen-pod-security-context.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
changelog:
- type: NEW_FEATURE
issueLink: https://github.com/solo-io/gloo-mesh-enterprise/issues/15710
resolvesIssue: false
description: |
Add support for configuring custom strategy and pod-level security context for operator deployments.
skipCI: false
296 changes: 296 additions & 0 deletions codegen/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
goyaml "gopkg.in/yaml.v3"
rbacv1 "k8s.io/api/rbac/v1"
v12 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/utils/pointer"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -1838,6 +1839,301 @@ roleRef:
[]v1.EnvVar{{Name: "FOO", ValueFrom: &v1.EnvVarSource{SecretKeyRef: &v1.SecretKeySelector{LocalObjectReference: v1.LocalObjectReference{Name: "bar"}, Key: "baz"}}}}),
)

DescribeTable("rendering deployment strategy",
func(deploymentStrategy *appsv1.DeploymentStrategy) {
cmd := &Command{
Chart: &Chart{
Operators: []Operator{
{
Name: "painter",
Deployment: Deployment{
Strategy: deploymentStrategy,
Container: Container{
Image: Image{
Tag: "v0.0.0",
Repository: "painter",
Registry: "quay.io/solo-io",
PullPolicy: "IfNotPresent",
},
},
},
},
},

Values: nil,
Data: Data{
ApiVersion: "v1",
Description: "",
Name: "Painting Operator",
Version: "v0.0.1",
Home: "https://docs.solo.io/skv2/latest",
Sources: []string{
"https://github.com/solo-io/skv2",
},
},
},

ManifestRoot: "codegen/test/chart-deployment-strategy",
}

err := cmd.Execute()
Expect(err).NotTo(HaveOccurred())

values := map[string]interface{}{"enabled": true}
helmValues := map[string]interface{}{"painter": values}

renderedManifests := helmTemplate("codegen/test/chart-deployment-strategy", helmValues)

var renderedDeployment *appsv1.Deployment
decoder := kubeyaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(renderedManifests), 4096)
for {
obj := &unstructured.Unstructured{}
err := decoder.Decode(obj)
if err != nil {
break
}
if obj.GetName() != "painter" || obj.GetKind() != "Deployment" {
continue
}

bytes, err := obj.MarshalJSON()
Expect(err).NotTo(HaveOccurred())
renderedDeployment = &appsv1.Deployment{}
err = json.Unmarshal(bytes, renderedDeployment)
Expect(err).NotTo(HaveOccurred())
}
Expect(renderedDeployment).NotTo(BeNil())
renderedDeploymentStrategy := renderedDeployment.Spec.Strategy
if deploymentStrategy == nil {
Expect(renderedDeploymentStrategy).To(Equal(appsv1.DeploymentStrategy{}))
} else {
Expect(renderedDeploymentStrategy).To(Equal(*deploymentStrategy))
}
},
Entry("when the deployment strategy is not defined",
nil),
Entry("when the deployment strategy is configured to recreate",
&appsv1.DeploymentStrategy{
Type: appsv1.RecreateDeploymentStrategyType,
}),
Entry("when the deployment strategy is configured to rolling update",
&appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
RollingUpdate: &appsv1.RollingUpdateDeployment{
MaxUnavailable: &intstr.IntOrString{
IntVal: 1,
},
},
}),
)

DescribeTable("rendering conditional deployment strategy",
func(values map[string]any, conditionalStrategy []model.ConditionalStrategy, expectedStrategy appsv1.DeploymentStrategy) {
cmd := &Command{
Chart: &Chart{
Operators: []Operator{
{
Name: "painter",
Deployment: Deployment{
ConditionalStrategy: conditionalStrategy,
Container: Container{
Image: Image{
Tag: "v0.0.0",
Repository: "painter",
Registry: "quay.io/solo-io",
PullPolicy: "IfNotPresent",
},
},
},
},
},

Values: nil,
Data: Data{
ApiVersion: "v1",
Description: "",
Name: "Painting Operator",
Version: "v0.0.1",
Home: "https://docs.solo.io/skv2/latest",
Sources: []string{
"https://github.com/solo-io/skv2",
},
},
},

ManifestRoot: "codegen/test/chart-conditional-deployment-strategy",
}

err := cmd.Execute()
Expect(err).NotTo(HaveOccurred())

helmValues := map[string]interface{}{"painter": values}
renderedManifests := helmTemplate("codegen/test/chart-conditional-deployment-strategy", helmValues)

var renderedDeployment *appsv1.Deployment
decoder := kubeyaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(renderedManifests), 4096)
for {
obj := &unstructured.Unstructured{}
err := decoder.Decode(obj)
if err != nil {
break
}
if obj.GetName() != "painter" || obj.GetKind() != "Deployment" {
continue
}

bytes, err := obj.MarshalJSON()
Expect(err).NotTo(HaveOccurred())
renderedDeployment = &appsv1.Deployment{}
err = json.Unmarshal(bytes, renderedDeployment)
Expect(err).NotTo(HaveOccurred())
}
Expect(renderedDeployment).NotTo(BeNil())
renderedDeploymentStrategy := renderedDeployment.Spec.Strategy
Expect(renderedDeploymentStrategy).To(Equal(expectedStrategy))
},
Entry("when the conditional strategy is not defined",
map[string]any{"enabled": true},
nil,
appsv1.DeploymentStrategy{},
),
Entry("when the condition is true",
map[string]any{"enabled": true, "condition": true},
[]model.ConditionalStrategy{
{
Condition: "$.Values.painter.condition",
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
},
{
Condition: "not $.Values.painter.condition",
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RecreateDeploymentStrategyType,
},
},
},
appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
),
Entry("when the condition is false",
map[string]any{"enabled": true, "condition": false},
[]model.ConditionalStrategy{
{
Condition: "$.Values.painter.condition",
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
},
{
Condition: "not $.Values.painter.condition",
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RecreateDeploymentStrategyType,
},
},
},
appsv1.DeploymentStrategy{
Type: appsv1.RecreateDeploymentStrategyType,
},
),
)

DescribeTable("rendering pod security context",
func(podSecurityContextValues map[string]interface{}, podSecurityContext *v1.PodSecurityContext, expectedPodSecurityContext *v1.PodSecurityContext) {
cmd := &Command{
Chart: &Chart{
Operators: []Operator{
{
Name: "painter",
Deployment: Deployment{
Container: Container{
Image: Image{
Tag: "v0.0.0",
Repository: "painter",
Registry: "quay.io/solo-io",
PullPolicy: "IfNotPresent",
},
},
PodSecurityContext: podSecurityContext,
},
},
},

Values: nil,
Data: Data{
ApiVersion: "v1",
Description: "",
Name: "Painting Operator",
Version: "v0.0.1",
Home: "https://docs.solo.io/skv2/latest",
Sources: []string{
"https://github.com/solo-io/skv2",
},
},
},

ManifestRoot: "codegen/test/chart-pod-security-context",
}

err := cmd.Execute()
Expect(err).NotTo(HaveOccurred())

values := map[string]interface{}{"enabled": true, "podSecurityContext": podSecurityContextValues}
helmValues := map[string]interface{}{"painter": values}

renderedManifests := helmTemplate("codegen/test/chart-pod-security-context", helmValues)

var renderedDeployment *appsv1.Deployment
decoder := kubeyaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(renderedManifests), 4096)
for {
obj := &unstructured.Unstructured{}
err := decoder.Decode(obj)
if err != nil {
break
}
if obj.GetName() != "painter" || obj.GetKind() != "Deployment" {
continue
}

bytes, err := obj.MarshalJSON()
Expect(err).NotTo(HaveOccurred())
renderedDeployment = &appsv1.Deployment{}
err = json.Unmarshal(bytes, renderedDeployment)
Expect(err).NotTo(HaveOccurred())
}
Expect(renderedDeployment).NotTo(BeNil())
renderedPodSecurityContext := renderedDeployment.Spec.Template.Spec.SecurityContext
Expect(renderedPodSecurityContext).To(Equal(expectedPodSecurityContext))
},
Entry("when PodSecurityContext is neither defined in values nor in the operator",
nil,
nil,
nil),
Entry("when PodSecurityContext is defined only in values",
map[string]interface{}{"fsGroup": 1000},
nil,
&v1.PodSecurityContext{
FSGroup: pointer.Int64(1000),
}),
Entry("when PodSecurityContext is defined only in the operator",
nil,
&v1.PodSecurityContext{
FSGroup: pointer.Int64(1000),
},
&v1.PodSecurityContext{
FSGroup: pointer.Int64(1000),
}),
Entry("when PodSecurityContext is defined in both values and the operator",
map[string]interface{}{"fsGroup": 1024},
&v1.PodSecurityContext{
FSGroup: pointer.Int64(1000),
},
&v1.PodSecurityContext{
FSGroup: pointer.Int64(1024), // should override the value defined in the operator
}),
)

Describe("rendering template env vars", func() {
var tmpDir string

Expand Down
9 changes: 9 additions & 0 deletions codegen/model/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/iancoleman/strcase"
"github.com/solo-io/skv2/codegen/doc"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
)
Expand Down Expand Up @@ -112,7 +113,10 @@ type Deployment struct {
// TODO support use of a DaemonSet instead of a Deployment
UseDaemonSet bool
Container
Strategy *appsv1.DeploymentStrategy
ConditionalStrategy []ConditionalStrategy
Sidecars []Sidecar
PodSecurityContext *corev1.PodSecurityContext
Volumes []corev1.Volume
ConditionalVolumes []ConditionalVolume
CustomPodLabels map[string]string
Expand All @@ -121,6 +125,11 @@ type Deployment struct {
CustomDeploymentAnnotations map[string]string
}

type ConditionalStrategy struct {
Condition string
Strategy appsv1.DeploymentStrategy
}

type ConditionalVolume struct {
Condition string
Volume corev1.Volume
Expand Down
11 changes: 6 additions & 5 deletions codegen/model/values/helm_chart_values.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ type UserValues struct {
UserContainerValues `json:",inline"`

// Required to have an interface value in order to use the `index` function in the template
Sidecars map[string]UserContainerValues `json:"sidecars" desc:"Optional configuration for the deployed containers."`
FloatingUserID bool `json:"floatingUserId" desc:"Allow the pod to be assigned a dynamic user ID. Required for OpenShift installations."`
RunAsUser uint32 `json:"runAsUser" desc:"Static user ID to run the containers as. Unused if floatingUserId is 'true'."`
ServiceType v1.ServiceType `json:"serviceType" desc:"Kubernetes service type. Can be either \"ClusterIP\", \"NodePort\", \"LoadBalancer\", or \"ExternalName\"."`
ServicePorts map[string]uint32 `json:"ports" desc:"Service ports as a map from port name to port number."`
Sidecars map[string]UserContainerValues `json:"sidecars" desc:"Optional configuration for the deployed containers."`
FloatingUserID bool `json:"floatingUserId" desc:"Allow the pod to be assigned a dynamic user ID. Required for OpenShift installations."`
RunAsUser uint32 `json:"runAsUser" desc:"Static user ID to run the containers as. Unused if floatingUserId is 'true'."`
ServiceType v1.ServiceType `json:"serviceType" desc:"Kubernetes service type. Can be either \"ClusterIP\", \"NodePort\", \"LoadBalancer\", or \"ExternalName\"."`
ServicePorts map[string]uint32 `json:"ports" desc:"Service ports as a map from port name to port number."`
PodSecurityContext *v1.PodSecurityContext `json:"podSecurityContext,omitempty" desc:"Pod-level security context. For more info, see the [Kubernetes documentation](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#podsecuritycontext-v1-core)." omitChildren:"true"`

// Overrides which can be set by the user
DeploymentOverrides *appsv1.Deployment `json:"deploymentOverrides" desc:"Arbitrary overrides for the component's [deployment template](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/deployment-v1/)." omitChildren:"true"`
Expand Down
Loading

0 comments on commit 063b531

Please sign in to comment.