From 134ac9279ce8a989c63382be835d406f31334792 Mon Sep 17 00:00:00 2001
From: Jing Qi <jinqi@redhat.com>
Date: Tue, 25 Jun 2024 15:56:15 +0800
Subject: [PATCH] feat(RELEASE-927): add e2e tests for multi-arch

Signed-off-by: Jing Qi <jinqi@redhat.com>
---
 Makefile                                      |   8 +-
 magefiles/magefile.go                         | 234 +++++++++++---
 pkg/constants/constants.go                    |   3 +
 tests/release/const.go                        |   1 +
 .../pipelines/multi_arch_advisories.go        | 289 ++++++++++++++++++
 5 files changed, 494 insertions(+), 41 deletions(-)
 create mode 100644 tests/release/pipelines/multi_arch_advisories.go

diff --git a/Makefile b/Makefile
index 1808d63d0b..dd8385012a 100644
--- a/Makefile
+++ b/Makefile
@@ -58,10 +58,7 @@ clean-gitops-repositories:
 	DRY_RUN=false ./mage -v local:cleanupGithubOrg
 
 clean-github-webhooks:
-	./mage -v cleanGitHubWebHooks
-
-clean-gitlab-webhooks:
-	./mage -v cleanGitLabWebHooks
+	./mage -v cleanWebHooks
 
 clean-quay-repos-and-robots:
 	./mage -v local:cleanupQuayReposAndRobots
@@ -75,6 +72,9 @@ clean-private-repos:
 clean-registered-servers:
 	./mage -v CleanupRegisteredPacServers
 
+setup-multi-arch-tests:
+	./mage -v SetupMultiArchTests
+
 setup-multi-platform-tests:
 	./mage -v SetupMultiPlatformTests
 
