github.com/redhat-appstudio/e2e-tests@v0.0.0-20240520140907-9709f6f59323/tests/build/build_templates.go (about)

     1  package build
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/devfile/library/v2/pkg/util"
    11  	ecp "github.com/enterprise-contract/enterprise-contract-controller/api/v1alpha1"
    12  	. "github.com/onsi/ginkgo/v2"
    13  	. "github.com/onsi/gomega"
    14  	"github.com/openshift/library-go/pkg/image/reference"
    15  	"github.com/redhat-appstudio/application-api/api/v1alpha1"
    16  	"github.com/redhat-appstudio/e2e-tests/pkg/clients/has"
    17  	kubeapi "github.com/redhat-appstudio/e2e-tests/pkg/clients/kubernetes"
    18  	"github.com/redhat-appstudio/e2e-tests/pkg/constants"
    19  	"github.com/redhat-appstudio/e2e-tests/pkg/framework"
    20  	"github.com/redhat-appstudio/e2e-tests/pkg/utils"
    21  	"github.com/redhat-appstudio/e2e-tests/pkg/utils/build"
    22  	"github.com/redhat-appstudio/e2e-tests/pkg/utils/contract"
    23  	"github.com/redhat-appstudio/e2e-tests/pkg/utils/pipeline"
    24  	"github.com/redhat-appstudio/e2e-tests/pkg/utils/tekton"
    25  
    26  	tektonpipeline "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
    27  
    28  	"k8s.io/apimachinery/pkg/api/errors"
    29  	"sigs.k8s.io/yaml"
    30  )
    31  
    32  var (
    33  	ecPipelineRunTimeout = time.Duration(10 * time.Minute)
    34  )
    35  
    36  const pipelineCompletionRetries = 2
    37  
    38  // OnboardComponent onboards a component from a test repository URL and returns the component's name
    39  func OnboardComponent(ctrl *has.HasController, gitUrl, revision, applicationName, componentName, namespace string) string {
    40  	// Create a component with Git Source URL being defined
    41  	// using cdq since git ref is not known
    42  	cdq, err := ctrl.CreateComponentDetectionQuery(
    43  		componentName, namespace, gitUrl, revision, "", "", false)
    44  	Expect(err).ShouldNot(HaveOccurred())
    45  	Expect(cdq.Status.ComponentDetected).To(
    46  		HaveLen(1), "Expected length of the detected Components was not 1")
    47  
    48  	for _, compDetected := range cdq.Status.ComponentDetected {
    49  		c, err := ctrl.CreateComponent(compDetected.ComponentStub, namespace, "", "", applicationName, false, map[string]string{})
    50  		Expect(err).ShouldNot(HaveOccurred())
    51  		return c.Name
    52  	}
    53  	return ""
    54  }
    55  
    56  func WaitForPipelineRunStarts(hub *framework.ControllerHub, applicationName, componentName, namespace string, timeout time.Duration) string {
    57  	namespacedName := fmt.Sprintf("%s/%s", namespace, componentName)
    58  	timeoutMsg := fmt.Sprintf(
    59  		"timed out when waiting for the PipelineRun to start for the Component %s", namespacedName)
    60  	var prName string
    61  	Eventually(func() error {
    62  		pipelineRun, err := hub.HasController.GetComponentPipelineRun(componentName, applicationName, namespace, "")
    63  		if err != nil {
    64  			GinkgoWriter.Printf("PipelineRun has not been created yet for Component %s\n", namespacedName)
    65  			return err
    66  		}
    67  		if !pipelineRun.HasStarted() {
    68  			return fmt.Errorf("pipelinerun %s/%s has not started yet", pipelineRun.GetNamespace(), pipelineRun.GetName())
    69  		}
    70  		err = hub.TektonController.AddFinalizerToPipelineRun(pipelineRun, constants.E2ETestFinalizerName)
    71  		if err != nil {
    72  			return fmt.Errorf("error while adding finalizer %q to the pipelineRun %q: %v",
    73  				constants.E2ETestFinalizerName, pipelineRun.GetName(), err)
    74  		}
    75  		prName = pipelineRun.GetName()
    76  		return nil
    77  	}, timeout, constants.PipelineRunPollingInterval).Should(Succeed(), timeoutMsg)
    78  	return prName
    79  }
    80  
    81  var _ = framework.BuildSuiteDescribe("Build templates E2E test", Label("build", "build-templates", "HACBS"), func() {
    82  	var f *framework.Framework
    83  	var err error
    84  	AfterEach(framework.ReportFailure(&f))
    85  
    86  	defer GinkgoRecover()
    87  	Describe("HACBS pipelines", Ordered, Label("pipeline"), func() {
    88  
    89  		var applicationName, componentName, symlinkComponentName, testNamespace string
    90  		var kubeadminClient *framework.ControllerHub
    91  		var pipelineRunsWithE2eFinalizer []string
    92  
    93  		BeforeAll(func() {
    94  			if os.Getenv("APP_SUFFIX") != "" {
    95  				applicationName = fmt.Sprintf("test-app-%s", os.Getenv("APP_SUFFIX"))
    96  			} else {
    97  				applicationName = fmt.Sprintf("test-app-%s", util.GenerateRandomString(4))
    98  			}
    99  			testNamespace = os.Getenv(constants.E2E_APPLICATIONS_NAMESPACE_ENV)
   100  			if len(testNamespace) > 0 {
   101  				asAdminClient, err := kubeapi.NewAdminKubernetesClient()
   102  				Expect(err).ShouldNot(HaveOccurred())
   103  				kubeadminClient, err = framework.InitControllerHub(asAdminClient)
   104  				Expect(err).ShouldNot(HaveOccurred())
   105  				_, err = kubeadminClient.CommonController.CreateTestNamespace(testNamespace)
   106  				Expect(err).ShouldNot(HaveOccurred())
   107  			} else {
   108  				f, err = framework.NewFramework(utils.GetGeneratedNamespace("build-e2e"))
   109  				Expect(err).NotTo(HaveOccurred())
   110  				testNamespace = f.UserNamespace
   111  				Expect(f.UserNamespace).NotTo(BeNil())
   112  				kubeadminClient = f.AsKubeAdmin
   113  			}
   114  
   115  			_, err = kubeadminClient.HasController.GetApplication(applicationName, testNamespace)
   116  			// In case the app with the same name exist in the selected namespace, delete it first
   117  			if err == nil {
   118  				Expect(kubeadminClient.HasController.DeleteApplication(applicationName, testNamespace, false)).To(Succeed())
   119  				Eventually(func() bool {
   120  					_, err := kubeadminClient.HasController.GetApplication(applicationName, testNamespace)
   121  					return errors.IsNotFound(err)
   122  				}, time.Minute*5, time.Second*1).Should(BeTrue(), fmt.Sprintf("timed out when waiting for the app %s to be deleted in %s namespace", applicationName, testNamespace))
   123  			}
   124  			_, err = kubeadminClient.HasController.CreateApplication(applicationName, testNamespace)
   125  			Expect(err).NotTo(HaveOccurred())
   126  
   127  			for _, gitUrl := range componentUrls {
   128  				gitUrl := gitUrl
   129  				componentName = fmt.Sprintf("%s-%s", "test-comp", util.GenerateRandomString(4))
   130  				name := OnboardComponent(kubeadminClient.HasController, gitUrl, "", applicationName, componentName, testNamespace)
   131  				Expect(name).ShouldNot(BeEmpty())
   132  				componentNames = append(componentNames, name)
   133  			}
   134  
   135  			// Create component for the repo containing symlink
   136  			symlinkComponentName = fmt.Sprintf("%s-%s", "test-symlink-comp", util.GenerateRandomString(4))
   137  			symlinkComponentName = OnboardComponent(
   138  				kubeadminClient.HasController, pythonComponentGitSourceURL, gitRepoContainsSymlinkBranchName,
   139  				applicationName, symlinkComponentName, testNamespace)
   140  		})
   141  
   142  		AfterAll(func() {
   143  			//Remove finalizers from pipelineruns
   144  			Eventually(func() error {
   145  				pipelineRuns, err := kubeadminClient.HasController.GetAllPipelineRunsForApplication(applicationName, testNamespace)
   146  				if err != nil {
   147  					GinkgoWriter.Printf("error while getting pipelineruns: %v\n", err)
   148  					return err
   149  				}
   150  				for i := 0; i < len(pipelineRuns.Items); i++ {
   151  					if utils.Contains(pipelineRunsWithE2eFinalizer, pipelineRuns.Items[i].GetName()) {
   152  						err = kubeadminClient.TektonController.RemoveFinalizerFromPipelineRun(&pipelineRuns.Items[i], constants.E2ETestFinalizerName)
   153  						if err != nil {
   154  							GinkgoWriter.Printf("error removing e2e test finalizer from %s : %v\n", pipelineRuns.Items[i].GetName(), err)
   155  							return err
   156  						}
   157  					}
   158  				}
   159  				return nil
   160  			}, time.Minute*1, time.Second*10).Should(Succeed(), "timed out when trying to remove the e2e-test finalizer from pipelineruns")
   161  			// Do cleanup only in case the test succeeded
   162  			if !CurrentSpecReport().Failed() {
   163  				// Clean up only Application CR (Component and Pipelines are included) in case we are targeting specific namespace
   164  				// Used e.g. in build-definitions e2e tests, where we are targeting build-templates-e2e namespace
   165  				if os.Getenv(constants.E2E_APPLICATIONS_NAMESPACE_ENV) != "" {
   166  					DeferCleanup(kubeadminClient.HasController.DeleteApplication, applicationName, testNamespace, false)
   167  				} else {
   168  					Expect(kubeadminClient.TektonController.DeleteAllPipelineRunsInASpecificNamespace(testNamespace)).To(Succeed())
   169  					Expect(f.SandboxController.DeleteUserSignup(f.UserName)).To(BeTrue())
   170  				}
   171  			}
   172  		})
   173  
   174  		It(fmt.Sprintf("triggers PipelineRun for symlink component with source URL %s", pythonComponentGitSourceURL), Label(buildTemplatesTestLabel), func() {
   175  			timeout := time.Minute * 5
   176  			prName := WaitForPipelineRunStarts(kubeadminClient, applicationName, symlinkComponentName, testNamespace, timeout)
   177  			Expect(prName).ShouldNot(BeEmpty())
   178  			pipelineRunsWithE2eFinalizer = append(pipelineRunsWithE2eFinalizer, prName)
   179  		})
   180  
   181  		for i, gitUrl := range componentUrls {
   182  			i := i
   183  			gitUrl := gitUrl
   184  			It(fmt.Sprintf("triggers PipelineRun for component with source URL %s", gitUrl), Label(buildTemplatesTestLabel), func() {
   185  				timeout := time.Minute * 5
   186  				prName := WaitForPipelineRunStarts(kubeadminClient, applicationName, componentNames[i], testNamespace, timeout)
   187  				Expect(prName).ShouldNot(BeEmpty())
   188  				pipelineRunsWithE2eFinalizer = append(pipelineRunsWithE2eFinalizer, prName)
   189  			})
   190  		}
   191  
   192  		for i, gitUrl := range componentUrls {
   193  			i := i
   194  			gitUrl := gitUrl
   195  			var pr *tektonpipeline.PipelineRun
   196  
   197  			It(fmt.Sprintf("should eventually finish successfully for component with Git source URL %s", gitUrl), Label(buildTemplatesTestLabel), func() {
   198  				component, err := kubeadminClient.HasController.GetComponent(componentNames[i], testNamespace)
   199  				Expect(err).ShouldNot(HaveOccurred())
   200  				Expect(kubeadminClient.HasController.WaitForComponentPipelineToBeFinished(component, "",
   201  					kubeadminClient.TektonController, &has.RetryOptions{Retries: pipelineCompletionRetries, Always: true}, nil)).To(Succeed())
   202  			})
   203  
   204  			It(fmt.Sprintf("should ensure SBOM is shown for component with Git source URL %s", gitUrl), Label(buildTemplatesTestLabel), func() {
   205  				pr, err = kubeadminClient.HasController.GetComponentPipelineRun(componentNames[i], applicationName, testNamespace, "")
   206  				Expect(err).ShouldNot(HaveOccurred())
   207  				Expect(pr).ToNot(BeNil(), fmt.Sprintf("PipelineRun for the component %s/%s not found", testNamespace, componentNames[i]))
   208  
   209  				logs, err := kubeadminClient.TektonController.GetTaskRunLogs(pr.GetName(), "show-sbom", testNamespace)
   210  				Expect(err).ShouldNot(HaveOccurred())
   211  				Expect(logs).To(HaveLen(1))
   212  				var sbomTaskLog string
   213  				for _, log := range logs {
   214  					sbomTaskLog = log
   215  				}
   216  
   217  				sbom := &build.SbomCyclonedx{}
   218  				GinkgoWriter.Printf("sbom task log: %s\n", sbomTaskLog)
   219  
   220  				err = json.Unmarshal([]byte(sbomTaskLog), sbom)
   221  				Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("failed to parse SBOM from show-sbom task output from %s/%s PipelineRun", pr.GetNamespace(), pr.GetName()))
   222  				Expect(sbom.BomFormat).ToNot(BeEmpty())
   223  				Expect(sbom.SpecVersion).ToNot(BeEmpty())
   224  				Expect(sbom.Components).ToNot(BeEmpty())
   225  			})
   226  
   227  			It(fmt.Sprintf("should ensure show-summary task ran for component with Git source URL %s", gitUrl), Label(buildTemplatesTestLabel), func() {
   228  				pr, err = kubeadminClient.HasController.GetComponentPipelineRun(componentNames[i], applicationName, testNamespace, "")
   229  				Expect(err).ShouldNot(HaveOccurred())
   230  				Expect(pr).ToNot(BeNil(), fmt.Sprintf("PipelineRun for the component %s/%s not found", testNamespace, componentNames[i]))
   231  
   232  				logs, err := kubeadminClient.TektonController.GetTaskRunLogs(pr.GetName(), "show-summary", testNamespace)
   233  				Expect(err).ShouldNot(HaveOccurred())
   234  				Expect(logs).To(HaveLen(1))
   235  				buildSummaryLog := logs["step-appstudio-summary"]
   236  				binaryImage := build.GetBinaryImage(pr)
   237  				Expect(buildSummaryLog).To(ContainSubstring(binaryImage))
   238  			})
   239  
   240  			It("check for source images if enabled in pipeline", Label(buildTemplatesTestLabel), func() {
   241  				if build.IsFBCBuild(pr) {
   242  					GinkgoWriter.Println("This is FBC build, which does not require source container build.")
   243  					Skip(fmt.Sprintf("Skiping FBC build %s", pr.GetName()))
   244  					return
   245  				}
   246  
   247  				isSourceBuildEnabled := build.IsSourceBuildEnabled(pr)
   248  				GinkgoWriter.Printf("Source build is enabled: %v\n", isSourceBuildEnabled)
   249  				if !isSourceBuildEnabled {
   250  					Skip("Skipping source image check since it is not enabled in the pipeline")
   251  				}
   252  
   253  				binaryImage := build.GetBinaryImage(pr)
   254  				if binaryImage == "" {
   255  					Fail("Failed to get the binary image url from pipelinerun")
   256  				}
   257  
   258  				binaryImageRef, err := reference.Parse(binaryImage)
   259  				Expect(err).ShouldNot(HaveOccurred(),
   260  					fmt.Sprintf("cannot parse binary image pullspec %s", binaryImage))
   261  
   262  				tagInfo, err := build.GetImageTag(binaryImageRef.Namespace, binaryImageRef.Name, binaryImageRef.Tag)
   263  				Expect(err).ShouldNot(HaveOccurred(),
   264  					fmt.Sprintf("failed to get tag %s info for constructing source container image", binaryImageRef.Tag),
   265  				)
   266  
   267  				srcImageRef := reference.DockerImageReference{
   268  					Registry:  binaryImageRef.Registry,
   269  					Namespace: binaryImageRef.Namespace,
   270  					Name:      binaryImageRef.Name,
   271  					Tag:       fmt.Sprintf("%s.src", strings.Replace(tagInfo.ManifestDigest, ":", "-", 1)),
   272  				}
   273  				srcImage := srcImageRef.String()
   274  				tagExists, err := build.DoesTagExistsInQuay(srcImage)
   275  				Expect(err).ShouldNot(HaveOccurred(),
   276  					fmt.Sprintf("failed to check existence of source container image %s", srcImage))
   277  				Expect(tagExists).To(BeTrue(),
   278  					fmt.Sprintf("cannot find source container image %s", srcImage))
   279  
   280  				CheckSourceImage(srcImage, gitUrl, kubeadminClient, pr)
   281  			})
   282  
   283  			When(fmt.Sprintf("Pipeline Results are stored for component with Git source URL %s", gitUrl), Label("pipeline"), func() {
   284  				var resultClient *pipeline.ResultClient
   285  				var pr *tektonpipeline.PipelineRun
   286  
   287  				BeforeAll(func() {
   288  					// create the proxyplugin for tekton-results
   289  					_, err = kubeadminClient.CommonController.CreateProxyPlugin("tekton-results", "toolchain-host-operator", "tekton-results", "tekton-results")
   290  					Expect(err).NotTo(HaveOccurred())
   291  
   292  					regProxyUrl := fmt.Sprintf("%s/plugins/tekton-results", f.ProxyUrl)
   293  					resultClient = pipeline.NewClient(regProxyUrl, f.UserToken)
   294  
   295  					pr, err = kubeadminClient.HasController.GetComponentPipelineRun(componentNames[i], applicationName, testNamespace, "")
   296  					Expect(err).ShouldNot(HaveOccurred())
   297  				})
   298  
   299  				AfterAll(func() {
   300  					Expect(kubeadminClient.CommonController.DeleteProxyPlugin("tekton-results", "toolchain-host-operator")).To(BeTrue())
   301  				})
   302  
   303  				It("should have Pipeline Records", func() {
   304  					records, err := resultClient.GetRecords(testNamespace, string(pr.GetUID()))
   305  					// temporary logs due to RHTAPBUGS-213
   306  					GinkgoWriter.Printf("records for PipelineRun %s:\n%s\n", pr.Name, records)
   307  					Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("got error getting records for PipelineRun %s: %v", pr.Name, err))
   308  					Expect(records.Record).NotTo(BeEmpty(), fmt.Sprintf("No records found for PipelineRun %s", pr.Name))
   309  				})
   310  
   311  				// Temporarily disabled until https://issues.redhat.com/browse/SRVKP-4348 is resolved
   312  				It("should have Pipeline Logs", Pending, func() {
   313  					// Verify if result is stored in Database
   314  					// temporary logs due to RHTAPBUGS-213
   315  					logs, err := resultClient.GetLogs(testNamespace, string(pr.GetUID()))
   316  					GinkgoWriter.Printf("logs for PipelineRun %s:\n%s\n", pr.GetName(), logs)
   317  					Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("got error getting logs for PipelineRun %s: %v", pr.Name, err))
   318  
   319  					timeout := time.Minute * 2
   320  					interval := time.Second * 10
   321  					// temporary timeout  due to RHTAPBUGS-213
   322  					Eventually(func() error {
   323  						// temporary logs due to RHTAPBUGS-213
   324  						logs, err = resultClient.GetLogs(testNamespace, string(pr.GetUID()))
   325  						if err != nil {
   326  							return fmt.Errorf("failed to get logs for PipelineRun %s: %v", pr.Name, err)
   327  						}
   328  						GinkgoWriter.Printf("logs for PipelineRun %s:\n%s\n", pr.Name, logs)
   329  
   330  						if len(logs.Record) == 0 {
   331  							return fmt.Errorf("logs for PipelineRun %s/%s are empty", pr.GetNamespace(), pr.GetName())
   332  						}
   333  						return nil
   334  					}, timeout, interval).Should(Succeed(), fmt.Sprintf("timed out when getting logs for PipelineRun %s/%s", pr.GetNamespace(), pr.GetName()))
   335  
   336  					// Verify if result is stored in S3
   337  					// temporary logs due to RHTAPBUGS-213
   338  					log, err := resultClient.GetLogByName(logs.Record[0].Name)
   339  					GinkgoWriter.Printf("log for record %s:\n%s\n", logs.Record[0].Name, log)
   340  					Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("got error getting log '%s' for PipelineRun %s: %v", logs.Record[0].Name, pr.GetName(), err))
   341  					Expect(log).NotTo(BeEmpty(), fmt.Sprintf("no log content '%s' found for PipelineRun %s", logs.Record[0].Name, pr.GetName()))
   342  				})
   343  			})
   344  
   345  			It(fmt.Sprintf("should validate tekton taskrun test results for component with Git source URL %s", gitUrl), Label(buildTemplatesTestLabel), func() {
   346  				pr, err := kubeadminClient.HasController.GetComponentPipelineRun(componentNames[i], applicationName, testNamespace, "")
   347  				Expect(err).ShouldNot(HaveOccurred())
   348  				Expect(build.ValidateBuildPipelineTestResults(pr, kubeadminClient.CommonController.KubeRest())).To(Succeed())
   349  			})
   350  
   351  			When(fmt.Sprintf("the container image for component with Git source URL %s is created and pushed to container registry", gitUrl), Label("sbom", "slow"), func() {
   352  				var imageWithDigest string
   353  				var pr *tektonpipeline.PipelineRun
   354  
   355  				BeforeAll(func() {
   356  					var err error
   357  					imageWithDigest, err = getImageWithDigest(kubeadminClient, componentNames[i], applicationName, testNamespace)
   358  					Expect(err).NotTo(HaveOccurred())
   359  				})
   360  				AfterAll(func() {
   361  					if !CurrentSpecReport().Failed() {
   362  						Expect(kubeadminClient.TektonController.DeletePipelineRun(pr.GetName(), pr.GetNamespace())).To(Succeed())
   363  					}
   364  				})
   365  
   366  				It("verify-enterprise-contract check should pass", Label(buildTemplatesTestLabel), func() {
   367  					// If the Tekton Chains controller is busy, it may take longer than usual for it
   368  					// to sign and attest the image built in BeforeAll.
   369  					err = kubeadminClient.TektonController.AwaitAttestationAndSignature(imageWithDigest, constants.ChainsAttestationTimeout)
   370  					Expect(err).ToNot(HaveOccurred())
   371  
   372  					cm, err := kubeadminClient.CommonController.GetConfigMap("ec-defaults", "enterprise-contract-service")
   373  					Expect(err).ToNot(HaveOccurred())
   374  
   375  					verifyECTaskBundle := cm.Data["verify_ec_task_bundle"]
   376  					Expect(verifyECTaskBundle).ToNot(BeEmpty())
   377  
   378  					publicSecretName := "cosign-public-key"
   379  					publicKey, err := kubeadminClient.TektonController.GetTektonChainsPublicKey()
   380  					Expect(err).ToNot(HaveOccurred())
   381  
   382  					Expect(kubeadminClient.TektonController.CreateOrUpdateSigningSecret(
   383  						publicKey, publicSecretName, testNamespace)).To(Succeed())
   384  
   385  					defaultECP, err := kubeadminClient.TektonController.GetEnterpriseContractPolicy("default", "enterprise-contract-service")
   386  					Expect(err).NotTo(HaveOccurred())
   387  
   388  					policy := contract.PolicySpecWithSourceConfig(
   389  						defaultECP.Spec,
   390  						ecp.SourceConfig{
   391  							Include: []string{"@slsa3"},
   392  							Exclude: []string{"cve"},
   393  						},
   394  					)
   395  					Expect(kubeadminClient.TektonController.CreateOrUpdatePolicyConfiguration(testNamespace, policy)).To(Succeed())
   396  
   397  					pipelineRun, err := kubeadminClient.HasController.GetComponentPipelineRun(componentNames[i], applicationName, testNamespace, "")
   398  					Expect(err).ToNot(HaveOccurred())
   399  
   400  					revision := pipelineRun.Annotations["build.appstudio.redhat.com/commit_sha"]
   401  					Expect(revision).ToNot(BeEmpty())
   402  
   403  					generator := tekton.VerifyEnterpriseContract{
   404  						Snapshot: v1alpha1.SnapshotSpec{
   405  							Application: applicationName,
   406  							Components: []v1alpha1.SnapshotComponent{
   407  								{
   408  									Name:           componentNames[i],
   409  									ContainerImage: imageWithDigest,
   410  									Source: v1alpha1.ComponentSource{
   411  										ComponentSourceUnion: v1alpha1.ComponentSourceUnion{
   412  											GitSource: &v1alpha1.GitSource{
   413  												URL:      gitUrl,
   414  												Revision: revision,
   415  											},
   416  										},
   417  									},
   418  								},
   419  							},
   420  						},
   421  						TaskBundle:          verifyECTaskBundle,
   422  						Name:                "verify-enterprise-contract",
   423  						Namespace:           testNamespace,
   424  						PolicyConfiguration: "ec-policy",
   425  						PublicKey:           fmt.Sprintf("k8s://%s/%s", testNamespace, publicSecretName),
   426  						Strict:              true,
   427  						EffectiveTime:       "now",
   428  						IgnoreRekor:         true,
   429  					}
   430  
   431  					pr, err = kubeadminClient.TektonController.RunPipeline(generator, testNamespace, int(ecPipelineRunTimeout.Seconds()))
   432  					Expect(err).NotTo(HaveOccurred())
   433  
   434  					Expect(kubeadminClient.TektonController.WatchPipelineRun(pr.Name, testNamespace, int(ecPipelineRunTimeout.Seconds()))).To(Succeed())
   435  
   436  					pr, err = kubeadminClient.TektonController.GetPipelineRun(pr.Name, pr.Namespace)
   437  					Expect(err).NotTo(HaveOccurred())
   438  
   439  					tr, err := kubeadminClient.TektonController.GetTaskRunStatus(kubeadminClient.CommonController.KubeRest(), pr, "verify-enterprise-contract")
   440  					Expect(err).NotTo(HaveOccurred())
   441  					Expect(tekton.DidTaskRunSucceed(tr)).To(BeTrue())
   442  					Expect(tr.Status.TaskRunStatusFields.Results).Should(Or(
   443  						// TODO: delete the first option after https://issues.redhat.com/browse/RHTAP-810 is completed
   444  						ContainElements(tekton.MatchTaskRunResultWithJSONPathValue(constants.OldTektonTaskTestOutputName, "{$.result}", `["SUCCESS"]`)),
   445  						ContainElements(tekton.MatchTaskRunResultWithJSONPathValue(constants.TektonTaskTestOutputName, "{$.result}", `["SUCCESS"]`)),
   446  					))
   447  				})
   448  				It("contains non-empty sbom files", Label(buildTemplatesTestLabel), func() {
   449  					purl, cyclonedx, err := build.GetParsedSbomFilesContentFromImage(imageWithDigest)
   450  					Expect(err).NotTo(HaveOccurred())
   451  
   452  					Expect(cyclonedx.BomFormat).To(Equal("CycloneDX"))
   453  					Expect(cyclonedx.SpecVersion).ToNot(BeEmpty())
   454  					Expect(cyclonedx.Version).ToNot(BeZero())
   455  					Expect(cyclonedx.Components).ToNot(BeEmpty())
   456  
   457  					numberOfLibraryComponents := 0
   458  					for _, component := range cyclonedx.Components {
   459  						Expect(component.Name).ToNot(BeEmpty())
   460  						Expect(component.Type).ToNot(BeEmpty())
   461  
   462  						if component.Type == "library" || component.Type == "application" {
   463  							Expect(component.Purl).ToNot(BeEmpty())
   464  							numberOfLibraryComponents++
   465  						}
   466  					}
   467  
   468  					Expect(purl.ImageContents.Dependencies).ToNot(BeEmpty())
   469  					Expect(purl.ImageContents.Dependencies).To(HaveLen(numberOfLibraryComponents))
   470  
   471  					for _, dependency := range purl.ImageContents.Dependencies {
   472  						Expect(dependency.Purl).ToNot(BeEmpty())
   473  					}
   474  				})
   475  			})
   476  
   477  			Context("build-definitions ec pipelines", Label(buildTemplatesTestLabel), func() {
   478  				ecPipelines := []string{
   479  					"pipelines/enterprise-contract.yaml",
   480  				}
   481  
   482  				var gitRevision, gitURL, imageWithDigest string
   483  
   484  				BeforeAll(func() {
   485  					// resolve the gitURL and gitRevision
   486  					var err error
   487  					gitURL, gitRevision, err = build.ResolveGitDetails(constants.EC_PIPELINES_REPO_URL_ENV, constants.EC_PIPELINES_REPO_REVISION_ENV)
   488  					Expect(err).NotTo(HaveOccurred())
   489  
   490  					// Double check that the component has finished. There's an earlier test that
   491  					// verifies this so this should be a no-op. It is added here in order to avoid
   492  					// unnecessary coupling of unrelated tests.
   493  					component, err := kubeadminClient.HasController.GetComponent(componentNames[i], testNamespace)
   494  					Expect(err).ShouldNot(HaveOccurred())
   495  					Expect(kubeadminClient.HasController.WaitForComponentPipelineToBeFinished(
   496  						component, "", kubeadminClient.TektonController, &has.RetryOptions{Retries: pipelineCompletionRetries, Always: true}, nil)).To(Succeed())
   497  
   498  					imageWithDigest, err = getImageWithDigest(kubeadminClient, componentNames[i], applicationName, testNamespace)
   499  					Expect(err).NotTo(HaveOccurred())
   500  
   501  					err = kubeadminClient.TektonController.AwaitAttestationAndSignature(imageWithDigest, constants.ChainsAttestationTimeout)
   502  					Expect(err).NotTo(HaveOccurred())
   503  				})
   504  
   505  				for _, pathInRepo := range ecPipelines {
   506  					pathInRepo := pathInRepo
   507  					It(fmt.Sprintf("runs ec pipeline %s", pathInRepo), func() {
   508  						generator := tekton.ECIntegrationTestScenario{
   509  							Image:                 imageWithDigest,
   510  							Namespace:             testNamespace,
   511  							PipelineGitURL:        gitURL,
   512  							PipelineGitRevision:   gitRevision,
   513  							PipelineGitPathInRepo: pathInRepo,
   514  						}
   515  
   516  						pr, err := kubeadminClient.TektonController.RunPipeline(generator, testNamespace, int(ecPipelineRunTimeout.Seconds()))
   517  						Expect(err).NotTo(HaveOccurred())
   518  						defer func(pr *tektonpipeline.PipelineRun) {
   519  							// Avoid blowing up PipelineRun usage
   520  							err := kubeadminClient.TektonController.DeletePipelineRun(pr.Name, pr.Namespace)
   521  							Expect(err).NotTo(HaveOccurred())
   522  						}(pr)
   523  						Expect(kubeadminClient.TektonController.WatchPipelineRun(pr.Name, testNamespace, int(ecPipelineRunTimeout.Seconds()))).To(Succeed())
   524  
   525  						// Refresh our copy of the PipelineRun for latest results
   526  						pr, err = kubeadminClient.TektonController.GetPipelineRun(pr.Name, pr.Namespace)
   527  						Expect(err).NotTo(HaveOccurred())
   528  
   529  						// The UI uses this label to display additional information.
   530  						Expect(pr.Labels["build.appstudio.redhat.com/pipeline"]).To(Equal("enterprise-contract"))
   531  
   532  						// The UI uses this label to display additional information.
   533  						tr, err := kubeadminClient.TektonController.GetTaskRunFromPipelineRun(kubeadminClient.CommonController.KubeRest(), pr, "verify")
   534  						Expect(err).NotTo(HaveOccurred())
   535  						Expect(tr.Labels["build.appstudio.redhat.com/pipeline"]).To(Equal("enterprise-contract"))
   536  
   537  						logs, err := kubeadminClient.TektonController.GetTaskRunLogs(pr.Name, "verify", pr.Namespace)
   538  						Expect(err).NotTo(HaveOccurred())
   539  
   540  						// The logs from the report step are used by the UI to display validation
   541  						// details. Let's make sure it has valid YAML.
   542  						reportLogs := logs["step-report"]
   543  						Expect(reportLogs).NotTo(BeEmpty())
   544  						var reportYAML any
   545  						err = yaml.Unmarshal([]byte(reportLogs), &reportYAML)
   546  						Expect(err).NotTo(HaveOccurred())
   547  
   548  						// The logs from the summary step are used by the UI to display an overview of
   549  						// the validation.
   550  						summaryLogs := logs["step-summary"]
   551  						GinkgoWriter.Printf("got step-summary log: %s\n", summaryLogs)
   552  						Expect(summaryLogs).NotTo(BeEmpty())
   553  						var summary build.TestOutput
   554  						err = json.Unmarshal([]byte(summaryLogs), &summary)
   555  						Expect(err).NotTo(HaveOccurred())
   556  						Expect(summary).NotTo(Equal(build.TestOutput{}))
   557  					})
   558  				}
   559  			})
   560  		}
   561  
   562  		It(fmt.Sprintf("pipelineRun should fail for symlink component with Git source URL %s", pythonComponentGitSourceURL), Label(buildTemplatesTestLabel), func() {
   563  			component, err := kubeadminClient.HasController.GetComponent(symlinkComponentName, testNamespace)
   564  			Expect(err).ShouldNot(HaveOccurred())
   565  			Expect(kubeadminClient.HasController.WaitForComponentPipelineToBeFinished(component, "",
   566  				kubeadminClient.TektonController, &has.RetryOptions{Retries: pipelineCompletionRetries}, nil)).Should(MatchError(ContainSubstring("cloned repository contains symlink pointing outside of the cloned repository")))
   567  		})
   568  	})
   569  })
   570  
   571  func getImageWithDigest(c *framework.ControllerHub, componentName, applicationName, namespace string) (string, error) {
   572  	var url string
   573  	var digest string
   574  	pipelineRun, err := c.HasController.GetComponentPipelineRun(componentName, applicationName, namespace, "")
   575  	if err != nil {
   576  		return "", err
   577  	}
   578  
   579  	for _, p := range pipelineRun.Spec.Params {
   580  		if p.Name == "output-image" {
   581  			url = p.Value.StringVal
   582  		}
   583  	}
   584  	if url == "" {
   585  		return "", fmt.Errorf("output-image of a component %q could not be found", componentName)
   586  	}
   587  
   588  	for _, r := range pipelineRun.Status.PipelineRunStatusFields.Results {
   589  		if r.Name == "IMAGE_DIGEST" {
   590  			digest = r.Value.StringVal
   591  		}
   592  	}
   593  	if digest == "" {
   594  		return "", fmt.Errorf("IMAGE_DIGEST for component %q could not be found", componentName)
   595  	}
   596  	return fmt.Sprintf("%s@%s", url, digest), nil
   597  }