mirror of
https://github.com/stakater/Reloader.git
synced 2026-05-17 06:06:39 +00:00
feat: A lot of refactoring and CSI test cases
This commit is contained in:
@@ -4,6 +4,6 @@ import "github.com/stakater/Reloader/internal/pkg/cmd"
|
||||
|
||||
// Run runs the command
|
||||
func Run() error {
|
||||
cmd := cmd.NewReloaderCommand()
|
||||
return cmd.Execute()
|
||||
rootCmd := cmd.NewReloaderCommand()
|
||||
return rootCmd.Execute()
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -16,6 +14,9 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
patchtypes "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
|
||||
"maps"
|
||||
|
||||
argorolloutv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
|
||||
@@ -265,158 +266,254 @@ func GetRolloutItems(clients kube.Clients, namespace string) []runtime.Object {
|
||||
|
||||
// GetDeploymentAnnotations returns the annotations of given deployment
|
||||
func GetDeploymentAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*appsv1.Deployment).Annotations == nil {
|
||||
item.(*appsv1.Deployment).Annotations = make(map[string]string)
|
||||
deployment, ok := item.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*appsv1.Deployment).Annotations
|
||||
if deployment.Annotations == nil {
|
||||
deployment.Annotations = make(map[string]string)
|
||||
}
|
||||
return deployment.Annotations
|
||||
}
|
||||
|
||||
// GetCronJobAnnotations returns the annotations of given cronjob
|
||||
func GetCronJobAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*batchv1.CronJob).Annotations == nil {
|
||||
item.(*batchv1.CronJob).Annotations = make(map[string]string)
|
||||
cronJob, ok := item.(*batchv1.CronJob)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*batchv1.CronJob).Annotations
|
||||
if cronJob.Annotations == nil {
|
||||
cronJob.Annotations = make(map[string]string)
|
||||
}
|
||||
return cronJob.Annotations
|
||||
}
|
||||
|
||||
// GetJobAnnotations returns the annotations of given job
|
||||
func GetJobAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*batchv1.Job).Annotations == nil {
|
||||
item.(*batchv1.Job).Annotations = make(map[string]string)
|
||||
job, ok := item.(*batchv1.Job)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*batchv1.Job).Annotations
|
||||
if job.Annotations == nil {
|
||||
job.Annotations = make(map[string]string)
|
||||
}
|
||||
return job.Annotations
|
||||
}
|
||||
|
||||
// GetDaemonSetAnnotations returns the annotations of given daemonSet
|
||||
func GetDaemonSetAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*appsv1.DaemonSet).Annotations == nil {
|
||||
item.(*appsv1.DaemonSet).Annotations = make(map[string]string)
|
||||
daemonSet, ok := item.(*appsv1.DaemonSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*appsv1.DaemonSet).Annotations
|
||||
if daemonSet.Annotations == nil {
|
||||
daemonSet.Annotations = make(map[string]string)
|
||||
}
|
||||
return daemonSet.Annotations
|
||||
}
|
||||
|
||||
// GetStatefulSetAnnotations returns the annotations of given statefulSet
|
||||
func GetStatefulSetAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*appsv1.StatefulSet).Annotations == nil {
|
||||
item.(*appsv1.StatefulSet).Annotations = make(map[string]string)
|
||||
statefulSet, ok := item.(*appsv1.StatefulSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*appsv1.StatefulSet).Annotations
|
||||
if statefulSet.Annotations == nil {
|
||||
statefulSet.Annotations = make(map[string]string)
|
||||
}
|
||||
return statefulSet.Annotations
|
||||
}
|
||||
|
||||
// GetRolloutAnnotations returns the annotations of given rollout
|
||||
func GetRolloutAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*argorolloutv1alpha1.Rollout).Annotations == nil {
|
||||
item.(*argorolloutv1alpha1.Rollout).Annotations = make(map[string]string)
|
||||
rollout, ok := item.(*argorolloutv1alpha1.Rollout)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*argorolloutv1alpha1.Rollout).Annotations
|
||||
if rollout.Annotations == nil {
|
||||
rollout.Annotations = make(map[string]string)
|
||||
}
|
||||
return rollout.Annotations
|
||||
}
|
||||
|
||||
// GetDeploymentPodAnnotations returns the pod's annotations of given deployment
|
||||
func GetDeploymentPodAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*appsv1.Deployment).Spec.Template.Annotations == nil {
|
||||
item.(*appsv1.Deployment).Spec.Template.Annotations = make(map[string]string)
|
||||
deployment, ok := item.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*appsv1.Deployment).Spec.Template.Annotations
|
||||
if deployment.Spec.Template.Annotations == nil {
|
||||
deployment.Spec.Template.Annotations = make(map[string]string)
|
||||
}
|
||||
return deployment.Spec.Template.Annotations
|
||||
}
|
||||
|
||||
// GetCronJobPodAnnotations returns the pod's annotations of given cronjob
|
||||
func GetCronJobPodAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*batchv1.CronJob).Spec.JobTemplate.Spec.Template.Annotations == nil {
|
||||
item.(*batchv1.CronJob).Spec.JobTemplate.Spec.Template.Annotations = make(map[string]string)
|
||||
cronJob, ok := item.(*batchv1.CronJob)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*batchv1.CronJob).Spec.JobTemplate.Spec.Template.Annotations
|
||||
if cronJob.Spec.JobTemplate.Spec.Template.Annotations == nil {
|
||||
cronJob.Spec.JobTemplate.Spec.Template.Annotations = make(map[string]string)
|
||||
}
|
||||
return cronJob.Spec.JobTemplate.Spec.Template.Annotations
|
||||
}
|
||||
|
||||
// GetJobPodAnnotations returns the pod's annotations of given job
|
||||
func GetJobPodAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*batchv1.Job).Spec.Template.Annotations == nil {
|
||||
item.(*batchv1.Job).Spec.Template.Annotations = make(map[string]string)
|
||||
job, ok := item.(*batchv1.Job)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*batchv1.Job).Spec.Template.Annotations
|
||||
if job.Spec.Template.Annotations == nil {
|
||||
job.Spec.Template.Annotations = make(map[string]string)
|
||||
}
|
||||
return job.Spec.Template.Annotations
|
||||
}
|
||||
|
||||
// GetDaemonSetPodAnnotations returns the pod's annotations of given daemonSet
|
||||
func GetDaemonSetPodAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*appsv1.DaemonSet).Spec.Template.Annotations == nil {
|
||||
item.(*appsv1.DaemonSet).Spec.Template.Annotations = make(map[string]string)
|
||||
daemonSet, ok := item.(*appsv1.DaemonSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*appsv1.DaemonSet).Spec.Template.Annotations
|
||||
if daemonSet.Spec.Template.Annotations == nil {
|
||||
daemonSet.Spec.Template.Annotations = make(map[string]string)
|
||||
}
|
||||
return daemonSet.Spec.Template.Annotations
|
||||
}
|
||||
|
||||
// GetStatefulSetPodAnnotations returns the pod's annotations of given statefulSet
|
||||
func GetStatefulSetPodAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*appsv1.StatefulSet).Spec.Template.Annotations == nil {
|
||||
item.(*appsv1.StatefulSet).Spec.Template.Annotations = make(map[string]string)
|
||||
statefulSet, ok := item.(*appsv1.StatefulSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*appsv1.StatefulSet).Spec.Template.Annotations
|
||||
if statefulSet.Spec.Template.Annotations == nil {
|
||||
statefulSet.Spec.Template.Annotations = make(map[string]string)
|
||||
}
|
||||
return statefulSet.Spec.Template.Annotations
|
||||
}
|
||||
|
||||
// GetRolloutPodAnnotations returns the pod's annotations of given rollout
|
||||
func GetRolloutPodAnnotations(item runtime.Object) map[string]string {
|
||||
if item.(*argorolloutv1alpha1.Rollout).Spec.Template.Annotations == nil {
|
||||
item.(*argorolloutv1alpha1.Rollout).Spec.Template.Annotations = make(map[string]string)
|
||||
rollout, ok := item.(*argorolloutv1alpha1.Rollout)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*argorolloutv1alpha1.Rollout).Spec.Template.Annotations
|
||||
if rollout.Spec.Template.Annotations == nil {
|
||||
rollout.Spec.Template.Annotations = make(map[string]string)
|
||||
}
|
||||
return rollout.Spec.Template.Annotations
|
||||
}
|
||||
|
||||
// GetDeploymentContainers returns the containers of given deployment
|
||||
func GetDeploymentContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*appsv1.Deployment).Spec.Template.Spec.Containers
|
||||
deployment, ok := item.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return deployment.Spec.Template.Spec.Containers
|
||||
}
|
||||
|
||||
// GetCronJobContainers returns the containers of given cronjob
|
||||
func GetCronJobContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*batchv1.CronJob).Spec.JobTemplate.Spec.Template.Spec.Containers
|
||||
cronJob, ok := item.(*batchv1.CronJob)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return cronJob.Spec.JobTemplate.Spec.Template.Spec.Containers
|
||||
}
|
||||
|
||||
// GetJobContainers returns the containers of given job
|
||||
func GetJobContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*batchv1.Job).Spec.Template.Spec.Containers
|
||||
job, ok := item.(*batchv1.Job)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return job.Spec.Template.Spec.Containers
|
||||
}
|
||||
|
||||
// GetDaemonSetContainers returns the containers of given daemonSet
|
||||
func GetDaemonSetContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*appsv1.DaemonSet).Spec.Template.Spec.Containers
|
||||
daemonSet, ok := item.(*appsv1.DaemonSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return daemonSet.Spec.Template.Spec.Containers
|
||||
}
|
||||
|
||||
// GetStatefulSetContainers returns the containers of given statefulSet
|
||||
func GetStatefulSetContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*appsv1.StatefulSet).Spec.Template.Spec.Containers
|
||||
statefulSet, ok := item.(*appsv1.StatefulSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return statefulSet.Spec.Template.Spec.Containers
|
||||
}
|
||||
|
||||
// GetRolloutContainers returns the containers of given rollout
|
||||
func GetRolloutContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*argorolloutv1alpha1.Rollout).Spec.Template.Spec.Containers
|
||||
rollout, ok := item.(*argorolloutv1alpha1.Rollout)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return rollout.Spec.Template.Spec.Containers
|
||||
}
|
||||
|
||||
// GetDeploymentInitContainers returns the containers of given deployment
|
||||
func GetDeploymentInitContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*appsv1.Deployment).Spec.Template.Spec.InitContainers
|
||||
deployment, ok := item.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return deployment.Spec.Template.Spec.InitContainers
|
||||
}
|
||||
|
||||
// GetCronJobInitContainers returns the containers of given cronjob
|
||||
func GetCronJobInitContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*batchv1.CronJob).Spec.JobTemplate.Spec.Template.Spec.InitContainers
|
||||
cronJob, ok := item.(*batchv1.CronJob)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return cronJob.Spec.JobTemplate.Spec.Template.Spec.InitContainers
|
||||
}
|
||||
|
||||
// GetJobInitContainers returns the containers of given job
|
||||
func GetJobInitContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*batchv1.Job).Spec.Template.Spec.InitContainers
|
||||
job, ok := item.(*batchv1.Job)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return job.Spec.Template.Spec.InitContainers
|
||||
}
|
||||
|
||||
// GetDaemonSetInitContainers returns the containers of given daemonSet
|
||||
func GetDaemonSetInitContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*appsv1.DaemonSet).Spec.Template.Spec.InitContainers
|
||||
daemonSet, ok := item.(*appsv1.DaemonSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return daemonSet.Spec.Template.Spec.InitContainers
|
||||
}
|
||||
|
||||
// GetStatefulSetInitContainers returns the containers of given statefulSet
|
||||
func GetStatefulSetInitContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*appsv1.StatefulSet).Spec.Template.Spec.InitContainers
|
||||
statefulSet, ok := item.(*appsv1.StatefulSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return statefulSet.Spec.Template.Spec.InitContainers
|
||||
}
|
||||
|
||||
// GetRolloutInitContainers returns the containers of given rollout
|
||||
func GetRolloutInitContainers(item runtime.Object) []v1.Container {
|
||||
return item.(*argorolloutv1alpha1.Rollout).Spec.Template.Spec.InitContainers
|
||||
rollout, ok := item.(*argorolloutv1alpha1.Rollout)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return rollout.Spec.Template.Spec.InitContainers
|
||||
}
|
||||
|
||||
// GetPatchTemplates returns patch templates
|
||||
@@ -430,21 +527,30 @@ func GetPatchTemplates() PatchTemplates {
|
||||
|
||||
// UpdateDeployment performs rolling upgrade on deployment
|
||||
func UpdateDeployment(clients kube.Clients, namespace string, resource runtime.Object) error {
|
||||
deployment := resource.(*appsv1.Deployment)
|
||||
deployment, ok := resource.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return errors.New("resource is not a Deployment")
|
||||
}
|
||||
_, err := clients.KubernetesClient.AppsV1().Deployments(namespace).Update(context.TODO(), deployment, meta_v1.UpdateOptions{FieldManager: "Reloader"})
|
||||
return err
|
||||
}
|
||||
|
||||
// PatchDeployment performs rolling upgrade on deployment
|
||||
func PatchDeployment(clients kube.Clients, namespace string, resource runtime.Object, patchType patchtypes.PatchType, bytes []byte) error {
|
||||
deployment := resource.(*appsv1.Deployment)
|
||||
deployment, ok := resource.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return errors.New("resource is not a Deployment")
|
||||
}
|
||||
_, err := clients.KubernetesClient.AppsV1().Deployments(namespace).Patch(context.TODO(), deployment.Name, patchType, bytes, meta_v1.PatchOptions{FieldManager: "Reloader"})
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateJobFromCronjob performs rolling upgrade on cronjob
|
||||
func CreateJobFromCronjob(clients kube.Clients, namespace string, resource runtime.Object) error {
|
||||
cronJob := resource.(*batchv1.CronJob)
|
||||
cronJob, ok := resource.(*batchv1.CronJob)
|
||||
if !ok {
|
||||
return errors.New("resource is not a CronJob")
|
||||
}
|
||||
|
||||
annotations := make(map[string]string)
|
||||
annotations["cronjob.kubernetes.io/instantiate"] = "manual"
|
||||
@@ -470,7 +576,10 @@ func PatchCronJob(clients kube.Clients, namespace string, resource runtime.Objec
|
||||
|
||||
// ReCreateJobFromjob performs rolling upgrade on job
|
||||
func ReCreateJobFromjob(clients kube.Clients, namespace string, resource runtime.Object) error {
|
||||
oldJob := resource.(*batchv1.Job)
|
||||
oldJob, ok := resource.(*batchv1.Job)
|
||||
if !ok {
|
||||
return errors.New("resource is not a Job")
|
||||
}
|
||||
job := oldJob.DeepCopy()
|
||||
|
||||
// Delete the old job
|
||||
@@ -506,33 +615,48 @@ func PatchJob(clients kube.Clients, namespace string, resource runtime.Object, p
|
||||
|
||||
// UpdateDaemonSet performs rolling upgrade on daemonSet
|
||||
func UpdateDaemonSet(clients kube.Clients, namespace string, resource runtime.Object) error {
|
||||
daemonSet := resource.(*appsv1.DaemonSet)
|
||||
daemonSet, ok := resource.(*appsv1.DaemonSet)
|
||||
if !ok {
|
||||
return errors.New("resource is not a DaemonSet")
|
||||
}
|
||||
_, err := clients.KubernetesClient.AppsV1().DaemonSets(namespace).Update(context.TODO(), daemonSet, meta_v1.UpdateOptions{FieldManager: "Reloader"})
|
||||
return err
|
||||
}
|
||||
|
||||
func PatchDaemonSet(clients kube.Clients, namespace string, resource runtime.Object, patchType patchtypes.PatchType, bytes []byte) error {
|
||||
daemonSet := resource.(*appsv1.DaemonSet)
|
||||
daemonSet, ok := resource.(*appsv1.DaemonSet)
|
||||
if !ok {
|
||||
return errors.New("resource is not a DaemonSet")
|
||||
}
|
||||
_, err := clients.KubernetesClient.AppsV1().DaemonSets(namespace).Patch(context.TODO(), daemonSet.Name, patchType, bytes, meta_v1.PatchOptions{FieldManager: "Reloader"})
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateStatefulSet performs rolling upgrade on statefulSet
|
||||
func UpdateStatefulSet(clients kube.Clients, namespace string, resource runtime.Object) error {
|
||||
statefulSet := resource.(*appsv1.StatefulSet)
|
||||
statefulSet, ok := resource.(*appsv1.StatefulSet)
|
||||
if !ok {
|
||||
return errors.New("resource is not a StatefulSet")
|
||||
}
|
||||
_, err := clients.KubernetesClient.AppsV1().StatefulSets(namespace).Update(context.TODO(), statefulSet, meta_v1.UpdateOptions{FieldManager: "Reloader"})
|
||||
return err
|
||||
}
|
||||
|
||||
func PatchStatefulSet(clients kube.Clients, namespace string, resource runtime.Object, patchType patchtypes.PatchType, bytes []byte) error {
|
||||
statefulSet := resource.(*appsv1.StatefulSet)
|
||||
statefulSet, ok := resource.(*appsv1.StatefulSet)
|
||||
if !ok {
|
||||
return errors.New("resource is not a StatefulSet")
|
||||
}
|
||||
_, err := clients.KubernetesClient.AppsV1().StatefulSets(namespace).Patch(context.TODO(), statefulSet.Name, patchType, bytes, meta_v1.PatchOptions{FieldManager: "Reloader"})
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateRollout performs rolling upgrade on rollout
|
||||
func UpdateRollout(clients kube.Clients, namespace string, resource runtime.Object) error {
|
||||
rollout := resource.(*argorolloutv1alpha1.Rollout)
|
||||
rollout, ok := resource.(*argorolloutv1alpha1.Rollout)
|
||||
if !ok {
|
||||
return errors.New("resource is not a Rollout")
|
||||
}
|
||||
strategy := rollout.GetAnnotations()[options.RolloutStrategyAnnotation]
|
||||
var err error
|
||||
switch options.ToArgoRolloutStrategy(strategy) {
|
||||
@@ -550,30 +674,54 @@ func PatchRollout(clients kube.Clients, namespace string, resource runtime.Objec
|
||||
|
||||
// GetDeploymentVolumes returns the Volumes of given deployment
|
||||
func GetDeploymentVolumes(item runtime.Object) []v1.Volume {
|
||||
return item.(*appsv1.Deployment).Spec.Template.Spec.Volumes
|
||||
deployment, ok := item.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return deployment.Spec.Template.Spec.Volumes
|
||||
}
|
||||
|
||||
// GetCronJobVolumes returns the Volumes of given cronjob
|
||||
func GetCronJobVolumes(item runtime.Object) []v1.Volume {
|
||||
return item.(*batchv1.CronJob).Spec.JobTemplate.Spec.Template.Spec.Volumes
|
||||
cronJob, ok := item.(*batchv1.CronJob)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return cronJob.Spec.JobTemplate.Spec.Template.Spec.Volumes
|
||||
}
|
||||
|
||||
// GetJobVolumes returns the Volumes of given job
|
||||
func GetJobVolumes(item runtime.Object) []v1.Volume {
|
||||
return item.(*batchv1.Job).Spec.Template.Spec.Volumes
|
||||
job, ok := item.(*batchv1.Job)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return job.Spec.Template.Spec.Volumes
|
||||
}
|
||||
|
||||
// GetDaemonSetVolumes returns the Volumes of given daemonSet
|
||||
func GetDaemonSetVolumes(item runtime.Object) []v1.Volume {
|
||||
return item.(*appsv1.DaemonSet).Spec.Template.Spec.Volumes
|
||||
daemonSet, ok := item.(*appsv1.DaemonSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return daemonSet.Spec.Template.Spec.Volumes
|
||||
}
|
||||
|
||||
// GetStatefulSetVolumes returns the Volumes of given statefulSet
|
||||
func GetStatefulSetVolumes(item runtime.Object) []v1.Volume {
|
||||
return item.(*appsv1.StatefulSet).Spec.Template.Spec.Volumes
|
||||
statefulSet, ok := item.(*appsv1.StatefulSet)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return statefulSet.Spec.Template.Spec.Volumes
|
||||
}
|
||||
|
||||
// GetRolloutVolumes returns the Volumes of given rollout
|
||||
func GetRolloutVolumes(item runtime.Object) []v1.Volume {
|
||||
return item.(*argorolloutv1alpha1.Rollout).Spec.Template.Spec.Volumes
|
||||
rollout, ok := item.(*argorolloutv1alpha1.Rollout)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return rollout.Spec.Template.Spec.Volumes
|
||||
}
|
||||
|
||||
@@ -6,12 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/handler"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/internal/pkg/util"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
@@ -24,12 +18,18 @@ import (
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/handler"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/internal/pkg/util"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
)
|
||||
|
||||
// Controller for checking events
|
||||
type Controller struct {
|
||||
client kubernetes.Interface
|
||||
indexer cache.Indexer
|
||||
queue workqueue.TypedRateLimitingInterface[any]
|
||||
informer cache.Controller
|
||||
namespace string
|
||||
@@ -48,7 +48,9 @@ var selectedNamespacesCache []string
|
||||
|
||||
// NewController for initializing a Controller
|
||||
func NewController(
|
||||
client kubernetes.Interface, resource string, namespace string, ignoredNamespaces []string, namespaceLabelSelector string, resourceLabelSelector string, collectors metrics.Collectors) (*Controller, error) {
|
||||
client kubernetes.Interface, resource string, namespace string, ignoredNamespaces []string, namespaceLabelSelector string, resourceLabelSelector string, collectors metrics.Collectors) (
|
||||
*Controller, error,
|
||||
) {
|
||||
|
||||
if options.SyncAfterRestart {
|
||||
secretControllerInitialized = true
|
||||
@@ -67,17 +69,18 @@ func NewController(
|
||||
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{
|
||||
Interface: client.CoreV1().Events(""),
|
||||
})
|
||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: fmt.Sprintf("reloader-%s", resource)})
|
||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme,
|
||||
v1.EventSource{Component: fmt.Sprintf("reloader-%s", resource)})
|
||||
|
||||
queue := workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]())
|
||||
|
||||
optionsModifier := func(options *metav1.ListOptions) {
|
||||
optionsModifier := func(opts *metav1.ListOptions) {
|
||||
if resource == "namespaces" {
|
||||
options.LabelSelector = c.namespaceSelector
|
||||
opts.LabelSelector = c.namespaceSelector
|
||||
} else if len(c.resourceSelector) > 0 {
|
||||
options.LabelSelector = c.resourceSelector
|
||||
opts.LabelSelector = c.resourceSelector
|
||||
} else {
|
||||
options.FieldSelector = fields.Everything().String()
|
||||
opts.FieldSelector = fields.Everything().String()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +302,12 @@ func (c *Controller) processNextItem() bool {
|
||||
startTime := time.Now()
|
||||
|
||||
// Invoke the method containing the business logic
|
||||
err := resourceHandler.(handler.ResourceHandler).Handle()
|
||||
rh, ok := resourceHandler.(handler.ResourceHandler)
|
||||
if !ok {
|
||||
logrus.Errorf("Invalid resource handler type: %T", resourceHandler)
|
||||
return true
|
||||
}
|
||||
err := rh.Handle()
|
||||
|
||||
duration := time.Since(startTime)
|
||||
|
||||
|
||||
@@ -1,17 +1,46 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/handler"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/handler"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/pkg/common"
|
||||
)
|
||||
|
||||
// mockResourceHandler implements handler.ResourceHandler and handler.TimedHandler for testing.
|
||||
type mockResourceHandler struct {
|
||||
handleErr error
|
||||
handleCalls int
|
||||
enqueueTime time.Time
|
||||
}
|
||||
|
||||
func (m *mockResourceHandler) Handle() error {
|
||||
m.handleCalls++
|
||||
return m.handleErr
|
||||
}
|
||||
|
||||
func (m *mockResourceHandler) GetConfig() (common.Config, string) {
|
||||
return common.Config{
|
||||
ResourceName: "test-resource",
|
||||
Namespace: "test-ns",
|
||||
Type: "configmap",
|
||||
SHAValue: "sha256:test",
|
||||
}, "test-resource"
|
||||
}
|
||||
|
||||
func (m *mockResourceHandler) GetEnqueueTime() time.Time {
|
||||
return m.enqueueTime
|
||||
}
|
||||
|
||||
// resetGlobalState resets global variables between tests
|
||||
func resetGlobalState() {
|
||||
secretControllerInitialized = false
|
||||
@@ -104,11 +133,13 @@ func TestResourceInIgnoredNamespace(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := newTestController(tt.ignoredNamespaces, "")
|
||||
result := c.resourceInIgnoredNamespace(tt.resource)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
c := newTestController(tt.ignoredNamespaces, "")
|
||||
result := c.resourceInIgnoredNamespace(tt.resource)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,14 +221,16 @@ func TestResourceInSelectedNamespaces(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
selectedNamespacesCache = tt.cachedNamespaces
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
selectedNamespacesCache = tt.cachedNamespaces
|
||||
|
||||
c := newTestController([]string{}, tt.namespaceSelector)
|
||||
result := c.resourceInSelectedNamespaces(tt.resource)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
c := newTestController([]string{}, tt.namespaceSelector)
|
||||
result := c.resourceInSelectedNamespaces(tt.resource)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,65 +259,67 @@ func TestAddSelectedNamespaceToCache(t *testing.T) {
|
||||
|
||||
func TestRemoveSelectedNamespaceFromCache(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
initialCache []string
|
||||
name string
|
||||
initialCache []string
|
||||
namespaceToRemove string
|
||||
expectedCache []string
|
||||
expectedCache []string
|
||||
}{
|
||||
{
|
||||
name: "Remove existing namespace",
|
||||
initialCache: []string{"ns-1", "ns-2", "ns-3"},
|
||||
name: "Remove existing namespace",
|
||||
initialCache: []string{"ns-1", "ns-2", "ns-3"},
|
||||
namespaceToRemove: "ns-2",
|
||||
expectedCache: []string{"ns-1", "ns-3"},
|
||||
expectedCache: []string{"ns-1", "ns-3"},
|
||||
},
|
||||
{
|
||||
name: "Remove non-existing namespace",
|
||||
initialCache: []string{"ns-1", "ns-2"},
|
||||
name: "Remove non-existing namespace",
|
||||
initialCache: []string{"ns-1", "ns-2"},
|
||||
namespaceToRemove: "ns-3",
|
||||
expectedCache: []string{"ns-1", "ns-2"},
|
||||
expectedCache: []string{"ns-1", "ns-2"},
|
||||
},
|
||||
{
|
||||
name: "Remove from empty cache",
|
||||
initialCache: []string{},
|
||||
name: "Remove from empty cache",
|
||||
initialCache: []string{},
|
||||
namespaceToRemove: "ns-1",
|
||||
expectedCache: []string{},
|
||||
expectedCache: []string{},
|
||||
},
|
||||
{
|
||||
name: "Remove only namespace",
|
||||
initialCache: []string{"ns-1"},
|
||||
name: "Remove only namespace",
|
||||
initialCache: []string{"ns-1"},
|
||||
namespaceToRemove: "ns-1",
|
||||
expectedCache: []string{},
|
||||
expectedCache: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
selectedNamespacesCache = tt.initialCache
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
selectedNamespacesCache = tt.initialCache
|
||||
|
||||
c := newTestController([]string{}, "env=prod")
|
||||
ns := v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: tt.namespaceToRemove},
|
||||
}
|
||||
c.removeSelectedNamespaceFromCache(ns)
|
||||
c := newTestController([]string{}, "env=prod")
|
||||
ns := v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: tt.namespaceToRemove},
|
||||
}
|
||||
c.removeSelectedNamespaceFromCache(ns)
|
||||
|
||||
assert.Equal(t, tt.expectedCache, selectedNamespacesCache)
|
||||
})
|
||||
assert.Equal(t, tt.expectedCache, selectedNamespacesCache)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddHandler(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reloadOnCreate string
|
||||
ignoredNamespaces []string
|
||||
resource interface{}
|
||||
controllersInit bool
|
||||
expectQueueItem bool
|
||||
name string
|
||||
reloadOnCreate string
|
||||
ignoredNamespaces []string
|
||||
resource interface{}
|
||||
controllersInit bool
|
||||
expectQueueItem bool
|
||||
}{
|
||||
{
|
||||
name: "Namespace resource - should not queue",
|
||||
reloadOnCreate: "true",
|
||||
name: "Namespace resource - should not queue",
|
||||
reloadOnCreate: "true",
|
||||
ignoredNamespaces: []string{},
|
||||
resource: &v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-ns"},
|
||||
@@ -293,8 +328,8 @@ func TestAddHandler(t *testing.T) {
|
||||
expectQueueItem: false,
|
||||
},
|
||||
{
|
||||
name: "ReloadOnCreate disabled",
|
||||
reloadOnCreate: "false",
|
||||
name: "ReloadOnCreate disabled",
|
||||
reloadOnCreate: "false",
|
||||
ignoredNamespaces: []string{},
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -306,8 +341,8 @@ func TestAddHandler(t *testing.T) {
|
||||
expectQueueItem: false,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap in ignored namespace",
|
||||
reloadOnCreate: "true",
|
||||
name: "ConfigMap in ignored namespace",
|
||||
reloadOnCreate: "true",
|
||||
ignoredNamespaces: []string{"kube-system"},
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -319,8 +354,8 @@ func TestAddHandler(t *testing.T) {
|
||||
expectQueueItem: false,
|
||||
},
|
||||
{
|
||||
name: "Controllers not initialized",
|
||||
reloadOnCreate: "true",
|
||||
name: "Controllers not initialized",
|
||||
reloadOnCreate: "true",
|
||||
ignoredNamespaces: []string{},
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -332,8 +367,8 @@ func TestAddHandler(t *testing.T) {
|
||||
expectQueueItem: false,
|
||||
},
|
||||
{
|
||||
name: "Valid ConfigMap - should queue",
|
||||
reloadOnCreate: "true",
|
||||
name: "Valid ConfigMap - should queue",
|
||||
reloadOnCreate: "true",
|
||||
ignoredNamespaces: []string{},
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -347,21 +382,23 @@ func TestAddHandler(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
options.ReloadOnCreate = tt.reloadOnCreate
|
||||
secretControllerInitialized = tt.controllersInit
|
||||
configmapControllerInitialized = tt.controllersInit
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
options.ReloadOnCreate = tt.reloadOnCreate
|
||||
secretControllerInitialized = tt.controllersInit
|
||||
configmapControllerInitialized = tt.controllersInit
|
||||
|
||||
c := newTestController(tt.ignoredNamespaces, "")
|
||||
c.Add(tt.resource)
|
||||
c := newTestController(tt.ignoredNamespaces, "")
|
||||
c.Add(tt.resource)
|
||||
|
||||
if tt.expectQueueItem {
|
||||
assert.Equal(t, 1, c.queue.Len(), "Expected queue to have 1 item")
|
||||
} else {
|
||||
assert.Equal(t, 0, c.queue.Len(), "Expected queue to be empty")
|
||||
}
|
||||
})
|
||||
if tt.expectQueueItem {
|
||||
assert.Equal(t, 1, c.queue.Len(), "Expected queue to have 1 item")
|
||||
} else {
|
||||
assert.Equal(t, 0, c.queue.Len(), "Expected queue to be empty")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,26 +498,28 @@ func TestUpdateHandler(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
if tt.cachedNamespaces != nil {
|
||||
selectedNamespacesCache = tt.cachedNamespaces
|
||||
}
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
if tt.cachedNamespaces != nil {
|
||||
selectedNamespacesCache = tt.cachedNamespaces
|
||||
}
|
||||
|
||||
c := newTestController(tt.ignoredNamespaces, tt.namespaceSelector)
|
||||
c.Update(tt.oldResource, tt.newResource)
|
||||
c := newTestController(tt.ignoredNamespaces, tt.namespaceSelector)
|
||||
c.Update(tt.oldResource, tt.newResource)
|
||||
|
||||
if tt.expectQueueItem {
|
||||
assert.Equal(t, 1, c.queue.Len(), "Expected queue to have 1 item")
|
||||
// Verify the queued item is the correct type
|
||||
item, _ := c.queue.Get()
|
||||
_, ok := item.(handler.ResourceUpdatedHandler)
|
||||
assert.True(t, ok, "Expected ResourceUpdatedHandler in queue")
|
||||
c.queue.Done(item)
|
||||
} else {
|
||||
assert.Equal(t, 0, c.queue.Len(), "Expected queue to be empty")
|
||||
}
|
||||
})
|
||||
if tt.expectQueueItem {
|
||||
assert.Equal(t, 1, c.queue.Len(), "Expected queue to have 1 item")
|
||||
// Verify the queued item is the correct type
|
||||
item, _ := c.queue.Get()
|
||||
_, ok := item.(handler.ResourceUpdatedHandler)
|
||||
assert.True(t, ok, "Expected ResourceUpdatedHandler in queue")
|
||||
c.queue.Done(item)
|
||||
} else {
|
||||
assert.Equal(t, 0, c.queue.Len(), "Expected queue to be empty")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,8 +533,8 @@ func TestDeleteHandler(t *testing.T) {
|
||||
expectQueueItem bool
|
||||
}{
|
||||
{
|
||||
name: "ReloadOnDelete disabled",
|
||||
reloadOnDelete: "false",
|
||||
name: "ReloadOnDelete disabled",
|
||||
reloadOnDelete: "false",
|
||||
ignoredNamespaces: []string{},
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -507,8 +546,8 @@ func TestDeleteHandler(t *testing.T) {
|
||||
expectQueueItem: false,
|
||||
},
|
||||
{
|
||||
name: "ConfigMap in ignored namespace",
|
||||
reloadOnDelete: "true",
|
||||
name: "ConfigMap in ignored namespace",
|
||||
reloadOnDelete: "true",
|
||||
ignoredNamespaces: []string{"kube-system"},
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -520,8 +559,8 @@ func TestDeleteHandler(t *testing.T) {
|
||||
expectQueueItem: false,
|
||||
},
|
||||
{
|
||||
name: "Controllers not initialized",
|
||||
reloadOnDelete: "true",
|
||||
name: "Controllers not initialized",
|
||||
reloadOnDelete: "true",
|
||||
ignoredNamespaces: []string{},
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -533,8 +572,8 @@ func TestDeleteHandler(t *testing.T) {
|
||||
expectQueueItem: false,
|
||||
},
|
||||
{
|
||||
name: "Valid ConfigMap delete - should queue",
|
||||
reloadOnDelete: "true",
|
||||
name: "Valid ConfigMap delete - should queue",
|
||||
reloadOnDelete: "true",
|
||||
ignoredNamespaces: []string{},
|
||||
resource: &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -546,8 +585,8 @@ func TestDeleteHandler(t *testing.T) {
|
||||
expectQueueItem: true,
|
||||
},
|
||||
{
|
||||
name: "Namespace delete - updates cache",
|
||||
reloadOnDelete: "false", // Disable to test cache update only
|
||||
name: "Namespace delete - updates cache",
|
||||
reloadOnDelete: "false", // Disable to test cache update only
|
||||
ignoredNamespaces: []string{},
|
||||
resource: &v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-ns"},
|
||||
@@ -558,64 +597,70 @@ func TestDeleteHandler(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
options.ReloadOnDelete = tt.reloadOnDelete
|
||||
secretControllerInitialized = tt.controllersInit
|
||||
configmapControllerInitialized = tt.controllersInit
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
options.ReloadOnDelete = tt.reloadOnDelete
|
||||
secretControllerInitialized = tt.controllersInit
|
||||
configmapControllerInitialized = tt.controllersInit
|
||||
|
||||
c := newTestController(tt.ignoredNamespaces, "")
|
||||
c.Delete(tt.resource)
|
||||
c := newTestController(tt.ignoredNamespaces, "")
|
||||
c.Delete(tt.resource)
|
||||
|
||||
if tt.expectQueueItem {
|
||||
assert.Equal(t, 1, c.queue.Len(), "Expected queue to have 1 item")
|
||||
// Verify the queued item is the correct type
|
||||
item, _ := c.queue.Get()
|
||||
_, ok := item.(handler.ResourceDeleteHandler)
|
||||
assert.True(t, ok, "Expected ResourceDeleteHandler in queue")
|
||||
c.queue.Done(item)
|
||||
} else {
|
||||
assert.Equal(t, 0, c.queue.Len(), "Expected queue to be empty")
|
||||
}
|
||||
})
|
||||
if tt.expectQueueItem {
|
||||
assert.Equal(t, 1, c.queue.Len(), "Expected queue to have 1 item")
|
||||
// Verify the queued item is the correct type
|
||||
item, _ := c.queue.Get()
|
||||
_, ok := item.(handler.ResourceDeleteHandler)
|
||||
assert.True(t, ok, "Expected ResourceDeleteHandler in queue")
|
||||
c.queue.Done(item)
|
||||
} else {
|
||||
assert.Equal(t, 0, c.queue.Len(), "Expected queue to be empty")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleErr(t *testing.T) {
|
||||
t.Run("No error - should forget key", func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
c := newTestController([]string{}, "")
|
||||
t.Run(
|
||||
"No error - should forget key", func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
c := newTestController([]string{}, "")
|
||||
|
||||
key := "test-key"
|
||||
// Add key to queue first
|
||||
c.queue.Add(key)
|
||||
item, _ := c.queue.Get()
|
||||
key := "test-key"
|
||||
// Add key to queue first
|
||||
c.queue.Add(key)
|
||||
item, _ := c.queue.Get()
|
||||
|
||||
// Handle with no error
|
||||
c.handleErr(nil, item)
|
||||
c.queue.Done(item)
|
||||
// Handle with no error
|
||||
c.handleErr(nil, item)
|
||||
c.queue.Done(item)
|
||||
|
||||
// Key should be forgotten (NumRequeues should be 0)
|
||||
assert.Equal(t, 0, c.queue.NumRequeues(key))
|
||||
})
|
||||
// Key should be forgotten (NumRequeues should be 0)
|
||||
assert.Equal(t, 0, c.queue.NumRequeues(key))
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("Error at max retries - should drop key", func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
c := newTestController([]string{}, "")
|
||||
t.Run(
|
||||
"Error at max retries - should drop key", func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
c := newTestController([]string{}, "")
|
||||
|
||||
key := "test-key-max"
|
||||
key := "test-key-max"
|
||||
|
||||
// Simulate 5 previous failures (max retries)
|
||||
for range 5 {
|
||||
c.queue.AddRateLimited(key)
|
||||
}
|
||||
// Simulate 5 previous failures (max retries)
|
||||
for range 5 {
|
||||
c.queue.AddRateLimited(key)
|
||||
}
|
||||
|
||||
// After max retries, handleErr should forget the key
|
||||
c.handleErr(assert.AnError, key)
|
||||
// After max retries, handleErr should forget the key
|
||||
c.handleErr(assert.AnError, key)
|
||||
|
||||
// Key should be forgotten
|
||||
assert.Equal(t, 0, c.queue.NumRequeues(key))
|
||||
})
|
||||
// Key should be forgotten
|
||||
assert.Equal(t, 0, c.queue.NumRequeues(key))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestAddHandlerWithNamespaceEvent(t *testing.T) {
|
||||
@@ -654,3 +699,57 @@ func TestDeleteHandlerWithNamespaceEvent(t *testing.T) {
|
||||
assert.Contains(t, selectedNamespacesCache, "ns-2")
|
||||
assert.Equal(t, 0, c.queue.Len(), "Namespace delete should not queue anything")
|
||||
}
|
||||
|
||||
func TestProcessNextItem(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
handler *mockResourceHandler
|
||||
expectContinue bool
|
||||
expectCalls int
|
||||
}{
|
||||
{
|
||||
name: "Successful handler execution",
|
||||
handler: &mockResourceHandler{
|
||||
handleErr: nil,
|
||||
enqueueTime: time.Now().Add(-10 * time.Millisecond),
|
||||
},
|
||||
expectContinue: true,
|
||||
expectCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "Handler returns error",
|
||||
handler: &mockResourceHandler{
|
||||
handleErr: errors.New("test error"),
|
||||
enqueueTime: time.Now().Add(-10 * time.Millisecond),
|
||||
},
|
||||
expectContinue: true,
|
||||
expectCalls: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.name, func(t *testing.T) {
|
||||
resetGlobalState()
|
||||
c := newTestController([]string{}, "")
|
||||
|
||||
c.queue.Add(tt.handler)
|
||||
|
||||
result := c.processNextItem()
|
||||
|
||||
assert.Equal(t, tt.expectContinue, result)
|
||||
assert.Equal(t, tt.expectCalls, tt.handler.handleCalls)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessNextItemQueueShutdown(t *testing.T) {
|
||||
resetGlobalState()
|
||||
c := newTestController([]string{}, "")
|
||||
|
||||
c.queue.ShutDown()
|
||||
|
||||
result := c.processNextItem()
|
||||
assert.False(t, result, "Should return false when queue is shutdown")
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/tools/record"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/pkg/common"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/tools/record"
|
||||
)
|
||||
|
||||
// ResourceCreatedHandler contains new objects
|
||||
@@ -59,10 +60,10 @@ func (r ResourceCreatedHandler) Handle() error {
|
||||
func (r ResourceCreatedHandler) GetConfig() (common.Config, string) {
|
||||
var oldSHAData string
|
||||
var config common.Config
|
||||
if _, ok := r.Resource.(*v1.ConfigMap); ok {
|
||||
config = common.GetConfigmapConfig(r.Resource.(*v1.ConfigMap))
|
||||
} else if _, ok := r.Resource.(*v1.Secret); ok {
|
||||
config = common.GetSecretConfig(r.Resource.(*v1.Secret))
|
||||
if cm, ok := r.Resource.(*v1.ConfigMap); ok {
|
||||
config = common.GetConfigmapConfig(cm)
|
||||
} else if secret, ok := r.Resource.(*v1.Secret); ok {
|
||||
config = common.GetSecretConfig(secret)
|
||||
} else {
|
||||
logrus.Warnf("Invalid resource: Resource should be 'Secret' or 'Configmap' but found, %v", r.Resource)
|
||||
}
|
||||
|
||||
@@ -3,20 +3,21 @@ package handler
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"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
|
||||
name string
|
||||
resource interface{}
|
||||
expectedName string
|
||||
expectedNS string
|
||||
expectedType string
|
||||
expectSHANotEmpty bool
|
||||
expectOldSHAEmpty bool
|
||||
}{
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/callbacks"
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
@@ -67,10 +68,10 @@ func (r ResourceDeleteHandler) Handle() error {
|
||||
func (r ResourceDeleteHandler) GetConfig() (common.Config, string) {
|
||||
var oldSHAData string
|
||||
var config common.Config
|
||||
if _, ok := r.Resource.(*v1.ConfigMap); ok {
|
||||
config = common.GetConfigmapConfig(r.Resource.(*v1.ConfigMap))
|
||||
} else if _, ok := r.Resource.(*v1.Secret); ok {
|
||||
config = common.GetSecretConfig(r.Resource.(*v1.Secret))
|
||||
if cm, ok := r.Resource.(*v1.ConfigMap); ok {
|
||||
config = common.GetConfigmapConfig(cm)
|
||||
} else if secret, ok := r.Resource.(*v1.Secret); ok {
|
||||
config = common.GetSecretConfig(secret)
|
||||
} else {
|
||||
logrus.Warnf("Invalid resource: Resource should be 'Secret' or 'Configmap' but found, %v", r.Resource)
|
||||
}
|
||||
@@ -98,7 +99,7 @@ func removeContainerEnvVars(upgradeFuncs callbacks.RollingUpgradeFuncs, item run
|
||||
return InvokeStrategyResult{constants.NoContainerFound, nil}
|
||||
}
|
||||
|
||||
//remove if env var exists
|
||||
// remove if env var exists
|
||||
if len(container.Env) > 0 {
|
||||
index := slices.IndexFunc(container.Env, func(envVariable v1.EnvVar) bool {
|
||||
return envVariable.Name == envVar
|
||||
|
||||
@@ -3,15 +3,16 @@ package handler
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"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
|
||||
|
||||
@@ -3,30 +3,31 @@ package handler
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"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(name, namespace string, data map[string]string) *v1.ConfigMap {
|
||||
func createTestConfigMap(data map[string]string) *v1.ConfigMap {
|
||||
return &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Name: "test-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a test Secret
|
||||
func createTestSecret(name, namespace string, data map[string][]byte) *v1.Secret {
|
||||
func createTestSecret(data map[string][]byte) *v1.Secret {
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Name: "test-secret",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: data,
|
||||
}
|
||||
@@ -42,7 +43,7 @@ func createTestCollectors() metrics.Collectors {
|
||||
// ============================================================
|
||||
|
||||
func TestResourceCreatedHandler_GetConfig_ConfigMap(t *testing.T) {
|
||||
cm := createTestConfigMap("test-cm", "default", map[string]string{"key": "value"})
|
||||
cm := createTestConfigMap(map[string]string{"key": "value"})
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: cm,
|
||||
Collectors: createTestCollectors(),
|
||||
@@ -58,7 +59,7 @@ func TestResourceCreatedHandler_GetConfig_ConfigMap(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResourceCreatedHandler_GetConfig_Secret(t *testing.T) {
|
||||
secret := createTestSecret("test-secret", "default", map[string][]byte{"key": []byte("value")})
|
||||
secret := createTestSecret(map[string][]byte{"key": []byte("value")})
|
||||
handler := ResourceCreatedHandler{
|
||||
Resource: secret,
|
||||
Collectors: createTestCollectors(),
|
||||
@@ -103,7 +104,7 @@ func TestResourceCreatedHandler_Handle_NilResource(t *testing.T) {
|
||||
// ============================================================
|
||||
|
||||
func TestResourceDeleteHandler_GetConfig_ConfigMap(t *testing.T) {
|
||||
cm := createTestConfigMap("test-cm", "default", map[string]string{"key": "value"})
|
||||
cm := createTestConfigMap(map[string]string{"key": "value"})
|
||||
handler := ResourceDeleteHandler{
|
||||
Resource: cm,
|
||||
Collectors: createTestCollectors(),
|
||||
@@ -119,7 +120,7 @@ func TestResourceDeleteHandler_GetConfig_ConfigMap(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResourceDeleteHandler_GetConfig_Secret(t *testing.T) {
|
||||
secret := createTestSecret("test-secret", "default", map[string][]byte{"key": []byte("value")})
|
||||
secret := createTestSecret(map[string][]byte{"key": []byte("value")})
|
||||
handler := ResourceDeleteHandler{
|
||||
Resource: secret,
|
||||
Collectors: createTestCollectors(),
|
||||
@@ -161,8 +162,8 @@ func TestResourceDeleteHandler_Handle_NilResource(t *testing.T) {
|
||||
// ============================================================
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_ConfigMap(t *testing.T) {
|
||||
oldCM := createTestConfigMap("test-cm", "default", map[string]string{"key": "old-value"})
|
||||
newCM := createTestConfigMap("test-cm", "default", map[string]string{"key": "new-value"})
|
||||
oldCM := createTestConfigMap(map[string]string{"key": "old-value"})
|
||||
newCM := createTestConfigMap(map[string]string{"key": "new-value"})
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
@@ -182,8 +183,8 @@ func TestResourceUpdatedHandler_GetConfig_ConfigMap(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_ConfigMap_SameData(t *testing.T) {
|
||||
oldCM := createTestConfigMap("test-cm", "default", map[string]string{"key": "same-value"})
|
||||
newCM := createTestConfigMap("test-cm", "default", map[string]string{"key": "same-value"})
|
||||
oldCM := createTestConfigMap(map[string]string{"key": "same-value"})
|
||||
newCM := createTestConfigMap(map[string]string{"key": "same-value"})
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newCM,
|
||||
@@ -199,8 +200,8 @@ func TestResourceUpdatedHandler_GetConfig_ConfigMap_SameData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_Secret(t *testing.T) {
|
||||
oldSecret := createTestSecret("test-secret", "default", map[string][]byte{"key": []byte("old-value")})
|
||||
newSecret := createTestSecret("test-secret", "default", map[string][]byte{"key": []byte("new-value")})
|
||||
oldSecret := createTestSecret(map[string][]byte{"key": []byte("old-value")})
|
||||
newSecret := createTestSecret(map[string][]byte{"key": []byte("new-value")})
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newSecret,
|
||||
@@ -219,8 +220,8 @@ func TestResourceUpdatedHandler_GetConfig_Secret(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_GetConfig_Secret_SameData(t *testing.T) {
|
||||
oldSecret := createTestSecret("test-secret", "default", map[string][]byte{"key": []byte("same-value")})
|
||||
newSecret := createTestSecret("test-secret", "default", map[string][]byte{"key": []byte("same-value")})
|
||||
oldSecret := createTestSecret(map[string][]byte{"key": []byte("same-value")})
|
||||
newSecret := createTestSecret(map[string][]byte{"key": []byte("same-value")})
|
||||
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: newSecret,
|
||||
@@ -260,7 +261,7 @@ func TestResourceUpdatedHandler_Handle_NilResource(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResourceUpdatedHandler_Handle_NilOldResource(t *testing.T) {
|
||||
cm := createTestConfigMap("test-cm", "default", map[string]string{"key": "value"})
|
||||
cm := createTestConfigMap(map[string]string{"key": "value"})
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: cm,
|
||||
OldResource: nil,
|
||||
@@ -275,7 +276,7 @@ func TestResourceUpdatedHandler_Handle_NilOldResource(t *testing.T) {
|
||||
|
||||
func TestResourceUpdatedHandler_Handle_NoChange(t *testing.T) {
|
||||
// When SHA values are the same, Handle should return nil without doing anything
|
||||
cm := createTestConfigMap("test-cm", "default", map[string]string{"key": "same-value"})
|
||||
cm := createTestConfigMap(map[string]string{"key": "same-value"})
|
||||
handler := ResourceUpdatedHandler{
|
||||
Resource: cm,
|
||||
OldResource: cm, // Same resource = same SHA
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
app "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
patchtypes "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
)
|
||||
|
||||
// Keeps track of currently active timers
|
||||
|
||||
@@ -6,14 +6,15 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
testclient "k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
)
|
||||
|
||||
func TestIsPaused(t *testing.T) {
|
||||
@@ -377,7 +378,7 @@ func FindDeploymentByName(deployments []runtime.Object, deploymentName string) (
|
||||
for _, deployment := range deployments {
|
||||
accessor, err := meta.Accessor(deployment)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting accessor for item: %v", err)
|
||||
return nil, fmt.Errorf("error getting accessor for item: %w", err)
|
||||
}
|
||||
if accessor.GetName() == deploymentName {
|
||||
deploymentObj, ok := deployment.(*appsv1.Deployment)
|
||||
|
||||
@@ -4,13 +4,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/tools/record"
|
||||
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/internal/pkg/util"
|
||||
"github.com/stakater/Reloader/pkg/common"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/tools/record"
|
||||
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
|
||||
)
|
||||
|
||||
// ResourceUpdatedHandler contains updated objects
|
||||
|
||||
@@ -3,11 +3,12 @@ package handler
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"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) {
|
||||
|
||||
@@ -14,14 +14,6 @@ import (
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/sirupsen/logrus"
|
||||
alert "github.com/stakater/Reloader/internal/pkg/alerts"
|
||||
"github.com/stakater/Reloader/internal/pkg/callbacks"
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/internal/pkg/util"
|
||||
"github.com/stakater/Reloader/pkg/common"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
app "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -32,6 +24,15 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/retry"
|
||||
|
||||
alert "github.com/stakater/Reloader/internal/pkg/alerts"
|
||||
"github.com/stakater/Reloader/internal/pkg/callbacks"
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/internal/pkg/util"
|
||||
"github.com/stakater/Reloader/pkg/common"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
)
|
||||
|
||||
// GetDeploymentRollingUpgradeFuncs returns all callback funcs for a deployment
|
||||
@@ -617,7 +618,7 @@ func updateContainerEnvVars(upgradeFuncs callbacks.RollingUpgradeFuncs, item run
|
||||
return InvokeStrategyResult{constants.NotUpdated, nil}
|
||||
}
|
||||
|
||||
//update if env var exists
|
||||
// update if env var exists
|
||||
updateResult := updateEnvVar(container, envVar, config.SHAValue)
|
||||
|
||||
// if no existing env var exists lets create one
|
||||
@@ -680,10 +681,10 @@ func populateAnnotationsFromSecretProviderClass(clients kube.Clients, config *co
|
||||
}
|
||||
|
||||
func jsonEscape(toEscape string) (string, error) {
|
||||
bytes, err := json.Marshal(toEscape)
|
||||
data, err := json.Marshal(toEscape)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
escaped := string(bytes)
|
||||
escaped := string(data)
|
||||
return escaped[1 : len(escaped)-1], nil
|
||||
}
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/util/retry"
|
||||
|
||||
"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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestGetRollingUpgradeFuncs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
getFuncs func() callbacks.RollingUpgradeFuncs
|
||||
resourceType string
|
||||
name string
|
||||
getFuncs func() callbacks.RollingUpgradeFuncs
|
||||
resourceType string
|
||||
supportsPatch bool
|
||||
}{
|
||||
{
|
||||
@@ -495,12 +504,12 @@ func TestGetEnvVarName(t *testing.T) {
|
||||
|
||||
func TestUpdateEnvVar(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
container *v1.Container
|
||||
envVar string
|
||||
shaData string
|
||||
expected constants.Result
|
||||
newValue string // expected value after update
|
||||
name string
|
||||
container *v1.Container
|
||||
envVar string
|
||||
shaData string
|
||||
expected constants.Result
|
||||
newValue string // expected value after update
|
||||
}{
|
||||
{
|
||||
name: "Update existing env var with different value",
|
||||
@@ -670,3 +679,704 @@ func TestCreateReloadedAnnotations(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a mock deployment for testing
|
||||
func createTestDeployment(containers []v1.Container, initContainers []v1.Container, volumes []v1.Volume) *appsv1.Deployment {
|
||||
return &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-deployment",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Template: v1.PodTemplateSpec{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: containers,
|
||||
InitContainers: initContainers,
|
||||
Volumes: volumes,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// mockRollingUpgradeFuncs creates mock callbacks for testing getContainerUsingResource
|
||||
func mockRollingUpgradeFuncs(deployment *appsv1.Deployment) callbacks.RollingUpgradeFuncs {
|
||||
return callbacks.RollingUpgradeFuncs{
|
||||
VolumesFunc: func(item runtime.Object) []v1.Volume {
|
||||
return deployment.Spec.Template.Spec.Volumes
|
||||
},
|
||||
ContainersFunc: func(item runtime.Object) []v1.Container {
|
||||
return deployment.Spec.Template.Spec.Containers
|
||||
},
|
||||
InitContainersFunc: func(item runtime.Object) []v1.Container {
|
||||
return deployment.Spec.Template.Spec.InitContainers
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContainerUsingResource(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []v1.Container
|
||||
initContainers []v1.Container
|
||||
volumes []v1.Volume
|
||||
config common.Config
|
||||
autoReload bool
|
||||
expectNil bool
|
||||
expectedName string
|
||||
}{
|
||||
{
|
||||
name: "Volume mount in regular container",
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{Name: "config-volume", MountPath: "/etc/config"},
|
||||
},
|
||||
},
|
||||
},
|
||||
initContainers: []v1.Container{},
|
||||
volumes: []v1.Volume{
|
||||
{
|
||||
Name: "config-volume",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
ConfigMap: &v1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: "my-configmap"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: common.Config{
|
||||
ResourceName: "my-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: false,
|
||||
expectNil: false,
|
||||
expectedName: "app",
|
||||
},
|
||||
{
|
||||
name: "Volume mount in init container returns first regular container",
|
||||
containers: []v1.Container{
|
||||
{Name: "main-app"},
|
||||
{Name: "sidecar"},
|
||||
},
|
||||
initContainers: []v1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{Name: "secret-volume", MountPath: "/etc/secrets"},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: []v1.Volume{
|
||||
{
|
||||
Name: "secret-volume",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Secret: &v1.SecretVolumeSource{SecretName: "my-secret"},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: common.Config{
|
||||
ResourceName: "my-secret",
|
||||
Type: constants.SecretEnvVarPostfix,
|
||||
},
|
||||
autoReload: false,
|
||||
expectNil: false,
|
||||
expectedName: "main-app", // Returns first container when init container has the mount
|
||||
},
|
||||
{
|
||||
name: "EnvFrom ConfigMap in regular container",
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: "env-configmap"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initContainers: []v1.Container{},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "env-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: false,
|
||||
expectNil: false,
|
||||
expectedName: "app",
|
||||
},
|
||||
{
|
||||
name: "EnvFrom Secret in init container returns first regular container",
|
||||
containers: []v1.Container{
|
||||
{Name: "main-app"},
|
||||
},
|
||||
initContainers: []v1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &v1.SecretEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: "init-secret"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "init-secret",
|
||||
Type: constants.SecretEnvVarPostfix,
|
||||
},
|
||||
autoReload: false,
|
||||
expectNil: false,
|
||||
expectedName: "main-app",
|
||||
},
|
||||
{
|
||||
name: "autoReload=false with no mount returns first container (explicit annotation)",
|
||||
containers: []v1.Container{
|
||||
{Name: "first-container"},
|
||||
{Name: "second-container"},
|
||||
},
|
||||
initContainers: []v1.Container{},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "external-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: false, // Explicit annotation should use first container fallback
|
||||
expectNil: false,
|
||||
expectedName: "first-container",
|
||||
},
|
||||
{
|
||||
name: "autoReload=true with no mount returns nil",
|
||||
containers: []v1.Container{
|
||||
{Name: "app"},
|
||||
},
|
||||
initContainers: []v1.Container{},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "unmounted-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: true, // Auto mode should NOT use first container fallback
|
||||
expectNil: true,
|
||||
},
|
||||
{
|
||||
name: "Empty containers returns nil",
|
||||
containers: []v1.Container{},
|
||||
initContainers: []v1.Container{},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "any-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: false,
|
||||
expectNil: true,
|
||||
},
|
||||
{
|
||||
name: "Init container with volume but no regular containers returns nil",
|
||||
containers: []v1.Container{},
|
||||
initContainers: []v1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{Name: "config-volume", MountPath: "/etc/config"},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: []v1.Volume{
|
||||
{
|
||||
Name: "config-volume",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
ConfigMap: &v1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: "init-only-cm"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: common.Config{
|
||||
ResourceName: "init-only-cm",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: false,
|
||||
expectNil: true, // No regular containers to return
|
||||
},
|
||||
{
|
||||
name: "CSI SecretProviderClass volume",
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{Name: "csi-volume", MountPath: "/mnt/secrets"},
|
||||
},
|
||||
},
|
||||
},
|
||||
initContainers: []v1.Container{},
|
||||
volumes: []v1.Volume{
|
||||
{
|
||||
Name: "csi-volume",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
CSI: &v1.CSIVolumeSource{
|
||||
Driver: "secrets-store.csi.k8s.io",
|
||||
VolumeAttributes: map[string]string{
|
||||
"secretProviderClass": "my-spc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config: common.Config{
|
||||
ResourceName: "my-spc",
|
||||
Type: constants.SecretProviderClassEnvVarPostfix,
|
||||
},
|
||||
autoReload: false,
|
||||
expectNil: false,
|
||||
expectedName: "app",
|
||||
},
|
||||
{
|
||||
name: "Env ValueFrom ConfigMapKeyRef",
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "CONFIG_VALUE",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: "keyref-cm"},
|
||||
Key: "my-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initContainers: []v1.Container{},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "keyref-cm",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
},
|
||||
autoReload: false,
|
||||
expectNil: false,
|
||||
expectedName: "app",
|
||||
},
|
||||
{
|
||||
name: "Env ValueFrom SecretKeyRef",
|
||||
containers: []v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Env: []v1.EnvVar{
|
||||
{
|
||||
Name: "SECRET_VALUE",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: "keyref-secret"},
|
||||
Key: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
initContainers: []v1.Container{},
|
||||
volumes: []v1.Volume{},
|
||||
config: common.Config{
|
||||
ResourceName: "keyref-secret",
|
||||
Type: constants.SecretEnvVarPostfix,
|
||||
},
|
||||
autoReload: false,
|
||||
expectNil: false,
|
||||
expectedName: "app",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
deployment := createTestDeployment(tt.containers, tt.initContainers, tt.volumes)
|
||||
funcs := mockRollingUpgradeFuncs(deployment)
|
||||
|
||||
result := getContainerUsingResource(funcs, deployment, tt.config, tt.autoReload)
|
||||
|
||||
if tt.expectNil {
|
||||
assert.Nil(t, result, "Expected nil container")
|
||||
} else {
|
||||
assert.NotNil(t, result, "Expected non-nil container")
|
||||
assert.Equal(t, tt.expectedName, result.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryOnConflict(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fnResults []struct {
|
||||
matched bool
|
||||
err error
|
||||
}
|
||||
expectMatched bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Success on first try",
|
||||
fnResults: []struct {
|
||||
matched bool
|
||||
err error
|
||||
}{
|
||||
{matched: true, err: nil},
|
||||
},
|
||||
expectMatched: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Conflict then success",
|
||||
fnResults: []struct {
|
||||
matched bool
|
||||
err error
|
||||
}{
|
||||
{matched: false, err: apierrors.NewConflict(schema.GroupResource{Group: "", Resource: "deployments"}, "test", errors.New("conflict"))},
|
||||
{matched: true, err: nil},
|
||||
},
|
||||
expectMatched: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Non-conflict error returns immediately",
|
||||
fnResults: []struct {
|
||||
matched bool
|
||||
err error
|
||||
}{
|
||||
{matched: false, err: errors.New("some other error")},
|
||||
},
|
||||
expectMatched: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Multiple conflicts then success",
|
||||
fnResults: []struct {
|
||||
matched bool
|
||||
err error
|
||||
}{
|
||||
{matched: false, err: apierrors.NewConflict(schema.GroupResource{}, "test", errors.New("conflict 1"))},
|
||||
{matched: false, err: apierrors.NewConflict(schema.GroupResource{}, "test", errors.New("conflict 2"))},
|
||||
{matched: true, err: nil},
|
||||
},
|
||||
expectMatched: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Not matched but no error",
|
||||
fnResults: []struct {
|
||||
matched bool
|
||||
err error
|
||||
}{
|
||||
{matched: false, err: nil},
|
||||
},
|
||||
expectMatched: false,
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
callCount := 0
|
||||
fn := func(fetchResource bool) (bool, error) {
|
||||
if callCount >= len(tt.fnResults) {
|
||||
// Should not happen in tests, but return success to prevent infinite loop
|
||||
return true, nil
|
||||
}
|
||||
result := tt.fnResults[callCount]
|
||||
callCount++
|
||||
return result.matched, result.err
|
||||
}
|
||||
|
||||
matched, err := retryOnConflict(retry.DefaultRetry, fn)
|
||||
|
||||
assert.Equal(t, tt.expectMatched, matched)
|
||||
if tt.expectError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVolumeMountNameCSI(t *testing.T) {
|
||||
// Test CSI SecretProviderClass volume specifically
|
||||
tests := []struct {
|
||||
name string
|
||||
volumes []v1.Volume
|
||||
mountType string
|
||||
volumeName string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "CSI SecretProviderClass volume match",
|
||||
volumes: []v1.Volume{
|
||||
{
|
||||
Name: "csi-secrets",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
CSI: &v1.CSIVolumeSource{
|
||||
Driver: "secrets-store.csi.k8s.io",
|
||||
VolumeAttributes: map[string]string{
|
||||
"secretProviderClass": "my-vault-spc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mountType: constants.SecretProviderClassEnvVarPostfix,
|
||||
volumeName: "my-vault-spc",
|
||||
expected: "csi-secrets",
|
||||
},
|
||||
{
|
||||
name: "CSI volume with different SPC name - no match",
|
||||
volumes: []v1.Volume{
|
||||
{
|
||||
Name: "csi-secrets",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
CSI: &v1.CSIVolumeSource{
|
||||
Driver: "secrets-store.csi.k8s.io",
|
||||
VolumeAttributes: map[string]string{
|
||||
"secretProviderClass": "other-spc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mountType: constants.SecretProviderClassEnvVarPostfix,
|
||||
volumeName: "my-vault-spc",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "CSI volume without secretProviderClass attribute",
|
||||
volumes: []v1.Volume{
|
||||
{
|
||||
Name: "csi-volume",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
CSI: &v1.CSIVolumeSource{
|
||||
Driver: "other-csi-driver",
|
||||
VolumeAttributes: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mountType: constants.SecretProviderClassEnvVarPostfix,
|
||||
volumeName: "any-spc",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "CSI volume with nil VolumeAttributes",
|
||||
volumes: []v1.Volume{
|
||||
{
|
||||
Name: "csi-volume",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
CSI: &v1.CSIVolumeSource{
|
||||
Driver: "secrets-store.csi.k8s.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mountType: constants.SecretProviderClassEnvVarPostfix,
|
||||
volumeName: "any-spc",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "Multiple volumes with CSI match",
|
||||
volumes: []v1.Volume{
|
||||
{
|
||||
Name: "config-volume",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
ConfigMap: &v1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: "my-cm"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "csi-secrets",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
CSI: &v1.CSIVolumeSource{
|
||||
Driver: "secrets-store.csi.k8s.io",
|
||||
VolumeAttributes: map[string]string{
|
||||
"secretProviderClass": "target-spc",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mountType: constants.SecretProviderClassEnvVarPostfix,
|
||||
volumeName: "target-spc",
|
||||
expected: "csi-secrets",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := getVolumeMountName(tt.volumes, tt.mountType, tt.volumeName)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretProviderClassAnnotationReloaded(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
oldAnnotations map[string]string
|
||||
newConfig common.Config
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Annotation contains matching SPC name and SHA",
|
||||
oldAnnotations: map[string]string{
|
||||
"reloader.stakater.com/last-reloaded-from": `{"name":"my-spc","sha":"abc123"}`,
|
||||
},
|
||||
newConfig: common.Config{
|
||||
ResourceName: "my-spc",
|
||||
SHAValue: "abc123",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Annotation contains SPC name but different SHA",
|
||||
oldAnnotations: map[string]string{
|
||||
"reloader.stakater.com/last-reloaded-from": `{"name":"my-spc","sha":"old-sha"}`,
|
||||
},
|
||||
newConfig: common.Config{
|
||||
ResourceName: "my-spc",
|
||||
SHAValue: "new-sha",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Annotation contains different SPC name",
|
||||
oldAnnotations: map[string]string{
|
||||
"reloader.stakater.com/last-reloaded-from": `{"name":"other-spc","sha":"abc123"}`,
|
||||
},
|
||||
newConfig: common.Config{
|
||||
ResourceName: "my-spc",
|
||||
SHAValue: "abc123",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty annotations",
|
||||
oldAnnotations: map[string]string{},
|
||||
newConfig: common.Config{
|
||||
ResourceName: "my-spc",
|
||||
SHAValue: "abc123",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Nil annotations",
|
||||
oldAnnotations: nil,
|
||||
newConfig: common.Config{
|
||||
ResourceName: "my-spc",
|
||||
SHAValue: "abc123",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Annotation key missing",
|
||||
oldAnnotations: map[string]string{
|
||||
"other-annotation": "some-value",
|
||||
},
|
||||
newConfig: common.Config{
|
||||
ResourceName: "my-spc",
|
||||
SHAValue: "abc123",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := secretProviderClassAnnotationReloaded(tt.oldAnnotations, tt.newConfig)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvokeReloadStrategy(t *testing.T) {
|
||||
// Save original value and restore after test
|
||||
originalStrategy := options.ReloadStrategy
|
||||
defer func() { options.ReloadStrategy = originalStrategy }()
|
||||
|
||||
// Create a minimal deployment for testing
|
||||
deployment := createTestDeployment(
|
||||
[]v1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: "my-configmap"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[]v1.Container{},
|
||||
[]v1.Volume{},
|
||||
)
|
||||
deployment.Spec.Template.Annotations = map[string]string{}
|
||||
|
||||
funcs := callbacks.RollingUpgradeFuncs{
|
||||
VolumesFunc: func(item runtime.Object) []v1.Volume {
|
||||
return deployment.Spec.Template.Spec.Volumes
|
||||
},
|
||||
ContainersFunc: func(item runtime.Object) []v1.Container {
|
||||
return deployment.Spec.Template.Spec.Containers
|
||||
},
|
||||
InitContainersFunc: func(item runtime.Object) []v1.Container {
|
||||
return deployment.Spec.Template.Spec.InitContainers
|
||||
},
|
||||
PodAnnotationsFunc: func(item runtime.Object) map[string]string {
|
||||
return deployment.Spec.Template.Annotations
|
||||
},
|
||||
SupportsPatch: false,
|
||||
}
|
||||
|
||||
config := common.Config{
|
||||
ResourceName: "my-configmap",
|
||||
Type: constants.ConfigmapEnvVarPostfix,
|
||||
SHAValue: "sha256:abc123",
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
reloadStrategy string
|
||||
autoReload bool
|
||||
expectResult constants.Result
|
||||
}{
|
||||
{
|
||||
name: "Annotations strategy",
|
||||
reloadStrategy: constants.AnnotationsReloadStrategy,
|
||||
autoReload: false,
|
||||
expectResult: constants.Updated,
|
||||
},
|
||||
{
|
||||
name: "Env vars strategy with container found",
|
||||
reloadStrategy: constants.EnvVarsReloadStrategy,
|
||||
autoReload: false,
|
||||
expectResult: constants.Updated, // Creates env var when not found
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
options.ReloadStrategy = tt.reloadStrategy
|
||||
// Reset annotations for each test
|
||||
deployment.Spec.Template.Annotations = map[string]string{}
|
||||
|
||||
result := invokeReloadStrategy(funcs, deployment, config, tt.autoReload)
|
||||
assert.Equal(t, tt.expectResult, result.Result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stakater/Reloader/internal/pkg/controller"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/tools/leaderelection"
|
||||
"k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/controller"
|
||||
|
||||
coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1"
|
||||
)
|
||||
|
||||
@@ -75,7 +76,7 @@ func RunLeaderElection(lock *resourcelock.LeaseLock, ctx context.Context, cancel
|
||||
|
||||
func runControllers(controllers []*controller.Controller, stopChannels []chan struct{}) {
|
||||
for i, c := range controllers {
|
||||
c := c
|
||||
|
||||
go c.Run(1, stopChannels[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/controller"
|
||||
"github.com/stakater/Reloader/internal/pkg/handler"
|
||||
|
||||
@@ -15,14 +15,6 @@ import (
|
||||
openshiftv1 "github.com/openshift/api/apps/v1"
|
||||
appsclient "github.com/openshift/client-go/apps/clientset/versioned"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stakater/Reloader/internal/pkg/callbacks"
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/crypto"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/internal/pkg/util"
|
||||
"github.com/stakater/Reloader/pkg/common"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -33,6 +25,15 @@ import (
|
||||
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
|
||||
csiclient "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned"
|
||||
csiclient_v1 "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/typed/apis/v1"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/callbacks"
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/crypto"
|
||||
"github.com/stakater/Reloader/internal/pkg/metrics"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
"github.com/stakater/Reloader/internal/pkg/util"
|
||||
"github.com/stakater/Reloader/pkg/common"
|
||||
"github.com/stakater/Reloader/pkg/kube"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -31,7 +31,7 @@ type ObjectMeta struct {
|
||||
func ToObjectMeta(kubernetesObject interface{}) ObjectMeta {
|
||||
objectValue := reflect.ValueOf(kubernetesObject)
|
||||
fieldName := reflect.TypeOf((*metav1.ObjectMeta)(nil)).Elem().Name()
|
||||
field := objectValue.FieldByName(fieldName).Interface().(metav1.ObjectMeta)
|
||||
field, _ := objectValue.FieldByName(fieldName).Interface().(metav1.ObjectMeta)
|
||||
|
||||
return ObjectMeta{
|
||||
ObjectMeta: field,
|
||||
@@ -41,9 +41,11 @@ func ToObjectMeta(kubernetesObject interface{}) ObjectMeta {
|
||||
// ParseBool returns result in bool format after parsing
|
||||
func ParseBool(value interface{}) bool {
|
||||
if reflect.Bool == reflect.TypeOf(value).Kind() {
|
||||
return value.(bool)
|
||||
b, _ := value.(bool)
|
||||
return b
|
||||
} else if reflect.String == reflect.TypeOf(value).Kind() {
|
||||
result, _ := strconv.ParseBool(value.(string))
|
||||
s, _ := value.(string)
|
||||
result, _ := strconv.ParseBool(s)
|
||||
return result
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -9,11 +9,12 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/crypto"
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
csiv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
|
||||
)
|
||||
|
||||
// ConvertToEnvVarName converts the given text into a usable env var
|
||||
|
||||
@@ -3,8 +3,9 @@ package util
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/options"
|
||||
)
|
||||
|
||||
func TestConvertToEnvVarName(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user