From 842de49fd7a51f48b1cf014d26c2b263d633561e Mon Sep 17 00:00:00 2001 From: Jing Qi Date: Mon, 15 Jul 2024 17:44:09 +0800 Subject: [PATCH] feat(RELEASE-983): add e2e tests for push-binaries-to-dev-portal Signed-off-by: Jing Qi --- pkg/constants/constants.go | 7 + tests/release/const.go | 1 + .../pipelines/push_binaries_to_dev_portal.go | 295 ++++++++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 tests/release/pipelines/push_binaries_to_dev_portal.go diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 6e0f4559d6..9bc9bdd113 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -69,6 +69,13 @@ const ( // Cert auth for accessing Pyxis stage external registry PYXIS_STAGE_CERT_ENV string = "PYXIS_STAGE_CERT" + + // Key auth for accessing exodus non_prod + EXODUS_NON_PROD_KEY_ENV string = "EXODUS_NON_PROD_KEY" + + // Cert auth for accessing exodus non_prod + EXODUS_NON_PROD_CERT_ENV string = "EXODUS_NON_PROD_CERT" + // Offline/refresh token used for getting Keycloak token in order to authenticate against stage/prod cluster // More details: https://access.redhat.com/articles/3626371 OFFLINE_TOKEN_ENV = "OFFLINE_TOKEN" diff --git a/tests/release/const.go b/tests/release/const.go index ef49274cf7..d576ca99ce 100644 --- a/tests/release/const.go +++ b/tests/release/const.go @@ -17,6 +17,7 @@ const ( HacbsReleaseTestsTokenSecret string = "redhat-appstudio-registry-pull-secret" PublicSecretNameAuth string = "cosign-public-key" ReleasePipelineServiceAccountDefault string = "release-service-account" + CgwSecret string = "publish-to-cgw-secret" SourceReleasePlanName string = "source-releaseplan" SecondReleasePlanName string = "the-second-releaseplan" diff --git a/tests/release/pipelines/push_binaries_to_dev_portal.go b/tests/release/pipelines/push_binaries_to_dev_portal.go new file mode 100644 index 0000000000..aeee2e7719 --- /dev/null +++ b/tests/release/pipelines/push_binaries_to_dev_portal.go @@ -0,0 +1,295 @@ +package pipelines + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" + "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" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "knative.dev/pkg/apis" +) + +const ( + devportalServiceAccountName = "release-service-account" + devportalSourceGitURL = "https://github.com/redhat-appstudio-qe/devfile-sample-go-basic" + devportalRepoOwner = "redhat-appstudio-qe" + devportalRepo = "devfile-sample-go-basic" + devportalCatalogPathInRepo = "pipelines/push-binaries-to-dev-portal/push-binaries-to-dev-portal.yaml" +) + +var devportalComponent *appservice.Component +var _ = framework.ReleasePipelinesSuiteDescribe("e2e tests for push-binaries-to-dev-portal pipeline", Label("release-pipelines", "push-binaries-to-dev-portal"), func() { + defer GinkgoRecover() + + 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 devportalApplicationName = "devportal-app-" + util.GenerateRandomString(4) + var devportalComponentName = "devportal-comp-" + util.GenerateRandomString(4) + var devportalReleasePlanName = "devportal-rp-" + util.GenerateRandomString(4) + var devportalReleasePlanAdmissionName = "devportal-rpa-" + util.GenerateRandomString(4) + var devportalEnterpriseContractPolicyName = "devportal-policy-" + util.GenerateRandomString(4) + + var snapshot *appservice.Snapshot + var releaseCR *releaseapi.Release + var buildPR *tektonv1.PipelineRun + + AfterEach(framework.ReportFailure(&devFw)) + + Describe("Push-to-dev-portal happy path", Label("pushToDevPortal"), func() { + BeforeAll(func() { + devFw = releasecommon.NewFramework(devWorkspace) + managedFw = releasecommon.NewFramework(managedWorkspace) + + managedNamespace = managedFw.UserNamespace + + // Linking the build secret to the pipeline service account in dev namespace. + err = devFw.AsKubeAdmin.CommonController.LinkSecretToServiceAccount(devNamespace, releasecommon.HacbsReleaseTestsTokenSecret, constants.DefaultPipelineServiceAccount, true) + Expect(err).ToNot(HaveOccurred()) + + keyExodusNonProd := os.Getenv(constants.EXODUS_NON_PROD_KEY_ENV) + Expect(keyExodusNonProd).ToNot(BeEmpty()) + + certExodusNonProd := os.Getenv(constants.EXODUS_NON_PROD_CERT_ENV) + Expect(certExodusNonProd).ToNot(BeEmpty()) + + // Creating k8s secret to access Exodus Non product based on base64 decoded of key and cert + exodusKeyDecoded, err := base64.StdEncoding.DecodeString(string(keyExodusNonProd)) + Expect(err).ToNot(HaveOccurred()) + + exodusCertDecoded, err := base64.StdEncoding.DecodeString(string(certExodusNonProd)) + Expect(err).ToNot(HaveOccurred()) + + _, err = managedFw.AsKubeAdmin.CommonController.GetSecret(managedNamespace, releasecommon.RedhatAppstudioQESecret) + if errors.IsNotFound(err) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "exodus-stage-secret", + Namespace: managedNamespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "cert": exodusCertDecoded, + "key": exodusKeyDecoded, + }, + } + + _, err = managedFw.AsKubeAdmin.CommonController.CreateSecret(managedNamespace, secret) + Expect(err).ToNot(HaveOccurred()) + } + + err = managedFw.AsKubeAdmin.CommonController.LinkSecretToServiceAccount(managedNamespace, "exodus-stage-secret", constants.DefaultPipelineServiceAccount, true) + Expect(err).ToNot(HaveOccurred()) + + githubToken := utils.GetEnv(constants.GITHUB_TOKEN_ENV, "") + Expect(githubToken).ToNot(BeEmpty()) + _, err = managedFw.AsKubeAdmin.CommonController.GetSecret(managedNamespace, releasecommon.RedhatAppstudioQESecret) + if errors.IsNotFound(err) { + githubSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: releasecommon.RedhatAppstudioQESecret, + Namespace: managedNamespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "token": []byte(githubToken), + }, + } + _, err = managedFw.AsKubeAdmin.CommonController.CreateSecret(managedNamespace, githubSecret) + Expect(err).ToNot(HaveOccurred()) + } + Expect(err).ToNot(HaveOccurred()) + + cgwUser := utils.GetEnv("CGW_USERNAME", "") + cgwToken := utils.GetEnv("CGW_TOKEN", "") + _, err = managedFw.AsKubeAdmin.CommonController.GetSecret(managedNamespace, releasecommon.CgwSecret) + if errors.IsNotFound(err) { + githubSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: releasecommon.CgwSecret, + Namespace: managedNamespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "username": []byte(cgwUser), + "token": []byte(cgwToken), + }, + } + _, err = managedFw.AsKubeAdmin.CommonController.CreateSecret(managedNamespace, githubSecret) + Expect(err).ToNot(HaveOccurred()) + } + Expect(err).ToNot(HaveOccurred()) + + err = managedFw.AsKubeAdmin.CommonController.LinkSecretToServiceAccount(managedNamespace, releasecommon.RedhatAppstudioQESecret, constants.DefaultPipelineServiceAccount, true) + Expect(err).ToNot(HaveOccurred()) + + err = managedFw.AsKubeAdmin.CommonController.LinkSecretToServiceAccount(managedNamespace, releasecommon.CgwSecret, constants.DefaultPipelineServiceAccount, true) + Expect(err).ToNot(HaveOccurred()) + + _, err = devFw.AsKubeDeveloper.HasController.CreateApplication(devportalApplicationName, devNamespace) + Expect(err).NotTo(HaveOccurred()) + + _, err = devFw.AsKubeDeveloper.ReleaseController.CreateReleasePlan(devportalReleasePlanName, devNamespace, devportalApplicationName, managedNamespace, "true", nil, nil) + Expect(err).NotTo(HaveOccurred()) + + devportalComponent = releasecommon.CreateComponent(*devFw, devNamespace, devportalApplicationName, devportalComponentName, devportalSourceGitURL, "", ".", "Dockerfile", constants.DefaultDockerBuildPipelineBundle) + + createDevPortalReleasePlanAdmission(devportalReleasePlanAdmissionName, *managedFw, devNamespace, managedNamespace, devportalApplicationName, devportalEnterpriseContractPolicyName, devportalCatalogPathInRepo, "false", "", "", "", "") + Expect(err).NotTo(HaveOccurred()) + + createDevPortalEnterpriseContractPolicy(devportalEnterpriseContractPolicyName, *managedFw, devNamespace, managedNamespace) + }) + + AfterAll(func() { + devFw = releasecommon.NewFramework(devWorkspace) + managedFw = releasecommon.NewFramework(managedWorkspace) + Expect(devFw.AsKubeDeveloper.HasController.DeleteApplication(devportalApplicationName, devNamespace, false)).NotTo(HaveOccurred()) + Expect(managedFw.AsKubeDeveloper.TektonController.DeleteEnterpriseContractPolicy(devportalEnterpriseContractPolicyName, managedNamespace, false)).NotTo(HaveOccurred()) + Expect(managedFw.AsKubeDeveloper.ReleaseController.DeleteReleasePlanAdmission(devportalReleasePlanAdmissionName, 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(devportalComponent.Name, devportalApplicationName, devNamespace, "") + if err != nil { + GinkgoWriter.Printf("Build PipelineRun has not been created yet for the component %s/%s\n", devNamespace, devportalComponent.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, devportalComponent.Name)) + }) + It("verifies 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())) + + _, err = managedFw.AsKubeAdmin.ReleaseController.GetPipelineRunInNamespace(managedFw.UserNamespace, releaseCR.GetName(), releaseCR.GetNamespace()) + Expect(err).NotTo(HaveOccurred()) + }) + + 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()) + }) + }) + }) +}) + +func createDevPortalEnterpriseContractPolicy(devportalECPName 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(devportalECPName, managedNamespace, defaultEcPolicySpec) + Expect(err).NotTo(HaveOccurred()) + +} + +func createDevPortalReleasePlanAdmission(devportalRPAName string, managedFw framework.Framework, devNamespace, managedNamespace, devportalAppName, devportalECPName, pathInRepoValue, hotfix, issueId, preGA, productName, productVersion string) { + var err error + + data, err := json.Marshal(map[string]interface{}{ + "exodus": map[string]interface{}{ + "env": "cdn-qa", + }, + "contentGateway": map[string]interface{}{ + "productName": "Konflux test product", + "productCode": "KTestProduct", + "productVersionName": "KTestProduct 1", + "components":[]map[string]interface{}{{ + "name": devportalComponent.GetName(), + "description": "Red Hat OpenShift Local Sandbox Test", + "label": "Checksum File Sandbox Test", + }}, + }, + }) + Expect(err).NotTo(HaveOccurred()) + + _, err = managedFw.AsKubeAdmin.ReleaseController.CreateReleasePlanAdmission(devportalRPAName, managedNamespace, "", devNamespace, devportalECPName, devportalServiceAccountName, []string{devportalAppName}, 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()) +}