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  })