github.com/redhat-appstudio/e2e-tests@v0.0.0-20230619105049-9a422b2094d7/pkg/utils/has/controller.go (about)

     1  package has
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/redhat-appstudio/e2e-tests/pkg/constants"
    11  	"github.com/redhat-appstudio/e2e-tests/pkg/utils"
    12  	"knative.dev/pkg/apis"
    13  
    14  	"github.com/devfile/library/pkg/util"
    15  	. "github.com/onsi/ginkgo/v2"
    16  	routev1 "github.com/openshift/api/route/v1"
    17  	appservice "github.com/redhat-appstudio/application-api/api/v1alpha1"
    18  	"github.com/redhat-appstudio/e2e-tests/pkg/apis/github"
    19  	kubeCl "github.com/redhat-appstudio/e2e-tests/pkg/apis/kubernetes"
    20  	"github.com/redhat-appstudio/e2e-tests/pkg/utils/tekton"
    21  	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
    22  	appsv1 "k8s.io/api/apps/v1"
    23  	corev1 "k8s.io/api/core/v1"
    24  	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  	"k8s.io/client-go/util/retry"
    30  	"k8s.io/utils/pointer"
    31  	rclient "sigs.k8s.io/controller-runtime/pkg/client"
    32  )
    33  
    34  type SuiteController struct {
    35  	Github *github.Github
    36  	*kubeCl.CustomClient
    37  }
    38  
    39  func NewSuiteController(kube *kubeCl.CustomClient) (*SuiteController, error) {
    40  	// Check if a github organization env var is set, if not use by default the redhat-appstudio-qe org. See: https://github.com/redhat-appstudio-qe
    41  	org := utils.GetEnv(constants.GITHUB_E2E_ORGANIZATION_ENV, "redhat-appstudio-qe")
    42  	token := utils.GetEnv(constants.GITHUB_TOKEN_ENV, "")
    43  	gh, err := github.NewGithubClient(token, org)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	return &SuiteController{
    48  		gh,
    49  		kube,
    50  	}, nil
    51  }
    52  
    53  func (h *SuiteController) refreshComponentForErrorDebug(component *appservice.Component) *appservice.Component {
    54  	retComp := &appservice.Component{}
    55  	key := rclient.ObjectKeyFromObject(component)
    56  	err := h.KubeRest().Get(context.Background(), key, retComp)
    57  	if err != nil {
    58  		//TODO let's log this somehow, but return the original component obj, as that is better than nothing
    59  		return component
    60  	}
    61  	return retComp
    62  }
    63  
    64  func (h *SuiteController) refreshApplicationForErrorDebug(application *appservice.Application) *appservice.Application {
    65  	retApp := &appservice.Application{}
    66  	key := rclient.ObjectKeyFromObject(application)
    67  	err := h.KubeRest().Get(context.Background(), key, retApp)
    68  	if err != nil {
    69  		return application
    70  	}
    71  	return retApp
    72  }
    73  
    74  // GetHasApplication return the Application Custom Resource object
    75  func (h *SuiteController) GetHasApplication(name, namespace string) (*appservice.Application, error) {
    76  	namespacedName := types.NamespacedName{
    77  		Name:      name,
    78  		Namespace: namespace,
    79  	}
    80  
    81  	application := appservice.Application{
    82  		Spec: appservice.ApplicationSpec{},
    83  	}
    84  	err := h.KubeRest().Get(context.TODO(), namespacedName, &application)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return &application, nil
    89  }
    90  
    91  // CreateHasApplication create an application Custom Resource object
    92  func (h *SuiteController) CreateHasApplication(name, namespace string) (*appservice.Application, error) {
    93  	return h.CreateHasApplicationWithTimeout(name, namespace, time.Minute*10)
    94  }
    95  
    96  // CreateHasApplicationWithTimeout create an application Custom Resource object
    97  func (h *SuiteController) CreateHasApplicationWithTimeout(name string, namespace string, timeout time.Duration) (*appservice.Application, error) {
    98  	application := &appservice.Application{
    99  		ObjectMeta: metav1.ObjectMeta{
   100  			Name:      name,
   101  			Namespace: namespace,
   102  		},
   103  		Spec: appservice.ApplicationSpec{
   104  			DisplayName: name,
   105  		},
   106  	}
   107  	err := h.KubeRest().Create(context.TODO(), application)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	if err := utils.WaitUntil(h.ApplicationDevfilePresent(application), timeout); err != nil {
   113  		application = h.refreshApplicationForErrorDebug(application)
   114  		return nil, fmt.Errorf("timed out when waiting for devfile content creation for application %s in %s namespace: %+v. applicattion: %s", name, namespace, err, utils.ToPrettyJSONString(application))
   115  	}
   116  
   117  	return application, nil
   118  }
   119  
   120  func (h *SuiteController) ApplicationDevfilePresent(application *appservice.Application) wait.ConditionFunc {
   121  	return func() (bool, error) {
   122  		app, err := h.GetHasApplication(application.Name, application.Namespace)
   123  		if err != nil {
   124  			return false, nil
   125  		}
   126  		application.Status = app.Status
   127  		return application.Status.Devfile != "", nil
   128  	}
   129  }
   130  
   131  // DeleteHasApplication delete a HAS Application resource from the namespace.
   132  // Optionally, it can avoid returning an error if the resource did not exist:
   133  // - specify 'false', if it's likely the Application has already been deleted (for example, because the Namespace was deleted)
   134  func (h *SuiteController) DeleteHasApplication(name, namespace string, reportErrorOnNotFound bool) error {
   135  	application := appservice.Application{
   136  		ObjectMeta: metav1.ObjectMeta{
   137  			Name:      name,
   138  			Namespace: namespace,
   139  		},
   140  	}
   141  	if err := h.KubeRest().Delete(context.TODO(), &application); err != nil {
   142  		if !k8sErrors.IsNotFound(err) || (k8sErrors.IsNotFound(err) && reportErrorOnNotFound) {
   143  			return fmt.Errorf("error deleting an application: %+v", err)
   144  		}
   145  	}
   146  	return utils.WaitUntil(h.ApplicationDeleted(&application), 1*time.Minute)
   147  }
   148  
   149  func (h *SuiteController) ApplicationDeleted(application *appservice.Application) wait.ConditionFunc {
   150  	return func() (bool, error) {
   151  		_, err := h.GetHasApplication(application.Name, application.Namespace)
   152  		return err != nil && k8sErrors.IsNotFound(err), nil
   153  	}
   154  }
   155  
   156  // GetHasComponent returns the Appstudio Component Custom Resource object
   157  func (h *SuiteController) GetHasComponent(name, namespace string) (*appservice.Component, error) {
   158  	namespacedName := types.NamespacedName{
   159  		Name:      name,
   160  		Namespace: namespace,
   161  	}
   162  
   163  	component := appservice.Component{}
   164  	err := h.KubeRest().Get(context.TODO(), namespacedName, &component)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	return &component, nil
   169  }
   170  
   171  // ScaleDeploymentReplicas scales the replicas of a given deployment
   172  func (h *SuiteController) ScaleComponentReplicas(component *appservice.Component, replicas *int) (*appservice.Component, error) {
   173  	component.Spec.Replicas = replicas
   174  
   175  	err := h.KubeRest().Update(context.TODO(), component, &rclient.UpdateOptions{})
   176  	if err != nil {
   177  		return &appservice.Component{}, err
   178  	}
   179  	return component, nil
   180  }
   181  
   182  // DeleteHasComponent delete an has component from a given name and namespace
   183  func (h *SuiteController) DeleteHasComponent(name string, namespace string, reportErrorOnNotFound bool) error {
   184  	component := appservice.Component{
   185  		ObjectMeta: metav1.ObjectMeta{
   186  			Name:      name,
   187  			Namespace: namespace,
   188  		},
   189  	}
   190  	if err := h.KubeRest().Delete(context.TODO(), &component); err != nil {
   191  		if !k8sErrors.IsNotFound(err) || (k8sErrors.IsNotFound(err) && reportErrorOnNotFound) {
   192  			return fmt.Errorf("error deleting a component: %+v", err)
   193  		}
   194  	}
   195  
   196  	return utils.WaitUntil(h.ComponentDeleted(&component), 1*time.Minute)
   197  }
   198  
   199  // CreateComponent create an has component from a given name, namespace, application, devfile and a container image
   200  func (h *SuiteController) CreateComponent(applicationName, componentName, namespace, gitSourceURL, gitSourceRevision, containerImageSource, outputContainerImage, secret string, skipInitialChecks bool) (*appservice.Component, error) {
   201  	var containerImage string
   202  	annotations := map[string]string{
   203  		// PLNSRVCE-957 - if true, run only basic build pipeline tasks
   204  		"skip-initial-checks": strconv.FormatBool(skipInitialChecks),
   205  	}
   206  	if outputContainerImage != "" {
   207  		containerImage = outputContainerImage
   208  	} else if containerImageSource != "" {
   209  		containerImage = containerImageSource
   210  	} else {
   211  		// When no image image is selected then add annotatation to generate new image repository
   212  		annotations = utils.MergeMaps(annotations, constants.ImageControllerAnnotationDeleteRepoTrue)
   213  	}
   214  	component := &appservice.Component{
   215  		ObjectMeta: metav1.ObjectMeta{
   216  			Annotations: annotations,
   217  			Labels:      constants.ComponentDefaultLabel,
   218  			Name:        componentName,
   219  			Namespace:   namespace,
   220  		},
   221  		Spec: appservice.ComponentSpec{
   222  			ComponentName: componentName,
   223  			Application:   applicationName,
   224  			Source: appservice.ComponentSource{
   225  				ComponentSourceUnion: appservice.ComponentSourceUnion{
   226  					GitSource: &appservice.GitSource{
   227  						URL:      gitSourceURL,
   228  						Revision: gitSourceRevision,
   229  					},
   230  				},
   231  			},
   232  			Secret:         secret,
   233  			ContainerImage: containerImage,
   234  			Replicas:       pointer.Int(1),
   235  			TargetPort:     8081,
   236  			Route:          "",
   237  		},
   238  	}
   239  	err := h.KubeRest().Create(context.TODO(), component)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	if err = utils.WaitUntil(h.ComponentReady(component), time.Minute*10); err != nil {
   244  		component = h.refreshComponentForErrorDebug(component)
   245  		return nil, fmt.Errorf("timed out when waiting for component %s to be ready in %s namespace. component: %s", componentName, namespace, utils.ToPrettyJSONString(component))
   246  	}
   247  	return component, nil
   248  }
   249  
   250  func (h *SuiteController) ComponentReady(component *appservice.Component) wait.ConditionFunc {
   251  	return func() (bool, error) {
   252  		messages, err := h.GetHasComponentConditionStatusMessages(component.Name, component.Namespace)
   253  		if err != nil {
   254  			return false, nil
   255  		}
   256  		for _, m := range messages {
   257  			if strings.Contains(m, "success") {
   258  				return true, nil
   259  			}
   260  		}
   261  		return false, nil
   262  	}
   263  }
   264  
   265  func (h *SuiteController) ComponentDeleted(component *appservice.Component) wait.ConditionFunc {
   266  	return func() (bool, error) {
   267  		_, err := h.GetHasComponent(component.Name, component.Namespace)
   268  		return err != nil && k8sErrors.IsNotFound(err), nil
   269  	}
   270  }
   271  
   272  // CreateComponentWithPaCEnabled creates a component with "pipelinesascode: '1'" annotation that is used for triggering PaC builds
   273  func (h *SuiteController) CreateComponentWithPaCEnabled(applicationName, componentName, namespace, gitSourceURL, baseBranch string, deleteRepo bool) (*appservice.Component, error) {
   274  
   275  	var annotations map[string]string
   276  	if deleteRepo {
   277  		annotations = utils.MergeMaps(constants.ComponentPaCRequestAnnotation, constants.ImageControllerAnnotationDeleteRepoTrue)
   278  	} else {
   279  		annotations = utils.MergeMaps(constants.ComponentPaCRequestAnnotation, constants.ImageControllerAnnotationDeleteRepoFalse)
   280  	}
   281  
   282  	component := &appservice.Component{
   283  		ObjectMeta: metav1.ObjectMeta{
   284  			Annotations: annotations,
   285  			Name:        componentName,
   286  			Namespace:   namespace,
   287  		},
   288  		Spec: appservice.ComponentSpec{
   289  			ComponentName: componentName,
   290  			Application:   applicationName,
   291  			Source: appservice.ComponentSource{
   292  				ComponentSourceUnion: appservice.ComponentSourceUnion{
   293  					GitSource: &appservice.GitSource{
   294  						URL:      gitSourceURL,
   295  						Revision: baseBranch,
   296  					},
   297  				},
   298  			},
   299  		},
   300  	}
   301  	err := h.KubeRest().Create(context.TODO(), component)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	if err = utils.WaitUntil(h.ComponentReady(component), time.Minute*10); err != nil {
   306  		component = h.refreshComponentForErrorDebug(component)
   307  		return nil, fmt.Errorf("timed out when waiting for component %s to be ready in %s namespace. component: %s", componentName, namespace, utils.ToPrettyJSONString(component))
   308  	}
   309  	return component, nil
   310  }
   311  
   312  func (h *SuiteController) CreateComponentFromStubSkipInitialChecks(compDetected appservice.ComponentDetectionDescription, namespace string, outputContainerImage string, secret string, applicationName string, skipInitialChecks bool) (*appservice.Component, error) {
   313  	component := &appservice.Component{
   314  		ObjectMeta: metav1.ObjectMeta{
   315  			// adding default label because of the BuildPipelineSelector in build test
   316  			Labels: constants.ComponentDefaultLabel,
   317  			Annotations: map[string]string{
   318  				"skip-initial-checks": strconv.FormatBool(skipInitialChecks),
   319  			},
   320  			Name:      compDetected.ComponentStub.ComponentName,
   321  			Namespace: namespace,
   322  		},
   323  		Spec: compDetected.ComponentStub,
   324  	}
   325  
   326  	if outputContainerImage != "" {
   327  		component.Spec.ContainerImage = outputContainerImage
   328  	} else {
   329  		component.Annotations = utils.MergeMaps(component.Annotations, constants.ImageControllerAnnotationDeleteRepoTrue)
   330  	}
   331  
   332  	if component.Spec.TargetPort == 0 {
   333  		component.Spec.TargetPort = 8081
   334  	}
   335  
   336  	component.Spec.Secret = secret
   337  	component.Spec.Application = applicationName
   338  
   339  	err := h.KubeRest().Create(context.TODO(), component)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  	if err = utils.WaitUntil(h.ComponentReady(component), time.Minute*10); err != nil {
   344  		component = h.refreshComponentForErrorDebug(component)
   345  		return nil, fmt.Errorf("timed out when waiting for component %s to be ready in %s namespace. component: %s", compDetected.ComponentStub.ComponentName, namespace, utils.ToPrettyJSONString(component))
   346  	}
   347  	return component, nil
   348  }
   349  
   350  // CreateComponentFromStub create a HAS Component resource from a Completed CDQ resource, which includes a stub Component CR
   351  // The Component from the CDQ is only a template, and needs things like name filled in
   352  func (h *SuiteController) CreateComponentFromStub(compDetected appservice.ComponentDetectionDescription, namespace string, outputContainerImage string, secret string, applicationName string) (*appservice.Component, error) {
   353  	return h.CreateComponentFromStubSkipInitialChecks(compDetected, namespace, outputContainerImage, secret, applicationName, true)
   354  }
   355  
   356  // DeleteHasComponent delete an has component from a given name and namespace
   357  func (h *SuiteController) DeleteHasComponentDetectionQuery(name string, namespace string) error {
   358  	component := appservice.ComponentDetectionQuery{
   359  		ObjectMeta: metav1.ObjectMeta{
   360  			Name:      name,
   361  			Namespace: namespace,
   362  		},
   363  	}
   364  	return h.KubeRest().Delete(context.TODO(), &component)
   365  }
   366  
   367  // CreateComponentDetectionQuery create a has componentdetectionquery from a given name, namespace, and git source
   368  func (h *SuiteController) CreateComponentDetectionQuery(cdqName, namespace, gitSourceURL, gitSourceRevision, gitSourceContext, secret string, isMultiComponent bool) (*appservice.ComponentDetectionQuery, error) {
   369  	return h.CreateComponentDetectionQueryWithTimeout(cdqName, namespace, gitSourceURL, gitSourceRevision, gitSourceContext, secret, isMultiComponent, 5*time.Minute)
   370  }
   371  
   372  // CreateComponentDetectionQueryWithTimeout create a has componentdetectionquery from a given name, namespace, and git source and waits for it to be read
   373  func (h *SuiteController) CreateComponentDetectionQueryWithTimeout(cdqName, namespace, gitSourceURL, gitSourceRevision, gitSourceContext, secret string, isMultiComponent bool, timeout time.Duration) (*appservice.ComponentDetectionQuery, error) {
   374  	componentDetectionQuery := &appservice.ComponentDetectionQuery{
   375  		ObjectMeta: metav1.ObjectMeta{
   376  			Name:      cdqName,
   377  			Namespace: namespace,
   378  		},
   379  		Spec: appservice.ComponentDetectionQuerySpec{
   380  			GitSource: appservice.GitSource{
   381  				URL:      gitSourceURL,
   382  				Revision: gitSourceRevision,
   383  				Context:  gitSourceContext,
   384  			},
   385  			Secret: secret,
   386  		},
   387  	}
   388  	err := h.KubeRest().Create(context.TODO(), componentDetectionQuery)
   389  	if err != nil {
   390  		return nil, err
   391  	}
   392  
   393  	err = utils.WaitUntil(func() (done bool, err error) {
   394  		componentDetectionQuery, err = h.GetComponentDetectionQuery(componentDetectionQuery.Name, componentDetectionQuery.Namespace)
   395  		if err != nil {
   396  			return false, err
   397  		}
   398  		for _, condition := range componentDetectionQuery.Status.Conditions {
   399  			if condition.Type == "Completed" && len(componentDetectionQuery.Status.ComponentDetected) > 0 {
   400  				return true, nil
   401  			}
   402  		}
   403  		return false, nil
   404  	}, timeout)
   405  
   406  	if err != nil {
   407  		return nil, fmt.Errorf("error waiting for cdq to be ready: %v", err)
   408  	}
   409  
   410  	return componentDetectionQuery, nil
   411  }
   412  
   413  // GetComponentDetectionQuery return the status from the ComponentDetectionQuery Custom Resource object
   414  func (h *SuiteController) GetComponentDetectionQuery(name, namespace string) (*appservice.ComponentDetectionQuery, error) {
   415  	namespacedName := types.NamespacedName{
   416  		Name:      name,
   417  		Namespace: namespace,
   418  	}
   419  
   420  	componentDetectionQuery := appservice.ComponentDetectionQuery{
   421  		Spec: appservice.ComponentDetectionQuerySpec{},
   422  	}
   423  	err := h.KubeRest().Get(context.TODO(), namespacedName, &componentDetectionQuery)
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  	return &componentDetectionQuery, nil
   428  }
   429  
   430  // GetComponentPipeline returns the pipeline for a given component labels
   431  func (h *SuiteController) GetComponentPipelineRun(componentName, applicationName, namespace, sha string) (*v1beta1.PipelineRun, error) {
   432  	pipelineRunLabels := map[string]string{"appstudio.openshift.io/component": componentName, "appstudio.openshift.io/application": applicationName}
   433  
   434  	if sha != "" {
   435  		pipelineRunLabels["pipelinesascode.tekton.dev/sha"] = sha
   436  	}
   437  
   438  	list := &v1beta1.PipelineRunList{}
   439  	err := h.KubeRest().List(context.TODO(), list, &rclient.ListOptions{LabelSelector: labels.SelectorFromSet(pipelineRunLabels), Namespace: namespace})
   440  
   441  	if err != nil && !k8sErrors.IsNotFound(err) {
   442  		return nil, fmt.Errorf("error listing pipelineruns in %s namespace: %v", namespace, err)
   443  	}
   444  
   445  	if len(list.Items) > 0 {
   446  		return &list.Items[0], nil
   447  	}
   448  
   449  	return nil, fmt.Errorf("no pipelinerun found for component %s", componentName)
   450  }
   451  
   452  // GetEventListenerRoute returns the route for a given component name's event listener
   453  func (h *SuiteController) GetEventListenerRoute(componentName string, componentNamespace string) (*routev1.Route, error) {
   454  	namespacedName := types.NamespacedName{
   455  		Name:      fmt.Sprintf("el%s", componentName),
   456  		Namespace: componentNamespace,
   457  	}
   458  	route := &routev1.Route{}
   459  	err := h.KubeRest().Get(context.TODO(), namespacedName, route)
   460  	if err != nil {
   461  		return &routev1.Route{}, err
   462  	}
   463  	return route, nil
   464  }
   465  
   466  // GetComponentDeployment returns the deployment for a given component name
   467  func (h *SuiteController) GetComponentDeployment(componentName string, componentNamespace string) (*appsv1.Deployment, error) {
   468  	namespacedName := types.NamespacedName{
   469  		Name:      fmt.Sprintf("el-%s", componentName),
   470  		Namespace: componentNamespace,
   471  	}
   472  
   473  	deployment := &appsv1.Deployment{}
   474  	err := h.KubeRest().Get(context.TODO(), namespacedName, deployment)
   475  	if err != nil {
   476  		return &appsv1.Deployment{}, err
   477  	}
   478  	return deployment, nil
   479  }
   480  
   481  // GetComponentService returns the service for a given component name
   482  func (h *SuiteController) GetComponentService(componentName string, componentNamespace string) (*corev1.Service, error) {
   483  	namespacedName := types.NamespacedName{
   484  		Name:      fmt.Sprintf("el-%s", componentName),
   485  		Namespace: componentNamespace,
   486  	}
   487  
   488  	service := &corev1.Service{}
   489  	err := h.KubeRest().Get(context.TODO(), namespacedName, service)
   490  	if err != nil {
   491  		return &corev1.Service{}, err
   492  	}
   493  	return service, nil
   494  }
   495  
   496  func (h *SuiteController) WaitForComponentPipelineToBeFinished(component *appservice.Component, sha string, maxRetries int) error {
   497  	attempts := 1
   498  	app := component.Spec.Application
   499  	var pr *v1beta1.PipelineRun
   500  
   501  	for {
   502  		err := wait.PollImmediate(20*time.Second, 30*time.Minute, func() (done bool, err error) {
   503  			pr, err = h.GetComponentPipelineRun(component.GetName(), app, component.GetNamespace(), sha)
   504  
   505  			if err != nil {
   506  				GinkgoWriter.Println("PipelineRun has not been created yet")
   507  				return false, nil
   508  			}
   509  
   510  			GinkgoWriter.Printf("PipelineRun %s reason: %s\n", pr.Name, pr.GetStatusCondition().GetCondition(apis.ConditionSucceeded).GetReason())
   511  
   512  			if !pr.IsDone() {
   513  				return false, nil
   514  			}
   515  
   516  			if pr.GetStatusCondition().GetCondition(apis.ConditionSucceeded).IsTrue() {
   517  				return true, nil
   518  			} else {
   519  				var prLogs string
   520  				if err = tekton.StorePipelineRun(pr, h.KubeRest(), h.KubeInterface()); err != nil {
   521  					GinkgoWriter.Printf("failed to store PipelineRun %s:%s: %s\n", pr.GetNamespace(), pr.GetName(), err.Error())
   522  				}
   523  				if prLogs, err = tekton.GetFailedPipelineRunLogs(h.KubeRest(), h.KubeInterface(), pr); err != nil {
   524  					GinkgoWriter.Printf("failed to get logs for PipelineRun %s:%s: %s\n", pr.GetNamespace(), pr.GetName(), err.Error())
   525  				}
   526  				return false, fmt.Errorf(prLogs)
   527  			}
   528  		})
   529  
   530  		if err != nil {
   531  			GinkgoWriter.Printf("attempt %d/%d: PipelineRun %q failed: %+v", attempts, maxRetries+1, pr.GetName(), err)
   532  			// Retry the PipelineRun only in case we hit the known issue https://issues.redhat.com/browse/SRVKP-2749
   533  			if attempts == maxRetries+1 || pr.GetStatusCondition().GetCondition(apis.ConditionSucceeded).GetReason() != "CouldntGetTask" {
   534  				return err
   535  			}
   536  			if sha, err = h.RetriggerComponentPipelineRun(component, pr); err != nil {
   537  				return fmt.Errorf("unable to retrigger component %s:%s: %+v", component.GetNamespace(), component.GetName(), err)
   538  			}
   539  			attempts++
   540  		} else {
   541  			break
   542  		}
   543  	}
   544  
   545  	return nil
   546  
   547  }
   548  
   549  // CreateComponentFromDevfile creates a has component from a given name, namespace, application, devfile and a container image
   550  func (h *SuiteController) CreateComponentFromDevfile(applicationName, componentName, namespace, gitSourceURL, devfile, containerImageSource, outputContainerImage, secret string) (*appservice.Component, error) {
   551  	component := &appservice.Component{
   552  		ObjectMeta: metav1.ObjectMeta{
   553  			Name:      componentName,
   554  			Namespace: namespace,
   555  		},
   556  		Spec: appservice.ComponentSpec{
   557  			ComponentName: componentName,
   558  			Application:   applicationName,
   559  			Source: appservice.ComponentSource{
   560  				ComponentSourceUnion: appservice.ComponentSourceUnion{
   561  					GitSource: &appservice.GitSource{
   562  						URL:        gitSourceURL,
   563  						DevfileURL: devfile,
   564  					},
   565  				},
   566  			},
   567  			Secret:     secret,
   568  			Replicas:   pointer.Int(1),
   569  			TargetPort: 8080,
   570  			Route:      "",
   571  		},
   572  	}
   573  	if outputContainerImage != "" {
   574  		component.Spec.ContainerImage = outputContainerImage
   575  	} else if containerImageSource != "" {
   576  		component.Spec.ContainerImage = containerImageSource
   577  	} else {
   578  		component.Annotations = constants.ImageControllerAnnotationDeleteRepoTrue
   579  	}
   580  	err := h.KubeRest().Create(context.TODO(), component)
   581  	if err != nil {
   582  		return nil, err
   583  	}
   584  	if err = utils.WaitUntil(h.ComponentReady(component), time.Minute*2); err != nil {
   585  		component = h.refreshComponentForErrorDebug(component)
   586  		return nil, fmt.Errorf("timed out when waiting for component %s to be ready in %s namespace. component: %s", componentName, namespace, utils.ToPrettyJSONString(component))
   587  	}
   588  	return component, nil
   589  }
   590  
   591  // DeleteAllComponentsInASpecificNamespace removes all component CRs from a specific namespace. Useful when creating a lot of resources and want to remove all of them
   592  func (h *SuiteController) DeleteAllComponentsInASpecificNamespace(namespace string, timeout time.Duration) error {
   593  	if err := h.KubeRest().DeleteAllOf(context.TODO(), &appservice.Component{}, rclient.InNamespace(namespace)); err != nil {
   594  		return fmt.Errorf("error deleting components from the namespace %s: %+v", namespace, err)
   595  	}
   596  
   597  	componentList := &appservice.ComponentList{}
   598  	return utils.WaitUntil(func() (done bool, err error) {
   599  		if err := h.KubeRest().List(context.Background(), componentList, &rclient.ListOptions{Namespace: namespace}); err != nil {
   600  			return false, nil
   601  		}
   602  		return len(componentList.Items) == 0, nil
   603  	}, timeout)
   604  }
   605  
   606  // DeleteAllApplicationsInASpecificNamespace removes all application CRs from a specific namespace. Useful when creating a lot of resources and want to remove all of them
   607  func (h *SuiteController) DeleteAllApplicationsInASpecificNamespace(namespace string, timeout time.Duration) error {
   608  	if err := h.KubeRest().DeleteAllOf(context.TODO(), &appservice.Application{}, rclient.InNamespace(namespace)); err != nil {
   609  		return fmt.Errorf("error deleting applications from the namespace %s: %+v", namespace, err)
   610  	}
   611  
   612  	applicationList := &appservice.ApplicationList{}
   613  	return utils.WaitUntil(func() (done bool, err error) {
   614  		if err := h.KubeRest().List(context.Background(), applicationList, &rclient.ListOptions{Namespace: namespace}); err != nil {
   615  			return false, nil
   616  		}
   617  		return len(applicationList.Items) == 0, nil
   618  	}, timeout)
   619  }
   620  
   621  func (h *SuiteController) GetHasComponentConditionStatusMessages(name, namespace string) (messages []string, err error) {
   622  	c, err := h.GetHasComponent(name, namespace)
   623  	if err != nil {
   624  		return messages, fmt.Errorf("error getting HAS component: %v", err)
   625  	}
   626  	for _, condition := range c.Status.Conditions {
   627  		messages = append(messages, condition.Message)
   628  	}
   629  	return
   630  }
   631  
   632  // DeleteAllSnapshotEnvBindingsInASpecificNamespace removes all snapshotEnvironmentBindings from a specific namespace. Useful when creating a lot of resources and want to remove all of them
   633  func (h *SuiteController) DeleteAllSnapshotEnvBindingsInASpecificNamespace(namespace string, timeout time.Duration) error {
   634  	if err := h.KubeRest().DeleteAllOf(context.TODO(), &appservice.SnapshotEnvironmentBinding{}, rclient.InNamespace(namespace)); err != nil {
   635  		return fmt.Errorf("error deleting snapshotEnvironmentBindings from the namespace %s: %+v", namespace, err)
   636  	}
   637  
   638  	snapshotEnvironmentBindingList := &appservice.SnapshotEnvironmentBindingList{}
   639  	return utils.WaitUntil(func() (done bool, err error) {
   640  		if err := h.KubeRest().List(context.Background(), snapshotEnvironmentBindingList, &rclient.ListOptions{Namespace: namespace}); err != nil {
   641  			return false, nil
   642  		}
   643  		return len(snapshotEnvironmentBindingList.Items) == 0, nil
   644  	}, timeout)
   645  }
   646  
   647  func (s *SuiteController) ApplicationGitopsRepoExists(devfileContent string) wait.ConditionFunc {
   648  	return func() (bool, error) {
   649  		gitOpsRepoURL := utils.ObtainGitOpsRepositoryName(devfileContent)
   650  		return s.Github.CheckIfRepositoryExist(gitOpsRepoURL), nil
   651  	}
   652  }
   653  
   654  func (h *SuiteController) PipelineRunDeleted(pr *v1beta1.PipelineRun) wait.ConditionFunc {
   655  	return func() (bool, error) {
   656  		o := &v1beta1.PipelineRun{}
   657  		err := h.KubeRest().Get(context.TODO(), types.NamespacedName{Name: pr.GetName(), Namespace: pr.GetNamespace()}, o)
   658  		return err != nil && k8sErrors.IsNotFound(err), nil
   659  	}
   660  }
   661  
   662  func (h *SuiteController) RetriggerComponentPipelineRun(component *appservice.Component, pr *v1beta1.PipelineRun) (sha string, err error) {
   663  	if err = h.KubeRest().Delete(context.TODO(), pr); err != nil {
   664  		return "", fmt.Errorf("failed to delete PipelineRun %q from %q namespace", pr.GetName(), pr.GetNamespace())
   665  	}
   666  
   667  	prLabels := pr.GetLabels()
   668  	// In case of PipelineRun managed by PaC we are able to retrigger the pipeline only
   669  	// by updating the related branch
   670  	if prLabels["app.kubernetes.io/managed-by"] == "pipelinesascode.tekton.dev" {
   671  		var ok bool
   672  		var repoName, eventType, branchName string
   673  		pacRepoNameLabelName := "pipelinesascode.tekton.dev/url-repository"
   674  		pacEventTypeLabelName := "pipelinesascode.tekton.dev/event-type"
   675  		componentLabelName := "appstudio.openshift.io/component"
   676  		targetBranchAnnotationName := "build.appstudio.redhat.com/target_branch"
   677  
   678  		if repoName, ok = prLabels[pacRepoNameLabelName]; !ok {
   679  			return "", fmt.Errorf("cannot retrigger PipelineRun - required label %q not found", pacRepoNameLabelName)
   680  		}
   681  		if eventType, ok = prLabels[pacEventTypeLabelName]; !ok {
   682  			return "", fmt.Errorf("cannot retrigger PipelineRun - required label %q not found", pacEventTypeLabelName)
   683  		}
   684  		// PipelineRun is triggered from a pull request, need to update the PaC PR source branch
   685  		if eventType == "pull_request" {
   686  			if len(prLabels[componentLabelName]) < 1 {
   687  				return "", fmt.Errorf("cannot retrigger PipelineRun - required label %q not found", componentLabelName)
   688  			}
   689  			branchName = constants.PaCPullRequestBranchPrefix + prLabels[componentLabelName]
   690  		} else {
   691  			// No straightforward way to get a target branch from PR labels -> using annotation
   692  			if branchName, ok = pr.GetAnnotations()[targetBranchAnnotationName]; !ok {
   693  				return "", fmt.Errorf("cannot retrigger PipelineRun - required annotation %q not found", targetBranchAnnotationName)
   694  			}
   695  		}
   696  		file, err := h.Github.CreateFile(repoName, util.GenerateRandomString(5), "test", branchName)
   697  		if err != nil {
   698  			return "", fmt.Errorf("failed to retrigger PipelineRun %s in %s namespace: %+v", pr.GetName(), pr.GetNamespace(), err)
   699  		}
   700  		sha = file.GetSHA()
   701  
   702  		// To retrigger simple build PipelineRun we just need to update the initial build annotation
   703  		// in Component CR
   704  	} else {
   705  		err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
   706  			component, err := h.GetHasComponent(component.GetName(), component.GetNamespace())
   707  			if err != nil {
   708  				return fmt.Errorf("failed to get component for PipelineRun %q in %q namespace: %+v", pr.GetName(), pr.GetNamespace(), err)
   709  			}
   710  			delete(component.Annotations, constants.ComponentInitialBuildAnnotationKey)
   711  			if err = h.KubeRest().Update(context.Background(), component); err != nil {
   712  				return fmt.Errorf("failed to update Component %q in %q namespace", component.GetName(), component.GetNamespace())
   713  			}
   714  			return err
   715  		})
   716  
   717  		if err != nil {
   718  			return "", err
   719  		}
   720  	}
   721  	watch, err := h.PipelineClient().TektonV1beta1().PipelineRuns(component.GetNamespace()).Watch(context.Background(), metav1.ListOptions{})
   722  	if err != nil {
   723  		return "", fmt.Errorf("error when initiating watch for new PipelineRun after retriggering it for component %s:%s", component.GetNamespace(), component.GetName())
   724  	}
   725  	newPRFound := false
   726  	for {
   727  		select {
   728  		case <-time.After(5 * time.Minute):
   729  			return "", fmt.Errorf("timed out waiting for new PipelineRun to appear after retriggering it for component %s:%s", component.GetNamespace(), component.GetName())
   730  		case event := <-watch.ResultChan():
   731  			if event.Object == nil {
   732  				continue
   733  			}
   734  			newPR, ok := event.Object.(*v1beta1.PipelineRun)
   735  			if !ok {
   736  				continue
   737  			}
   738  			if pr.GetName() != newPR.GetName() {
   739  				newPRFound = true
   740  			}
   741  		}
   742  		if newPRFound {
   743  			break
   744  		}
   745  	}
   746  
   747  	return sha, nil
   748  }