mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-20 04:26:28 +00:00
feat: Implement NamespaceReconciler for namespace label selector filtering
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
//go:build integration
|
||||
// +build integration
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/stakater/Reloader/internal/pkg/config"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
// NamespaceCache provides thread-safe access to the set of namespaces
|
||||
// that match the configured namespace label selector.
|
||||
type NamespaceCache struct {
|
||||
mu sync.RWMutex
|
||||
namespaces map[string]struct{}
|
||||
enabled bool
|
||||
}
|
||||
|
||||
// NewNamespaceCache creates a new NamespaceCache.
|
||||
// If enabled is false, all namespace checks return true (allow all).
|
||||
func NewNamespaceCache(enabled bool) *NamespaceCache {
|
||||
return &NamespaceCache{
|
||||
namespaces: make(map[string]struct{}),
|
||||
enabled: enabled,
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a namespace to the cache.
|
||||
func (c *NamespaceCache) Add(name string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.namespaces[name] = struct{}{}
|
||||
}
|
||||
|
||||
// Remove removes a namespace from the cache.
|
||||
func (c *NamespaceCache) Remove(name string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
delete(c.namespaces, name)
|
||||
}
|
||||
|
||||
// Contains checks if a namespace is in the cache.
|
||||
// If namespace selectors are not enabled, always returns true.
|
||||
func (c *NamespaceCache) Contains(name string) bool {
|
||||
if !c.enabled {
|
||||
return true
|
||||
}
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
_, ok := c.namespaces[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// List returns a copy of all namespace names in the cache.
|
||||
func (c *NamespaceCache) List() []string {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
result := make([]string, 0, len(c.namespaces))
|
||||
for name := range c.namespaces {
|
||||
result = append(result, name)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IsEnabled returns whether namespace selector filtering is enabled.
|
||||
func (c *NamespaceCache) IsEnabled() bool {
|
||||
return c.enabled
|
||||
}
|
||||
|
||||
// NamespaceReconciler watches Namespace objects and maintains a cache
|
||||
// of namespaces that match the configured label selector.
|
||||
type NamespaceReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Config *config.Config
|
||||
Cache *NamespaceCache
|
||||
}
|
||||
|
||||
// Reconcile handles Namespace events and updates the namespace cache.
|
||||
func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := r.Log.WithValues("namespace", req.Name)
|
||||
|
||||
var ns corev1.Namespace
|
||||
if err := r.Get(ctx, req.NamespacedName, &ns); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
// Namespace was deleted - remove from cache
|
||||
r.Cache.Remove(req.Name)
|
||||
log.V(1).Info("removed namespace from cache (deleted)")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
log.Error(err, "failed to get Namespace")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Check if namespace matches any of the configured selectors
|
||||
if r.matchesSelectors(&ns) {
|
||||
r.Cache.Add(ns.Name)
|
||||
log.V(1).Info("added namespace to cache")
|
||||
} else {
|
||||
// Labels might have changed, remove from cache if no longer matches
|
||||
r.Cache.Remove(ns.Name)
|
||||
log.V(1).Info("removed namespace from cache (labels no longer match)")
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// matchesSelectors checks if the namespace matches any configured label selector.
|
||||
func (r *NamespaceReconciler) matchesSelectors(ns *corev1.Namespace) bool {
|
||||
if len(r.Config.NamespaceSelectors) == 0 {
|
||||
// No selectors configured - should not happen since reconciler is only
|
||||
// set up when selectors are configured, but handle gracefully
|
||||
return true
|
||||
}
|
||||
|
||||
nsLabels := ns.GetLabels()
|
||||
if nsLabels == nil {
|
||||
nsLabels = make(map[string]string)
|
||||
}
|
||||
|
||||
for _, selector := range r.Config.NamespaceSelectors {
|
||||
if selector.Matches(nsLabelsSet(nsLabels)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// nsLabelsSet implements labels.Labels interface for a map.
|
||||
type nsLabelsSet map[string]string
|
||||
|
||||
func (ls nsLabelsSet) Has(key string) bool {
|
||||
_, ok := ls[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (ls nsLabelsSet) Get(key string) string {
|
||||
return ls[key]
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&corev1.Namespace{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
// Ensure NamespaceReconciler implements reconcile.Reconciler
|
||||
var _ reconcile.Reconciler = &NamespaceReconciler{}
|
||||
@@ -0,0 +1,295 @@
|
||||
package controller_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr/testr"
|
||||
"github.com/stakater/Reloader/internal/pkg/config"
|
||||
"github.com/stakater/Reloader/internal/pkg/controller"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
func TestNamespaceCache_Basic(t *testing.T) {
|
||||
cache := controller.NewNamespaceCache(true)
|
||||
|
||||
// Test Add and Contains
|
||||
cache.Add("namespace-1")
|
||||
if !cache.Contains("namespace-1") {
|
||||
t.Error("Cache should contain namespace-1")
|
||||
}
|
||||
if cache.Contains("namespace-2") {
|
||||
t.Error("Cache should not contain namespace-2")
|
||||
}
|
||||
|
||||
// Test Remove
|
||||
cache.Remove("namespace-1")
|
||||
if cache.Contains("namespace-1") {
|
||||
t.Error("Cache should not contain namespace-1 after removal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceCache_Disabled(t *testing.T) {
|
||||
cache := controller.NewNamespaceCache(false)
|
||||
|
||||
// When disabled, Contains should always return true
|
||||
if !cache.Contains("any-namespace") {
|
||||
t.Error("Disabled cache should return true for any namespace")
|
||||
}
|
||||
if !cache.Contains("other-namespace") {
|
||||
t.Error("Disabled cache should return true for any namespace")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceCache_List(t *testing.T) {
|
||||
cache := controller.NewNamespaceCache(true)
|
||||
cache.Add("ns-1")
|
||||
cache.Add("ns-2")
|
||||
cache.Add("ns-3")
|
||||
|
||||
list := cache.List()
|
||||
if len(list) != 3 {
|
||||
t.Errorf("Expected 3 namespaces, got %d", len(list))
|
||||
}
|
||||
|
||||
// Check all namespaces are in the list
|
||||
found := make(map[string]bool)
|
||||
for _, ns := range list {
|
||||
found[ns] = true
|
||||
}
|
||||
for _, expected := range []string{"ns-1", "ns-2", "ns-3"} {
|
||||
if !found[expected] {
|
||||
t.Errorf("Expected %s in list", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceCache_IsEnabled(t *testing.T) {
|
||||
enabledCache := controller.NewNamespaceCache(true)
|
||||
disabledCache := controller.NewNamespaceCache(false)
|
||||
|
||||
if !enabledCache.IsEnabled() {
|
||||
t.Error("EnabledCache.IsEnabled() should return true")
|
||||
}
|
||||
if disabledCache.IsEnabled() {
|
||||
t.Error("DisabledCache.IsEnabled() should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceReconciler_Add(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ns",
|
||||
Labels: map[string]string{"env": "production"},
|
||||
},
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ns).
|
||||
Build()
|
||||
|
||||
cfg := config.NewDefault()
|
||||
selector, _ := labels.Parse("env=production")
|
||||
cfg.NamespaceSelectors = []labels.Selector{selector}
|
||||
|
||||
cache := controller.NewNamespaceCache(true)
|
||||
reconciler := &controller.NamespaceReconciler{
|
||||
Client: fakeClient,
|
||||
Log: testr.New(t),
|
||||
Config: cfg,
|
||||
Cache: cache,
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{Name: "test-ns"},
|
||||
}
|
||||
|
||||
_, err := reconciler.Reconcile(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Reconcile failed: %v", err)
|
||||
}
|
||||
|
||||
if !cache.Contains("test-ns") {
|
||||
t.Error("Cache should contain test-ns after reconcile")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceReconciler_Remove_LabelChange(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Namespace with non-matching labels
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ns",
|
||||
Labels: map[string]string{"env": "staging"},
|
||||
},
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ns).
|
||||
Build()
|
||||
|
||||
cfg := config.NewDefault()
|
||||
selector, _ := labels.Parse("env=production")
|
||||
cfg.NamespaceSelectors = []labels.Selector{selector}
|
||||
|
||||
cache := controller.NewNamespaceCache(true)
|
||||
// Pre-populate cache
|
||||
cache.Add("test-ns")
|
||||
|
||||
reconciler := &controller.NamespaceReconciler{
|
||||
Client: fakeClient,
|
||||
Log: testr.New(t),
|
||||
Config: cfg,
|
||||
Cache: cache,
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{Name: "test-ns"},
|
||||
}
|
||||
|
||||
_, err := reconciler.Reconcile(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Reconcile failed: %v", err)
|
||||
}
|
||||
|
||||
if cache.Contains("test-ns") {
|
||||
t.Error("Cache should not contain test-ns after reconcile (labels no longer match)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceReconciler_Remove_Delete(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// No namespace in cluster (simulates delete)
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
Build()
|
||||
|
||||
cfg := config.NewDefault()
|
||||
selector, _ := labels.Parse("env=production")
|
||||
cfg.NamespaceSelectors = []labels.Selector{selector}
|
||||
|
||||
cache := controller.NewNamespaceCache(true)
|
||||
// Pre-populate cache
|
||||
cache.Add("deleted-ns")
|
||||
|
||||
reconciler := &controller.NamespaceReconciler{
|
||||
Client: fakeClient,
|
||||
Log: testr.New(t),
|
||||
Config: cfg,
|
||||
Cache: cache,
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{Name: "deleted-ns"},
|
||||
}
|
||||
|
||||
_, err := reconciler.Reconcile(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Reconcile failed: %v", err)
|
||||
}
|
||||
|
||||
if cache.Contains("deleted-ns") {
|
||||
t.Error("Cache should not contain deleted-ns after reconcile")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceReconciler_MultipleSelectors(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ns",
|
||||
Labels: map[string]string{"team": "platform"},
|
||||
},
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ns).
|
||||
Build()
|
||||
|
||||
cfg := config.NewDefault()
|
||||
selector1, _ := labels.Parse("env=production")
|
||||
selector2, _ := labels.Parse("team=platform")
|
||||
cfg.NamespaceSelectors = []labels.Selector{selector1, selector2}
|
||||
|
||||
cache := controller.NewNamespaceCache(true)
|
||||
reconciler := &controller.NamespaceReconciler{
|
||||
Client: fakeClient,
|
||||
Log: testr.New(t),
|
||||
Config: cfg,
|
||||
Cache: cache,
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{Name: "test-ns"},
|
||||
}
|
||||
|
||||
_, err := reconciler.Reconcile(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Reconcile failed: %v", err)
|
||||
}
|
||||
|
||||
// Should be added because it matches second selector (team=platform)
|
||||
if !cache.Contains("test-ns") {
|
||||
t.Error("Cache should contain test-ns (matches second selector)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceReconciler_NoLabels(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
_ = corev1.AddToScheme(scheme)
|
||||
|
||||
// Namespace with no labels
|
||||
ns := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ns",
|
||||
},
|
||||
}
|
||||
|
||||
fakeClient := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithObjects(ns).
|
||||
Build()
|
||||
|
||||
cfg := config.NewDefault()
|
||||
selector, _ := labels.Parse("env=production")
|
||||
cfg.NamespaceSelectors = []labels.Selector{selector}
|
||||
|
||||
cache := controller.NewNamespaceCache(true)
|
||||
reconciler := &controller.NamespaceReconciler{
|
||||
Client: fakeClient,
|
||||
Log: testr.New(t),
|
||||
Config: cfg,
|
||||
Cache: cache,
|
||||
}
|
||||
|
||||
req := ctrl.Request{
|
||||
NamespacedName: types.NamespacedName{Name: "test-ns"},
|
||||
}
|
||||
|
||||
_, err := reconciler.Reconcile(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatalf("Reconcile failed: %v", err)
|
||||
}
|
||||
|
||||
if cache.Contains("test-ns") {
|
||||
t.Error("Cache should not contain test-ns (no labels)")
|
||||
}
|
||||
}
|
||||
@@ -72,8 +72,19 @@ func SecretPredicates(cfg *config.Config, hasher *Hasher) predicate.Predicate {
|
||||
}
|
||||
}
|
||||
|
||||
// NamespaceChecker defines the interface for checking if a namespace is allowed.
|
||||
type NamespaceChecker interface {
|
||||
Contains(name string) bool
|
||||
}
|
||||
|
||||
// NamespaceFilterPredicate returns a predicate that filters resources by namespace.
|
||||
func NamespaceFilterPredicate(cfg *config.Config) predicate.Predicate {
|
||||
return NamespaceFilterPredicateWithCache(cfg, nil)
|
||||
}
|
||||
|
||||
// NamespaceFilterPredicateWithCache returns a predicate that filters resources by namespace,
|
||||
// using the provided NamespaceChecker for namespace selector filtering.
|
||||
func NamespaceFilterPredicateWithCache(cfg *config.Config, nsCache NamespaceChecker) predicate.Predicate {
|
||||
return predicate.NewPredicateFuncs(func(obj client.Object) bool {
|
||||
namespace := obj.GetNamespace()
|
||||
|
||||
@@ -82,9 +93,11 @@ func NamespaceFilterPredicate(cfg *config.Config) predicate.Predicate {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check namespace selectors
|
||||
// Note: For now, we pass through and let the controller handle selector matching
|
||||
// A more efficient implementation would check labels here
|
||||
// Check namespace selector cache if provided
|
||||
if nsCache != nil && !nsCache.Contains(namespace) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -500,3 +500,110 @@ func TestExistsLabelSelector(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// mockNamespaceChecker implements NamespaceChecker for testing.
|
||||
type mockNamespaceChecker struct {
|
||||
allowed map[string]bool
|
||||
}
|
||||
|
||||
func (m *mockNamespaceChecker) Contains(name string) bool {
|
||||
return m.allowed[name]
|
||||
}
|
||||
|
||||
func TestNamespaceFilterPredicateWithCache(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ignoredNamespaces []string
|
||||
cacheAllowed map[string]bool
|
||||
eventNamespace string
|
||||
wantAllow bool
|
||||
}{
|
||||
{
|
||||
name: "allowed by cache and not ignored",
|
||||
ignoredNamespaces: []string{"kube-system"},
|
||||
cacheAllowed: map[string]bool{"production": true},
|
||||
eventNamespace: "production",
|
||||
wantAllow: true,
|
||||
},
|
||||
{
|
||||
name: "blocked by cache",
|
||||
ignoredNamespaces: []string{},
|
||||
cacheAllowed: map[string]bool{"production": true},
|
||||
eventNamespace: "staging",
|
||||
wantAllow: false,
|
||||
},
|
||||
{
|
||||
name: "blocked by ignore list even if in cache",
|
||||
ignoredNamespaces: []string{"kube-system"},
|
||||
cacheAllowed: map[string]bool{"kube-system": true},
|
||||
eventNamespace: "kube-system",
|
||||
wantAllow: false,
|
||||
},
|
||||
{
|
||||
name: "ignore list checked before cache",
|
||||
ignoredNamespaces: []string{"blocked-ns"},
|
||||
cacheAllowed: map[string]bool{"blocked-ns": true},
|
||||
eventNamespace: "blocked-ns",
|
||||
wantAllow: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
cfg.IgnoredNamespaces = tt.ignoredNamespaces
|
||||
|
||||
cache := &mockNamespaceChecker{allowed: tt.cacheAllowed}
|
||||
predicate := NamespaceFilterPredicateWithCache(cfg, cache)
|
||||
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-cm",
|
||||
Namespace: tt.eventNamespace,
|
||||
},
|
||||
}
|
||||
|
||||
e := event.CreateEvent{Object: cm}
|
||||
got := predicate.Create(e)
|
||||
|
||||
if got != tt.wantAllow {
|
||||
t.Errorf("Create() = %v, want %v", got, tt.wantAllow)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceFilterPredicateWithCache_NilCache(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
cfg.IgnoredNamespaces = []string{"kube-system"}
|
||||
|
||||
// Nil cache should allow all namespaces (only check ignore list)
|
||||
predicate := NamespaceFilterPredicateWithCache(cfg, nil)
|
||||
|
||||
tests := []struct {
|
||||
namespace string
|
||||
wantAllow bool
|
||||
}{
|
||||
{"default", true},
|
||||
{"production", true},
|
||||
{"kube-system", false}, // Should still respect ignore list
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.namespace, func(t *testing.T) {
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-cm",
|
||||
Namespace: tt.namespace,
|
||||
},
|
||||
}
|
||||
|
||||
e := event.CreateEvent{Object: cm}
|
||||
got := predicate.Create(e)
|
||||
|
||||
if got != tt.wantAllow {
|
||||
t.Errorf("Create() = %v, want %v for namespace %s", got, tt.wantAllow, tt.namespace)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,694 @@
|
||||
package workload
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestDeploymentWorkload_BasicGetters(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-deploy",
|
||||
Namespace: "test-ns",
|
||||
Annotations: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if w.Kind() != KindDeployment {
|
||||
t.Errorf("Kind() = %v, want %v", w.Kind(), KindDeployment)
|
||||
}
|
||||
if w.GetName() != "test-deploy" {
|
||||
t.Errorf("GetName() = %v, want test-deploy", w.GetName())
|
||||
}
|
||||
if w.GetNamespace() != "test-ns" {
|
||||
t.Errorf("GetNamespace() = %v, want test-ns", w.GetNamespace())
|
||||
}
|
||||
if w.GetAnnotations()["key"] != "value" {
|
||||
t.Errorf("GetAnnotations()[key] = %v, want value", w.GetAnnotations()["key"])
|
||||
}
|
||||
if w.GetObject() != deploy {
|
||||
t.Error("GetObject() should return the underlying deployment")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_PodTemplateAnnotations(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"existing": "annotation",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
// Test get
|
||||
annotations := w.GetPodTemplateAnnotations()
|
||||
if annotations["existing"] != "annotation" {
|
||||
t.Errorf("GetPodTemplateAnnotations()[existing] = %v, want annotation", annotations["existing"])
|
||||
}
|
||||
|
||||
// Test set
|
||||
w.SetPodTemplateAnnotation("new-key", "new-value")
|
||||
if w.GetPodTemplateAnnotations()["new-key"] != "new-value" {
|
||||
t.Error("SetPodTemplateAnnotation should add new annotation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_PodTemplateAnnotations_NilInit(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
// No annotations set
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
// Should initialize nil map
|
||||
annotations := w.GetPodTemplateAnnotations()
|
||||
if annotations == nil {
|
||||
t.Error("GetPodTemplateAnnotations should initialize nil map")
|
||||
}
|
||||
|
||||
// Should work with nil initial map
|
||||
w.SetPodTemplateAnnotation("key", "value")
|
||||
if w.GetPodTemplateAnnotations()["key"] != "value" {
|
||||
t.Error("SetPodTemplateAnnotation should work with nil initial map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_Containers(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "main", Image: "nginx"},
|
||||
},
|
||||
InitContainers: []corev1.Container{
|
||||
{Name: "init", Image: "busybox"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
// Test get containers
|
||||
containers := w.GetContainers()
|
||||
if len(containers) != 1 || containers[0].Name != "main" {
|
||||
t.Errorf("GetContainers() = %v, want [main]", containers)
|
||||
}
|
||||
|
||||
// Test get init containers
|
||||
initContainers := w.GetInitContainers()
|
||||
if len(initContainers) != 1 || initContainers[0].Name != "init" {
|
||||
t.Errorf("GetInitContainers() = %v, want [init]", initContainers)
|
||||
}
|
||||
|
||||
// Test set containers
|
||||
newContainers := []corev1.Container{{Name: "new-main", Image: "alpine"}}
|
||||
w.SetContainers(newContainers)
|
||||
if w.GetContainers()[0].Name != "new-main" {
|
||||
t.Error("SetContainers should update containers")
|
||||
}
|
||||
|
||||
// Test set init containers
|
||||
newInitContainers := []corev1.Container{{Name: "new-init", Image: "alpine"}}
|
||||
w.SetInitContainers(newInitContainers)
|
||||
if w.GetInitContainers()[0].Name != "new-init" {
|
||||
t.Error("SetInitContainers should update init containers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_Volumes(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "config-vol"},
|
||||
{Name: "secret-vol"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
volumes := w.GetVolumes()
|
||||
if len(volumes) != 2 {
|
||||
t.Errorf("GetVolumes() length = %d, want 2", len(volumes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesConfigMap_Volume(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "config-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "my-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesConfigMap("my-config") {
|
||||
t.Error("UsesConfigMap should return true for ConfigMap volume")
|
||||
}
|
||||
if w.UsesConfigMap("other-config") {
|
||||
t.Error("UsesConfigMap should return false for non-existent ConfigMap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesConfigMap_ProjectedVolume(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "projected-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: []corev1.VolumeProjection{
|
||||
{
|
||||
ConfigMap: &corev1.ConfigMapProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "projected-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesConfigMap("projected-config") {
|
||||
t.Error("UsesConfigMap should return true for projected ConfigMap volume")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesConfigMap_EnvFrom(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "env-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesConfigMap("env-config") {
|
||||
t.Error("UsesConfigMap should return true for envFrom ConfigMap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesConfigMap_EnvVar(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "CONFIG_VALUE",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "var-config",
|
||||
},
|
||||
Key: "some-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesConfigMap("var-config") {
|
||||
t.Error("UsesConfigMap should return true for env var ConfigMapKeyRef")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesConfigMap_InitContainer(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "init-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesConfigMap("init-config") {
|
||||
t.Error("UsesConfigMap should return true for init container ConfigMap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesSecret_Volume(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "secret-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: "my-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesSecret("my-secret") {
|
||||
t.Error("UsesSecret should return true for Secret volume")
|
||||
}
|
||||
if w.UsesSecret("other-secret") {
|
||||
t.Error("UsesSecret should return false for non-existent Secret")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesSecret_ProjectedVolume(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "projected-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: []corev1.VolumeProjection{
|
||||
{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "projected-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesSecret("projected-secret") {
|
||||
t.Error("UsesSecret should return true for projected Secret volume")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesSecret_EnvFrom(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "env-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesSecret("env-secret") {
|
||||
t.Error("UsesSecret should return true for envFrom Secret")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesSecret_EnvVar(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "SECRET_VALUE",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "var-secret",
|
||||
},
|
||||
Key: "some-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesSecret("var-secret") {
|
||||
t.Error("UsesSecret should return true for env var SecretKeyRef")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_UsesSecret_InitContainer(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "init-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
if !w.UsesSecret("init-secret") {
|
||||
t.Error("UsesSecret should return true for init container Secret")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_GetEnvFromSources(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{ConfigMapRef: &corev1.ConfigMapEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "cm1"}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "sidecar",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "secret1"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{ConfigMapRef: &corev1.ConfigMapEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "init-cm"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
sources := w.GetEnvFromSources()
|
||||
if len(sources) != 3 {
|
||||
t.Errorf("GetEnvFromSources() returned %d sources, want 3", len(sources))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_DeepCopy(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "main", Image: "nginx"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
copy := w.DeepCopy()
|
||||
|
||||
// Modify original
|
||||
w.SetPodTemplateAnnotation("modified", "true")
|
||||
|
||||
// Copy should not be affected
|
||||
copyAnnotations := copy.GetPodTemplateAnnotations()
|
||||
if copyAnnotations["modified"] == "true" {
|
||||
t.Error("DeepCopy should create independent copy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentWorkload_GetOwnerReferences(t *testing.T) {
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "test-rs",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDeploymentWorkload(deploy)
|
||||
|
||||
refs := w.GetOwnerReferences()
|
||||
if len(refs) != 1 || refs[0].Name != "test-rs" {
|
||||
t.Errorf("GetOwnerReferences() = %v, want owner ref to test-rs", refs)
|
||||
}
|
||||
}
|
||||
|
||||
// DaemonSet tests
|
||||
func TestDaemonSetWorkload_BasicGetters(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ds",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
if w.Kind() != KindDaemonSet {
|
||||
t.Errorf("Kind() = %v, want %v", w.Kind(), KindDaemonSet)
|
||||
}
|
||||
if w.GetName() != "test-ds" {
|
||||
t.Errorf("GetName() = %v, want test-ds", w.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_UsesConfigMap(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "config-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "ds-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
if !w.UsesConfigMap("ds-config") {
|
||||
t.Error("DaemonSet UsesConfigMap should return true for ConfigMap volume")
|
||||
}
|
||||
}
|
||||
|
||||
// StatefulSet tests
|
||||
func TestStatefulSetWorkload_BasicGetters(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-sts",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
if w.Kind() != KindStatefulSet {
|
||||
t.Errorf("Kind() = %v, want %v", w.Kind(), KindStatefulSet)
|
||||
}
|
||||
if w.GetName() != "test-sts" {
|
||||
t.Errorf("GetName() = %v, want test-sts", w.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_UsesSecret(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "secret-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: "sts-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
if !w.UsesSecret("sts-secret") {
|
||||
t.Error("StatefulSet UsesSecret should return true for Secret volume")
|
||||
}
|
||||
}
|
||||
|
||||
// Test that workloads implement the interface
|
||||
func TestWorkloadInterface(t *testing.T) {
|
||||
var _ WorkloadAccessor = (*DeploymentWorkload)(nil)
|
||||
var _ WorkloadAccessor = (*DaemonSetWorkload)(nil)
|
||||
var _ WorkloadAccessor = (*StatefulSetWorkload)(nil)
|
||||
}
|
||||
Reference in New Issue
Block a user