Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v0.36.x] codegen: support pod security context and deployment strategy #553

Merged
merged 4 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading