mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-20 04:26:28 +00:00
feat: DeploymentConfig support
This commit is contained in:
@@ -14,12 +14,14 @@ import (
|
||||
"github.com/go-logr/zerologr"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/client-go/discovery"
|
||||
controllerruntime "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/config"
|
||||
"github.com/stakater/Reloader/internal/pkg/controller"
|
||||
"github.com/stakater/Reloader/internal/pkg/metadata"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/openshift"
|
||||
)
|
||||
|
||||
// Environment variable names for pod identity in HA mode.
|
||||
@@ -115,6 +117,16 @@ func run(cmd *cobra.Command, args []string) error {
|
||||
return fmt.Errorf("creating manager: %w", err)
|
||||
}
|
||||
|
||||
if config.ShouldAutoDetectOpenShift() {
|
||||
restConfig := controllerruntime.GetConfigOrDie()
|
||||
discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig)
|
||||
if err != nil {
|
||||
log.V(1).Info("Failed to create discovery client for DeploymentConfig detection", "error", err)
|
||||
} else if openshift.HasDeploymentConfigSupport(discoveryClient, log) {
|
||||
cfg.DeploymentConfigEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := controller.SetupReconcilers(mgr, cfg, log, &collectors); err != nil {
|
||||
return fmt.Errorf("setting up reconcilers: %w", err)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,14 @@ require (
|
||||
github.com/argoproj/argo-rollouts v1.8.3
|
||||
github.com/go-logr/logr v1.4.3
|
||||
github.com/go-logr/zerologr v1.2.3
|
||||
github.com/openshift/api v0.0.0-20251223163548-3f584b29ee4a
|
||||
github.com/openshift/client-go v0.0.0-20251223102348-558b0eef16bc
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.21.0
|
||||
k8s.io/api v0.35.0
|
||||
k8s.io/apimachinery v0.35.0
|
||||
k8s.io/client-go v0.35.0
|
||||
@@ -47,7 +50,6 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -57,11 +59,10 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common v0.67.4 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
|
||||
@@ -8,7 +8,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -19,6 +18,8 @@ github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCv
|
||||
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
|
||||
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
@@ -116,6 +117,12 @@ github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/openshift/api v0.0.0-20251222154221-d4b2fef98af2 h1:q7fp/9fnJuXWCzpBzbHZne6aMLGYPKhzPy1uULfaJqA=
|
||||
github.com/openshift/api v0.0.0-20251222154221-d4b2fef98af2/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
|
||||
github.com/openshift/api v0.0.0-20251223163548-3f584b29ee4a h1:lz22938uOBlzTHjGpobGeVWkcxGu6fDQ7oZWheClTHE=
|
||||
github.com/openshift/api v0.0.0-20251223163548-3f584b29ee4a/go.mod h1:d5uzF0YN2nQQFA0jIEWzzOZ+edmo6wzlGLvx5Fhz4uY=
|
||||
github.com/openshift/client-go v0.0.0-20251223102348-558b0eef16bc h1:nIlRaJfr/yGjPV15MNF5eVHLAGyXFjcUzO+hXeWDDk8=
|
||||
github.com/openshift/client-go v0.0.0-20251223102348-558b0eef16bc/go.mod h1:cs9BwTu96sm2vQvy7r9rOiltgu90M6ju2qIHFG9WU+o=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -138,6 +145,8 @@ github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
|
||||
@@ -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
+128
-10
@@ -10,12 +10,15 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/zerologr"
|
||||
openshiftclient "github.com/openshift/client-go/apps/clientset/versioned"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stakater/Reloader/internal/pkg/config"
|
||||
"github.com/stakater/Reloader/internal/pkg/controller"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/openshift"
|
||||
"github.com/stakater/Reloader/internal/pkg/testutil"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
@@ -32,12 +35,14 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
k8sClient kubernetes.Interface
|
||||
cfg *config.Config
|
||||
namespace string
|
||||
skipE2ETests bool
|
||||
cancelManager context.CancelFunc
|
||||
restCfg *rest.Config
|
||||
k8sClient kubernetes.Interface
|
||||
osClient openshiftclient.Interface
|
||||
cfg *config.Config
|
||||
namespace string
|
||||
skipE2ETests bool
|
||||
skipDeploymentConfigTests bool
|
||||
cancelManager context.CancelFunc
|
||||
restCfg *rest.Config
|
||||
)
|
||||
|
||||
// testFixture provides a clean way to set up and tear down test resources.
|
||||
@@ -155,7 +160,7 @@ func (f *testFixture) assertDeploymentReloaded(name string, testCfg *config.Conf
|
||||
if testCfg == nil {
|
||||
testCfg = cfg
|
||||
}
|
||||
updated, err := testutil.WaitForDeploymentReloadedAnnotation(k8sClient, namespace, name, testCfg, waitTimeout)
|
||||
updated, err := testutil.WaitForDeploymentReloadedAnnotation(k8sClient, namespace, name, testCfg.Annotations.LastReloadedFrom, waitTimeout)
|
||||
if err != nil {
|
||||
f.t.Fatalf("Error waiting for deployment %s update: %v", name, err)
|
||||
}
|
||||
@@ -171,7 +176,7 @@ func (f *testFixture) assertDeploymentNotReloaded(name string, testCfg *config.C
|
||||
testCfg = cfg
|
||||
}
|
||||
time.Sleep(negativeTestTimeout)
|
||||
updated, _ := testutil.WaitForDeploymentReloadedAnnotation(k8sClient, namespace, name, testCfg, negativeTestTimeout)
|
||||
updated, _ := testutil.WaitForDeploymentReloadedAnnotation(k8sClient, namespace, name, testCfg.Annotations.LastReloadedFrom, negativeTestTimeout)
|
||||
if updated {
|
||||
f.t.Errorf("Deployment %s should not have been updated", name)
|
||||
}
|
||||
@@ -180,7 +185,7 @@ func (f *testFixture) assertDeploymentNotReloaded(name string, testCfg *config.C
|
||||
// assertDaemonSetReloaded asserts that a daemonset was reloaded.
|
||||
func (f *testFixture) assertDaemonSetReloaded(name string) {
|
||||
f.t.Helper()
|
||||
updated, err := testutil.WaitForDaemonSetReloadedAnnotation(k8sClient, namespace, name, cfg, waitTimeout)
|
||||
updated, err := testutil.WaitForDaemonSetReloadedAnnotation(k8sClient, namespace, name, cfg.Annotations.LastReloadedFrom, waitTimeout)
|
||||
if err != nil {
|
||||
f.t.Fatalf("Error waiting for daemonset %s update: %v", name, err)
|
||||
}
|
||||
@@ -192,7 +197,7 @@ func (f *testFixture) assertDaemonSetReloaded(name string) {
|
||||
// assertStatefulSetReloaded asserts that a statefulset was reloaded.
|
||||
func (f *testFixture) assertStatefulSetReloaded(name string) {
|
||||
f.t.Helper()
|
||||
updated, err := testutil.WaitForStatefulSetReloadedAnnotation(k8sClient, namespace, name, cfg, waitTimeout)
|
||||
updated, err := testutil.WaitForStatefulSetReloadedAnnotation(k8sClient, namespace, name, cfg.Annotations.LastReloadedFrom, waitTimeout)
|
||||
if err != nil {
|
||||
f.t.Fatalf("Error waiting for statefulset %s update: %v", name, err)
|
||||
}
|
||||
@@ -201,6 +206,28 @@ func (f *testFixture) assertStatefulSetReloaded(name string) {
|
||||
}
|
||||
}
|
||||
|
||||
// createDeploymentConfig creates a DeploymentConfig and registers it for cleanup.
|
||||
func (f *testFixture) createDeploymentConfig(name string, useConfigMap bool, annotations map[string]string) {
|
||||
f.t.Helper()
|
||||
_, err := testutil.CreateDeploymentConfig(osClient, name, namespace, useConfigMap, annotations)
|
||||
if err != nil {
|
||||
f.t.Fatalf("Failed to create DeploymentConfig %s: %v", name, err)
|
||||
}
|
||||
f.workloads = append(f.workloads, workloadInfo{name: name, kind: "deploymentconfig"})
|
||||
}
|
||||
|
||||
// assertDeploymentConfigReloaded asserts that a DeploymentConfig was reloaded.
|
||||
func (f *testFixture) assertDeploymentConfigReloaded(name string) {
|
||||
f.t.Helper()
|
||||
updated, err := testutil.WaitForDeploymentConfigReloadedAnnotation(osClient, namespace, name, cfg.Annotations.LastReloadedFrom, waitTimeout)
|
||||
if err != nil {
|
||||
f.t.Fatalf("Error waiting for DeploymentConfig %s update: %v", name, err)
|
||||
}
|
||||
if !updated {
|
||||
f.t.Errorf("DeploymentConfig %s was not updated after resource change", name)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup removes all created resources.
|
||||
func (f *testFixture) cleanup() {
|
||||
for _, w := range f.workloads {
|
||||
@@ -211,6 +238,10 @@ func (f *testFixture) cleanup() {
|
||||
_ = testutil.DeleteDaemonSet(k8sClient, namespace, w.name)
|
||||
case "statefulset":
|
||||
_ = testutil.DeleteStatefulSet(k8sClient, namespace, w.name)
|
||||
case "deploymentconfig":
|
||||
if osClient != nil {
|
||||
_ = testutil.DeleteDeploymentConfig(osClient, namespace, w.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range f.configMaps {
|
||||
@@ -266,6 +297,25 @@ func TestMain(m *testing.M) {
|
||||
cfg = config.NewDefault()
|
||||
cfg.AutoReloadAll = false
|
||||
|
||||
// Check if cluster supports DeploymentConfig
|
||||
discoveryClient, err := discovery.NewDiscoveryClientForConfig(restCfg)
|
||||
if err != nil {
|
||||
skipDeploymentConfigTests = true
|
||||
} else {
|
||||
// Use a nop logger for detection
|
||||
nopLog := ctrl.Log.WithName("dc-detection")
|
||||
if openshift.HasDeploymentConfigSupport(discoveryClient, nopLog) {
|
||||
cfg.DeploymentConfigEnabled = true
|
||||
// Create OpenShift client for DeploymentConfig tests
|
||||
osClient, err = testutil.NewOpenshiftClient(restCfg)
|
||||
if err != nil {
|
||||
skipDeploymentConfigTests = true
|
||||
}
|
||||
} else {
|
||||
skipDeploymentConfigTests = true
|
||||
}
|
||||
}
|
||||
|
||||
_, cancelManager = startManagerWithConfig(cfg, restCfg)
|
||||
|
||||
code := m.Run()
|
||||
@@ -285,6 +335,13 @@ func skipIfNoCluster(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func skipIfNoDeploymentConfig(t *testing.T) {
|
||||
skipIfNoCluster(t)
|
||||
if skipDeploymentConfigTests {
|
||||
t.Skip("Skipping DeploymentConfig test: cluster does not support DeploymentConfig API")
|
||||
}
|
||||
}
|
||||
|
||||
// TestConfigMapUpdate tests that updating a ConfigMap triggers a workload reload.
|
||||
func TestConfigMapUpdate(t *testing.T) {
|
||||
f := newFixture(t, "cm-update")
|
||||
@@ -497,6 +554,67 @@ func TestAutoWithBothExplicitAndReferencedChange(t *testing.T) {
|
||||
f.assertDeploymentReloaded(referencedCM, nil)
|
||||
}
|
||||
|
||||
// newFixtureForDeploymentConfig creates a new test fixture for DeploymentConfig tests.
|
||||
func newFixtureForDeploymentConfig(t *testing.T, prefix string) *testFixture {
|
||||
t.Helper()
|
||||
skipIfNoDeploymentConfig(t)
|
||||
return &testFixture{
|
||||
t: t,
|
||||
name: prefix + "-" + testutil.RandSeq(5),
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeploymentConfigReloadConfigMap tests that updating a ConfigMap triggers a DeploymentConfig reload.
|
||||
func TestDeploymentConfigReloadConfigMap(t *testing.T) {
|
||||
f := newFixtureForDeploymentConfig(t, "dc-cm-reload")
|
||||
defer f.cleanup()
|
||||
|
||||
f.createConfigMap(f.name, "initial-data")
|
||||
f.createDeploymentConfig(
|
||||
f.name, true, map[string]string{
|
||||
cfg.Annotations.ConfigmapReload: f.name,
|
||||
},
|
||||
)
|
||||
f.waitForReady()
|
||||
|
||||
f.updateConfigMap(f.name, "updated-data")
|
||||
f.assertDeploymentConfigReloaded(f.name)
|
||||
}
|
||||
|
||||
// TestDeploymentConfigReloadSecret tests that updating a Secret triggers a DeploymentConfig reload.
|
||||
func TestDeploymentConfigReloadSecret(t *testing.T) {
|
||||
f := newFixtureForDeploymentConfig(t, "dc-secret-reload")
|
||||
defer f.cleanup()
|
||||
|
||||
f.createSecret(f.name, "initial-secret")
|
||||
f.createDeploymentConfig(
|
||||
f.name, false, map[string]string{
|
||||
cfg.Annotations.SecretReload: f.name,
|
||||
},
|
||||
)
|
||||
f.waitForReady()
|
||||
|
||||
f.updateSecret(f.name, "updated-secret")
|
||||
f.assertDeploymentConfigReloaded(f.name)
|
||||
}
|
||||
|
||||
// TestDeploymentConfigAutoReload tests the auto-reload annotation on DeploymentConfig.
|
||||
func TestDeploymentConfigAutoReload(t *testing.T) {
|
||||
f := newFixtureForDeploymentConfig(t, "dc-auto-reload")
|
||||
defer f.cleanup()
|
||||
|
||||
f.createConfigMap(f.name, "initial-data")
|
||||
f.createDeploymentConfig(
|
||||
f.name, true, map[string]string{
|
||||
cfg.Annotations.Auto: "true",
|
||||
},
|
||||
)
|
||||
f.waitForReady()
|
||||
|
||||
f.updateConfigMap(f.name, "updated-data")
|
||||
f.assertDeploymentConfigReloaded(f.name)
|
||||
}
|
||||
|
||||
// startManagerWithConfig creates and starts a controller-runtime manager for e2e testing.
|
||||
func startManagerWithConfig(cfg *config.Config, restConfig *rest.Config) (manager.Manager, context.CancelFunc) {
|
||||
collectors := metrics.NewCollectors()
|
||||
|
||||
Reference in New Issue
Block a user