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  }