feat: DeploymentConfig support

This commit is contained in:
TheiLLeniumStudios
2025-12-28 16:43:28 +01:00
parent 9f331cac4f
commit fa5f1858f1
17 changed files with 1442 additions and 1040 deletions
+143
View File
@@ -0,0 +1,143 @@
package workload
import (
"context"
openshiftv1 "github.com/openshift/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// DeploymentConfigWorkload wraps an OpenShift DeploymentConfig.
type DeploymentConfigWorkload struct {
dc *openshiftv1.DeploymentConfig
}
// NewDeploymentConfigWorkload creates a new DeploymentConfigWorkload.
func NewDeploymentConfigWorkload(dc *openshiftv1.DeploymentConfig) *DeploymentConfigWorkload {
return &DeploymentConfigWorkload{dc: dc}
}
// Ensure DeploymentConfigWorkload implements WorkloadAccessor.
var _ WorkloadAccessor = (*DeploymentConfigWorkload)(nil)
func (w *DeploymentConfigWorkload) Kind() Kind {
return KindDeploymentConfig
}
func (w *DeploymentConfigWorkload) GetObject() client.Object {
return w.dc
}
func (w *DeploymentConfigWorkload) GetName() string {
return w.dc.Name
}
func (w *DeploymentConfigWorkload) GetNamespace() string {
return w.dc.Namespace
}
func (w *DeploymentConfigWorkload) GetAnnotations() map[string]string {
return w.dc.Annotations
}
func (w *DeploymentConfigWorkload) GetPodTemplateAnnotations() map[string]string {
if w.dc.Spec.Template == nil {
return nil
}
if w.dc.Spec.Template.Annotations == nil {
w.dc.Spec.Template.Annotations = make(map[string]string)
}
return w.dc.Spec.Template.Annotations
}
func (w *DeploymentConfigWorkload) SetPodTemplateAnnotation(key, value string) {
if w.dc.Spec.Template == nil {
w.dc.Spec.Template = &corev1.PodTemplateSpec{}
}
if w.dc.Spec.Template.Annotations == nil {
w.dc.Spec.Template.Annotations = make(map[string]string)
}
w.dc.Spec.Template.Annotations[key] = value
}
func (w *DeploymentConfigWorkload) GetContainers() []corev1.Container {
if w.dc.Spec.Template == nil {
return nil
}
return w.dc.Spec.Template.Spec.Containers
}
func (w *DeploymentConfigWorkload) SetContainers(containers []corev1.Container) {
if w.dc.Spec.Template == nil {
w.dc.Spec.Template = &corev1.PodTemplateSpec{}
}
w.dc.Spec.Template.Spec.Containers = containers
}
func (w *DeploymentConfigWorkload) GetInitContainers() []corev1.Container {
if w.dc.Spec.Template == nil {
return nil
}
return w.dc.Spec.Template.Spec.InitContainers
}
func (w *DeploymentConfigWorkload) SetInitContainers(containers []corev1.Container) {
if w.dc.Spec.Template == nil {
w.dc.Spec.Template = &corev1.PodTemplateSpec{}
}
w.dc.Spec.Template.Spec.InitContainers = containers
}
func (w *DeploymentConfigWorkload) GetVolumes() []corev1.Volume {
if w.dc.Spec.Template == nil {
return nil
}
return w.dc.Spec.Template.Spec.Volumes
}
func (w *DeploymentConfigWorkload) Update(ctx context.Context, c client.Client) error {
return c.Update(ctx, w.dc)
}
func (w *DeploymentConfigWorkload) DeepCopy() Workload {
return &DeploymentConfigWorkload{dc: w.dc.DeepCopy()}
}
func (w *DeploymentConfigWorkload) GetEnvFromSources() []corev1.EnvFromSource {
if w.dc.Spec.Template == nil {
return nil
}
var sources []corev1.EnvFromSource
for _, container := range w.dc.Spec.Template.Spec.Containers {
sources = append(sources, container.EnvFrom...)
}
for _, container := range w.dc.Spec.Template.Spec.InitContainers {
sources = append(sources, container.EnvFrom...)
}
return sources
}
func (w *DeploymentConfigWorkload) UsesConfigMap(name string) bool {
if w.dc.Spec.Template == nil {
return false
}
return SpecUsesConfigMap(&w.dc.Spec.Template.Spec, name)
}
func (w *DeploymentConfigWorkload) UsesSecret(name string) bool {
if w.dc.Spec.Template == nil {
return false
}
return SpecUsesSecret(&w.dc.Spec.Template.Spec, name)
}
func (w *DeploymentConfigWorkload) GetOwnerReferences() []metav1.OwnerReference {
return w.dc.OwnerReferences
}
// GetDeploymentConfig returns the underlying DeploymentConfig for special handling.
func (w *DeploymentConfigWorkload) GetDeploymentConfig() *openshiftv1.DeploymentConfig {
return w.dc
}
+7 -6
View File
@@ -18,12 +18,13 @@ import (
type Kind string
const (
KindDeployment Kind = "Deployment"
KindDaemonSet Kind = "DaemonSet"
KindStatefulSet Kind = "StatefulSet"
KindArgoRollout Kind = "Rollout"
KindJob Kind = "Job"
KindCronJob Kind = "CronJob"
KindDeployment Kind = "Deployment"
KindDaemonSet Kind = "DaemonSet"
KindStatefulSet Kind = "StatefulSet"
KindArgoRollout Kind = "Rollout"
KindJob Kind = "Job"
KindCronJob Kind = "CronJob"
KindDeploymentConfig Kind = "DeploymentConfig"
)
// Workload provides a uniform interface for managing Kubernetes workloads.
+13
View File
@@ -4,6 +4,7 @@ import (
"context"
argorolloutv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
openshiftv1 "github.com/openshift/api/apps/v1"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -128,3 +129,15 @@ func listRollouts(ctx context.Context, c client.Client, namespace string) ([]Wor
}
return result, nil
}
func listDeploymentConfigs(ctx context.Context, c client.Client, namespace string) ([]WorkloadAccessor, error) {
var list openshiftv1.DeploymentConfigList
if err := c.List(ctx, &list, client.InNamespace(namespace)); err != nil {
return nil, err
}
result := make([]WorkloadAccessor, len(list.Items))
for i := range list.Items {
result[i] = NewDeploymentConfigWorkload(&list.Items[i])
}
return result, nil
}
+39 -17
View File
@@ -6,6 +6,7 @@ import (
"strings"
argorolloutv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
openshiftv1 "github.com/openshift/api/apps/v1"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -14,16 +15,24 @@ import (
// WorkloadLister is a function that lists workloads of a specific kind.
type WorkloadLister func(ctx context.Context, c client.Client, namespace string) ([]WorkloadAccessor, error)
// RegistryOptions configures the workload registry.
type RegistryOptions struct {
ArgoRolloutsEnabled bool
DeploymentConfigEnabled bool
}
// Registry provides factory methods for creating Workload instances.
type Registry struct {
argoRolloutsEnabled bool
listers map[Kind]WorkloadLister
argoRolloutsEnabled bool
deploymentConfigEnabled bool
listers map[Kind]WorkloadLister
}
// NewRegistry creates a new workload registry.
func NewRegistry(argoRolloutsEnabled bool) *Registry {
func NewRegistry(opts RegistryOptions) *Registry {
r := &Registry{
argoRolloutsEnabled: argoRolloutsEnabled,
argoRolloutsEnabled: opts.ArgoRolloutsEnabled,
deploymentConfigEnabled: opts.DeploymentConfigEnabled,
listers: map[Kind]WorkloadLister{
KindDeployment: listDeployments,
KindDaemonSet: listDaemonSets,
@@ -32,9 +41,12 @@ func NewRegistry(argoRolloutsEnabled bool) *Registry {
KindCronJob: listCronJobs,
},
}
if argoRolloutsEnabled {
if opts.ArgoRolloutsEnabled {
r.listers[KindArgoRollout] = listRollouts
}
if opts.DeploymentConfigEnabled {
r.listers[KindDeploymentConfig] = listDeploymentConfigs
}
return r
}
@@ -55,6 +67,9 @@ func (r *Registry) SupportedKinds() []Kind {
if r.argoRolloutsEnabled {
kinds = append(kinds, KindArgoRollout)
}
if r.deploymentConfigEnabled {
kinds = append(kinds, KindDeploymentConfig)
}
return kinds
}
@@ -76,6 +91,11 @@ func (r *Registry) FromObject(obj client.Object) (WorkloadAccessor, error) {
return nil, fmt.Errorf("argo Rollouts support is not enabled")
}
return NewRolloutWorkload(o), nil
case *openshiftv1.DeploymentConfig:
if !r.deploymentConfigEnabled {
return nil, fmt.Errorf("openShift DeploymentConfig support is not enabled")
}
return NewDeploymentConfigWorkload(o), nil
default:
return nil, fmt.Errorf("unsupported object type: %T", obj)
}
@@ -84,18 +104,20 @@ func (r *Registry) FromObject(obj client.Object) (WorkloadAccessor, error) {
// kindAliases maps string representations to Kind constants.
// Supports lowercase, title case, and plural forms for user convenience.
var kindAliases = map[string]Kind{
"deployment": KindDeployment,
"deployments": KindDeployment,
"daemonset": KindDaemonSet,
"daemonsets": KindDaemonSet,
"statefulset": KindStatefulSet,
"statefulsets": KindStatefulSet,
"rollout": KindArgoRollout,
"rollouts": KindArgoRollout,
"job": KindJob,
"jobs": KindJob,
"cronjob": KindCronJob,
"cronjobs": KindCronJob,
"deployment": KindDeployment,
"deployments": KindDeployment,
"daemonset": KindDaemonSet,
"daemonsets": KindDaemonSet,
"statefulset": KindStatefulSet,
"statefulsets": KindStatefulSet,
"rollout": KindArgoRollout,
"rollouts": KindArgoRollout,
"job": KindJob,
"jobs": KindJob,
"cronjob": KindCronJob,
"cronjobs": KindCronJob,
"deploymentconfig": KindDeploymentConfig,
"deploymentconfigs": KindDeploymentConfig,
}
// KindFromString converts a string to a Kind.
+132 -12
View File
@@ -4,6 +4,7 @@ import (
"testing"
argorolloutv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
openshiftv1 "github.com/openshift/api/apps/v1"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
@@ -11,7 +12,7 @@ import (
)
func TestNewRegistry_WithoutArgoRollouts(t *testing.T) {
r := NewRegistry(false)
r := NewRegistry(RegistryOptions{ArgoRolloutsEnabled: false})
kinds := r.SupportedKinds()
if len(kinds) != 5 {
@@ -30,7 +31,7 @@ func TestNewRegistry_WithoutArgoRollouts(t *testing.T) {
}
func TestNewRegistry_WithArgoRollouts(t *testing.T) {
r := NewRegistry(true)
r := NewRegistry(RegistryOptions{ArgoRolloutsEnabled: true})
kinds := r.SupportedKinds()
if len(kinds) != 6 {
@@ -54,7 +55,7 @@ func TestNewRegistry_WithArgoRollouts(t *testing.T) {
}
func TestRegistry_ListerFor_AllKinds(t *testing.T) {
r := NewRegistry(true)
r := NewRegistry(RegistryOptions{ArgoRolloutsEnabled: true})
tests := []struct {
kind Kind
@@ -78,7 +79,7 @@ func TestRegistry_ListerFor_AllKinds(t *testing.T) {
}
func TestRegistry_FromObject_Deployment(t *testing.T) {
r := NewRegistry(false)
r := NewRegistry(RegistryOptions{})
deploy := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
@@ -93,7 +94,7 @@ func TestRegistry_FromObject_Deployment(t *testing.T) {
}
func TestRegistry_FromObject_DaemonSet(t *testing.T) {
r := NewRegistry(false)
r := NewRegistry(RegistryOptions{})
ds := &appsv1.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
@@ -108,7 +109,7 @@ func TestRegistry_FromObject_DaemonSet(t *testing.T) {
}
func TestRegistry_FromObject_StatefulSet(t *testing.T) {
r := NewRegistry(false)
r := NewRegistry(RegistryOptions{})
sts := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
@@ -123,7 +124,7 @@ func TestRegistry_FromObject_StatefulSet(t *testing.T) {
}
func TestRegistry_FromObject_Job(t *testing.T) {
r := NewRegistry(false)
r := NewRegistry(RegistryOptions{})
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
@@ -138,7 +139,7 @@ func TestRegistry_FromObject_Job(t *testing.T) {
}
func TestRegistry_FromObject_CronJob(t *testing.T) {
r := NewRegistry(false)
r := NewRegistry(RegistryOptions{})
cj := &batchv1.CronJob{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
@@ -153,7 +154,7 @@ func TestRegistry_FromObject_CronJob(t *testing.T) {
}
func TestRegistry_FromObject_Rollout_Enabled(t *testing.T) {
r := NewRegistry(true)
r := NewRegistry(RegistryOptions{ArgoRolloutsEnabled: true})
rollout := &argorolloutv1alpha1.Rollout{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
@@ -168,7 +169,7 @@ func TestRegistry_FromObject_Rollout_Enabled(t *testing.T) {
}
func TestRegistry_FromObject_Rollout_Disabled(t *testing.T) {
r := NewRegistry(false)
r := NewRegistry(RegistryOptions{})
rollout := &argorolloutv1alpha1.Rollout{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
@@ -180,7 +181,7 @@ func TestRegistry_FromObject_Rollout_Disabled(t *testing.T) {
}
func TestRegistry_FromObject_UnsupportedType(t *testing.T) {
r := NewRegistry(false)
r := NewRegistry(RegistryOptions{})
cm := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
@@ -234,7 +235,7 @@ func TestKindFromString(t *testing.T) {
}
func TestNewLister(t *testing.T) {
r := NewRegistry(false)
r := NewRegistry(RegistryOptions{})
l := NewLister(nil, r, nil)
if l == nil {
@@ -244,3 +245,122 @@ func TestNewLister(t *testing.T) {
t.Error("NewLister should set Registry")
}
}
// DeploymentConfig registry tests
func TestNewRegistry_WithDeploymentConfig(t *testing.T) {
r := NewRegistry(RegistryOptions{DeploymentConfigEnabled: true})
kinds := r.SupportedKinds()
if len(kinds) != 6 {
t.Errorf("SupportedKinds() = %d kinds, want 6", len(kinds))
}
found := false
for _, k := range kinds {
if k == KindDeploymentConfig {
found = true
break
}
}
if !found {
t.Error("SupportedKinds() should include DeploymentConfig when enabled")
}
if r.ListerFor(KindDeploymentConfig) == nil {
t.Error("ListerFor(KindDeploymentConfig) should return a function when enabled")
}
}
func TestNewRegistry_WithoutDeploymentConfig(t *testing.T) {
r := NewRegistry(RegistryOptions{DeploymentConfigEnabled: false})
for _, k := range r.SupportedKinds() {
if k == KindDeploymentConfig {
t.Error("SupportedKinds() should not include DeploymentConfig when disabled")
}
}
if r.ListerFor(KindDeploymentConfig) != nil {
t.Error("ListerFor(KindDeploymentConfig) should return nil when disabled")
}
}
func TestNewRegistry_WithBothOptionalWorkloads(t *testing.T) {
r := NewRegistry(RegistryOptions{
ArgoRolloutsEnabled: true,
DeploymentConfigEnabled: true,
})
kinds := r.SupportedKinds()
if len(kinds) != 7 {
t.Errorf("SupportedKinds() = %d kinds, want 7 (5 base + ArgoRollout + DeploymentConfig)", len(kinds))
}
foundRollout := false
foundDC := false
for _, k := range kinds {
if k == KindArgoRollout {
foundRollout = true
}
if k == KindDeploymentConfig {
foundDC = true
}
}
if !foundRollout {
t.Error("SupportedKinds() should include ArgoRollout")
}
if !foundDC {
t.Error("SupportedKinds() should include DeploymentConfig")
}
}
func TestRegistry_FromObject_DeploymentConfig_Enabled(t *testing.T) {
r := NewRegistry(RegistryOptions{DeploymentConfigEnabled: true})
dc := &openshiftv1.DeploymentConfig{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
w, err := r.FromObject(dc)
if err != nil {
t.Fatalf("FromObject(DeploymentConfig) error = %v", err)
}
if w.Kind() != KindDeploymentConfig {
t.Errorf("FromObject(DeploymentConfig).Kind() = %v, want %v", w.Kind(), KindDeploymentConfig)
}
}
func TestRegistry_FromObject_DeploymentConfig_Disabled(t *testing.T) {
r := NewRegistry(RegistryOptions{DeploymentConfigEnabled: false})
dc := &openshiftv1.DeploymentConfig{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
}
_, err := r.FromObject(dc)
if err == nil {
t.Error("FromObject(DeploymentConfig) should return error when DeploymentConfig disabled")
}
}
func TestKindFromString_DeploymentConfig(t *testing.T) {
tests := []struct {
input string
want Kind
wantErr bool
}{
{"deploymentconfig", KindDeploymentConfig, false},
{"deploymentconfigs", KindDeploymentConfig, false},
{"DeploymentConfig", KindDeploymentConfig, false},
{"DEPLOYMENTCONFIG", KindDeploymentConfig, false},
}
for _, tt := range tests {
got, err := KindFromString(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("KindFromString(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
continue
}
if got != tt.want {
t.Errorf("KindFromString(%q) = %v, want %v", tt.input, got, tt.want)
}
}
}
File diff suppressed because it is too large Load Diff