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

codegen: allow setting Strategy and PodSecurityContext #538

Merged
merged 8 commits into from
Apr 15, 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.38.1/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
184 changes: 184 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,189 @@ 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 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
3 changes: 3 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,9 @@ type Deployment struct {
// TODO support use of a DaemonSet instead of a Deployment
UseDaemonSet bool
Container
Strategy *appsv1.DeploymentStrategy
Sidecars []Sidecar
PodSecurityContext *corev1.PodSecurityContext
Volumes []corev1.Volume
ConditionalVolumes []ConditionalVolume
CustomPodLabels map[string]string
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
22 changes: 22 additions & 0 deletions codegen/templates/chart/operator-deployment.yamltmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Expressions evaluating SKv2 Config use "[[" and "]]"

[[/* custom values defined in codegen model */]]
[[- $containers := containerConfigs $operator -]]
[[- $deploymentStrategy := $operator.Deployment.Strategy -]]
[[- $podSecurityContext := $operator.Deployment.PodSecurityContext -]]
[[- $volumes := $operator.Deployment.Volumes -]]
[[- $conditionalVolumes := $operator.Deployment.ConditionalVolumes -]]
[[- $customPodLabels := $operator.Deployment.CustomPodLabels -]]
Expand Down Expand Up @@ -58,6 +60,16 @@ spec:
[[- range $key, $value := $customPodLabels ]]
[[ $key ]]: [[ $value ]]
[[- end ]]
[[- if $deploymentStrategy ]]
strategy:
[[- if $deploymentStrategy.Type ]]
type: [[ $deploymentStrategy.Type ]]
[[- end ]]
[[- if $deploymentStrategy.RollingUpdate ]]
rollingUpdate:
[[ toYaml $deploymentStrategy.RollingUpdate | indent 6 ]]
[[- end ]]
[[- end ]]
template:
metadata:
labels:
Expand All @@ -72,6 +84,16 @@ spec:
[[- end ]]
spec:
serviceAccountName: [[ $operator.Name ]]
{{- /* Override the default podSecurityContext config if it is set. */}}
{{- if or ([[ (opVar $operator) ]].podSecurityContext) (eq "map[]" (printf "%v" [[ (opVar $operator) ]].podSecurityContext)) }}
securityContext:
{{ toYaml [[ (opVar $operator) ]].podSecurityContext | indent 8 }}
[[- if $podSecurityContext ]]
{{- else}}
securityContext:
[[ toYaml $podSecurityContext | indent 8 ]]
[[- end ]]
{{- end }}
[[- if $volumes ]]
volumes:
[[ toYaml $volumes | indent 6 ]]
Expand Down
8 changes: 8 additions & 0 deletions codegen/test/chart-deployment-strategy/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Code generated by skv2. DO NOT EDIT.

apiVersion: v1
home: https://docs.solo.io/skv2/latest
name: Painting Operator
sources:
- https://github.com/solo-io/skv2
version: v0.0.1
54 changes: 54 additions & 0 deletions codegen/test/chart-deployment-strategy/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Code generated by skv2. DO NOT EDIT.



{{/* Below are library functions provided by skv2 */}}

{{- /*

"skv2.utils.merge" takes an array of three values:
- the top context
- the yaml block that will be merged in (override)
- the name of the base template (source)

note: the source must be a named template (helm partial). This is necessary for the merging logic.

The behaviour is as follows, to align with already existing helm behaviour:
- If no source is found (template is empty), the merged output will be empty
- If no overrides are specified, the source is rendered as is
- If overrides are specified and source is not empty, overrides will be merged in to the source.

Overrides can replace / add to deeply nested dictionaries, but will completely replace lists.
Examples:

┌─────────────────────┬───────────────────────┬────────────────────────┐
│ Source (template) │ Overrides │ Result │
├─────────────────────┼───────────────────────┼────────────────────────┤
│ metadata: │ metadata: │ metadata: │
│ labels: │ labels: │ labels: │
│ app: gloo │ app: gloo1 │ app: gloo1 │
│ cluster: useast │ author: infra-team │ author: infra-team │
│ │ │ cluster: useast │
├─────────────────────┼───────────────────────┼────────────────────────┤
│ lists: │ lists: │ lists: │
│ groceries: │ groceries: │ groceries: │
│ - apple │ - grapes │ - grapes │
│ - banana │ │ │
└─────────────────────┴───────────────────────┴────────────────────────┘

skv2.utils.merge is a fork of a helm library chart function (https://github.com/helm/charts/blob/master/incubator/common/templates/_util.tpl).
This includes some optimizations to speed up chart rendering time, and merges in a value (overrides) with a named template, unlike the upstream
version, which merges two named templates.

*/ -}}
{{- define "skv2.utils.merge" -}}
{{- $top := first . -}}
{{- $overrides := (index . 1) -}}
{{- $tpl := fromYaml (include (index . 2) $top) -}}
{{- if or (empty $overrides) (empty $tpl) -}}
{{ include (index . 2) $top }} {{/* render source as is */}}
{{- else -}}
{{- $merged := merge $overrides $tpl -}}
{{- toYaml $merged -}} {{/* render source with overrides as YAML */}}
{{- end -}}
{{- end -}}
Loading
Loading