mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-23 22:16:45 +00:00
Merge pull request #1141 from stakater/takeover-e2e-tests
Takeover e2e tests
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Container{}
|
||||
}
|
||||
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 []v1.Volume{}
|
||||
}
|
||||
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 []v1.Volume{}
|
||||
}
|
||||
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 []v1.Volume{}
|
||||
}
|
||||
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 []v1.Volume{}
|
||||
}
|
||||
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 []v1.Volume{}
|
||||
}
|
||||
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 []v1.Volume{}
|
||||
}
|
||||
return rollout.Spec.Template.Spec.Volumes
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func startReloader(cmd *cobra.Command, args []string) {
|
||||
lock := leadership.GetNewLock(clientset.CoordinationV1(), constants.LockName, podName, podNamespace)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go leadership.RunLeaderElection(lock, ctx, cancel, podName, controllers)
|
||||
leadership.RunLeaderElection(lock, ctx, cancel, podName, controllers)
|
||||
}
|
||||
|
||||
common.PublishMetaInfoConfigmap(clientset)
|
||||
|
||||
@@ -2,16 +2,11 @@ package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"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 +19,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
|
||||
@@ -41,18 +42,56 @@ type Controller struct {
|
||||
resourceSelector string
|
||||
}
|
||||
|
||||
// controllerInitialized flag determines whether controlled is being initialized
|
||||
var secretControllerInitialized bool = false
|
||||
var configmapControllerInitialized bool = false
|
||||
var selectedNamespacesCache []string
|
||||
// controllerInitialized flags guard against processing Add/Delete events before
|
||||
// the worker goroutines have started. Written by runWorker (in a goroutine) and
|
||||
// read by the informer event handlers, so they must be atomic.
|
||||
var secretControllerInitialized atomic.Bool
|
||||
var configmapControllerInitialized atomic.Bool
|
||||
|
||||
// selectedNamespacesCache holds an immutable snapshot of the set of namespace
|
||||
// names that match the namespace label selector. Written exclusively by the
|
||||
// namespace controller's informer goroutine; read concurrently by configmap/
|
||||
// secret controller informer goroutines. Using atomic.Value with an immutable
|
||||
// map[string]struct{} snapshot avoids mutexes and prevents data races.
|
||||
var selectedNamespacesCache atomic.Value // always stores map[string]struct{}
|
||||
|
||||
// loadSelectedNamespaces returns the current namespace snapshot (never nil).
|
||||
func loadSelectedNamespaces() map[string]struct{} {
|
||||
if v := selectedNamespacesCache.Load(); v != nil {
|
||||
if m, ok := v.(map[string]struct{}); ok {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return map[string]struct{}{}
|
||||
}
|
||||
|
||||
// storeSelectedNamespaces replaces the current snapshot with one built from ns.
|
||||
// It is the only mutator of selectedNamespacesCache and is called only from
|
||||
// the namespace controller's informer goroutine (or from tests for setup).
|
||||
func storeSelectedNamespaces(ns []string) {
|
||||
m := make(map[string]struct{}, len(ns))
|
||||
for _, n := range ns {
|
||||
m[n] = struct{}{}
|
||||
}
|
||||
selectedNamespacesCache.Store(m)
|
||||
}
|
||||
|
||||
// loadSelectedNamespacesList returns the current namespace names as a slice.
|
||||
// Intended for use in tests where slice-based assertions are more convenient.
|
||||
func loadSelectedNamespacesList() []string {
|
||||
m := loadSelectedNamespaces()
|
||||
result := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
result = append(result, k)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
func NewController(client kubernetes.Interface, resource string, namespace string, ignoredNamespaces []string, namespaceLabelSelector string, resourceLabelSelector string, collectors metrics.Collectors) (*Controller, error) {
|
||||
if options.SyncAfterRestart {
|
||||
secretControllerInitialized = true
|
||||
configmapControllerInitialized = true
|
||||
secretControllerInitialized.Store(true)
|
||||
configmapControllerInitialized.Store(true)
|
||||
}
|
||||
|
||||
c := Controller{
|
||||
@@ -67,17 +106,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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +161,7 @@ func (c *Controller) Add(obj interface{}) {
|
||||
}
|
||||
|
||||
if options.ReloadOnCreate == "true" {
|
||||
if !c.resourceInIgnoredNamespace(obj) && c.resourceInSelectedNamespaces(obj) && secretControllerInitialized && configmapControllerInitialized {
|
||||
if !c.resourceInIgnoredNamespace(obj) && c.resourceInSelectedNamespaces(obj) && secretControllerInitialized.Load() && configmapControllerInitialized.Load() {
|
||||
c.enqueue(handler.ResourceCreatedHandler{
|
||||
Resource: obj,
|
||||
Collectors: c.collectors,
|
||||
@@ -151,36 +191,45 @@ func (c *Controller) resourceInSelectedNamespaces(raw interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
namespaces := loadSelectedNamespaces()
|
||||
var ns string
|
||||
switch object := raw.(type) {
|
||||
case *v1.ConfigMap:
|
||||
if slices.Contains(selectedNamespacesCache, object.GetNamespace()) {
|
||||
return true
|
||||
}
|
||||
ns = object.GetNamespace()
|
||||
case *v1.Secret:
|
||||
if slices.Contains(selectedNamespacesCache, object.GetNamespace()) {
|
||||
return true
|
||||
}
|
||||
ns = object.GetNamespace()
|
||||
case *csiv1.SecretProviderClassPodStatus:
|
||||
if slices.Contains(selectedNamespacesCache, object.GetNamespace()) {
|
||||
return true
|
||||
}
|
||||
ns = object.GetNamespace()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
_, ok := namespaces[ns]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (c *Controller) addSelectedNamespaceToCache(namespace v1.Namespace) {
|
||||
selectedNamespacesCache = append(selectedNamespacesCache, namespace.GetName())
|
||||
old := loadSelectedNamespaces()
|
||||
next := make(map[string]struct{}, len(old)+1)
|
||||
for k := range old {
|
||||
next[k] = struct{}{}
|
||||
}
|
||||
next[namespace.GetName()] = struct{}{}
|
||||
selectedNamespacesCache.Store(next)
|
||||
logrus.Infof("added namespace to be watched: %s", namespace.GetName())
|
||||
}
|
||||
|
||||
func (c *Controller) removeSelectedNamespaceFromCache(namespace v1.Namespace) {
|
||||
for i, v := range selectedNamespacesCache {
|
||||
if v == namespace.GetName() {
|
||||
selectedNamespacesCache = append(selectedNamespacesCache[:i], selectedNamespacesCache[i+1:]...)
|
||||
logrus.Infof("removed namespace from watch: %s", namespace.GetName())
|
||||
return
|
||||
}
|
||||
old := loadSelectedNamespaces()
|
||||
if _, ok := old[namespace.GetName()]; !ok {
|
||||
return
|
||||
}
|
||||
next := make(map[string]struct{}, len(old))
|
||||
for k := range old {
|
||||
next[k] = struct{}{}
|
||||
}
|
||||
delete(next, namespace.GetName())
|
||||
selectedNamespacesCache.Store(next)
|
||||
logrus.Infof("removed namespace from watch: %s", namespace.GetName())
|
||||
}
|
||||
|
||||
// Update function to add an old object and a new object to the queue in case of updating a resource
|
||||
@@ -214,7 +263,7 @@ func (c *Controller) Delete(old interface{}) {
|
||||
}
|
||||
|
||||
if options.ReloadOnDelete == "true" {
|
||||
if !c.resourceInIgnoredNamespace(old) && c.resourceInSelectedNamespaces(old) && secretControllerInitialized && configmapControllerInitialized {
|
||||
if !c.resourceInIgnoredNamespace(old) && c.resourceInSelectedNamespaces(old) && secretControllerInitialized.Load() && configmapControllerInitialized.Load() {
|
||||
c.enqueue(handler.ResourceDeleteHandler{
|
||||
Resource: old,
|
||||
Collectors: c.collectors,
|
||||
@@ -244,31 +293,44 @@ func (c *Controller) enqueue(item interface{}) {
|
||||
func (c *Controller) Run(threadiness int, stopCh chan struct{}) {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
// Let the workers stop when we are done
|
||||
defer c.queue.ShutDown()
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go c.informer.Run(stopCh)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.informer.Run(stopCh)
|
||||
}()
|
||||
|
||||
// Wait for all involved caches to be synced, before processing items from the queue is started
|
||||
if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
|
||||
runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
|
||||
c.queue.ShutDown()
|
||||
wg.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < threadiness; i++ {
|
||||
go wait.Until(c.runWorker, time.Second, stopCh)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wait.Until(c.runWorker, time.Second, stopCh)
|
||||
}()
|
||||
}
|
||||
|
||||
<-stopCh
|
||||
logrus.Infof("Stopping Controller")
|
||||
logrus.Infof("Stopping Controller for %s", c.resource)
|
||||
c.queue.ShutDown() // unblock workers so they drain and exit
|
||||
logrus.Infof("Queue shut down for %s, waiting for goroutines", c.resource)
|
||||
wg.Wait() // block until informer and all workers have exited
|
||||
logrus.Infof("All goroutines exited for %s", c.resource)
|
||||
}
|
||||
|
||||
func (c *Controller) runWorker() {
|
||||
// At this point the controller is fully initialized and we can start processing the resources
|
||||
if c.resource == string(v1.ResourceSecrets) {
|
||||
secretControllerInitialized = true
|
||||
secretControllerInitialized.Store(true)
|
||||
} else if c.resource == string(v1.ResourceConfigMaps) {
|
||||
configmapControllerInitialized = true
|
||||
configmapControllerInitialized.Store(true)
|
||||
}
|
||||
|
||||
for c.processNextItem() {
|
||||
@@ -299,7 +361,15 @@ 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)
|
||||
// Clear rate-limiter state so the item doesn't leak memory in the queue.
|
||||
c.queue.Forget(resourceHandler)
|
||||
c.collectors.RecordError("invalid_handler_type")
|
||||
return true
|
||||
}
|
||||
err := rh.Handle()
|
||||
|
||||
duration := time.Since(startTime)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+1352
-5129
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
)
|
||||
|
||||
@@ -34,50 +35,71 @@ func GetNewLock(client coordinationv1.CoordinationV1Interface, lockName, podname
|
||||
}
|
||||
}
|
||||
|
||||
// runLeaderElection runs leadership election. If an instance of the controller is the leader and stops leading it will shutdown.
|
||||
func RunLeaderElection(lock *resourcelock.LeaseLock, ctx context.Context, cancel context.CancelFunc, id string, controllers []*controller.Controller) {
|
||||
// Construct channels for the controllers to use
|
||||
var stopChannels []chan struct{}
|
||||
for i := 0; i < len(controllers); i++ {
|
||||
stop := make(chan struct{})
|
||||
stopChannels = append(stopChannels, stop)
|
||||
}
|
||||
// RunLeaderElection runs leadership election in a background goroutine and
|
||||
// returns a channel that is closed once the goroutine has fully exited
|
||||
// (i.e., OnStoppedLeading has run and all controller goroutines have returned).
|
||||
func RunLeaderElection(lock *resourcelock.LeaseLock, ctx context.Context, cancel context.CancelFunc, id string, controllers []*controller.Controller) <-chan struct{} {
|
||||
stopped := make(chan struct{})
|
||||
|
||||
leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{
|
||||
Lock: lock,
|
||||
ReleaseOnCancel: true,
|
||||
LeaseDuration: 15 * time.Second,
|
||||
RenewDeadline: 10 * time.Second,
|
||||
RetryPeriod: 2 * time.Second,
|
||||
Callbacks: leaderelection.LeaderCallbacks{
|
||||
OnStartedLeading: func(c context.Context) {
|
||||
logrus.Info("became leader, starting controllers")
|
||||
runControllers(controllers, stopChannels)
|
||||
},
|
||||
OnStoppedLeading: func() {
|
||||
logrus.Info("no longer leader, shutting down")
|
||||
stopControllers(stopChannels)
|
||||
cancel()
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
healthy = false
|
||||
},
|
||||
OnNewLeader: func(current_id string) {
|
||||
if current_id == id {
|
||||
logrus.Info("still the leader!")
|
||||
return
|
||||
}
|
||||
logrus.Infof("new leader is %s", current_id)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
go func() {
|
||||
defer close(stopped)
|
||||
|
||||
func runControllers(controllers []*controller.Controller, stopChannels []chan struct{}) {
|
||||
for i, c := range controllers {
|
||||
c := c
|
||||
go c.Run(1, stopChannels[i])
|
||||
}
|
||||
var stopChannels []chan struct{}
|
||||
for range controllers {
|
||||
stopChannels = append(stopChannels, make(chan struct{}))
|
||||
}
|
||||
|
||||
// controllerWg tracks the controller.Run goroutines so that
|
||||
// OnStoppedLeading can wait for them to fully exit before returning.
|
||||
var controllerWg sync.WaitGroup
|
||||
|
||||
leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{
|
||||
Lock: lock,
|
||||
ReleaseOnCancel: true,
|
||||
LeaseDuration: 15 * time.Second,
|
||||
RenewDeadline: 10 * time.Second,
|
||||
RetryPeriod: 2 * time.Second,
|
||||
Callbacks: leaderelection.LeaderCallbacks{
|
||||
OnStartedLeading: func(c context.Context) {
|
||||
m.Lock()
|
||||
healthy = true
|
||||
m.Unlock()
|
||||
logrus.Info("became leader, starting controllers")
|
||||
for i, ctrl := range controllers {
|
||||
controllerWg.Add(1)
|
||||
go func(ctrl *controller.Controller, stopCh chan struct{}) {
|
||||
defer controllerWg.Done()
|
||||
ctrl.Run(1, stopCh)
|
||||
}(ctrl, stopChannels[i])
|
||||
}
|
||||
},
|
||||
OnStoppedLeading: func() {
|
||||
logrus.Info("no longer leader, shutting down")
|
||||
stopControllers(stopChannels)
|
||||
// Wait for all controller.Run goroutines to fully exit.
|
||||
// controller.Run blocks until its informer and workers exit,
|
||||
// so this guarantees no controller goroutine is still running
|
||||
// when OnStoppedLeading returns.
|
||||
logrus.Info("waiting for all controller goroutines to exit")
|
||||
controllerWg.Wait()
|
||||
logrus.Info("all controller goroutines exited")
|
||||
cancel()
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
healthy = false
|
||||
},
|
||||
OnNewLeader: func(current_id string) {
|
||||
if current_id == id {
|
||||
logrus.Info("still the leader!")
|
||||
return
|
||||
}
|
||||
logrus.Infof("new leader is %s", current_id)
|
||||
},
|
||||
},
|
||||
})
|
||||
}()
|
||||
|
||||
return stopped
|
||||
}
|
||||
|
||||
func stopControllers(stopChannels []chan struct{}) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/constants"
|
||||
"github.com/stakater/Reloader/internal/pkg/controller"
|
||||
"github.com/stakater/Reloader/internal/pkg/handler"
|
||||
@@ -70,13 +72,18 @@ func TestHealthz(t *testing.T) {
|
||||
// TestRunLeaderElection validates that the liveness endpoint serves 500 when
|
||||
// leadership election fails
|
||||
func TestRunLeaderElection(t *testing.T) {
|
||||
// Reset shared state left by TestHealthz
|
||||
m.Lock()
|
||||
healthy = true
|
||||
m.Unlock()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
|
||||
lock := GetNewLock(testutil.Clients.KubernetesClient.CoordinationV1(), constants.LockName, testutil.Pod, testutil.Namespace)
|
||||
|
||||
go RunLeaderElection(lock, ctx, cancel, testutil.Pod, []*controller.Controller{})
|
||||
stopped := RunLeaderElection(lock, ctx, cancel, testutil.Pod, []*controller.Controller{})
|
||||
|
||||
// Liveness probe should be serving OK
|
||||
// Before leadership is acquired the probe still reads the current healthy value (true)
|
||||
request, err := http.NewRequest(http.MethodGet, "/live", nil)
|
||||
if err != nil {
|
||||
t.Fatalf(("failed to create request"))
|
||||
@@ -86,7 +93,7 @@ func TestRunLeaderElection(t *testing.T) {
|
||||
|
||||
healthz(response, request)
|
||||
got := response.Code
|
||||
want := 500
|
||||
want := 200
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("got: %d, want: %d", got, want)
|
||||
@@ -95,6 +102,7 @@ func TestRunLeaderElection(t *testing.T) {
|
||||
// Cancel the leader election context, so leadership is released and
|
||||
// live endpoint serves 500
|
||||
cancel()
|
||||
<-stopped
|
||||
|
||||
request, err = http.NewRequest(http.MethodGet, "/live", nil)
|
||||
if err != nil {
|
||||
@@ -119,6 +127,16 @@ func TestRunLeaderElectionWithControllers(t *testing.T) {
|
||||
t.Logf("Creating controller")
|
||||
var controllers []*controller.Controller
|
||||
for k := range kube.ResourceMap {
|
||||
// Skip namespace controller when there is no namespace label selector
|
||||
// (mirrors production behavior in startReloader).
|
||||
if k == "namespaces" {
|
||||
continue
|
||||
}
|
||||
// Skip CSI controller when CSI is not installed
|
||||
// (mirrors production behavior in startReloader).
|
||||
if k == constants.SecretProviderClassController {
|
||||
continue
|
||||
}
|
||||
c, err := controller.NewController(testutil.Clients.KubernetesClient, k, testutil.Namespace, []string{}, "", "", metrics.NewCollectors())
|
||||
if err != nil {
|
||||
logrus.Fatalf("%s", err)
|
||||
@@ -133,7 +151,7 @@ func TestRunLeaderElectionWithControllers(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
|
||||
// Start running leadership election, this also starts the controllers
|
||||
go RunLeaderElection(lock, ctx, cancel, testutil.Pod, controllers)
|
||||
stopped := RunLeaderElection(lock, ctx, cancel, testutil.Pod, controllers)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Create some stuff and do a thing
|
||||
@@ -172,16 +190,48 @@ func TestRunLeaderElectionWithControllers(t *testing.T) {
|
||||
}
|
||||
time.Sleep(testutil.SleepDuration)
|
||||
|
||||
// Add reloader.stakater.com/ignore: "true" to the configmap BEFORE cancelling
|
||||
// leadership. This prevents any Reloader instance running in the cluster
|
||||
// (including ones external to this test) from processing the second configmap
|
||||
// update below, making the assertion reliable in shared cluster environments.
|
||||
// The ignore annotation is on the configmap itself: ShouldReload checks
|
||||
// config.ResourceAnnotations (= configmap annotations) for this annotation.
|
||||
// Note: only the annotation is changed here — the data SHA is unchanged so
|
||||
// the still-running controllers will see no diff and skip the rolling upgrade.
|
||||
cm, getCMErr := testutil.Clients.KubernetesClient.CoreV1().ConfigMaps(testutil.Namespace).Get(
|
||||
context.TODO(), configmapName, metav1.GetOptions{})
|
||||
if getCMErr != nil {
|
||||
t.Fatalf("Failed to get configmap to add ignore annotation: %v", getCMErr)
|
||||
}
|
||||
if cm.Annotations == nil {
|
||||
cm.Annotations = make(map[string]string)
|
||||
}
|
||||
cm.Annotations[options.IgnoreResourceAnnotation] = "true"
|
||||
if _, err = testutil.Clients.KubernetesClient.CoreV1().ConfigMaps(testutil.Namespace).Update(
|
||||
context.TODO(), cm, metav1.UpdateOptions{}); err != nil {
|
||||
t.Fatalf("Failed to add ignore annotation to configmap: %v", err)
|
||||
}
|
||||
|
||||
// Cancel the leader election context, so leadership is released
|
||||
logrus.Info("shutting down controller from test")
|
||||
cancel()
|
||||
time.Sleep(5 * time.Second)
|
||||
<-stopped // wait until OnStoppedLeading has run and all controller goroutines have exited
|
||||
|
||||
// Updating configmap again
|
||||
updateErr = testutil.UpdateConfigMap(configmapClient, testutil.Namespace, configmapName, "", "www.stakater.com/new")
|
||||
if updateErr != nil {
|
||||
t.Fatalf("Configmap was not updated")
|
||||
// Update the configmap data for the second time using a Get+modify+Update
|
||||
// pattern so that the ignore annotation added above is preserved.
|
||||
// Any Reloader (including external ones) will see ignore=true and skip the update.
|
||||
cm, err = testutil.Clients.KubernetesClient.CoreV1().ConfigMaps(testutil.Namespace).Get(
|
||||
context.TODO(), configmapName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get configmap for second update: %v", err)
|
||||
}
|
||||
cm.Data["test.url"] = "www.stakater.com/new"
|
||||
// ignore annotation is still present from the update above
|
||||
if _, err = testutil.Clients.KubernetesClient.CoreV1().ConfigMaps(testutil.Namespace).Update(
|
||||
context.TODO(), cm, metav1.UpdateOptions{}); err != nil {
|
||||
t.Fatalf("Failed to update configmap: %v", err)
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Verifying that the deployment was not updated as leadership has been lost
|
||||
logrus.Infof("Verifying pod envvars has not been updated")
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// InterfaceSlice converts an interface to an interface array
|
||||
func InterfaceSlice(slice interface{}) []interface{} {
|
||||
s := reflect.ValueOf(slice)
|
||||
if s.Kind() != reflect.Slice {
|
||||
logrus.Errorf("InterfaceSlice() given a non-slice type")
|
||||
}
|
||||
|
||||
ret := make([]interface{}, s.Len())
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
ret[i] = s.Index(i).Interface()
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
type ObjectMeta struct {
|
||||
metav1.ObjectMeta
|
||||
}
|
||||
|
||||
func ToObjectMeta(kubernetesObject interface{}) ObjectMeta {
|
||||
objectValue := reflect.ValueOf(kubernetesObject)
|
||||
fieldName := reflect.TypeOf((*metav1.ObjectMeta)(nil)).Elem().Name()
|
||||
field := objectValue.FieldByName(fieldName).Interface().(metav1.ObjectMeta)
|
||||
|
||||
return ObjectMeta{
|
||||
ObjectMeta: field,
|
||||
}
|
||||
}
|
||||
|
||||
// ParseBool returns result in bool format after parsing
|
||||
func ParseBool(value interface{}) bool {
|
||||
if reflect.Bool == reflect.TypeOf(value).Kind() {
|
||||
return value.(bool)
|
||||
} else if reflect.String == reflect.TypeOf(value).Kind() {
|
||||
result, _ := strconv.ParseBool(value.(string))
|
||||
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