@@ -18,6 +18,7 @@ import (
1818 "github.com/stakater/Reloader/internal/pkg/util"
1919 "github.com/stakater/Reloader/pkg/kube"
2020 "github.com/stretchr/testify/assert"
21+ v1 "k8s.io/api/core/v1"
2122 "k8s.io/apimachinery/pkg/api/errors"
2223 "k8s.io/apimachinery/pkg/api/meta"
2324 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -672,7 +673,7 @@ func teardownArs() {
672673 logrus .Errorf ("Error while deleting statefulSet with secret as env var source %v" , statefulSetError )
673674 }
674675
675- // Deleting Deployment with pasuse annotation
676+ // Deleting Deployment with pause annotation
676677 deploymentError = testutil .DeleteDeployment (clients .KubernetesClient , arsNamespace , arsConfigmapWithPausedDeployment )
677678 if deploymentError != nil {
678679 logrus .Errorf ("Error while deleting deployment with configmap %v" , deploymentError )
@@ -708,7 +709,7 @@ func teardownArs() {
708709 logrus .Errorf ("Error while deleting the configmap %v" , err )
709710 }
710711
711- // Deleting Configmap used projected volume in init containers
712+ // Deleting secret used in projected volume in init containers
712713 err = testutil .DeleteSecret (clients .KubernetesClient , arsNamespace , arsProjectedSecretWithInitContainer )
713714 if err != nil {
714715 logrus .Errorf ("Error while deleting the secret %v" , err )
@@ -1392,7 +1393,7 @@ func teardownErs() {
13921393 logrus .Errorf ("Error while deleting the configmap %v" , err )
13931394 }
13941395
1395- // Deleting Configmap used projected volume in init containers
1396+ // Deleting secret used in projected volume in init containers
13961397 err = testutil .DeleteSecret (clients .KubernetesClient , ersNamespace , ersProjectedSecretWithInitContainer )
13971398 if err != nil {
13981399 logrus .Errorf ("Error while deleting the secret %v" , err )
@@ -1475,7 +1476,7 @@ func teardownErs() {
14751476 logrus .Errorf ("Error while deleting the configmap used with configmap exclude annotation: %v" , err )
14761477 }
14771478
1478- // Deleting ConfigMap for testins pausing deployments
1479+ // Deleting ConfigMap for testing pausing deployments
14791480 err = testutil .DeleteConfigMap (clients .KubernetesClient , ersNamespace , ersConfigmapWithPausedDeployment )
14801481 if err != nil {
14811482 logrus .Errorf ("Error while deleting the configmap: %v" , err )
@@ -2247,7 +2248,7 @@ func TestRollingUpgradeForDeploymentWithSecretExcludeAnnotationUsingArs(t *testi
22472248 logrus .Infof ("Verifying deployment did not update" )
22482249 updated := testutil .VerifyResourceAnnotationUpdate (clients , config , deploymentFuncs )
22492250 if updated {
2250- t .Errorf ("Deployment which had to be exluded was updated" )
2251+ t .Errorf ("Deployment which had to be excluded was updated" )
22512252 }
22522253}
22532254
@@ -4214,3 +4215,268 @@ func waitForDeploymentPausedAtAnnotation(clients kube.Clients, deploymentFuncs c
42144215
42154216 return fmt .Errorf ("timeout waiting for deployment %s to have pause-period annotation" , deploymentName )
42164217}
4218+
4219+ // MockDeploymentWithEmptyContainers creates a mock deployment with no containers
4220+ // This simulates the scenario where Argo Rollouts with workloadRef return empty containers
4221+ func MockDeploymentWithEmptyContainers (namespace , name string ) * runtime.Object {
4222+ deployment := & v1.Pod {
4223+ ObjectMeta : metav1.ObjectMeta {
4224+ Name : name ,
4225+ Namespace : namespace ,
4226+ },
4227+ Spec : v1.PodSpec {
4228+ Containers : []v1.Container {}, // Empty containers slice
4229+ InitContainers : []v1.Container {}, // Empty init containers slice
4230+ },
4231+ }
4232+ var obj runtime.Object = deployment
4233+ return & obj
4234+ }
4235+
4236+ // MockUpgradeFuncsWithEmptyContainers creates upgrade functions that return empty containers
4237+ // This simulates the behavior of GetRolloutContainers for workloadRef rollouts
4238+ func MockUpgradeFuncsWithEmptyContainers () callbacks.RollingUpgradeFuncs {
4239+ return callbacks.RollingUpgradeFuncs {
4240+ ContainersFunc : func (item runtime.Object ) []v1.Container {
4241+ return []v1.Container {} // Always return empty slice
4242+ },
4243+ InitContainersFunc : func (item runtime.Object ) []v1.Container {
4244+ return []v1.Container {} // Always return empty slice
4245+ },
4246+ VolumesFunc : func (item runtime.Object ) []v1.Volume {
4247+ return []v1.Volume {} // No volumes
4248+ },
4249+ ResourceType : "MockResource" ,
4250+ }
4251+ }
4252+
4253+ // TestGetContainerUsingResourceWithEmptyContainers tests that the function
4254+ // handles empty containers gracefully without panicking
4255+ func TestGetContainerUsingResourceWithEmptyContainers (t * testing.T ) {
4256+ // Setup test data
4257+ namespace := "test-namespace"
4258+ resourceName := "test-resource"
4259+
4260+ // Create a mock deployment with empty containers
4261+ mockDeployment := MockDeploymentWithEmptyContainers (namespace , "test-deployment" )
4262+
4263+ // Create upgrade functions that return empty containers (simulating workloadRef rollouts)
4264+ upgradeFuncs := MockUpgradeFuncsWithEmptyContainers ()
4265+
4266+ // Create config for configmap test
4267+ config := util.Config {
4268+ Namespace : namespace ,
4269+ ResourceName : resourceName ,
4270+ Type : constants .ConfigmapEnvVarPostfix ,
4271+ SHAValue : "test-sha" ,
4272+ }
4273+
4274+ // Test cases
4275+ testCases := []struct {
4276+ name string
4277+ autoReload bool
4278+ expected * v1.Container
4279+ }{
4280+ {
4281+ name : "Non-auto reload with empty containers should return nil" ,
4282+ autoReload : false ,
4283+ expected : nil ,
4284+ },
4285+ {
4286+ name : "Auto reload with empty containers should return nil" ,
4287+ autoReload : true ,
4288+ expected : nil ,
4289+ },
4290+ }
4291+
4292+ for _ , tc := range testCases {
4293+ t .Run (tc .name , func (t * testing.T ) {
4294+ // This should not panic - our fix prevents the index out of range error
4295+ result := getContainerUsingResource (upgradeFuncs , * mockDeployment , config , tc .autoReload )
4296+
4297+ if result != tc .expected {
4298+ t .Errorf ("Expected %v, got %v" , tc .expected , result )
4299+ }
4300+ })
4301+ }
4302+ }
4303+
4304+ // TestGetContainerUsingResourceWithEmptyInitContainers tests scenarios where
4305+ // init containers exist but are empty, and main containers are also empty
4306+ func TestGetContainerUsingResourceWithEmptyInitContainers (t * testing.T ) {
4307+ namespace := "test-namespace"
4308+ resourceName := "test-resource"
4309+
4310+ // Create upgrade functions with empty init containers as well
4311+ upgradeFuncs := callbacks.RollingUpgradeFuncs {
4312+ ContainersFunc : func (item runtime.Object ) []v1.Container {
4313+ return []v1.Container {} // Empty main containers
4314+ },
4315+ InitContainersFunc : func (item runtime.Object ) []v1.Container {
4316+ // Return init containers that use the resource, but main containers are empty
4317+ return []v1.Container {
4318+ {
4319+ Name : "init-container" ,
4320+ Env : []v1.EnvVar {
4321+ {
4322+ Name : "CONFIGMAP_" + util .ConvertToEnvVarName (resourceName ),
4323+ ValueFrom : & v1.EnvVarSource {
4324+ ConfigMapKeyRef : & v1.ConfigMapKeySelector {
4325+ LocalObjectReference : v1.LocalObjectReference {
4326+ Name : resourceName ,
4327+ },
4328+ Key : "test.key" ,
4329+ },
4330+ },
4331+ },
4332+ },
4333+ },
4334+ }
4335+ },
4336+ VolumesFunc : func (item runtime.Object ) []v1.Volume {
4337+ return []v1.Volume {}
4338+ },
4339+ ResourceType : "MockResource" ,
4340+ }
4341+
4342+ mockDeployment := MockDeploymentWithEmptyContainers (namespace , "test-deployment" )
4343+
4344+ config := util.Config {
4345+ Namespace : namespace ,
4346+ ResourceName : resourceName ,
4347+ Type : constants .ConfigmapEnvVarPostfix ,
4348+ SHAValue : "test-sha" ,
4349+ }
4350+
4351+ // This should return nil instead of panicking when trying to access containers[0]
4352+ result := getContainerUsingResource (upgradeFuncs , * mockDeployment , config , false )
4353+
4354+ if result != nil {
4355+ t .Errorf ("Expected nil when main containers are empty, got %v" , result )
4356+ }
4357+ }
4358+
4359+ // TestGetContainerUsingResourceWithVolumeMount tests the volume mount path with empty containers
4360+ func TestGetContainerUsingResourceWithVolumeMount (t * testing.T ) {
4361+ namespace := "test-namespace"
4362+ resourceName := "test-resource"
4363+ volumeName := "test-volume"
4364+
4365+ // Create upgrade functions that return a volume but no containers
4366+ upgradeFuncs := callbacks.RollingUpgradeFuncs {
4367+ ContainersFunc : func (item runtime.Object ) []v1.Container {
4368+ return []v1.Container {} // Empty containers
4369+ },
4370+ InitContainersFunc : func (item runtime.Object ) []v1.Container {
4371+ // Init container with volume mount
4372+ return []v1.Container {
4373+ {
4374+ Name : "init-container" ,
4375+ VolumeMounts : []v1.VolumeMount {
4376+ {
4377+ Name : volumeName ,
4378+ MountPath : "/test" ,
4379+ },
4380+ },
4381+ },
4382+ }
4383+ },
4384+ VolumesFunc : func (item runtime.Object ) []v1.Volume {
4385+ return []v1.Volume {
4386+ {
4387+ Name : volumeName ,
4388+ VolumeSource : v1.VolumeSource {
4389+ ConfigMap : & v1.ConfigMapVolumeSource {
4390+ LocalObjectReference : v1.LocalObjectReference {
4391+ Name : resourceName ,
4392+ },
4393+ },
4394+ },
4395+ },
4396+ }
4397+ },
4398+ ResourceType : "MockResource" ,
4399+ }
4400+
4401+ mockDeployment := MockDeploymentWithEmptyContainers (namespace , "test-deployment" )
4402+
4403+ config := util.Config {
4404+ Namespace : namespace ,
4405+ ResourceName : resourceName ,
4406+ Type : constants .ConfigmapEnvVarPostfix ,
4407+ SHAValue : "test-sha" ,
4408+ }
4409+
4410+ // This should return nil instead of panicking when containers are empty
4411+ result := getContainerUsingResource (upgradeFuncs , * mockDeployment , config , false )
4412+
4413+ if result != nil {
4414+ t .Errorf ("Expected nil when main containers are empty even with volume mount, got %v" , result )
4415+ }
4416+ }
4417+
4418+ // TestGetContainerUsingResourceWithEmptyContainersSecret tests the fix with Secret resources
4419+ func TestGetContainerUsingResourceWithEmptyContainersSecret (t * testing.T ) {
4420+ namespace := "test-namespace"
4421+ resourceName := "test-secret"
4422+
4423+ mockDeployment := MockDeploymentWithEmptyContainers (namespace , "test-deployment" )
4424+ upgradeFuncs := MockUpgradeFuncsWithEmptyContainers ()
4425+
4426+ // Test with Secret instead of ConfigMap
4427+ config := util.Config {
4428+ Namespace : namespace ,
4429+ ResourceName : resourceName ,
4430+ Type : constants .SecretEnvVarPostfix ,
4431+ SHAValue : "test-sha" ,
4432+ }
4433+
4434+ // Both autoReload scenarios should return nil without panicking
4435+ result1 := getContainerUsingResource (upgradeFuncs , * mockDeployment , config , false )
4436+ result2 := getContainerUsingResource (upgradeFuncs , * mockDeployment , config , true )
4437+
4438+ if result1 != nil || result2 != nil {
4439+ t .Errorf ("Expected nil for both autoReload scenarios with empty containers and Secret resource" )
4440+ }
4441+ }
4442+
4443+ // TestGetContainerUsingResourceWithArgoRolloutEmptyContainers tests with real Argo Rollout functions
4444+ func TestGetContainerUsingResourceWithArgoRolloutEmptyContainers (t * testing.T ) {
4445+ namespace := "test-namespace"
4446+ resourceName := "test-configmap"
4447+
4448+ // Use real Argo Rollout functions but mock the containers function
4449+ rolloutFuncs := GetArgoRolloutRollingUpgradeFuncs ()
4450+ originalContainersFunc := rolloutFuncs .ContainersFunc
4451+ originalInitContainersFunc := rolloutFuncs .InitContainersFunc
4452+
4453+ // Override to return empty containers (simulating workloadRef scenario)
4454+ rolloutFuncs .ContainersFunc = func (item runtime.Object ) []v1.Container {
4455+ return []v1.Container {} // Empty like workloadRef rollouts
4456+ }
4457+ rolloutFuncs .InitContainersFunc = func (item runtime.Object ) []v1.Container {
4458+ return []v1.Container {} // Empty like workloadRef rollouts
4459+ }
4460+
4461+ // Restore original functions after test
4462+ defer func () {
4463+ rolloutFuncs .ContainersFunc = originalContainersFunc
4464+ rolloutFuncs .InitContainersFunc = originalInitContainersFunc
4465+ }()
4466+
4467+ mockRollout := MockDeploymentWithEmptyContainers (namespace , "test-rollout" )
4468+
4469+ config := util.Config {
4470+ Namespace : namespace ,
4471+ ResourceName : resourceName ,
4472+ Type : constants .ConfigmapEnvVarPostfix ,
4473+ SHAValue : "test-sha" ,
4474+ }
4475+
4476+ // This tests the actual fix in the context of Argo Rollouts
4477+ result := getContainerUsingResource (rolloutFuncs , * mockRollout , config , false )
4478+
4479+ if result != nil {
4480+ t .Errorf ("Expected nil when using real Argo Rollout functions with empty containers (workloadRef scenario), got %v" , result )
4481+ }
4482+ }
0 commit comments