Files
Reloader/test/e2e/utils/workload_openshift.go
2026-01-08 11:06:45 +01:00

341 lines
11 KiB
Go

package utils
import (
"context"
"fmt"
"strings"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/dynamic"
)
// DeploymentConfigAdapter implements WorkloadAdapter for OpenShift DeploymentConfigs.
type DeploymentConfigAdapter struct {
dynamicClient dynamic.Interface
}
// NewDeploymentConfigAdapter creates a new DeploymentConfigAdapter.
func NewDeploymentConfigAdapter(dynamicClient dynamic.Interface) *DeploymentConfigAdapter {
return &DeploymentConfigAdapter{dynamicClient: dynamicClient}
}
// Type returns the workload type.
func (a *DeploymentConfigAdapter) Type() WorkloadType {
return WorkloadDeploymentConfig
}
// Create creates a DeploymentConfig with the given config.
func (a *DeploymentConfigAdapter) Create(ctx context.Context, namespace, name string, cfg WorkloadConfig) error {
opts := buildDCOptions(cfg)
return CreateDeploymentConfig(ctx, a.dynamicClient, namespace, name, opts...)
}
// Delete removes the DeploymentConfig.
func (a *DeploymentConfigAdapter) Delete(ctx context.Context, namespace, name string) error {
return DeleteDeploymentConfig(ctx, a.dynamicClient, namespace, name)
}
// WaitReady waits for the DeploymentConfig to be ready.
func (a *DeploymentConfigAdapter) WaitReady(ctx context.Context, namespace, name string, timeout time.Duration) error {
return WaitForDeploymentConfigReady(ctx, a.dynamicClient, namespace, name, timeout)
}
// WaitReloaded waits for the DeploymentConfig to have the reload annotation.
func (a *DeploymentConfigAdapter) WaitReloaded(ctx context.Context, namespace, name, annotationKey string, timeout time.Duration) (bool, error) {
return WaitForDeploymentConfigReloaded(ctx, a.dynamicClient, namespace, name, annotationKey, timeout)
}
// WaitEnvVar waits for the DeploymentConfig to have a STAKATER_ env var.
func (a *DeploymentConfigAdapter) WaitEnvVar(ctx context.Context, namespace, name, prefix string, timeout time.Duration) (bool, error) {
return WaitForDeploymentConfigEnvVar(ctx, a.dynamicClient, namespace, name, prefix, timeout)
}
// SupportsEnvVarStrategy returns true as DeploymentConfigs support env var reload strategy.
func (a *DeploymentConfigAdapter) SupportsEnvVarStrategy() bool {
return true
}
// RequiresSpecialHandling returns false as DeploymentConfigs use standard rolling restart.
func (a *DeploymentConfigAdapter) RequiresSpecialHandling() bool {
return false
}
// buildDCOptions converts WorkloadConfig to DCOption slice.
func buildDCOptions(cfg WorkloadConfig) []DCOption {
var opts []DCOption
// Add annotations (to pod template)
if len(cfg.Annotations) > 0 {
opts = append(opts, WithDCAnnotations(cfg.Annotations))
}
// Add envFrom references
if cfg.UseConfigMapEnvFrom && cfg.ConfigMapName != "" {
opts = append(opts, WithDCConfigMapEnvFrom(cfg.ConfigMapName))
}
if cfg.UseSecretEnvFrom && cfg.SecretName != "" {
opts = append(opts, WithDCSecretEnvFrom(cfg.SecretName))
}
// Add volume mounts
if cfg.UseConfigMapVolume && cfg.ConfigMapName != "" {
opts = append(opts, WithDCConfigMapVolume(cfg.ConfigMapName))
}
if cfg.UseSecretVolume && cfg.SecretName != "" {
opts = append(opts, WithDCSecretVolume(cfg.SecretName))
}
// Add projected volume
if cfg.UseProjectedVolume {
opts = append(opts, WithDCProjectedVolume(cfg.ConfigMapName, cfg.SecretName))
}
// Add valueFrom references
if cfg.UseConfigMapKeyRef && cfg.ConfigMapName != "" {
key := cfg.ConfigMapKey
if key == "" {
key = "key"
}
envVar := cfg.EnvVarName
if envVar == "" {
envVar = "CONFIG_VAR"
}
opts = append(opts, WithDCConfigMapKeyRef(cfg.ConfigMapName, key, envVar))
}
if cfg.UseSecretKeyRef && cfg.SecretName != "" {
key := cfg.SecretKey
if key == "" {
key = "key"
}
envVar := cfg.EnvVarName
if envVar == "" {
envVar = "SECRET_VAR"
}
opts = append(opts, WithDCSecretKeyRef(cfg.SecretName, key, envVar))
}
// Add init container with envFrom
if cfg.UseInitContainer {
opts = append(opts, WithDCInitContainer(cfg.ConfigMapName, cfg.SecretName))
}
// Add init container with volume mount
if cfg.UseInitContainerVolume {
opts = append(opts, WithDCInitContainerVolume(cfg.ConfigMapName, cfg.SecretName))
}
return opts
}
// WithDCProjectedVolume adds a projected volume with ConfigMap and/or Secret sources to a DeploymentConfig.
func WithDCProjectedVolume(cmName, secretName string) DCOption {
return func(dc *unstructured.Unstructured) {
volumeName := "projected-config"
sources := []interface{}{}
if cmName != "" {
sources = append(sources, map[string]interface{}{
"configMap": map[string]interface{}{
"name": cmName,
},
})
}
if secretName != "" {
sources = append(sources, map[string]interface{}{
"secret": map[string]interface{}{
"name": secretName,
},
})
}
// Add volume
volumes, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "volumes")
volumes = append(volumes, map[string]interface{}{
"name": volumeName,
"projected": map[string]interface{}{
"sources": sources,
},
})
_ = unstructured.SetNestedSlice(dc.Object, volumes, "spec", "template", "spec", "volumes")
// Add volumeMount
containers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "containers")
if len(containers) > 0 {
container := containers[0].(map[string]interface{})
volumeMounts, _, _ := unstructured.NestedSlice(container, "volumeMounts")
volumeMounts = append(volumeMounts, map[string]interface{}{
"name": volumeName,
"mountPath": "/etc/projected",
})
container["volumeMounts"] = volumeMounts
containers[0] = container
_ = unstructured.SetNestedSlice(dc.Object, containers, "spec", "template", "spec", "containers")
}
}
}
// WithDCConfigMapKeyRef adds an env var with valueFrom.configMapKeyRef to a DeploymentConfig.
func WithDCConfigMapKeyRef(cmName, key, envVarName string) DCOption {
return func(dc *unstructured.Unstructured) {
containers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "containers")
if len(containers) > 0 {
container := containers[0].(map[string]interface{})
env, _, _ := unstructured.NestedSlice(container, "env")
env = append(env, map[string]interface{}{
"name": envVarName,
"valueFrom": map[string]interface{}{
"configMapKeyRef": map[string]interface{}{
"name": cmName,
"key": key,
},
},
})
container["env"] = env
containers[0] = container
_ = unstructured.SetNestedSlice(dc.Object, containers, "spec", "template", "spec", "containers")
}
}
}
// WithDCSecretKeyRef adds an env var with valueFrom.secretKeyRef to a DeploymentConfig.
func WithDCSecretKeyRef(secretName, key, envVarName string) DCOption {
return func(dc *unstructured.Unstructured) {
containers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "containers")
if len(containers) > 0 {
container := containers[0].(map[string]interface{})
env, _, _ := unstructured.NestedSlice(container, "env")
env = append(env, map[string]interface{}{
"name": envVarName,
"valueFrom": map[string]interface{}{
"secretKeyRef": map[string]interface{}{
"name": secretName,
"key": key,
},
},
})
container["env"] = env
containers[0] = container
_ = unstructured.SetNestedSlice(dc.Object, containers, "spec", "template", "spec", "containers")
}
}
}
// WithDCInitContainer adds an init container that references ConfigMap and/or Secret via envFrom.
func WithDCInitContainer(cmName, secretName string) DCOption {
return func(dc *unstructured.Unstructured) {
initContainer := map[string]interface{}{
"name": "init",
"image": DefaultImage,
"command": []interface{}{"sh", "-c", "echo init done"},
}
envFrom := []interface{}{}
if cmName != "" {
envFrom = append(envFrom, map[string]interface{}{
"configMapRef": map[string]interface{}{
"name": cmName,
},
})
}
if secretName != "" {
envFrom = append(envFrom, map[string]interface{}{
"secretRef": map[string]interface{}{
"name": secretName,
},
})
}
if len(envFrom) > 0 {
initContainer["envFrom"] = envFrom
}
initContainers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "initContainers")
initContainers = append(initContainers, initContainer)
_ = unstructured.SetNestedSlice(dc.Object, initContainers, "spec", "template", "spec", "initContainers")
}
}
// WithDCInitContainerVolume adds an init container with ConfigMap/Secret volume mounts to a DeploymentConfig.
func WithDCInitContainerVolume(cmName, secretName string) DCOption {
return func(dc *unstructured.Unstructured) {
initContainer := map[string]interface{}{
"name": "init",
"image": DefaultImage,
"command": []interface{}{"sh", "-c", "echo init done"},
}
volumeMounts := []interface{}{}
volumes, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "volumes")
if cmName != "" {
volumeName := fmt.Sprintf("init-cm-%s", cmName)
volumes = append(volumes, map[string]interface{}{
"name": volumeName,
"configMap": map[string]interface{}{
"name": cmName,
},
})
volumeMounts = append(volumeMounts, map[string]interface{}{
"name": volumeName,
"mountPath": fmt.Sprintf("/etc/init-config/%s", cmName),
})
}
if secretName != "" {
volumeName := fmt.Sprintf("init-secret-%s", secretName)
volumes = append(volumes, map[string]interface{}{
"name": volumeName,
"secret": map[string]interface{}{
"secretName": secretName,
},
})
volumeMounts = append(volumeMounts, map[string]interface{}{
"name": volumeName,
"mountPath": fmt.Sprintf("/etc/init-secrets/%s", secretName),
})
}
if len(volumeMounts) > 0 {
initContainer["volumeMounts"] = volumeMounts
}
_ = unstructured.SetNestedSlice(dc.Object, volumes, "spec", "template", "spec", "volumes")
initContainers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "initContainers")
initContainers = append(initContainers, initContainer)
_ = unstructured.SetNestedSlice(dc.Object, initContainers, "spec", "template", "spec", "initContainers")
}
}
// WaitForDeploymentConfigEnvVar waits for a DeploymentConfig's container to have an env var with the given prefix.
func WaitForDeploymentConfigEnvVar(ctx context.Context, dynamicClient dynamic.Interface, namespace, name, prefix string, timeout time.Duration) (bool, error) {
var found bool
err := wait.PollUntilContextTimeout(ctx, DefaultInterval, timeout, true, func(ctx context.Context) (bool, error) {
dc, err := dynamicClient.Resource(DeploymentConfigGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return false, nil
}
containers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "containers")
for _, c := range containers {
container := c.(map[string]interface{})
env, _, _ := unstructured.NestedSlice(container, "env")
for _, e := range env {
envVar := e.(map[string]interface{})
if envName, ok := envVar["name"].(string); ok && strings.HasPrefix(envName, prefix) {
found = true
return true, nil
}
}
}
return false, nil
})
if err != nil && err != context.DeadlineExceeded {
return false, err
}
return found, nil
}