mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-27 14:37:17 +00:00
feat: DeploymentConfig support
This commit is contained in:
@@ -26,16 +26,17 @@ const (
|
||||
|
||||
// Config holds all configuration for Reloader.
|
||||
type Config struct {
|
||||
Annotations AnnotationConfig `json:"annotations"`
|
||||
AutoReloadAll bool `json:"autoReloadAll"`
|
||||
ReloadStrategy ReloadStrategy `json:"reloadStrategy"`
|
||||
ArgoRolloutsEnabled bool `json:"argoRolloutsEnabled"`
|
||||
ArgoRolloutStrategy ArgoRolloutStrategy `json:"argoRolloutStrategy"`
|
||||
ReloadOnCreate bool `json:"reloadOnCreate"`
|
||||
ReloadOnDelete bool `json:"reloadOnDelete"`
|
||||
SyncAfterRestart bool `json:"syncAfterRestart"`
|
||||
EnableHA bool `json:"enableHA"`
|
||||
WebhookURL string `json:"webhookUrl,omitempty"`
|
||||
Annotations AnnotationConfig `json:"annotations"`
|
||||
AutoReloadAll bool `json:"autoReloadAll"`
|
||||
ReloadStrategy ReloadStrategy `json:"reloadStrategy"`
|
||||
ArgoRolloutsEnabled bool `json:"argoRolloutsEnabled"`
|
||||
ArgoRolloutStrategy ArgoRolloutStrategy `json:"argoRolloutStrategy"`
|
||||
DeploymentConfigEnabled bool `json:"deploymentConfigEnabled"`
|
||||
ReloadOnCreate bool `json:"reloadOnCreate"`
|
||||
ReloadOnDelete bool `json:"reloadOnDelete"`
|
||||
SyncAfterRestart bool `json:"syncAfterRestart"`
|
||||
EnableHA bool `json:"enableHA"`
|
||||
WebhookURL string `json:"webhookUrl,omitempty"`
|
||||
|
||||
IgnoredResources []string `json:"ignoredResources,omitempty"`
|
||||
IgnoredWorkloads []string `json:"ignoredWorkloads,omitempty"`
|
||||
@@ -101,28 +102,29 @@ type LeaderElectionConfig struct {
|
||||
// NewDefault creates a Config with default values.
|
||||
func NewDefault() *Config {
|
||||
return &Config{
|
||||
Annotations: DefaultAnnotations(),
|
||||
AutoReloadAll: false,
|
||||
ReloadStrategy: ReloadStrategyEnvVars,
|
||||
ArgoRolloutsEnabled: false,
|
||||
ArgoRolloutStrategy: ArgoRolloutStrategyRollout,
|
||||
ReloadOnCreate: false,
|
||||
ReloadOnDelete: false,
|
||||
SyncAfterRestart: false,
|
||||
EnableHA: false,
|
||||
WebhookURL: "",
|
||||
IgnoredResources: []string{},
|
||||
IgnoredWorkloads: []string{},
|
||||
IgnoredNamespaces: []string{},
|
||||
NamespaceSelectors: []labels.Selector{},
|
||||
ResourceSelectors: []labels.Selector{},
|
||||
LogFormat: "",
|
||||
LogLevel: "info",
|
||||
MetricsAddr: ":9090",
|
||||
HealthAddr: ":8081",
|
||||
EnablePProf: false,
|
||||
PProfAddr: ":6060",
|
||||
Alerting: AlertingConfig{},
|
||||
Annotations: DefaultAnnotations(),
|
||||
AutoReloadAll: false,
|
||||
ReloadStrategy: ReloadStrategyEnvVars,
|
||||
ArgoRolloutsEnabled: false,
|
||||
ArgoRolloutStrategy: ArgoRolloutStrategyRollout,
|
||||
DeploymentConfigEnabled: false,
|
||||
ReloadOnCreate: false,
|
||||
ReloadOnDelete: false,
|
||||
SyncAfterRestart: false,
|
||||
EnableHA: false,
|
||||
WebhookURL: "",
|
||||
IgnoredResources: []string{},
|
||||
IgnoredWorkloads: []string{},
|
||||
IgnoredNamespaces: []string{},
|
||||
NamespaceSelectors: []labels.Selector{},
|
||||
ResourceSelectors: []labels.Selector{},
|
||||
LogFormat: "",
|
||||
LogLevel: "info",
|
||||
MetricsAddr: ":9090",
|
||||
HealthAddr: ":8081",
|
||||
EnablePProf: false,
|
||||
PProfAddr: ":6060",
|
||||
Alerting: AlertingConfig{},
|
||||
LeaderElection: LeaderElectionConfig{
|
||||
LockName: "reloader-leader-election",
|
||||
LeaseDuration: 15 * time.Second,
|
||||
|
||||
@@ -39,6 +39,12 @@ func BindFlags(fs *pflag.FlagSet, cfg *Config) {
|
||||
"Enable Argo Rollouts support (true/false)",
|
||||
)
|
||||
|
||||
// OpenShift DeploymentConfig
|
||||
fs.String(
|
||||
"is-openshift", "",
|
||||
"Enable OpenShift DeploymentConfig support (true/false/auto). Empty or 'auto' enables auto-detection",
|
||||
)
|
||||
|
||||
// Event watching
|
||||
fs.String(
|
||||
"reload-on-create", "false",
|
||||
@@ -239,6 +245,14 @@ func ApplyFlags(cfg *Config) error {
|
||||
cfg.ReloadOnCreate = parseBoolString(v.GetString("reload-on-create"))
|
||||
cfg.ReloadOnDelete = parseBoolString(v.GetString("reload-on-delete"))
|
||||
|
||||
switch strings.ToLower(strings.TrimSpace(v.GetString("is-openshift"))) {
|
||||
case "true":
|
||||
cfg.DeploymentConfigEnabled = true
|
||||
case "false":
|
||||
cfg.DeploymentConfigEnabled = false
|
||||
default:
|
||||
}
|
||||
|
||||
// String flags
|
||||
cfg.ReloadStrategy = ReloadStrategy(v.GetString("reload-strategy"))
|
||||
cfg.WebhookURL = v.GetString("webhook-url")
|
||||
@@ -321,6 +335,13 @@ func parseBoolString(s string) bool {
|
||||
return s == "true" || s == "1" || s == "yes"
|
||||
}
|
||||
|
||||
// ShouldAutoDetectOpenShift returns true if OpenShift DeploymentConfig support
|
||||
// should be auto-detected (i.e., the --is-openshift flag was not explicitly set).
|
||||
func ShouldAutoDetectOpenShift() bool {
|
||||
val := strings.ToLower(strings.TrimSpace(v.GetString("is-openshift")))
|
||||
return val == "" || val == "auto"
|
||||
}
|
||||
|
||||
// splitAndTrim splits a comma-separated string and trims whitespace.
|
||||
func splitAndTrim(s string) []string {
|
||||
if s == "" {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
argorolloutsv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
|
||||
"github.com/go-logr/logr"
|
||||
openshiftv1 "github.com/openshift/api/apps/v1"
|
||||
"github.com/stakater/Reloader/internal/pkg/alerting"
|
||||
"github.com/stakater/Reloader/internal/pkg/config"
|
||||
"github.com/stakater/Reloader/internal/pkg/events"
|
||||
@@ -27,6 +28,7 @@ var runtimeScheme = runtime.NewScheme()
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(runtimeScheme))
|
||||
utilruntime.Must(argorolloutsv1alpha1.AddToScheme(runtimeScheme))
|
||||
utilruntime.Must(openshiftv1.AddToScheme(runtimeScheme))
|
||||
}
|
||||
|
||||
// ManagerOptions contains options for creating a new Manager.
|
||||
@@ -115,7 +117,10 @@ func NewManagerWithRestConfig(opts ManagerOptions, restConfig *rest.Config) (ctr
|
||||
|
||||
// SetupReconcilers sets up all reconcilers with the manager.
|
||||
func SetupReconcilers(mgr ctrl.Manager, cfg *config.Config, log logr.Logger, collectors *metrics.Collectors) error {
|
||||
registry := workload.NewRegistry(cfg.ArgoRolloutsEnabled)
|
||||
registry := workload.NewRegistry(workload.RegistryOptions{
|
||||
ArgoRolloutsEnabled: cfg.ArgoRolloutsEnabled,
|
||||
DeploymentConfigEnabled: cfg.DeploymentConfigEnabled,
|
||||
})
|
||||
reloadService := reload.NewService(cfg)
|
||||
eventRecorder := events.NewRecorder(mgr.GetEventRecorderFor("reloader"))
|
||||
pauseHandler := reload.NewPauseHandler(cfg)
|
||||
|
||||
@@ -36,7 +36,10 @@ func newConfigMapReconciler(t *testing.T, cfg *config.Config, objects ...runtime
|
||||
Log: testr.New(t),
|
||||
Config: cfg,
|
||||
ReloadService: reload.NewService(cfg),
|
||||
Registry: workload.NewRegistry(cfg.ArgoRolloutsEnabled),
|
||||
Registry: workload.NewRegistry(workload.RegistryOptions{
|
||||
ArgoRolloutsEnabled: cfg.ArgoRolloutsEnabled,
|
||||
DeploymentConfigEnabled: cfg.DeploymentConfigEnabled,
|
||||
}),
|
||||
Collectors: &collectors,
|
||||
EventRecorder: events.NewRecorder(nil),
|
||||
WebhookClient: webhook.NewClient("", testr.New(t)),
|
||||
@@ -59,7 +62,10 @@ func newSecretReconciler(t *testing.T, cfg *config.Config, objects ...runtime.Ob
|
||||
Log: testr.New(t),
|
||||
Config: cfg,
|
||||
ReloadService: reload.NewService(cfg),
|
||||
Registry: workload.NewRegistry(cfg.ArgoRolloutsEnabled),
|
||||
Registry: workload.NewRegistry(workload.RegistryOptions{
|
||||
ArgoRolloutsEnabled: cfg.ArgoRolloutsEnabled,
|
||||
DeploymentConfigEnabled: cfg.DeploymentConfigEnabled,
|
||||
}),
|
||||
Collectors: &collectors,
|
||||
EventRecorder: events.NewRecorder(nil),
|
||||
WebhookClient: webhook.NewClient("", testr.New(t)),
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package openshift
|
||||
|
||||
import (
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/client-go/discovery"
|
||||
)
|
||||
|
||||
const (
|
||||
// DeploymentConfigAPIGroup is the API group for DeploymentConfig.
|
||||
DeploymentConfigAPIGroup = "apps.openshift.io"
|
||||
// DeploymentConfigAPIVersion is the API version for DeploymentConfig.
|
||||
DeploymentConfigAPIVersion = "v1"
|
||||
// DeploymentConfigResource is the resource name for DeploymentConfig.
|
||||
DeploymentConfigResource = "deploymentconfigs"
|
||||
)
|
||||
|
||||
// HasDeploymentConfigSupport checks if the cluster supports DeploymentConfig
|
||||
func HasDeploymentConfigSupport(client discovery.DiscoveryInterface, log logr.Logger) bool {
|
||||
resources, err := client.ServerResourcesForGroupVersion(DeploymentConfigAPIGroup + "/" + DeploymentConfigAPIVersion)
|
||||
if err != nil {
|
||||
log.V(1).Info("DeploymentConfig API not available", "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, r := range resources.APIResources {
|
||||
if r.Name == DeploymentConfigResource {
|
||||
log.Info("DeploymentConfig API detected, enabling support")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
log.V(1).Info("DeploymentConfig resource not found in apps.openshift.io/v1")
|
||||
return false
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
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"
|
||||
@@ -8,12 +9,69 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// NewDeploymentConfig creates a minimal DeploymentConfig for unit testing.
|
||||
func NewDeploymentConfig(name, namespace string, annotations map[string]string) *openshiftv1.DeploymentConfig {
|
||||
replicas := int32(1)
|
||||
return &openshiftv1.DeploymentConfig{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Annotations: annotations,
|
||||
},
|
||||
Spec: openshiftv1.DeploymentConfigSpec{
|
||||
Replicas: replicas,
|
||||
Selector: map[string]string{"app": name},
|
||||
Template: &corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{"app": name},
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewDeploymentConfigWithEnvFrom creates a DeploymentConfig with EnvFrom referencing a ConfigMap or Secret.
|
||||
func NewDeploymentConfigWithEnvFrom(name, namespace string, configMapName, secretName string) *openshiftv1.DeploymentConfig {
|
||||
dc := NewDeploymentConfig(name, namespace, nil)
|
||||
if configMapName != "" {
|
||||
dc.Spec.Template.Spec.Containers[0].EnvFrom = append(
|
||||
dc.Spec.Template.Spec.Containers[0].EnvFrom,
|
||||
corev1.EnvFromSource{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: configMapName},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
if secretName != "" {
|
||||
dc.Spec.Template.Spec.Containers[0].EnvFrom = append(
|
||||
dc.Spec.Template.Spec.Containers[0].EnvFrom,
|
||||
corev1.EnvFromSource{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: secretName},
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
return dc
|
||||
}
|
||||
|
||||
// NewScheme creates a scheme with common types for testing.
|
||||
func NewScheme() *runtime.Scheme {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
_ = appsv1.AddToScheme(scheme)
|
||||
_ = batchv1.AddToScheme(scheme)
|
||||
_ = openshiftv1.AddToScheme(scheme)
|
||||
return scheme
|
||||
}
|
||||
|
||||
@@ -35,10 +93,12 @@ func NewDeployment(name, namespace string, annotations map[string]string) *appsv
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "main",
|
||||
Image: "nginx",
|
||||
}},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -74,30 +134,36 @@ func NewDeploymentWithEnvFrom(name, namespace string, configMapName, secretName
|
||||
// NewDeploymentWithVolume creates a Deployment with a volume from ConfigMap or Secret.
|
||||
func NewDeploymentWithVolume(name, namespace string, configMapName, secretName string) *appsv1.Deployment {
|
||||
d := NewDeployment(name, namespace, nil)
|
||||
d.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{{
|
||||
Name: "config",
|
||||
MountPath: "/etc/config",
|
||||
}}
|
||||
d.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
|
||||
{
|
||||
Name: "config",
|
||||
MountPath: "/etc/config",
|
||||
},
|
||||
}
|
||||
|
||||
if configMapName != "" {
|
||||
d.Spec.Template.Spec.Volumes = []corev1.Volume{{
|
||||
Name: "config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: configMapName},
|
||||
d.Spec.Template.Spec.Volumes = []corev1.Volume{
|
||||
{
|
||||
Name: "config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: configMapName},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
}
|
||||
if secretName != "" {
|
||||
d.Spec.Template.Spec.Volumes = []corev1.Volume{{
|
||||
Name: "config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: secretName,
|
||||
d.Spec.Template.Spec.Volumes = []corev1.Volume{
|
||||
{
|
||||
Name: "config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: secretName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
@@ -105,33 +171,41 @@ func NewDeploymentWithVolume(name, namespace string, configMapName, secretName s
|
||||
// NewDeploymentWithProjectedVolume creates a Deployment with a projected volume.
|
||||
func NewDeploymentWithProjectedVolume(name, namespace string, configMapName, secretName string) *appsv1.Deployment {
|
||||
d := NewDeployment(name, namespace, nil)
|
||||
d.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{{
|
||||
Name: "config",
|
||||
MountPath: "/etc/config",
|
||||
}}
|
||||
d.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{
|
||||
{
|
||||
Name: "config",
|
||||
MountPath: "/etc/config",
|
||||
},
|
||||
}
|
||||
|
||||
sources := []corev1.VolumeProjection{}
|
||||
if configMapName != "" {
|
||||
sources = append(sources, corev1.VolumeProjection{
|
||||
ConfigMap: &corev1.ConfigMapProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: configMapName},
|
||||
sources = append(
|
||||
sources, corev1.VolumeProjection{
|
||||
ConfigMap: &corev1.ConfigMapProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: configMapName},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
if secretName != "" {
|
||||
sources = append(sources, corev1.VolumeProjection{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: secretName},
|
||||
sources = append(
|
||||
sources, corev1.VolumeProjection{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: secretName},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
d.Spec.Template.Spec.Volumes = []corev1.Volume{{
|
||||
Name: "config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{Sources: sources},
|
||||
d.Spec.Template.Spec.Volumes = []corev1.Volume{
|
||||
{
|
||||
Name: "config",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{Sources: sources},
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -153,10 +227,12 @@ func NewDaemonSet(name, namespace string, annotations map[string]string) *appsv1
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "main",
|
||||
Image: "nginx",
|
||||
}},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -181,10 +257,12 @@ func NewStatefulSet(name, namespace string, annotations map[string]string) *apps
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "main",
|
||||
Image: "nginx",
|
||||
}},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
Image: "nginx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -200,18 +278,30 @@ func NewJob(name, namespace string) *batchv1.Job {
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
Containers: []corev1.Container{{
|
||||
Name: "main",
|
||||
Image: "busybox",
|
||||
}},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
Image: "busybox",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewJobWithAnnotations creates a Job with annotations.
|
||||
func NewJobWithAnnotations(name, namespace string, annotations map[string]string) *batchv1.Job {
|
||||
job := NewJob(name, namespace)
|
||||
job.Annotations = annotations
|
||||
return job
|
||||
}
|
||||
|
||||
// NewCronJob creates a minimal CronJob for unit testing.
|
||||
func NewCronJob(name, namespace string) *batchv1.CronJob {
|
||||
return &batchv1.CronJob{
|
||||
@@ -225,12 +315,17 @@ func NewCronJob(name, namespace string) *batchv1.CronJob {
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
Containers: []corev1.Container{{
|
||||
Name: "main",
|
||||
Image: "busybox",
|
||||
}},
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
Image: "busybox",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -239,6 +334,13 @@ func NewCronJob(name, namespace string) *batchv1.CronJob {
|
||||
}
|
||||
}
|
||||
|
||||
// NewCronJobWithAnnotations creates a CronJob with annotations.
|
||||
func NewCronJobWithAnnotations(name, namespace string, annotations map[string]string) *batchv1.CronJob {
|
||||
cj := NewCronJob(name, namespace)
|
||||
cj.Annotations = annotations
|
||||
return cj
|
||||
}
|
||||
|
||||
// NewConfigMap creates a ConfigMap for unit testing.
|
||||
func NewConfigMap(name, namespace string) *corev1.ConfigMap {
|
||||
return &corev1.ConfigMap{
|
||||
|
||||
@@ -7,13 +7,15 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/config"
|
||||
openshiftv1 "github.com/openshift/api/apps/v1"
|
||||
openshiftclient "github.com/openshift/client-go/apps/clientset/versioned"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -133,7 +135,9 @@ func DeleteSecret(client kubernetes.Interface, namespace, name string) error {
|
||||
}
|
||||
|
||||
// CreateDeployment creates a Deployment that references a ConfigMap/Secret.
|
||||
func CreateDeployment(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (*appsv1.Deployment, error) {
|
||||
func CreateDeployment(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (
|
||||
*appsv1.Deployment, error,
|
||||
) {
|
||||
var deployment *appsv1.Deployment
|
||||
if useConfigMap {
|
||||
deployment = NewDeploymentWithEnvFrom(name, namespace, name, "")
|
||||
@@ -154,7 +158,9 @@ func DeleteDeployment(client kubernetes.Interface, namespace, name string) error
|
||||
}
|
||||
|
||||
// CreateDaemonSet creates a DaemonSet that references a ConfigMap/Secret.
|
||||
func CreateDaemonSet(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (*appsv1.DaemonSet, error) {
|
||||
func CreateDaemonSet(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (
|
||||
*appsv1.DaemonSet, error,
|
||||
) {
|
||||
daemonset := NewDaemonSet(name, namespace, annotations)
|
||||
// Override image for integration tests
|
||||
daemonset.Spec.Template.Spec.Containers[0].Image = "busybox:1.36"
|
||||
@@ -187,7 +193,9 @@ func DeleteDaemonSet(client kubernetes.Interface, namespace, name string) error
|
||||
}
|
||||
|
||||
// CreateStatefulSet creates a StatefulSet that references a ConfigMap/Secret.
|
||||
func CreateStatefulSet(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (*appsv1.StatefulSet, error) {
|
||||
func CreateStatefulSet(client kubernetes.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (
|
||||
*appsv1.StatefulSet, error,
|
||||
) {
|
||||
statefulset := NewStatefulSet(name, namespace, annotations)
|
||||
statefulset.Spec.ServiceName = name
|
||||
// Override image for integration tests
|
||||
@@ -266,88 +274,157 @@ func ConvertResourceToSHA(resourceType, namespace, name, data string) string {
|
||||
func WaitForDeploymentAnnotation(client kubernetes.Interface, namespace, name, annotation, expectedValue string, timeout time.Duration) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
return wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
deployment, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
value, ok := deployment.Spec.Template.Annotations[annotation]
|
||||
if !ok {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
return value == expectedValue, nil
|
||||
})
|
||||
return wait.PollUntilContextTimeout(
|
||||
ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
deployment, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
value, ok := deployment.Spec.Template.Annotations[annotation]
|
||||
if !ok {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
return value == expectedValue, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// WaitForDeploymentReloadedAnnotation waits for a deployment to have any reloaded annotation.
|
||||
func WaitForDeploymentReloadedAnnotation(client kubernetes.Interface, namespace, name string, cfg *config.Config, timeout time.Duration) (bool, error) {
|
||||
// WaitForDeploymentReloadedAnnotation waits for a deployment to have the specified reloaded annotation.
|
||||
func WaitForDeploymentReloadedAnnotation(client kubernetes.Interface, namespace, name, annotationName string, timeout time.Duration) (
|
||||
bool, error,
|
||||
) {
|
||||
var found bool
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
deployment, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
// Check for the last-reloaded-from annotation in pod template
|
||||
if deployment.Spec.Template.Annotations != nil {
|
||||
if _, ok := deployment.Spec.Template.Annotations[cfg.Annotations.LastReloadedFrom]; ok {
|
||||
found = true
|
||||
return true, nil
|
||||
err := wait.PollUntilContextTimeout(
|
||||
ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
deployment, err := client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
// Check for the last-reloaded-from annotation in pod template
|
||||
if deployment.Spec.Template.Annotations != nil {
|
||||
if _, ok := deployment.Spec.Template.Annotations[annotationName]; ok {
|
||||
found = true
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
)
|
||||
if wait.Interrupted(err) {
|
||||
return found, nil
|
||||
}
|
||||
return found, err
|
||||
}
|
||||
|
||||
// WaitForDaemonSetReloadedAnnotation waits for a daemonset to have any reloaded annotation.
|
||||
func WaitForDaemonSetReloadedAnnotation(client kubernetes.Interface, namespace, name string, cfg *config.Config, timeout time.Duration) (bool, error) {
|
||||
// WaitForDaemonSetReloadedAnnotation waits for a daemonset to have the specified reloaded annotation.
|
||||
func WaitForDaemonSetReloadedAnnotation(client kubernetes.Interface, namespace, name, annotationName string, timeout time.Duration) (
|
||||
bool, error,
|
||||
) {
|
||||
var found bool
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
daemonset, err := client.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
// Check for the last-reloaded-from annotation in pod template
|
||||
if daemonset.Spec.Template.Annotations != nil {
|
||||
if _, ok := daemonset.Spec.Template.Annotations[cfg.Annotations.LastReloadedFrom]; ok {
|
||||
found = true
|
||||
return true, nil
|
||||
err := wait.PollUntilContextTimeout(
|
||||
ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
daemonset, err := client.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
// Check for the last-reloaded-from annotation in pod template
|
||||
if daemonset.Spec.Template.Annotations != nil {
|
||||
if _, ok := daemonset.Spec.Template.Annotations[annotationName]; ok {
|
||||
found = true
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
)
|
||||
if wait.Interrupted(err) {
|
||||
return found, nil
|
||||
}
|
||||
return found, err
|
||||
}
|
||||
|
||||
// WaitForStatefulSetReloadedAnnotation waits for a statefulset to have any reloaded annotation.
|
||||
func WaitForStatefulSetReloadedAnnotation(client kubernetes.Interface, namespace, name string, cfg *config.Config, timeout time.Duration) (bool, error) {
|
||||
// WaitForStatefulSetReloadedAnnotation waits for a statefulset to have the specified reloaded annotation.
|
||||
func WaitForStatefulSetReloadedAnnotation(client kubernetes.Interface, namespace, name, annotationName string, timeout time.Duration) (
|
||||
bool, error,
|
||||
) {
|
||||
var found bool
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
statefulset, err := client.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
// Check for the last-reloaded-from annotation in pod template
|
||||
if statefulset.Spec.Template.Annotations != nil {
|
||||
if _, ok := statefulset.Spec.Template.Annotations[cfg.Annotations.LastReloadedFrom]; ok {
|
||||
found = true
|
||||
return true, nil
|
||||
err := wait.PollUntilContextTimeout(
|
||||
ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
statefulset, err := client.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
// Check for the last-reloaded-from annotation in pod template
|
||||
if statefulset.Spec.Template.Annotations != nil {
|
||||
if _, ok := statefulset.Spec.Template.Annotations[annotationName]; ok {
|
||||
found = true
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
)
|
||||
if wait.Interrupted(err) {
|
||||
return found, nil
|
||||
}
|
||||
return found, err
|
||||
}
|
||||
|
||||
// NewOpenshiftClient creates an OpenShift client from the given rest config.
|
||||
func NewOpenshiftClient(restCfg *rest.Config) (openshiftclient.Interface, error) {
|
||||
return openshiftclient.NewForConfig(restCfg)
|
||||
}
|
||||
|
||||
// CreateDeploymentConfig creates a DeploymentConfig that references a ConfigMap/Secret.
|
||||
func CreateDeploymentConfig(client openshiftclient.Interface, name, namespace string, useConfigMap bool, annotations map[string]string) (
|
||||
*openshiftv1.DeploymentConfig, error,
|
||||
) {
|
||||
var dc *openshiftv1.DeploymentConfig
|
||||
if useConfigMap {
|
||||
dc = NewDeploymentConfigWithEnvFrom(name, namespace, name, "")
|
||||
} else {
|
||||
dc = NewDeploymentConfigWithEnvFrom(name, namespace, "", name)
|
||||
}
|
||||
dc.Annotations = annotations
|
||||
dc.Spec.Template.Spec.Containers[0].Image = "busybox:1.36"
|
||||
dc.Spec.Template.Spec.Containers[0].Command = []string{"sh", "-c", "while true; do sleep 3600; done"}
|
||||
|
||||
return client.AppsV1().DeploymentConfigs(namespace).Create(context.Background(), dc, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
// DeleteDeploymentConfig deletes the DeploymentConfig with the given name.
|
||||
func DeleteDeploymentConfig(client openshiftclient.Interface, namespace, name string) error {
|
||||
return client.AppsV1().DeploymentConfigs(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
// WaitForDeploymentConfigReloadedAnnotation waits for a DeploymentConfig to have the specified reloaded annotation.
|
||||
func WaitForDeploymentConfigReloadedAnnotation(client openshiftclient.Interface, namespace, name, annotationName string, timeout time.Duration) (
|
||||
bool, error,
|
||||
) {
|
||||
var found bool
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
err := wait.PollUntilContextTimeout(
|
||||
ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) {
|
||||
dc, err := client.AppsV1().DeploymentConfigs(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false, nil // Keep waiting
|
||||
}
|
||||
if dc.Spec.Template != nil && dc.Spec.Template.Annotations != nil {
|
||||
if _, ok := dc.Spec.Template.Annotations[annotationName]; ok {
|
||||
found = true
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
)
|
||||
if wait.Interrupted(err) {
|
||||
return found, nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user