github.com/redhat-appstudio/e2e-tests@v0.0.0-20240520140907-9709f6f59323/pkg/clients/common/namespace.go (about) 1 package common 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/redhat-appstudio/e2e-tests/pkg/constants" 9 "github.com/redhat-appstudio/e2e-tests/pkg/utils" 10 corev1 "k8s.io/api/core/v1" 11 rbacv1 "k8s.io/api/rbac/v1" 12 k8sErrors "k8s.io/apimachinery/pkg/api/errors" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/runtime/schema" 15 "k8s.io/apimachinery/pkg/util/wait" 16 "k8s.io/client-go/dynamic" 17 "k8s.io/client-go/kubernetes" 18 ) 19 20 // DeleteNamespace deletes the give namespace. 21 func (s *SuiteController) DeleteNamespace(namespace string) error { 22 _, err := s.KubeInterface().CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) 23 24 if err != nil && !k8sErrors.IsNotFound(err) { 25 return fmt.Errorf("could not check for namespace '%s' existence: %v", namespace, err) 26 } 27 28 if err := s.KubeInterface().CoreV1().Namespaces().Delete(context.Background(), namespace, metav1.DeleteOptions{}); err != nil { 29 return fmt.Errorf("unable to delete namespace '%s': %v", namespace, err) 30 } 31 32 // Wait for the namespace to no longer exist. The namespace may remain stuck in 'Terminating' state 33 // if it contains with finalizers that are not handled. We detect this case here, and report any resources still 34 // in the Namespace. 35 if err := utils.WaitUntil(s.namespaceDoesNotExist(namespace), time.Minute*10); err != nil { 36 37 // On failure to delete, list all namespace-scoped resources still in the namespace. 38 resourcesInNamespace := s.ListNamespaceScopedResourcesAsString(namespace, s.KubeInterface(), s.DynamicClient()) 39 40 return fmt.Errorf("namespace was not deleted in expected timeframe: '%s': %v. Remaining resources in namespace: %s", namespace, err, resourcesInNamespace) 41 } 42 43 return nil 44 } 45 46 // ListNamespaceScopedResourcesAsString returns a list of resources in a namespace as a string, for test debugging purposes. 47 func (s *SuiteController) ListNamespaceScopedResourcesAsString(namespace string, k8sInterface kubernetes.Interface, dynamicInterface dynamic.Interface) string { 48 crdList, err := k8sInterface.Discovery().ServerPreferredNamespacedResources() 49 if err != nil { 50 // Ignore errors: this function is for diagnostic purposes only. 51 return "" 52 } 53 resourceList := "" 54 55 for _, crd := range crdList { 56 57 for _, apiResource := range crd.APIResources { 58 59 if !apiResource.Namespaced { 60 continue 61 } 62 63 name := apiResource.Name 64 65 // package manifests is projected into every Namespace: so just ignore it. 66 if name == "packagemanifests" { 67 continue 68 } 69 70 groupResource, err := schema.ParseGroupVersion(crd.GroupVersion) 71 if err != nil { 72 // Ignore errors: this function is for diagnostic purposes only. 73 continue 74 } 75 76 group := apiResource.Group 77 if group == "" { 78 group = groupResource.Group 79 } 80 81 version := apiResource.Version 82 if version == "" { 83 version = groupResource.Version 84 } 85 86 gvr := schema.GroupVersionResource{ 87 Group: group, 88 Version: version, 89 Resource: apiResource.Name, 90 } 91 92 unstructuredList, err := dynamicInterface.Resource(gvr).Namespace(namespace).List(context.Background(), metav1.ListOptions{}) 93 if err != nil { 94 // Ignore errors: this function is for diagnostic purposes only. 95 continue 96 } 97 if len(unstructuredList.Items) > 0 { 98 resourceList += "( " + name + ": " 99 for _, unstructuredItem := range unstructuredList.Items { 100 resourceList += unstructuredItem.GetName() + " " 101 } 102 resourceList += ")\n" 103 } 104 105 } 106 107 } 108 109 return resourceList 110 } 111 112 // CreateTestNamespace creates a namespace where Application and Component CR will be created 113 func (s *SuiteController) CreateTestNamespace(name string) (*corev1.Namespace, error) { 114 // Check if the E2E test namespace already exists 115 ns, err := s.KubeInterface().CoreV1().Namespaces().Get(context.Background(), name, metav1.GetOptions{}) 116 117 if err != nil { 118 if k8sErrors.IsNotFound(err) { 119 // Create the E2E test namespace if it doesn't exist 120 nsTemplate := corev1.Namespace{ 121 ObjectMeta: metav1.ObjectMeta{ 122 Name: name, 123 Labels: map[string]string{constants.ArgoCDLabelKey: constants.ArgoCDLabelValue}, 124 }} 125 ns, err = s.KubeInterface().CoreV1().Namespaces().Create(context.Background(), &nsTemplate, metav1.CreateOptions{}) 126 if err != nil { 127 return nil, fmt.Errorf("error when creating %s namespace: %v", name, err) 128 } 129 } else { 130 return nil, fmt.Errorf("error when getting the '%s' namespace: %v", name, err) 131 } 132 } else { 133 // Check whether the test namespace contains correct label 134 if val, ok := ns.Labels[constants.ArgoCDLabelKey]; ok && val == constants.ArgoCDLabelValue { 135 return ns, nil 136 } 137 // Update test namespace labels in case they are missing argoCD label 138 ns.Labels[constants.ArgoCDLabelKey] = constants.ArgoCDLabelValue 139 ns, err = s.KubeInterface().CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{}) 140 if err != nil { 141 return nil, fmt.Errorf("error when updating labels in '%s' namespace: %v", name, err) 142 } 143 } 144 145 // Create ServiceAccount which is used by Pipelines but created by Toolchain host operator 146 _, err = s.KubeInterface().CoreV1().ServiceAccounts(name).Get(context.Background(), constants.DefaultPipelineServiceAccount, metav1.GetOptions{}) 147 if err != nil { 148 if k8sErrors.IsNotFound(err) { 149 saTemplate := corev1.ServiceAccount{ 150 ObjectMeta: metav1.ObjectMeta{ 151 Name: constants.DefaultPipelineServiceAccount, 152 }, 153 } 154 _, err = s.KubeInterface().CoreV1().ServiceAccounts(name).Create(context.Background(), &saTemplate, metav1.CreateOptions{}) 155 if err != nil { 156 return nil, fmt.Errorf("error when creating %s serviceaccount: %v", constants.DefaultPipelineServiceAccount, err) 157 } 158 } else { 159 return nil, fmt.Errorf("error when getting the '%s' serviceaccount: %v", constants.DefaultPipelineServiceAccount, err) 160 } 161 } 162 163 _, err = s.KubeInterface().RbacV1().RoleBindings(name).Get(context.Background(), constants.DefaultPipelineServiceAccountRoleBinding, metav1.GetOptions{}) 164 if err != nil { 165 if k8sErrors.IsNotFound(err) { 166 roleBindingTemplate := rbacv1.RoleBinding{ 167 TypeMeta: metav1.TypeMeta{}, 168 ObjectMeta: metav1.ObjectMeta{Name: constants.DefaultPipelineServiceAccountRoleBinding}, 169 Subjects: []rbacv1.Subject{ 170 { 171 Kind: "ServiceAccount", 172 Name: constants.DefaultPipelineServiceAccount, 173 Namespace: name, 174 }, 175 }, 176 RoleRef: rbacv1.RoleRef{ 177 Kind: "ClusterRole", 178 Name: constants.DefaultPipelineServiceAccountClusterRole, 179 }, 180 } 181 _, err = s.KubeInterface().RbacV1().RoleBindings(name).Create(context.Background(), &roleBindingTemplate, metav1.CreateOptions{}) 182 if err != nil { 183 return nil, fmt.Errorf("error when creating %s roleBinding: %v", constants.DefaultPipelineServiceAccountRoleBinding, err) 184 } 185 } else { 186 return nil, fmt.Errorf("error when getting the '%s' roleBinding: %v", constants.DefaultPipelineServiceAccountRoleBinding, err) 187 } 188 } 189 190 // Argo CD role/rolebinding need to be present in the namespace before we create GitOpsDeployments. 191 // - These role bindings are created in namespaces labeled with 'argocd.argoproj.io/managed-by' (see above) 192 if err := utils.WaitUntil(s.argoCDNamespaceRBACPresent(name), time.Second*120); err != nil { 193 return nil, fmt.Errorf("argo CD Namespace RBAC was never present in '%s': %v", name, err) 194 } 195 196 return ns, nil 197 } 198 199 // namespaceDoesNotExist returns a condition that can be used to wait for the namespace to not exist 200 func (s *SuiteController) namespaceDoesNotExist(namespace string) wait.ConditionFunc { 201 return func() (bool, error) { 202 203 _, err := s.KubeInterface().CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) 204 205 return err != nil && k8sErrors.IsNotFound(err), nil 206 } 207 } 208 209 // GetNamespace returns the requested Namespace object 210 func (s *SuiteController) GetNamespace(namespace string) (*corev1.Namespace, error) { 211 return s.KubeInterface().CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) 212 }