github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/operatorcondition_controller_test.go (about) 1 package operators 2 3 import ( 4 "context" 5 "fmt" 6 7 . "github.com/onsi/ginkgo/v2" 8 . "github.com/onsi/gomega" 9 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 10 operatorsv2 "github.com/operator-framework/api/pkg/operators/v2" 11 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 12 13 appsv1 "k8s.io/api/apps/v1" 14 corev1 "k8s.io/api/core/v1" 15 rbacv1 "k8s.io/api/rbac/v1" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/types" 18 ) 19 20 var _ = Describe("OperatorCondition", func() { 21 Context("The ensureEnvVarIsPresent function", func() { 22 It("returns the existing array and true if the envVar was already present", func() { 23 actualEnvVars, envVarPresent := ensureEnvVarIsPresent([]corev1.EnvVar{{Name: "foo", Value: "bar"}, {Name: "foo2", Value: "bar2"}}, corev1.EnvVar{Name: "foo", Value: "bar"}) 24 Expect(actualEnvVars).To(Equal([]corev1.EnvVar{{Name: "foo", Value: "bar"}, {Name: "foo2", Value: "bar2"}})) 25 Expect(envVarPresent).To(BeTrue()) 26 }) 27 28 It("appends the envVar to an empty array and return false", func() { 29 actualEnvVars, envVarPresent := ensureEnvVarIsPresent([]corev1.EnvVar{}, corev1.EnvVar{Name: "foo", Value: "bar"}) 30 Expect(actualEnvVars).To(Equal([]corev1.EnvVar{{Name: "foo", Value: "bar"}})) 31 Expect(envVarPresent).To(BeFalse()) 32 }) 33 34 It("appends the envVar to an EnvVar array that contains other envVars and return false", func() { 35 actualEnvVars, envVarPresent := ensureEnvVarIsPresent([]corev1.EnvVar{{Name: "notFoo", Value: "bar"}}, corev1.EnvVar{Name: "foo", Value: "bar"}) 36 Expect(actualEnvVars).To(Equal([]corev1.EnvVar{{Name: "notFoo", Value: "bar"}, {Name: "foo", Value: "bar"}})) 37 Expect(envVarPresent).To(BeFalse()) 38 }) 39 40 It("updates the value of an envVar in an envVar array if the envVar's key already exists but the value is different and returns false", func() { 41 actualEnvVars, envVarPresent := ensureEnvVarIsPresent([]corev1.EnvVar{{Name: "foo", Value: "bar"}}, corev1.EnvVar{Name: "foo", Value: "notBar"}) 42 Expect(actualEnvVars).To(Equal([]corev1.EnvVar{{Name: "foo", Value: "notBar"}})) 43 Expect(envVarPresent).To(BeFalse()) 44 }) 45 }) 46 47 Context("The OperatorCondition Reconciler", func() { 48 var ( 49 ctx context.Context 50 operatorCondition *operatorsv2.OperatorCondition 51 namespace *corev1.Namespace 52 namespacedName types.NamespacedName 53 ) 54 55 BeforeEach(func() { 56 ctx = context.Background() 57 namespace = &corev1.Namespace{ 58 ObjectMeta: metav1.ObjectMeta{ 59 Name: genName("ns-"), 60 }, 61 } 62 Expect(k8sClient.Create(ctx, namespace)).To(Succeed()) 63 64 namespacedName = types.NamespacedName{Name: "test", Namespace: namespace.GetName()} 65 }) 66 67 When("an operatorCondition is created that specifies an array of ServiceAccounts", func() { 68 69 BeforeEach(func() { 70 operatorCondition = &operatorsv2.OperatorCondition{ 71 ObjectMeta: metav1.ObjectMeta{ 72 Name: namespacedName.Name, 73 Namespace: namespacedName.Namespace, 74 }, 75 Spec: operatorsv2.OperatorConditionSpec{ 76 ServiceAccounts: []string{"serviceaccount"}, 77 Deployments: []string{}, 78 }, 79 } 80 Expect(k8sClient.Create(ctx, operatorCondition)).To(Succeed()) 81 }) 82 83 It("creates and recreates the expected Role", func() { 84 role := &rbacv1.Role{} 85 86 Eventually(func() error { 87 return k8sClient.Get(ctx, namespacedName, role) 88 }, timeout, interval).Should(BeNil()) 89 90 Expect(len(role.OwnerReferences)).Should(Equal(1)) 91 92 falseBool := false 93 trueBool := true 94 95 Expect(role.OwnerReferences).Should(ContainElement(metav1.OwnerReference{ 96 APIVersion: "operators.coreos.com/v2", 97 Kind: "OperatorCondition", 98 Name: "test", 99 UID: operatorCondition.UID, 100 Controller: &trueBool, 101 BlockOwnerDeletion: &falseBool, 102 })) 103 Expect(role.Rules).Should(Equal([]rbacv1.PolicyRule{ 104 { 105 Verbs: []string{"get", "update", "patch"}, 106 APIGroups: []string{"operators.coreos.com"}, 107 Resources: []string{"operatorconditions"}, 108 ResourceNames: []string{namespacedName.Name}, 109 }, 110 })) 111 Expect(k8sClient.Delete(ctx, role)).To(Succeed()) 112 Eventually(func() error { 113 return k8sClient.Get(ctx, namespacedName, role) 114 }, timeout, interval).Should(Succeed()) 115 116 }) 117 118 It("creates and recreates the expected RoleBinding", func() { 119 roleBinding := &rbacv1.RoleBinding{} 120 falseBool := false 121 trueBool := true 122 123 Eventually(func() error { 124 return k8sClient.Get(ctx, namespacedName, roleBinding) 125 }, timeout, interval).Should(BeNil()) 126 Expect(len(roleBinding.OwnerReferences)).To(Equal(1)) 127 Expect(roleBinding.OwnerReferences).Should(ContainElement(metav1.OwnerReference{ 128 APIVersion: "operators.coreos.com/v2", 129 Kind: "OperatorCondition", 130 Name: "test", 131 UID: operatorCondition.UID, 132 Controller: &trueBool, 133 BlockOwnerDeletion: &falseBool, 134 })) 135 Expect(len(roleBinding.Subjects)).To(Equal(1)) 136 Expect(roleBinding.Subjects).Should(ContainElement(rbacv1.Subject{ 137 Kind: "ServiceAccount", 138 Name: operatorCondition.Spec.ServiceAccounts[0], 139 })) 140 Expect(roleBinding.RoleRef).To(Equal(rbacv1.RoleRef{ 141 Kind: "Role", 142 Name: roleBinding.GetName(), 143 APIGroup: "rbac.authorization.k8s.io", 144 })) 145 146 Expect(k8sClient.Delete(ctx, roleBinding)).To(Succeed()) 147 Eventually(func() error { 148 return k8sClient.Get(ctx, namespacedName, roleBinding) 149 }, timeout, interval).Should(Succeed()) 150 }) 151 }) 152 153 When("a CSV exists that owns a deployment", func() { 154 var csv *operatorsv1alpha1.ClusterServiceVersion 155 156 BeforeEach(func() { 157 // Create a coppied csv used as an owner in the following tests. 158 // Copied CSVs are ignored by the OperatorConditionGenerator Reconciler, which we don't want to intervine in this test. 159 csv = &operatorsv1alpha1.ClusterServiceVersion{ 160 TypeMeta: metav1.TypeMeta{ 161 Kind: operatorsv1alpha1.ClusterServiceVersionKind, 162 APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion, 163 }, 164 ObjectMeta: metav1.ObjectMeta{ 165 Name: namespacedName.Name, 166 Namespace: namespace.GetName(), 167 Labels: map[string]string{ 168 operatorsv1alpha1.CopiedLabelKey: "", 169 }, 170 }, 171 Spec: operatorsv1alpha1.ClusterServiceVersionSpec{ 172 InstallModes: []operatorsv1alpha1.InstallMode{ 173 { 174 Type: operatorsv1alpha1.InstallModeTypeOwnNamespace, 175 Supported: true, 176 }, 177 { 178 Type: operatorsv1alpha1.InstallModeTypeSingleNamespace, 179 Supported: true, 180 }, 181 { 182 Type: operatorsv1alpha1.InstallModeTypeMultiNamespace, 183 Supported: true, 184 }, 185 { 186 Type: operatorsv1alpha1.InstallModeTypeAllNamespaces, 187 Supported: true, 188 }, 189 }, 190 InstallStrategy: newNginxInstallStrategy("deployment", nil, nil), 191 }, 192 } 193 Expect(k8sClient.Create(ctx, csv)).To(Succeed()) 194 195 // Create the deployment 196 labels := map[string]string{ 197 "foo": "bar", 198 } 199 deployment := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ 200 Name: "deployment", 201 Namespace: namespacedName.Namespace, 202 }, 203 Spec: appsv1.DeploymentSpec{ 204 Selector: &metav1.LabelSelector{ 205 MatchLabels: labels, 206 }, 207 Template: corev1.PodTemplateSpec{ 208 ObjectMeta: metav1.ObjectMeta{ 209 GenerateName: "nginx-", 210 Namespace: namespacedName.Namespace, 211 Labels: labels, 212 }, 213 Spec: corev1.PodSpec{ 214 Containers: []corev1.Container{ 215 { 216 Name: "web", 217 Image: "nginx", 218 Ports: []corev1.ContainerPort{ 219 { 220 Name: "web", 221 ContainerPort: 80, 222 Protocol: corev1.ProtocolTCP, 223 }, 224 }, 225 }, 226 }, 227 }, 228 }, 229 }, 230 } 231 ownerutil.AddNonBlockingOwner(deployment, csv) 232 233 Expect(k8sClient.Create(ctx, deployment)).To(Succeed()) 234 }) 235 236 Context("and an OperatorCondition with a different name than the CSV includes that deployment in its spec.Deployments array", func() { 237 238 BeforeEach(func() { 239 operatorCondition = &operatorsv2.OperatorCondition{ 240 ObjectMeta: metav1.ObjectMeta{ 241 Name: namespacedName.Name + "different", 242 Namespace: namespacedName.Namespace, 243 }, 244 Spec: operatorsv2.OperatorConditionSpec{ 245 ServiceAccounts: []string{}, 246 Deployments: []string{"deployment"}, 247 }, 248 } 249 Expect(k8sClient.Create(ctx, operatorCondition)).To(Succeed()) 250 }) 251 It("does not inject the OperatorCondition name into the deployment's Environment Variables", func() { 252 deployment := &appsv1.Deployment{} 253 Consistently(func() error { 254 err := k8sClient.Get(ctx, types.NamespacedName{Name: operatorCondition.Spec.Deployments[0], Namespace: namespace.GetName()}, deployment) 255 if err != nil { 256 return err 257 } 258 if len(deployment.Spec.Template.Spec.Containers) != 1 { 259 return fmt.Errorf("Deployment should contain a single container") 260 } 261 for _, container := range deployment.Spec.Template.Spec.Containers { 262 if len(container.Env) != 0 { 263 264 return fmt.Errorf("env vars should not exist: %v", container.Env) 265 } 266 } 267 return nil 268 }, timeout, interval).Should(BeNil()) 269 }) 270 }) 271 272 Context("and an OperatorCondition with the same name as the CSV includes that deployment in its spec.Deployments array", func() { 273 274 BeforeEach(func() { 275 operatorCondition = &operatorsv2.OperatorCondition{ 276 ObjectMeta: metav1.ObjectMeta{ 277 Name: namespacedName.Name, 278 Namespace: namespacedName.Namespace, 279 }, 280 Spec: operatorsv2.OperatorConditionSpec{ 281 ServiceAccounts: []string{}, 282 Deployments: []string{"deployment"}, 283 }, 284 } 285 ownerutil.AddNonBlockingOwner(operatorCondition, csv) 286 Expect(k8sClient.Create(ctx, operatorCondition)).To(Succeed()) 287 }) 288 289 It("should always inject the OperatorCondition Environment Variable into containers defined in the deployment", func() { 290 deployment := &appsv1.Deployment{} 291 Eventually(func() error { 292 err := k8sClient.Get(ctx, types.NamespacedName{Name: operatorCondition.Spec.Deployments[0], Namespace: namespace.GetName()}, deployment) 293 if err != nil { 294 return err 295 } 296 if len(deployment.Spec.Template.Spec.Containers) != 1 { 297 return fmt.Errorf("Deployment should contain a single container") 298 } 299 for _, container := range deployment.Spec.Template.Spec.Containers { 300 if len(container.Env) == 0 { 301 return fmt.Errorf("env vars should exist") 302 } 303 } 304 return nil 305 }, timeout, interval).Should(BeNil()) 306 307 Expect(len(deployment.Spec.Template.Spec.Containers)).Should(Equal(1)) 308 for _, container := range deployment.Spec.Template.Spec.Containers { 309 Expect(len(container.Env)).Should(Equal(1)) 310 Expect(container.Env).Should(ContainElement(corev1.EnvVar{ 311 Name: "OPERATOR_CONDITION_NAME", 312 Value: operatorCondition.GetName(), 313 })) 314 } 315 316 // Remove the container's Environment Variables defined in the deployment 317 Eventually(func() error { 318 err := k8sClient.Get(ctx, types.NamespacedName{Name: operatorCondition.Spec.Deployments[0], Namespace: namespace.GetName()}, deployment) 319 if err != nil { 320 return err 321 } 322 deployment.Spec.Template.Spec.Containers[0].Env = nil 323 return k8sClient.Update(ctx, deployment) 324 }, timeout, interval).Should(BeNil()) 325 326 // Ensure that the OPERATOR_CONDITION_NAME Environment Variable is recreated 327 Eventually(func() error { 328 err := k8sClient.Get(ctx, types.NamespacedName{Name: operatorCondition.Spec.Deployments[0], Namespace: namespace.GetName()}, deployment) 329 if err != nil { 330 return err 331 } 332 if len(deployment.Spec.Template.Spec.Containers) != 1 { 333 return fmt.Errorf("Deployment should contain a single container") 334 } 335 for _, container := range deployment.Spec.Template.Spec.Containers { 336 if len(container.Env) == 0 { 337 return fmt.Errorf("env vars should exist") 338 } 339 } 340 return nil 341 }, timeout, interval).Should(BeNil()) 342 343 Expect(len(deployment.Spec.Template.Spec.Containers)).Should(Equal(1)) 344 for _, container := range deployment.Spec.Template.Spec.Containers { 345 Expect(len(container.Env)).Should(Equal(1)) 346 Expect(container.Env).Should(ContainElement(corev1.EnvVar{ 347 Name: "OPERATOR_CONDITION_NAME", 348 Value: operatorCondition.GetName(), 349 })) 350 } 351 }) 352 }) 353 }) 354 }) 355 })