diff --git a/magefiles/magefile.go b/magefiles/magefile.go
index 4b2841de51..5022a8c163 100644
--- a/magefiles/magefile.go
+++ b/magefiles/magefile.go
@@ -26,7 +26,6 @@ import (
 	"github.com/konflux-ci/e2e-tests/magefiles/installation"
 	"github.com/konflux-ci/e2e-tests/magefiles/upgrade"
 	"github.com/konflux-ci/e2e-tests/pkg/clients/github"
-	"github.com/konflux-ci/e2e-tests/pkg/clients/gitlab"
 	"github.com/konflux-ci/e2e-tests/pkg/clients/slack"
 	"github.com/konflux-ci/e2e-tests/pkg/clients/sprayproxy"
 	"github.com/konflux-ci/e2e-tests/pkg/constants"
@@ -36,7 +35,7 @@ import (
 	"github.com/konflux-ci/image-controller/pkg/quay"
 	"github.com/magefile/mage/sh"
 	tektonapi "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
-	gl "github.com/xanzy/go-gitlab"
+	"k8s.io/apimachinery/pkg/selection"
 )
 
 const (
@@ -59,6 +58,7 @@ var (
 	requiresSprayProxyRegistering bool
 
 	requiresMultiPlatformTests bool
+	requiresMultiArchTests bool
 	platforms                  = []string{"linux/arm64", "linux/s390x", "linux/ppc64le"}
 
 	sprayProxyConfig       *sprayproxy.SprayProxyConfig
@@ -274,6 +274,12 @@ func (ci CI) TestE2E() error {
 		}
 	}
 
+	if requiresMultiArchTests {
+		if err := SetupMultiArchTests(); err != nil {
+			return err
+		}
+	}
+
 	if requiresMultiPlatformTests {
 		if err := SetupMultiPlatformTests(); err != nil {
 			return err
@@ -502,6 +508,7 @@ func setRequiredEnvVars() error {
 			*/
 			os.Setenv("E2E_TEST_SUITE_LABEL", "e2e-demo,rhtap-demo,spi-suite,remote-secret,integration-service,ec,build-templates,multi-platform")
 		} else if strings.Contains(jobName, "release-service-catalog") { // release-service-catalog jobs (pull, rehearsal)
+			requiresMultiArchTests = true
 			envVarPrefix := "RELEASE_SERVICE"
 			os.Setenv("E2E_TEST_SUITE_LABEL", "release-pipelines")
 			// "rehearse" jobs metadata are not relevant for testing
@@ -542,6 +549,192 @@ func setRequiredEnvVars() error {
 	return nil
 }
 
+func SetupMultiArchTests() error {
+	klog.Infof("going to create new Tekton bundle remote-build for the purpose of testing multi-arch PR")
+	var err error
+	var defaultBundleRef string
+	var tektonObj runtime.Object
+	//currently, we can only create one image and the image index 
+	platformType := "linux/amd64"
+
+	tag := fmt.Sprintf("%d-%s", time.Now().Unix(), util.GenerateRandomString(4))
+	quayOrg := utils.GetEnv(constants.DEFAULT_QUAY_ORG_ENV, constants.DefaultQuayOrg)
+	newMultiPlatformBuilderPipelineImg := strings.ReplaceAll(constants.DefaultImagePushRepo, constants.DefaultQuayOrg, quayOrg)
+	var newRemotePipeline, _ = name.ParseReference(fmt.Sprintf("%s:pipeline-bundle-%s", newMultiPlatformBuilderPipelineImg, tag))
+	var newPipelineYaml []byte
+
+	if err = utils.CreateDockerConfigFile(os.Getenv("QUAY_TOKEN")); err != nil {
+		return fmt.Errorf("failed to create docker config file: %+v", err)
+	}
+	if defaultBundleRef, err = tekton.GetDefaultPipelineBundleRef(constants.BuildPipelineConfigConfigMapYamlURL, "docker-build"); err != nil {
+		return fmt.Errorf("failed to get the pipeline bundle ref: %+v", err)
+	}
+	if tektonObj, err = tekton.ExtractTektonObjectFromBundle(defaultBundleRef, "pipeline", "docker-build"); err != nil {
+		return fmt.Errorf("failed to extract the Tekton Pipeline from bundle: %+v", err)
+	}
+	dockerPipelineObject := tektonObj.(*tektonapi.Pipeline)
+
+	var currentBuildahTaskRef string
+	for i := range dockerPipelineObject.PipelineSpec().Tasks {
+		t := &dockerPipelineObject.PipelineSpec().Tasks[i]
+		if t.Name == "build-source-image" {
+			t.RunAfter = []string{"build-container"}
+			for i, p := range t.Params {
+				tmpParam := &t.Params[i]
+				if p.Name == "BASE_IMAGES" {
+					tmpParam.Value = *tektonapi.NewStructuredValues("$(tasks.build-container-amd64.results.BASE_IMAGES_DIGESTS)")
+				}
+			}
+		}
+		if t.Name == "ecosystem-cert-preflight-checks" {
+			t.RunAfter = []string{"build-container"}
+			for i, p := range t.Params {
+				tmpParam := &t.Params[i]
+				if p.Name == "image-url" {
+					tmpParam.Value = *tektonapi.NewStructuredValues("$(tasks.build-container-amd64.results.IMAGE_URL)")
+				}
+			}
+		}
+		if t.Name == "clair-scan" || t.Name == "clamav-scan" {
+			t.RunAfter = []string{"build-container"}
+			for i, p := range t.Params {
+				tmpParam := &t.Params[i]
+				if p.Name == "image-digest" {
+					tmpParam.Value = *tektonapi.NewStructuredValues("$(tasks.build-container-amd64.results.IMAGE_DIGEST)")
+				}
+				if p.Name == "image-url" {
+					tmpParam.Value = *tektonapi.NewStructuredValues("$(tasks.build-container-amd64.results.IMAGE_URL)")
+				}
+			}
+		}
+		if t.Name == "deprecated-base-image-check" || t.Name == "sbom-json-check" {
+			t.RunAfter = []string{"build-container"}
+			for i, p := range t.Params {
+				tmpParam := &t.Params[i]
+				if p.Name == "IMAGE_URL" {
+					tmpParam.Value = *tektonapi.NewStructuredValues("$(tasks.build-container-amd64.results.IMAGE_URL)")
+				}
+				if p.Name == "IMAGES_DIGESTS" {
+					tmpParam.Value = *tektonapi.NewStructuredValues("$(tasks.build-container-amd64.results.IMAGES_DIGESTS)")
+				}
+				if t.Name == "deprecated-base-image-check" && p.Name == "BASE_IMAGES_DIGESTS" {
+					tmpParam.Value = *tektonapi.NewStructuredValues("$(tasks.build-container-amd64.results.BASE_IMAGES_DIGESTS)")
+				}
+			}
+		}
+	}
+	for i := range dockerPipelineObject.PipelineSpec().Finally {
+		t := &dockerPipelineObject.PipelineSpec().Finally[i]
+		if t.Name == "show-sbom" {
+			for i, p := range t.Params {
+				tmpParam := &t.Params[i]
+				if p.Name == "IMAGE_URL" {
+					tmpParam.Value = *tektonapi.NewStructuredValues("$(tasks.build-container-amd64.results.IMAGE_URL)")
+				}
+			}
+		}
+	}
+	for i := range dockerPipelineObject.PipelineSpec().Tasks {
+		t := &dockerPipelineObject.PipelineSpec().Tasks[i]
+		params := t.TaskRef.Params
+		var lastBundle *tektonapi.Param
+		var lastName *tektonapi.Param
+		buildahTask := false
+		for i, param := range params {
+			if param.Name == "bundle" {
+				lastBundle = &t.TaskRef.Params[i]
+				} else if param.Name == "name" && param.Value.StringVal == "buildah" {
+					lastName = &t.TaskRef.Params[i]
+					buildahTask = true
+				}
+			}
+			if buildahTask {
+				t.Name = "build-container-amd64"
+				currentBuildahTaskRef = lastBundle.Value.StringVal
+				klog.Infof("Found current task ref %s", currentBuildahTaskRef)
+				//TODO: current use pinned sha?
+				lastBundle.Value = *tektonapi.NewStructuredValues("quay.io/redhat-appstudio-tekton-catalog/task-buildah-remote:0.1-ac185e95bbd7a25c1c4acf86995cbaf30eebedc4")
+				lastName.Value = *tektonapi.NewStructuredValues("buildah-remote")
+				t.Params = append(t.Params, tektonapi.Param{Name: "PLATFORM", Value: *tektonapi.NewStructuredValues("$(params.PLATFORM)")})
+				dockerPipelineObject.Spec.Params = append(dockerPipelineObject.PipelineSpec().Params, tektonapi.ParamSpec{Name: "PLATFORM", Default: tektonapi.NewStructuredValues(platformType)})
+				for i, result := range dockerPipelineObject.Spec.Results {
+					if result.Name == "JAVA_COMMUNITY_DEPENDENCIES" {
+						javaResult := &dockerPipelineObject.Spec.Results[i]
+						javaResult.Value = *tektonapi.NewStructuredValues("$(tasks.build-container-amd64.results.JAVA_COMMUNITY_DEPENDENCIES)")
+					}
+				}
+				dockerPipelineObject.Name = "multi-arch-pipeline"
+				break
+			}
+		}
+		newTaskRef := &tektonapi.TaskRef{
+			ResolverRef: tektonapi.ResolverRef{
+				Params:	[]tektonapi.Param{
+					{
+						Name: "name",
+						Value: *tektonapi.NewStructuredValues("build-image-manifest"),
+					},
+					{
+						Name: "bundle",
+						//?use the fixed sha
+						Value: *tektonapi.NewStructuredValues("quay.io/redhat-appstudio-tekton-catalog/task-build-image-manifest:0.1@sha256:e064b63b2311d23d6bf6538347cb4eb18c980d61883f48149bc9c728f76b276c"),
+					},
+					{
+						Name: "kind",
+						Value: *tektonapi.NewStructuredValues("task"),
+					},
+				},
+				Resolver: "bundles",
+			},
+		}
+
+		newTask := &tektonapi.PipelineTask{
+			Name: "build-container",
+			RunAfter: []string{"build-container-amd64"},
+			TaskRef: newTaskRef,
+			Params: []tektonapi.Param{
+				{
+					Name: "IMAGE",
+					Value: *tektonapi.NewStructuredValues("$(params.output-image)"),
+				},
+				{
+					Name: "COMMIT_SHA",
+					Value: *tektonapi.NewStructuredValues("$(tasks.clone-repository.results.commit)"),
+				},
+				{
+					Name: "IMAGES",
+					Value: tektonapi.ParamValue{
+						Type: tektonapi.ParamTypeArray,
+						ArrayVal: []string{
+							"$(tasks.build-container-amd64.results.IMAGE_URL)@$(tasks.build-container-amd64.results.IMAGE_DIGEST)",
+						},
+					},
+				},
+			},
+			When: tektonapi.WhenExpressions{{
+				Input: "$(tasks.init.results.build)",
+				Operator: selection.In,
+				Values:   []string{"true"},
+			}},
+		}
+
+		dockerPipelineObject.Spec.Tasks = append(dockerPipelineObject.PipelineSpec().Tasks, *newTask)
+		if newPipelineYaml, err = yaml.Marshal(dockerPipelineObject); err != nil {
+			return fmt.Errorf("error when marshalling a new pipeline to YAML: %v", err)
+		}
+
+		keychain := authn.NewMultiKeychain(authn.DefaultKeychain)
+		authOption := remoteimg.WithAuthFromKeychain(keychain)
+
+		if err = tekton.BuildAndPushTektonBundle(newPipelineYaml, newRemotePipeline, authOption); err != nil {
+			return fmt.Errorf("error when building/pushing a tekton pipeline bundle: %v", err)
+		}
+		klog.Infof("SETTING ENV VAR %s to value %s\n", constants.CUSTOM_MULTI_ARCH_PIPELINE_BUILD_BUNDLE_ENV, newRemotePipeline.String())
+		os.Setenv(constants.CUSTOM_MULTI_ARCH_PIPELINE_BUILD_BUNDLE_ENV, newRemotePipeline.String())
+
+	return nil
+}
+
 func SetupMultiPlatformTests() error {
 	klog.Infof("going to create new Tekton bundle remote-build for the purpose of testing multi-platform-controller PR")
 	var err error
@@ -825,9 +1018,8 @@ func GenerateTestSuiteFile(packageName string) error {
 	return nil
 }
 
-// Remove all webhooks older than 1 day from GitHub repo.
-// By default will delete webhooks from redhat-appstudio-qe
-func CleanGitHubWebHooks() error {
+// Remove all webhooks which with 1 day lifetime. By default will delete webooks from redhat-appstudio-qe
+func CleanWebHooks() error {
 	token := utils.GetEnv(constants.GITHUB_TOKEN_ENV, "")
 	if token == "" {
 		return fmt.Errorf("empty GITHUB_TOKEN env. Please provide a valid github token")
@@ -856,38 +1048,6 @@ func CleanGitHubWebHooks() error {
 	return nil
 }
 
-// Remove all webhooks older than 1 day from GitLab repo.
-func CleanGitLabWebHooks() error {
-	gcToken := utils.GetEnv(constants.GITLAB_TOKEN_ENV, "")
-	if gcToken == "" {
-		return fmt.Errorf("empty PAC_GITLAB_TOKEN env")
-	}
-	projectID := utils.GetEnv(constants.GITLAB_PROJECT_ID, "")
-	if projectID == "" {
-		return fmt.Errorf("empty PAC_PROJECT_ID env. Please provide a valid GitLab Project ID")
-	}
-	gitlabURL := utils.GetEnv(constants.GITLAB_URL_ENV, "https://gitlab.com/api/v4")
-	gc, err := gitlab.NewGitlabClient(gcToken, gitlabURL)
-	if err != nil {
-		return err
-	}
-	webhooks, _, err := gc.GetClient().Projects.ListProjectHooks(projectID, &gl.ListProjectHooksOptions{PerPage: 100})
-	if err != nil {
-		return fmt.Errorf("failed to list project hooks: %v", err)
-	}
-	// Delete webhooks that are older than 1 day
-	for _, webhook := range webhooks {
-		dayDuration, _ := time.ParseDuration("24h")
-		if time.Since(*webhook.CreatedAt) > dayDuration {
-			klog.Infof("removing webhookURL: %s", webhook.URL)
-			if _, err := gc.GetClient().Projects.DeleteProjectHook(projectID, webhook.ID); err != nil {
-				return fmt.Errorf("failed to delete webhook (URL: %s): %v", webhook.URL, err)
-			}
-		}
-	}
-	return nil
-}
-
 // Generate a Text Outline file from a Ginkgo Spec
 func GenerateTextOutlineFromGinkgoSpec(source string, destination string) error {
 
diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go
index 9c8cc48685..8c5f62a4a5 100644
--- a/pkg/constants/constants.go
+++ b/pkg/constants/constants.go
@@ -85,6 +85,9 @@ const (
 	// Managed workspace for release pipelines tests
 	RELEASE_MANAGED_WORKSPACE_ENV = "RELEASE_MANAGED_WORKSPACE"
 
+	// Bundle ref for overriding the default Java build bundle specified in BuildPipelineConfigConfigMapYamlURL
+	CUSTOM_MULTI_ARCH_PIPELINE_BUILD_BUNDLE_ENV string = "CUSTOM_MULTI_ARCH_PIPELINE_BUILD_BUNDLE"
+
 	// Bundle ref for overriding the default Java build bundle specified in BuildPipelineConfigConfigMapYamlURL
 	CUSTOM_JAVA_PIPELINE_BUILD_BUNDLE_ENV string = "CUSTOM_JAVA_PIPELINE_BUILD_BUNDLE"
 
diff --git a/tests/release/const.go b/tests/release/const.go
index ef49274cf7..71936558c3 100644
--- a/tests/release/const.go
+++ b/tests/release/const.go
@@ -36,6 +36,7 @@ const (
 	ComponentName                   string = "dc-metro-map"
 	GitSourceComponentUrl           string = "https://github.com/scoheb/dc-metro-map"
 	AdditionalComponentName         string = "simple-python"
+	MultiArchComponentUrl           string = "https://github.com/jinqi7/multi-platform-test-prod"
 	AdditionalGitSourceComponentUrl string = "https://github.com/devfile-samples/devfile-sample-python-basic"
 	ReleasedImagePushRepo           string = "quay.io/redhat-appstudio-qe/dcmetromap"
 	AdditionalReleasedImagePushRepo string = "quay.io/redhat-appstudio-qe/simplepython"
diff --git a/tests/release/pipelines/multi_arch_advisories.go b/tests/release/pipelines/multi_arch_advisories.go
new file mode 100644
index 0000000000..55027f1d05
--- /dev/null
+++ b/tests/release/pipelines/multi_arch_advisories.go
@@ -0,0 +1,289 @@
+package pipelines
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"os"
+	"regexp"
+	"time"
+
+	"github.com/devfile/library/v2/pkg/util"
+	ecp "github.com/enterprise-contract/enterprise-contract-controller/api/v1alpha1"
+	appservice "github.com/konflux-ci/application-api/api/v1alpha1"
+	"github.com/konflux-ci/e2e-tests/pkg/constants"
+	"github.com/konflux-ci/e2e-tests/pkg/framework"
+	"github.com/konflux-ci/e2e-tests/pkg/utils"
+	"github.com/konflux-ci/e2e-tests/pkg/utils/tekton"
+	releasecommon "github.com/konflux-ci/e2e-tests/tests/release"
+	releaseapi "github.com/konflux-ci/release-service/api/v1alpha1"
+	tektonutils "github.com/konflux-ci/release-service/tekton/utils"
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"knative.dev/pkg/apis"
+)
+
+const (
+	multiServiceAccountName = "release-service-account"
+	multiCatalogPathInRepo  = "pipelines/rh-advisories/rh-advisories.yaml"
+)
+
+var mcomponent *appservice.Component
+
+var _ = framework.ReleasePipelinesSuiteDescribe("e2e tests for rh-advisories pipeline", Label("release-pipelines", "multiarch-advisories"), func() {
+	defer GinkgoRecover()
+	var pyxisKeyDecoded, pyxisCertDecoded []byte
+
+	var devWorkspace = utils.GetEnv(constants.RELEASE_DEV_WORKSPACE_ENV, constants.DevReleaseTeam)
+	var managedWorkspace = utils.GetEnv(constants.RELEASE_MANAGED_WORKSPACE_ENV, constants.ManagedReleaseTeam)
+
+	var devNamespace = devWorkspace + "-tenant"
+	var managedNamespace = managedWorkspace + "-tenant"
+
+	var err error
+	var devFw *framework.Framework
+	var managedFw *framework.Framework
+	var multiApplicationName = "multi-app-" + util.GenerateRandomString(4)
+	var multiComponentName = "multi-comp-" + util.GenerateRandomString(4)
+	var multiReleasePlanName = "multi-rp-" + util.GenerateRandomString(4)
+	var multiReleasePlanAdmissionName = "multi-rpa-" + util.GenerateRandomString(4)
+	var multiEnterpriseContractPolicyName = "multi-policy-" + util.GenerateRandomString(4)
+
+	var snapshot *appservice.Snapshot
+	var releaseCR *releaseapi.Release
+	var releasePR, buildPR *tektonv1.PipelineRun
+
+	AfterEach(framework.ReportFailure(&devFw))
+
+	Describe("Multiarch-advisories happy path", Label("multiArchAdvisories"), func() {
+		BeforeAll(func() {
+			devFw = releasecommon.NewFramework(devWorkspace)
+			managedFw = releasecommon.NewFramework(managedWorkspace)
+			managedNamespace = managedFw.UserNamespace
+
+			keyPyxisStage := os.Getenv(constants.PYXIS_STAGE_KEY_ENV)
+			Expect(keyPyxisStage).ToNot(BeEmpty())
+
+			certPyxisStage := os.Getenv(constants.PYXIS_STAGE_CERT_ENV)
+			Expect(certPyxisStage).ToNot(BeEmpty())
+
+			// Creating k8s secret to access Pyxis stage based on base64 decoded of key and cert
+			pyxisKeyDecoded, err = base64.StdEncoding.DecodeString(string(keyPyxisStage))
+			Expect(err).ToNot(HaveOccurred())
+
+			pyxisCertDecoded, err = base64.StdEncoding.DecodeString(string(certPyxisStage))
+			Expect(err).ToNot(HaveOccurred())
+
+			secret := &corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "pyxis",
+					Namespace: managedNamespace,
+				},
+				Type: corev1.SecretTypeOpaque,
+				Data: map[string][]byte{
+					"cert": pyxisCertDecoded,
+					"key":  pyxisKeyDecoded,
+				},
+			}
+
+			// Delete the secret if it exists in case it is not correct
+			_ = managedFw.AsKubeAdmin.CommonController.DeleteSecret(managedNamespace, "pyxis")
+			_, err = managedFw.AsKubeAdmin.CommonController.CreateSecret(managedNamespace, secret)
+			Expect(err).ToNot(HaveOccurred())
+
+			err = managedFw.AsKubeAdmin.CommonController.LinkSecretToServiceAccount(managedNamespace, releasecommon.RedhatAppstudioUserSecret, constants.DefaultPipelineServiceAccount, true)
+			Expect(err).ToNot(HaveOccurred())
+
+			_, err = devFw.AsKubeDeveloper.HasController.CreateApplication(multiApplicationName, devNamespace)
+			Expect(err).NotTo(HaveOccurred())
+
+			createMULTIReleasePlan(multiReleasePlanName, *devFw, devNamespace, multiApplicationName, managedNamespace, "true")
+
+			customBuildahRemotePipeline := utils.GetEnv("CUSTOM_MULTI_ARCH_PIPELINE_BUILD_BUNDLE", "quay.io/redhat-appstudio-qe/test-images@sha256:f527c1a4e77559cc94ee32c9751fac36cf32883e6c2e28c0df0f4773d2c25f54")
+
+			buildPipelineAnnotation := map[string]string{
+				"build.appstudio.openshift.io/pipeline": fmt.Sprintf(`{"name":"multi-arch-pipeline", "bundle": "%s"}`, customBuildahRemotePipeline),
+			}
+
+			mcomponent = releasecommon.CreateComponent(*devFw, devNamespace, multiApplicationName, multiComponentName, releasecommon.MultiArchComponentUrl, "", ".", constants.DockerFilePath, buildPipelineAnnotation)
+
+			createMULTIReleasePlanAdmission(multiReleasePlanAdmissionName, *managedFw, devNamespace, managedNamespace, multiApplicationName, multiEnterpriseContractPolicyName, multiCatalogPathInRepo)
+
+			createMULTIEnterpriseContractPolicy(multiEnterpriseContractPolicyName, *managedFw, devNamespace, managedNamespace)
+		})
+
+		AfterAll(func() {
+			devFw = releasecommon.NewFramework(devWorkspace)
+			managedFw = releasecommon.NewFramework(managedWorkspace)
+			Expect(devFw.AsKubeDeveloper.HasController.DeleteApplication(multiApplicationName, devNamespace, false)).NotTo(HaveOccurred())
+			Expect(managedFw.AsKubeDeveloper.TektonController.DeleteEnterpriseContractPolicy(multiEnterpriseContractPolicyName, managedNamespace, false)).NotTo(HaveOccurred())
+			Expect(managedFw.AsKubeDeveloper.ReleaseController.DeleteReleasePlanAdmission(multiReleasePlanAdmissionName, managedNamespace, false)).NotTo(HaveOccurred())
+		})
+
+		var _ = Describe("Post-release verification", func() {
+			It("verifies that a build PipelineRun is created in dev namespace and succeeds", func() {
+				devFw = releasecommon.NewFramework(devWorkspace)
+				managedFw = releasecommon.NewFramework(managedWorkspace)
+				// Create a ticker that ticks every 3 minutes
+				ticker := time.NewTicker(3 * time.Minute)
+				// Schedule the stop of the ticker after 15 minutes
+				time.AfterFunc(15*time.Minute, func() {
+					ticker.Stop()
+					fmt.Println("Stopped executing every 3 minutes.")
+				})
+				// Run a goroutine to handle the ticker ticks
+				go func() {
+					for range ticker.C {
+						devFw = releasecommon.NewFramework(devWorkspace)
+						managedFw = releasecommon.NewFramework(managedWorkspace)
+					}
+				}()
+				Eventually(func() error {
+					buildPR, err = devFw.AsKubeDeveloper.HasController.GetComponentPipelineRun(mcomponent.Name, multiApplicationName, devNamespace, "")
+					if err != nil {
+						GinkgoWriter.Printf("Build PipelineRun has not been created yet for the component %s/%s\n", devNamespace, mcomponent.Name)
+						return err
+					}
+					GinkgoWriter.Printf("PipelineRun %s reason: %s\n", buildPR.Name, buildPR.GetStatusCondition().GetCondition(apis.ConditionSucceeded).GetReason())
+					if !buildPR.IsDone() {
+						return fmt.Errorf("build pipelinerun %s in namespace %s did not finish yet", buildPR.Name, buildPR.Namespace)
+					}
+					if buildPR.GetStatusCondition().GetCondition(apis.ConditionSucceeded).IsTrue() {
+						snapshot, err = devFw.AsKubeDeveloper.IntegrationController.GetSnapshot("", buildPR.Name, "", devNamespace)
+						if err != nil {
+							return err
+						}
+						return nil
+					} else {
+						return fmt.Errorf(tekton.GetFailedPipelineRunLogs(devFw.AsKubeDeveloper.HasController.KubeRest(), devFw.AsKubeDeveloper.HasController.KubeInterface(), buildPR))
+					}
+				}, releasecommon.BuildPipelineRunCompletionTimeout, releasecommon.DefaultInterval).Should(Succeed(), fmt.Sprintf("timed out when waiting for the build PipelineRun to be finished for the component %s/%s", devNamespace, mcomponent.Name))
+			})
+			It("verifies the multi release pipelinerun is running and succeeds", func() {
+				devFw = releasecommon.NewFramework(devWorkspace)
+				managedFw = releasecommon.NewFramework(managedWorkspace)
+
+				releaseCR, err = devFw.AsKubeDeveloper.ReleaseController.GetRelease("", snapshot.Name, devNamespace)
+				Expect(err).ShouldNot(HaveOccurred())
+
+				Expect(managedFw.AsKubeAdmin.ReleaseController.WaitForReleasePipelineToBeFinished(releaseCR, managedNamespace)).To(Succeed(), fmt.Sprintf("Error when waiting for a release pipelinerun for release %s/%s to finish", releaseCR.GetNamespace(), releaseCR.GetName()))
+			})
+
+			It("verifies release CR completed and set succeeded.", func() {
+				devFw = releasecommon.NewFramework(devWorkspace)
+				Eventually(func() error {
+					releaseCr, err := devFw.AsKubeDeveloper.ReleaseController.GetRelease("", snapshot.Name, devNamespace)
+					if err != nil {
+						return err
+					}
+					GinkgoWriter.Println("Release CR: ", releaseCr.Name)
+					if !releaseCr.IsReleased() {
+						return fmt.Errorf("release %s/%s is not marked as finished yet", releaseCR.GetNamespace(), releaseCR.GetName())
+					}
+					return nil
+				}, 10*time.Minute, releasecommon.DefaultInterval).Should(Succeed())
+			})
+
+			It("verifies if the repository URL is valid", func() {
+				managedFw = releasecommon.NewFramework(managedWorkspace)
+				releasePR, err = managedFw.AsKubeAdmin.ReleaseController.GetPipelineRunInNamespace(managedFw.UserNamespace, releaseCR.GetName(), releaseCR.GetNamespace())
+				Expect(err).NotTo(HaveOccurred())
+				advisoryURL := releasePR.Status.PipelineRunStatusFields.Results[0].Value.StringVal
+				pattern := `https?://[^/\s]+/[^/\s]+/[^/\s]+/+\-\/blob\/main\/data\/advisories\/[^\/]+\/[^\/]+\/[^\/]+\/advisory\.yaml`
+				re, err := regexp.Compile(pattern)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(re.MatchString(advisoryURL)).To(BeTrue(), fmt.Sprintf("Advisory_url %s is not valid", advisoryURL))
+			})
+		})
+	})
+})
+
+func createMULTIEnterpriseContractPolicy(multiECPName string, managedFw framework.Framework, devNamespace, managedNamespace string) {
+	defaultEcPolicySpec := ecp.EnterpriseContractPolicySpec{
+		Description: "Red Hat's enterprise requirements",
+		PublicKey:   "k8s://openshift-pipelines/public-key",
+		Sources: []ecp.Source{{
+			Name:   "Default",
+			Policy: []string{releasecommon.EcPolicyLibPath, releasecommon.EcPolicyReleasePath},
+			Data:   []string{releasecommon.EcPolicyDataBundle, releasecommon.EcPolicyDataPath},
+		}},
+		Configuration: &ecp.EnterpriseContractPolicyConfiguration{
+			Exclude: []string{"step_image_registries", "tasks.required_tasks_found:prefetch-dependencies"},
+			Include: []string{"@slsa3"},
+		},
+	}
+
+	_, err := managedFw.AsKubeDeveloper.TektonController.CreateEnterpriseContractPolicy(multiECPName, managedNamespace, defaultEcPolicySpec)
+	Expect(err).NotTo(HaveOccurred())
+
+}
+
+func createMULTIReleasePlan(multiReleasePlanName string, devFw framework.Framework, devNamespace, multiAppName, managedNamespace string, autoRelease string) {
+	var err error
+
+	data, err := json.Marshal(map[string]interface{}{
+		"releaseNotes": map[string]interface{}{
+			"description": "releaseNotes description",
+			"references":  []string{"https://server.com/ref1", "http://server2.com/ref2"},
+			"solution":    "some solution",
+			"synopsis":    "test synopsis",
+			"topic":       "test topic",
+		},
+	})
+	Expect(err).NotTo(HaveOccurred())
+
+	_, err = devFw.AsKubeDeveloper.ReleaseController.CreateReleasePlan(multiReleasePlanName, devNamespace, multiAppName,
+		managedNamespace, autoRelease, &runtime.RawExtension{
+			Raw: data,
+		})
+	Expect(err).NotTo(HaveOccurred())
+}
+
+func createMULTIReleasePlanAdmission(multiRPAName string, managedFw framework.Framework, devNamespace, managedNamespace, multiAppName, multiECPName, pathInRepoValue string) {
+	var err error
+
+	data, err := json.Marshal(map[string]interface{}{
+		"mapping": map[string]interface{}{
+			"components": []map[string]interface{}{
+				{
+					"name":       mcomponent.GetName(),
+					"repository": "quay.io/redhat-pending/rhtap----konflux-release-e2e",
+					"tags": []string{"latest", "latest-{{ timestamp }}", "testtag",
+						"testtag-{{ timestamp }}", "testtag2", "testtag2-{{ timestamp }}"},
+				},
+			},
+		},
+		"pyxis": map[string]interface{}{
+			"server": "stage",
+			"secret": "pyxis",
+		},
+		"releaseNotes": map[string]interface{}{
+			"cpe":             "cpe:/a:example.com",
+			"product_id":      "555",
+			"product_name":    "test product",
+			"product_stream":  "rhtas-tp1",
+			"product_version": "v1.0",
+			"type":            "RHSA",
+		},
+		"sign": map[string]interface{}{
+			"configMapName": "hacbs-signing-pipeline-config-redhatbeta2",
+		},
+	})
+	Expect(err).NotTo(HaveOccurred())
+
+	_, err = managedFw.AsKubeAdmin.ReleaseController.CreateReleasePlanAdmission(multiRPAName, managedNamespace, "", devNamespace, multiECPName, multiServiceAccountName, []string{multiAppName}, true, &tektonutils.PipelineRef{
+		Resolver: "git",
+		Params: []tektonutils.Param{
+			{Name: "url", Value: releasecommon.RelSvcCatalogURL},
+			{Name: "revision", Value: releasecommon.RelSvcCatalogRevision},
+			{Name: "pathInRepo", Value: pathInRepoValue},
+		},
+	}, &runtime.RawExtension{
+		Raw: data,
+	})
+	Expect(err).NotTo(HaveOccurred())
+}