mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-20 04:26:28 +00:00
Merge branch 'v2' into feat/takeover-v2
This commit is contained in:
@@ -4,23 +4,20 @@ go 1.26.3
|
||||
|
||||
require (
|
||||
github.com/argoproj/argo-rollouts v1.9.0
|
||||
github.com/go-logr/logr v1.4.3
|
||||
github.com/go-logr/zerologr v1.2.3
|
||||
github.com/onsi/ginkgo/v2 v2.27.4
|
||||
github.com/onsi/gomega v1.39.0
|
||||
github.com/openshift/api v0.0.0-20260402111718-ad9eb11110b6
|
||||
github.com/openshift/client-go v0.0.0-20260330134249-7e1499aaacd7
|
||||
github.com/parnurzeal/gorequest v0.3.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.12.0
|
||||
k8s.io/api v0.36.0
|
||||
k8s.io/apimachinery v0.36.0
|
||||
k8s.io/client-go v0.36.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
k8s.io/api v0.35.3
|
||||
k8s.io/apimachinery v0.35.3
|
||||
k8s.io/client-go v0.35.3
|
||||
k8s.io/kubectl v0.35.3
|
||||
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5
|
||||
sigs.k8s.io/controller-runtime v0.24.1
|
||||
sigs.k8s.io/secrets-store-csi-driver v1.5.5
|
||||
)
|
||||
|
||||
@@ -55,7 +52,6 @@ require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
|
||||
github.com/bombsimon/wsl/v5 v5.3.0 // indirect
|
||||
@@ -79,18 +75,18 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.6 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.18 // indirect
|
||||
github.com/go-critic/go-critic v0.14.3 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.5 // indirect
|
||||
github.com/go-openapi/swag v0.25.5 // indirect
|
||||
@@ -178,20 +174,23 @@ require (
|
||||
github.com/mgechev/revive v1.13.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/spdystream v0.5.1 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/moricho/tparallel v0.3.2 // indirect
|
||||
github.com/moul/http2curl v1.0.0 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.21.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.4.5 // indirect
|
||||
@@ -209,18 +208,18 @@ require (
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.22.11 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/smartystreets/goconvey v1.7.2 // indirect
|
||||
github.com/sonatard/noctx v0.4.0 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.12.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/tetafro/godot v1.5.4 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
|
||||
@@ -233,7 +232,6 @@ require (
|
||||
github.com/uudashr/iface v1.4.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xen0n/gosmopolitan v1.3.0 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
github.com/yeya24/promlinter v0.3.0 // indirect
|
||||
@@ -245,7 +243,7 @@ require (
|
||||
go.augendre.info/fatcontext v0.9.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546 // indirect
|
||||
@@ -259,25 +257,18 @@ require (
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
honnef.co/go/tools v0.6.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.36.0 // indirect
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 // indirect
|
||||
k8s.io/streaming v0.36.0 // indirect
|
||||
mvdan.cc/gofumpt v0.9.2 // indirect
|
||||
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.20.1 // indirect
|
||||
sigs.k8s.io/kustomize/cmd/config v0.20.1 // indirect
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
)
|
||||
|
||||
func TestResourceCreatedHandler_GetConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
resource interface{}
|
||||
expectedName string
|
||||
expectedNS string
|
||||
expectedType string
|
||||
expectSHANotEmpty bool
|
||||
expectOldSHAEmpty bool
|
||||
}{
|
||||
{
|
||||
name: "ConfigMap with data",
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-configmap",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
expectedName: "my-configmap",
|
||||
expectedNS: "test-ns",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap with empty data",
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "empty-configmap",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{},
|
||||
},
|
||||
expectedName: "empty-configmap",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap with binary data",
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "binary-configmap",
|
||||
Namespace: "default",
|
||||
},
|
||||
BinaryData: map[string][]byte{
|
||||
"binary-key": []byte("binary-value"),
|
||||
},
|
||||
},
|
||||
expectedName: "binary-configmap",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap with annotations",
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "annotated-configmap",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"reloader.stakater.com/match": "true",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{"key": "value"},
|
||||
},
|
||||
expectedName: "annotated-configmap",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "Secret with data",
|
||||
resource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-secret",
|
||||
Namespace: "secret-ns",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"password": []byte("secret-password"),
|
||||
},
|
||||
},
|
||||
expectedName: "my-secret",
|
||||
expectedNS: "secret-ns",
|
||||
expectedType: constants.SecretEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "Secret with empty data",
|
||||
resource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "empty-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{},
|
||||
},
|
||||
expectedName: "empty-secret",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.SecretEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "Secret with StringData",
|
||||
resource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "stringdata-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
StringData: map[string]string{
|
||||
"username": "admin",
|
||||
},
|
||||
},
|
||||
expectedName: "stringdata-secret",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.SecretEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "Secret with labels",
|
||||
resource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "labeled-secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "test",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{"key": []byte("value")},
|
||||
},
|
||||
expectedName: "labeled-secret",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.SecretEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid resource type - string",
|
||||
resource: "invalid-string",
|
||||
expectedName: "",
|
||||
expectedNS: "",
|
||||
expectedType: "",
|
||||
expectSHANotEmpty: false,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid resource type - int",
|
||||
resource: 123,
|
||||
expectedName: "",
|
||||
expectedNS: "",
|
||||
expectedType: "",
|
||||
expectSHANotEmpty: false,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid resource type - struct",
|
||||
resource: struct{ Name string }{Name: "test"},
|
||||
expectedName: "",
|
||||
expectedNS: "",
|
||||
expectedType: "",
|
||||
expectSHANotEmpty: false,
|
||||
expectOldSHAEmpty: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: tt.resource,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, tt.expectedName, config.ResourceName)
|
||||
assert.Equal(t, tt.expectedNS, config.Namespace)
|
||||
assert.Equal(t, tt.expectedType, config.Type)
|
||||
|
||||
if tt.expectSHANotEmpty {
|
||||
assert.NotEmpty(t, config.SHAValue, "SHA should not be empty")
|
||||
}
|
||||
|
||||
if tt.expectOldSHAEmpty {
|
||||
assert.Empty(t, oldSHA, "oldSHA should always be empty for create handler")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceCreatedHandler_GetConfig_Annotations(t *testing.T) {
|
||||
cm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "annotated-cm",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"reloader.stakater.com/match": "true",
|
||||
"reloader.stakater.com/search": "true",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}
|
||||
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: cm,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, _ := handler.GetConfig()
|
||||
|
||||
assert.NotNil(t, config.ResourceAnnotations)
|
||||
assert.Equal(t, "true", config.ResourceAnnotations["reloader.stakater.com/match"])
|
||||
assert.Equal(t, "true", config.ResourceAnnotations["reloader.stakater.com/search"])
|
||||
}
|
||||
|
||||
func TestResourceCreatedHandler_GetConfig_Labels(t *testing.T) {
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "labeled-secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app": "myapp",
|
||||
"version": "v1",
|
||||
},
|
||||
},
|
||||
Data: map[string][]byte{"key": []byte("value")},
|
||||
}
|
||||
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: secret,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, _ := handler.GetConfig()
|
||||
|
||||
assert.NotNil(t, config.Labels)
|
||||
assert.Equal(t, "myapp", config.Labels["app"])
|
||||
assert.Equal(t, "v1", config.Labels["version"])
|
||||
}
|
||||
|
||||
func TestResourceCreatedHandler_Handle(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
resource interface{}
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Nil resource",
|
||||
resource: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Valid ConfigMap - no workloads to update",
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{"key": "value"},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Valid Secret - no workloads to update",
|
||||
resource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string][]byte{"key": []byte("value")},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: tt.resource,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
err := handler.Handle()
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceCreatedHandler_SHAConsistency(t *testing.T) {
|
||||
data := map[string]string{"key": "value"}
|
||||
|
||||
cm1 := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm1", Namespace: "default"},
|
||||
Data: data,
|
||||
}
|
||||
cm2 := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm2", Namespace: "default"},
|
||||
Data: data,
|
||||
}
|
||||
|
||||
handler1 := ResourceCreatedHandler{Resource: cm1, Collectors: metrics.NewCollectors()}
|
||||
handler2 := ResourceCreatedHandler{Resource: cm2, Collectors: metrics.NewCollectors()}
|
||||
|
||||
config1, _ := handler1.GetConfig()
|
||||
config2, _ := handler2.GetConfig()
|
||||
|
||||
assert.Equal(t, config1.SHAValue, config2.SHAValue)
|
||||
}
|
||||
|
||||
func TestResourceCreatedHandler_SHADifference(t *testing.T) {
|
||||
cm1 := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value1"},
|
||||
}
|
||||
cm2 := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value2"},
|
||||
}
|
||||
|
||||
handler1 := ResourceCreatedHandler{Resource: cm1, Collectors: metrics.NewCollectors()}
|
||||
handler2 := ResourceCreatedHandler{Resource: cm2, Collectors: metrics.NewCollectors()}
|
||||
|
||||
config1, _ := handler1.GetConfig()
|
||||
config2, _ := handler2.GetConfig()
|
||||
|
||||
assert.NotEqual(t, config1.SHAValue, config2.SHAValue)
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/callbacks"
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/pkg/common"
|
||||
)
|
||||
|
||||
// mockDeploymentForDelete creates a deployment with containers for testing delete strategies
|
||||
func mockDeploymentForDelete(name, namespace string, containers []v1.Container, volumes []v1.Volume) *appsv1.Deployment {
|
||||
return &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: containers,
|
||||
Volumes: volumes,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Mock funcs for testing
|
||||
func mockContainersFunc(item runtime.Object) []v1.Container {
|
||||
deployment, ok := item.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return deployment.Spec.Template.Spec.Containers
|
||||
}
|
||||
|
||||
func mockInitContainersFunc(item runtime.Object) []v1.Container {
|
||||
deployment, ok := item.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return deployment.Spec.Template.Spec.InitContainers
|
||||
}
|
||||
|
||||
func mockVolumesFunc(item runtime.Object) []v1.Volume {
|
||||
deployment, ok := item.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return deployment.Spec.Template.Spec.Volumes
|
||||
}
|
||||
|
||||
func mockPodAnnotationsFunc(item runtime.Object) map[string]string {
|
||||
deployment, ok := item.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return deployment.Spec.Template.Annotations
|
||||
}
|
||||
|
||||
func mockPatchTemplatesFunc() callbacks.PatchTemplates {
|
||||
return callbacks.PatchTemplates{
|
||||
AnnotationTemplate: `{"spec":{"template":{"metadata":{"annotations":{"%s":"%s"}}}}}`,
|
||||
EnvVarTemplate: `{"spec":{"template":{"spec":{"containers":[{"name":"%s","env":[{"name":"%s","value":"%s"}]}]}}}}`,
|
||||
DeleteEnvVarTemplate: `[{"op":"remove","path":"/spec/template/spec/containers/%d/env/%d"}]`,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainerEnvVars(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []v1.Container
|
||||
volumes []v1.Volume
|
||||
config common.Config
|
||||
autoReload bool
|
||||
expected constants.Result
|
||||
envVarRemoved bool
|
||||
}{
|
||||
{
|
||||
name: "Remove existing env var - configmap envFrom",
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "my-configmap",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []v1.EnvVar{
|
||||
{Name: "STAKATER_MY_CONFIGMAP_CONFIGMAP", Value: "sha-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "my-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: true,
|
||||
expected: constants.Updated,
|
||||
envVarRemoved: true,
|
||||
},
|
||||
{
|
||||
name: "No env var to remove",
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "my-configmap",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []v1.EnvVar{},
|
||||
},
|
||||
},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "my-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: true,
|
||||
expected: constants.NotUpdated,
|
||||
envVarRemoved: false,
|
||||
},
|
||||
{
|
||||
name: "Remove existing env var - secret envFrom",
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &v1.SecretEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "my-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []v1.EnvVar{
|
||||
{Name: "STAKATER_MY_SECRET_SECRET", Value: "sha-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "my-secret",
|
||||
Type: constants.SecretEnvVarPostfix,
|
||||
},
|
||||
autoReload: true,
|
||||
expected: constants.Updated,
|
||||
envVarRemoved: true,
|
||||
},
|
||||
{
|
||||
name: "No container found",
|
||||
containers: []v1.Container{},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "my-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: true,
|
||||
expected: constants.NoContainerFound,
|
||||
envVarRemoved: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
deployment := mockDeploymentForDelete("test-deploy", "default", tt.containers, tt.volumes)
|
||||
|
||||
funcs := callbacks.RollingUpgradeFuncs{
|
||||
ContainersFunc: mockContainersFunc,
|
||||
InitContainersFunc: mockInitContainersFunc,
|
||||
VolumesFunc: mockVolumesFunc,
|
||||
PodAnnotationsFunc: mockPodAnnotationsFunc,
|
||||
PatchTemplatesFunc: mockPatchTemplatesFunc,
|
||||
SupportsPatch: true,
|
||||
}
|
||||
|
||||
result := removeContainerEnvVars(funcs, deployment, tt.config, tt.autoReload)
|
||||
|
||||
assert.Equal(t, tt.expected, result.Result)
|
||||
|
||||
if tt.envVarRemoved {
|
||||
containers := deployment.Spec.Template.Spec.Containers
|
||||
for _, c := range containers {
|
||||
for _, env := range c.Env {
|
||||
envVarName := getEnvVarName(tt.config.ResourceName, tt.config.Type)
|
||||
assert.NotEqual(t, envVarName, env.Name, "Env var should have been removed")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvokeDeleteStrategy(t *testing.T) {
|
||||
originalStrategy := options.ReloadStrategy
|
||||
defer func() {
|
||||
options.ReloadStrategy = originalStrategy
|
||||
}()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
reloadStrategy string
|
||||
containers []v1.Container
|
||||
volumes []v1.Volume
|
||||
config common.Config
|
||||
}{
|
||||
{
|
||||
name: "Annotations strategy",
|
||||
reloadStrategy: constants.AnnotationsReloadStrategy,
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "my-configmap",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "my-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
SHAValue: "sha-value",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "EnvVars strategy",
|
||||
reloadStrategy: constants.EnvVarsReloadStrategy,
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "my-configmap",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []v1.EnvVar{
|
||||
{Name: "STAKATER_MY_CONFIGMAP_CONFIGMAP", Value: "sha-value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "my-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
options.ReloadStrategy = tt.reloadStrategy
|
||||
|
||||
deployment := mockDeploymentForDelete("test-deploy", "default", tt.containers, tt.volumes)
|
||||
|
||||
funcs := callbacks.RollingUpgradeFuncs{
|
||||
ContainersFunc: mockContainersFunc,
|
||||
InitContainersFunc: mockInitContainersFunc,
|
||||
VolumesFunc: mockVolumesFunc,
|
||||
PodAnnotationsFunc: mockPodAnnotationsFunc,
|
||||
PatchTemplatesFunc: mockPatchTemplatesFunc,
|
||||
SupportsPatch: true,
|
||||
}
|
||||
|
||||
result := invokeDeleteStrategy(funcs, deployment, tt.config, true)
|
||||
|
||||
assert.NotNil(t, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemovePodAnnotations(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []v1.Container
|
||||
volumes []v1.Volume
|
||||
config common.Config
|
||||
}{
|
||||
{
|
||||
name: "Remove pod annotations - configmap",
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "my-configmap",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "my-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
SHAValue: "sha-value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
deployment := mockDeploymentForDelete("test-deploy", "default", tt.containers, tt.volumes)
|
||||
|
||||
funcs := callbacks.RollingUpgradeFuncs{
|
||||
ContainersFunc: mockContainersFunc,
|
||||
InitContainersFunc: mockInitContainersFunc,
|
||||
VolumesFunc: mockVolumesFunc,
|
||||
PodAnnotationsFunc: mockPodAnnotationsFunc,
|
||||
PatchTemplatesFunc: mockPatchTemplatesFunc,
|
||||
SupportsPatch: false,
|
||||
}
|
||||
|
||||
result := removePodAnnotations(funcs, deployment, tt.config, true)
|
||||
|
||||
assert.Equal(t, constants.Updated, result.Result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
)
|
||||
|
||||
// Helper function to create a test ConfigMap
|
||||
func createTestConfigMap(data map[string]string) *v1.ConfigMap {
|
||||
return &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a test Secret
|
||||
func createTestSecret(data map[string][]byte) *v1.Secret {
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create test metrics collectors
|
||||
func createTestCollectors() metrics.Collectors {
|
||||
return metrics.NewCollectors()
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ResourceCreatedHandler Tests
|
||||
// ============================================================
|
||||
|
||||
func TestResourceCreatedHandler_GetConfig_ConfigMap(t *testing.T) {
|
||||
cm := createTestConfigMap(map[string]string{"key": "value"})
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: cm,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "test-cm", config.ResourceName)
|
||||
assert.Equal(t, "default", config.Namespace)
|
||||
assert.Equal(t, constants.ConfigmapEnvVarPostfix, config.Type)
|
||||
assert.NotEmpty(t, config.SHAValue)
|
||||
assert.Empty(t, oldSHA)
|
||||
}
|
||||
|
||||
func TestResourceCreatedHandler_GetConfig_Secret(t *testing.T) {
|
||||
secret := createTestSecret(map[string][]byte{"key": []byte("value")})
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: secret,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "test-secret", config.ResourceName)
|
||||
assert.Equal(t, "default", config.Namespace)
|
||||
assert.Equal(t, constants.SecretEnvVarPostfix, config.Type)
|
||||
assert.NotEmpty(t, config.SHAValue)
|
||||
assert.Empty(t, oldSHA)
|
||||
}
|
||||
|
||||
func TestResourceCreatedHandler_GetConfig_InvalidResource(t *testing.T) {
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: "invalid",
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, _ := handler.GetConfig()
|
||||
|
||||
assert.Empty(t, config.ResourceName)
|
||||
}
|
||||
|
||||
func TestResourceCreatedHandler_Handle_NilResource(t *testing.T) {
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: nil,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
err := handler.Handle()
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ResourceDeleteHandler Tests
|
||||
// ============================================================
|
||||
|
||||
func TestResourceDeleteHandler_GetConfig_ConfigMap(t *testing.T) {
|
||||
cm := createTestConfigMap(map[string]string{"key": "value"})
|
||||
handler := ResourceDeleteHandler{
|
||||
Resource: cm,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "test-cm", config.ResourceName)
|
||||
assert.Equal(t, "default", config.Namespace)
|
||||
assert.Equal(t, constants.ConfigmapEnvVarPostfix, config.Type)
|
||||
assert.NotEmpty(t, config.SHAValue)
|
||||
assert.Empty(t, oldSHA)
|
||||
}
|
||||
|
||||
func TestResourceDeleteHandler_GetConfig_Secret(t *testing.T) {
|
||||
secret := createTestSecret(map[string][]byte{"key": []byte("value")})
|
||||
handler := ResourceDeleteHandler{
|
||||
Resource: secret,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "test-secret", config.ResourceName)
|
||||
assert.Equal(t, "default", config.Namespace)
|
||||
assert.Equal(t, constants.SecretEnvVarPostfix, config.Type)
|
||||
assert.NotEmpty(t, config.SHAValue)
|
||||
assert.Empty(t, oldSHA)
|
||||
}
|
||||
|
||||
func TestResourceDeleteHandler_GetConfig_InvalidResource(t *testing.T) {
|
||||
handler := ResourceDeleteHandler{
|
||||
Resource: "invalid",
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, _ := handler.GetConfig()
|
||||
|
||||
assert.Empty(t, config.ResourceName)
|
||||
}
|
||||
|
||||
func TestResourceDeleteHandler_Handle_NilResource(t *testing.T) {
|
||||
handler := ResourceDeleteHandler{
|
||||
Resource: nil,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
err := handler.Handle()
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ResourceUpdatedHandler Tests
|
||||
// ============================================================
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_ConfigMap(t *testing.T) {
|
||||
oldCM := createTestConfigMap(map[string]string{"key": "old-value"})
|
||||
newCM := createTestConfigMap(map[string]string{"key": "new-value"})
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
OldResource: oldCM,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "test-cm", config.ResourceName)
|
||||
assert.Equal(t, "default", config.Namespace)
|
||||
assert.Equal(t, constants.ConfigmapEnvVarPostfix, config.Type)
|
||||
assert.NotEmpty(t, config.SHAValue)
|
||||
assert.NotEmpty(t, oldSHA)
|
||||
assert.NotEqual(t, config.SHAValue, oldSHA)
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_ConfigMap_SameData(t *testing.T) {
|
||||
oldCM := createTestConfigMap(map[string]string{"key": "same-value"})
|
||||
newCM := createTestConfigMap(map[string]string{"key": "same-value"})
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
OldResource: oldCM,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "test-cm", config.ResourceName)
|
||||
assert.Equal(t, config.SHAValue, oldSHA)
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_Secret(t *testing.T) {
|
||||
oldSecret := createTestSecret(map[string][]byte{"key": []byte("old-value")})
|
||||
newSecret := createTestSecret(map[string][]byte{"key": []byte("new-value")})
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newSecret,
|
||||
OldResource: oldSecret,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "test-secret", config.ResourceName)
|
||||
assert.Equal(t, "default", config.Namespace)
|
||||
assert.Equal(t, constants.SecretEnvVarPostfix, config.Type)
|
||||
assert.NotEmpty(t, config.SHAValue)
|
||||
assert.NotEmpty(t, oldSHA)
|
||||
assert.NotEqual(t, config.SHAValue, oldSHA)
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_Secret_SameData(t *testing.T) {
|
||||
oldSecret := createTestSecret(map[string][]byte{"key": []byte("same-value")})
|
||||
newSecret := createTestSecret(map[string][]byte{"key": []byte("same-value")})
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newSecret,
|
||||
OldResource: oldSecret,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "test-secret", config.ResourceName)
|
||||
assert.Equal(t, config.SHAValue, oldSHA)
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_InvalidResource(t *testing.T) {
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: "invalid",
|
||||
OldResource: "invalid",
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
config, _ := handler.GetConfig()
|
||||
|
||||
assert.Empty(t, config.ResourceName)
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_Handle_NilResource(t *testing.T) {
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: nil,
|
||||
OldResource: nil,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
err := handler.Handle()
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_Handle_NilOldResource(t *testing.T) {
|
||||
cm := createTestConfigMap(map[string]string{"key": "value"})
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: cm,
|
||||
OldResource: nil,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
err := handler.Handle()
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_Handle_NoChange(t *testing.T) {
|
||||
cm := createTestConfigMap(map[string]string{"key": "same-value"})
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: cm,
|
||||
OldResource: cm,
|
||||
Collectors: createTestCollectors(),
|
||||
}
|
||||
|
||||
err := handler.Handle()
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
)
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
oldResource any
|
||||
newResource any
|
||||
expectedName string
|
||||
expectedNS string
|
||||
expectedType string
|
||||
expectSHANotEmpty bool
|
||||
expectSHAChanged bool
|
||||
}{
|
||||
{
|
||||
name: "ConfigMap data changed",
|
||||
oldResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "old-value"},
|
||||
},
|
||||
newResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "new-value"},
|
||||
},
|
||||
expectedName: "my-cm",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: true,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap data unchanged",
|
||||
oldResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "same-value"},
|
||||
},
|
||||
newResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "same-value"},
|
||||
},
|
||||
expectedName: "my-cm",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: false,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap key added",
|
||||
oldResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
|
||||
Data: map[string]string{"key1": "value1"},
|
||||
},
|
||||
newResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
|
||||
Data: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
},
|
||||
expectedName: "my-cm",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: true,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap key removed",
|
||||
oldResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
|
||||
Data: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
},
|
||||
newResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"},
|
||||
Data: map[string]string{"key1": "value1"},
|
||||
},
|
||||
expectedName: "my-cm",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: true,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap only labels changed - SHA unchanged",
|
||||
oldResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-cm",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"version": "v1"},
|
||||
},
|
||||
Data: map[string]string{"key": "value"},
|
||||
},
|
||||
newResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-cm",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"version": "v2"},
|
||||
},
|
||||
Data: map[string]string{"key": "value"},
|
||||
},
|
||||
expectedName: "my-cm",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: false,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap only annotations changed - SHA unchanged",
|
||||
oldResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-cm",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{"note": "old"},
|
||||
},
|
||||
Data: map[string]string{"key": "value"},
|
||||
},
|
||||
newResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-cm",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{"note": "new"},
|
||||
},
|
||||
Data: map[string]string{"key": "value"},
|
||||
},
|
||||
expectedName: "my-cm",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.ConfigmapEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: false,
|
||||
},
|
||||
{
|
||||
name: "Secret data changed",
|
||||
oldResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"password": []byte("old-pass")},
|
||||
},
|
||||
newResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"password": []byte("new-pass")},
|
||||
},
|
||||
expectedName: "my-secret",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.SecretEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: true,
|
||||
},
|
||||
{
|
||||
name: "Secret data unchanged",
|
||||
oldResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"password": []byte("same-pass")},
|
||||
},
|
||||
newResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"password": []byte("same-pass")},
|
||||
},
|
||||
expectedName: "my-secret",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.SecretEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: false,
|
||||
},
|
||||
{
|
||||
name: "Secret key added",
|
||||
oldResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"key1": []byte("value1")},
|
||||
},
|
||||
newResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "my-secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"key1": []byte("value1"), "key2": []byte("value2")},
|
||||
},
|
||||
expectedName: "my-secret",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.SecretEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: true,
|
||||
},
|
||||
{
|
||||
name: "Secret only labels changed - SHA unchanged",
|
||||
oldResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"env": "dev"},
|
||||
},
|
||||
Data: map[string][]byte{"key": []byte("value")},
|
||||
},
|
||||
newResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"env": "prod"},
|
||||
},
|
||||
Data: map[string][]byte{"key": []byte("value")},
|
||||
},
|
||||
expectedName: "my-secret",
|
||||
expectedNS: "default",
|
||||
expectedType: constants.SecretEnvVarPostfix,
|
||||
expectSHANotEmpty: true,
|
||||
expectSHAChanged: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid resource type",
|
||||
oldResource: "invalid",
|
||||
newResource: "invalid",
|
||||
expectedName: "",
|
||||
expectedNS: "",
|
||||
expectedType: "",
|
||||
expectSHANotEmpty: false,
|
||||
expectSHAChanged: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: tt.newResource,
|
||||
OldResource: tt.oldResource,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, tt.expectedName, config.ResourceName)
|
||||
assert.Equal(t, tt.expectedNS, config.Namespace)
|
||||
assert.Equal(t, tt.expectedType, config.Type)
|
||||
|
||||
if tt.expectSHANotEmpty {
|
||||
assert.NotEmpty(t, config.SHAValue, "new SHA should not be empty")
|
||||
assert.NotEmpty(t, oldSHA, "old SHA should not be empty")
|
||||
}
|
||||
|
||||
if tt.expectSHAChanged {
|
||||
assert.NotEqual(t, config.SHAValue, oldSHA, "SHA should have changed")
|
||||
} else if tt.expectSHANotEmpty {
|
||||
assert.Equal(t, config.SHAValue, oldSHA, "SHA should not have changed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_Handle(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
oldResource any
|
||||
newResource any
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Both resources nil",
|
||||
oldResource: nil,
|
||||
newResource: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Old resource nil",
|
||||
oldResource: nil,
|
||||
newResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value"},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "New resource nil",
|
||||
oldResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value"},
|
||||
},
|
||||
newResource: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap unchanged - no action",
|
||||
oldResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "same"},
|
||||
},
|
||||
newResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "same"},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap changed - triggers update",
|
||||
oldResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "old"},
|
||||
},
|
||||
newResource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "new"},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Secret unchanged - no action",
|
||||
oldResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"key": []byte("same")},
|
||||
},
|
||||
newResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"key": []byte("same")},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Secret changed - triggers update",
|
||||
oldResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"key": []byte("old")},
|
||||
},
|
||||
newResource: &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "secret", Namespace: "default"},
|
||||
Data: map[string][]byte{"key": []byte("new")},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: tt.newResource,
|
||||
OldResource: tt.oldResource,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
err := handler.Handle()
|
||||
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_Annotations(t *testing.T) {
|
||||
oldCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cm",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"old-annotation": "old-value",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}
|
||||
|
||||
newCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cm",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"new-annotation": "new-value",
|
||||
},
|
||||
},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
OldResource: oldCM,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, _ := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "new-value", config.ResourceAnnotations["new-annotation"])
|
||||
_, hasOld := config.ResourceAnnotations["old-annotation"]
|
||||
assert.False(t, hasOld)
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_Labels(t *testing.T) {
|
||||
oldSecret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"version": "v1"},
|
||||
},
|
||||
Data: map[string][]byte{"key": []byte("value")},
|
||||
}
|
||||
|
||||
newSecret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "secret",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{"version": "v2"},
|
||||
},
|
||||
Data: map[string][]byte{"key": []byte("value")},
|
||||
}
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newSecret,
|
||||
OldResource: oldSecret,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, _ := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "v2", config.Labels["version"])
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_EmptyToNonEmpty(t *testing.T) {
|
||||
oldCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{},
|
||||
}
|
||||
newCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
OldResource: oldCM,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.NotEqual(t, config.SHAValue, oldSHA, "SHA should change when data is added")
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_NonEmptyToEmpty(t *testing.T) {
|
||||
oldCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}
|
||||
newCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{},
|
||||
}
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
OldResource: oldCM,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.NotEqual(t, config.SHAValue, oldSHA, "SHA should change when data is removed")
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_BinaryDataChange(t *testing.T) {
|
||||
oldCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
BinaryData: map[string][]byte{"binary": []byte("old-binary")},
|
||||
}
|
||||
newCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
BinaryData: map[string][]byte{"binary": []byte("new-binary")},
|
||||
}
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
OldResource: oldCM,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.NotEqual(t, config.SHAValue, oldSHA, "SHA should change when binary data changes")
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_MixedDataAndBinaryData(t *testing.T) {
|
||||
oldCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"text": "value"},
|
||||
BinaryData: map[string][]byte{"binary": []byte("binary-value")},
|
||||
}
|
||||
newCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "default"},
|
||||
Data: map[string]string{"text": "value"},
|
||||
BinaryData: map[string][]byte{"binary": []byte("new-binary-value")},
|
||||
}
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
OldResource: oldCM,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, oldSHA := handler.GetConfig()
|
||||
|
||||
assert.NotEqual(t, config.SHAValue, oldSHA, "SHA should change when binary data changes")
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_DifferentNamespaces(t *testing.T) {
|
||||
oldCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "ns1"},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}
|
||||
newCM := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "cm", Namespace: "ns2"},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
OldResource: oldCM,
|
||||
Collectors: metrics.NewCollectors(),
|
||||
}
|
||||
|
||||
config, _ := handler.GetConfig()
|
||||
|
||||
assert.Equal(t, "ns2", config.Namespace)
|
||||
}
|
||||
Reference in New Issue
Block a user