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 }