#292 CI/CD: Job refactoring
Merged 5 years ago by rayson. Opened 5 years ago by rayson.
rayson/waiverdb cicd-split-dev-job  into  master

@@ -1,2 +1,3 @@ 

+ # This job is deprecated. New jobs are waiverdb-premerge and waiverdb-postmerge

  NAME=waiverdb-dev

  PAGURE_DOC_REPO_NAME= # Temporarily disable doc push to workaround https://pagure.io/pagure/issue/3919. Remove this line when it is fixed.

@@ -1,1 +1,1 @@ 

- waiverdb-dev-template.yaml

+ waiverdb-build-template.yaml

@@ -1,4 +1,3 @@ 

  NAME=waiverdb-polling-for-master

  PAGURE_POLLING_SCHEDULE="H/5 * * * *"

  PAGURE_POLLED_BRANCH=master

- DEV_PIPELINE_BC_NAME=waiverdb-dev

@@ -1,4 +1,3 @@ 

  NAME=waiverdb-polling-for-prs

  PAGURE_POLLING_FOR_PR=true

  PAGURE_POLLING_SCHEDULE="H/5 * * * *"

- DEV_PIPELINE_BC_NAME=waiverdb-dev

@@ -0,0 +1,2 @@ 

+ NAME=waiverdb-postmerge

+ PAGURE_DOC_REPO_NAME= # Temporarily disable doc push to workaround https://pagure.io/pagure/issue/3919. Remove this line when it is fixed.

@@ -0,0 +1,1 @@ 

+ waiverdb-build-template.yaml

@@ -0,0 +1,1 @@ 

+ NAME=waiverdb-premerge

@@ -0,0 +1,1 @@ 

+ waiverdb-build-template.yaml

openshift/pipelines/templates/waiverdb-build-template.yaml openshift/pipelines/templates/waiverdb-dev-template.yaml
file renamed
+33 -13
@@ -1,6 +1,6 @@ 

- # Template to produce a new WaiverDB dev CI/CD pipeline in OpenShift.

+ # Template to produce a new WaiverDB build job in OpenShift.

  #

- # Dev pipeline is a part of the WaiverDB Pipeline, covers the following steps:

+ # WaiverDB build job is a part of the WaiverDB C3I Pipeline, covering the following steps:

  #

  # - Run Flake8 and Pylint checks

  # - Run unit tests
@@ -24,13 +24,13 @@ 

  apiVersion: v1

  kind: Template

  metadata:

-   name: waiverdb-dev-pipeline

+   name: waiverdb-build-pipeline

  parameters:

  - name: NAME

    displayName: Short unique identifier for the templated instances

    description: This field is used to deploy multiple pipelines to one OpenShift project from this template.

    required: true

-   value: waiverdb-dev

+   value: waiverdb-build

  - name: WAIVERDB_GIT_REPO

    displayName: WaiverDB Git repo URL

    description: Default WaiverDB Git repo URL in which to run dev tests against
@@ -43,7 +43,7 @@ 

    value: master

  - name: WAIVERDB_MAIN_BRANCH

    displayName: Name of the main branch.

-   description: If WAIVERDB_MAIN_BRANCH equals WAIVERDB_GIT_REF, publishing steps will be automatically performed.

+   description: If WAIVERDB_MAIN_BRANCH equals WAIVERDB_GIT_REF, this is a post-merge build, otherwise it's a pre-merge build.

    value: master

    required: true

  - name: JENKINS_AGENT_CLOUD_NAME
@@ -56,7 +56,7 @@ 

    value: docker-registry.engineering.redhat.com/factory2/waiverdb-jenkins-slave:latest

  - name: PAGURE_DOC_REPO_NAME

    displayName: namespace/project of Pagure doc repo for publishing docs

-   description: If not emptry and WAIVERDB_GIT_REF is master, docs will be published to the specified Pagure doc repo

+   description: If not emptry, docs will be published to the specified Pagure doc repo when this is a post-merge build

    required: false

    value: waiverdb

  - name: PAGURE_DOC_SECRET
@@ -64,10 +64,10 @@ 

    required: false

    value: pagure-doc-secret

  - name: WAIVERDB_DEV_IMAGE_DESTINATIONS

-   displayName: Comma seperated list of container repository/tag to which the built WaiverDB dev image will be pushed

+   displayName: Comma seperated list of container repositories (without tag) to which the built WaiverDB dev image will be pushed

    description: OpenShift registries must be prefixed with 'atomic:'

    required: false

-   value: "atomic:docker-registry.engineering.redhat.com/factory2/waiverdb:latest,quay.io/factory2/waiverdb:latest"

+   value: "quay.io/factory2/waiverdb,atomic:docker-registry.engineering.redhat.com/factory2/waiverdb"

  - name: CONTAINER_REGISTRY_CREDENTIALS

    displayName: Secret name of container registries used for pulling and pushing images

    value: factory2-pipeline-registry-credentials
@@ -102,8 +102,20 @@ 

    displayName: Whether to tag the pushed image as dev

    value: "true"

    required: true

+ - name: PAGURE_URL

+   displayName: Pagure URL

+   value: https://pagure.io

+ - name: PAGURE_REPO_NAME

+   value: waiverdb

+ - name: PAGURE_REPO_IS_FORK

+   value: 'false'

+ - name: PAGURE_API_KEY_SECRET_NAME

+   displayName: Name of Pagure API key secret for updating Pagure pull-request statuses

+   value: 'pagure-api-key'

+ - name: MAIL_ADDRESS

+   displayName: If set, build failure messages to this mail address.

  labels:

-   template: waiverdb-dev

+   template: waiverdb-build

  objects:

  - kind: "BuildConfig"

    apiVersion: "v1"
@@ -167,7 +179,7 @@ 

      source:

        git:

          uri: "${WAIVERDB_GIT_REPO}"

-         ref: "${WAIVERDB_GIT_REF}"

+         ref: "${WAIVERDB_MAIN_BRANCH}"

      strategy:

        type: JenkinsPipeline

        jenkinsPipelineStrategy:
@@ -208,6 +220,14 @@ 

            value: "${WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAME}"

          - name: "WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAMESPACE"

            value: "${WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAMESPACE}"

-         - name: BUILD_DISPLAY_RENAME_TO

-           value: ""

-         jenkinsfilePath: openshift/pipelines/templates/waiverdb-dev.Jenkinsfile

+         - name: PAGURE_REPO_NAME

+           value: "${PAGURE_REPO_NAME}"

+         - name: PAGURE_REPO_IS_FORK

+           value: "${PAGURE_REPO_IS_FORK}"

+         - name: PAGURE_URL

+           value: "${PAGURE_URL}"

+         - name: PAGURE_API_KEY_SECRET_NAME

+           value: "${PAGURE_API_KEY_SECRET_NAME}"

+         - name: MAIL_ADDRESS

+           value: "${MAIL_ADDRESS}"

+         jenkinsfilePath: openshift/pipelines/templates/waiverdb-build.Jenkinsfile

openshift/pipelines/templates/waiverdb-build.Jenkinsfile openshift/pipelines/templates/waiverdb-dev.Jenkinsfile
file renamed
+162 -58
@@ -1,3 +1,6 @@ 

+ library identifier: 'c3i@master', changelog: false,

+   retriever: modernSCM([$class: 'GitSCMSource', remote: 'https://pagure.io/c3i-library.git'])

+ import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;

  pipeline {

    agent {

      kubernetes {
@@ -11,7 +14,7 @@ 

        metadata:

          labels:

            app: "jenkins-${env.JOB_BASE_NAME}"

-           factory2-pipeline-kind: "waiverdb-dev-pipeline"

+           factory2-pipeline-kind: "waiverdb-build-pipeline"

            factory2-pipeline-build-number: "${env.BUILD_NUMBER}"

        spec:

          containers:
@@ -75,25 +78,72 @@ 

    environment {

      PIPELINE_NAMESPACE = readFile('/run/secrets/kubernetes.io/serviceaccount/namespace').trim()

      PIPELINE_USERNAME = sh(returnStdout: true, script: 'id -un').trim()

+     PAGURE_API = "${params.PAGURE_URL}/api/0"

+     PAGURE_REPO_IS_FORK = "${params.PAGURE_REPO_IS_FORK}"

+     PAGURE_REPO_HOME = "${env.PAGURE_URL}${env.PAGURE_REPO_IS_FORK == 'true' ? '/fork' : ''}/${params.PAGURE_REPO_NAME}"

    }

    stages {

      stage('Prepare') {

        steps {

          script {

-           if (params.BUILD_DISPLAY_RENAME_TO) {

-             currentBuild.displayName = params.BUILD_DISPLAY_RENAME_TO

-           }

-           def scmVars = checkout([$class: 'GitSCM',

+           // check out specified branch/commit

+           /*def scmVars =*/ checkout([$class: 'GitSCM',

              branches: [[name: params.WAIVERDB_GIT_REF]],

              userRemoteConfigs: [[url: params.WAIVERDB_GIT_REPO, refspec: '+refs/heads/*:refs/remotes/origin/* +refs/pull/*/head:refs/remotes/origin/pull/*/head']],

            ])

-           env.WAIVERDB_GIT_COMMIT = scmVars.GIT_COMMIT

+ 

+           // get current commit ID

+           // FIXME: Due to a bug discribed in https://issues.jenkins-ci.org/browse/JENKINS-45489,

+           // the return value of checkout() is unreliable.

+           // Not working: env.WAIVERDB_GIT_COMMIT = scmVars.GIT_COMMIT

+           env.WAIVERDB_GIT_COMMIT = sh(returnStdout: true, script: 'git rev-parse HEAD').trim()

+           echo "Build ${params.WAIVERDB_GIT_REF}, commit=${env.WAIVERDB_GIT_COMMIT}"

+ 

+           // Is the current branch a pull-request? If no, env.PR_NO will be empty.

+           env.PR_NO = getPrNo(params.WAIVERDB_GIT_REF)

+ 

            // Generate a version-release number for the target Git commit

            def versions = sh(returnStdout: true, script: 'source ./version.sh && echo -en "$WAIVERDB_VERSION\n$WAIVERDB_CONTAINER_VERSION"').split('\n')

            env.WAIVERDB_VERSION = versions[0]

            env.WAIVERDB_CONTAINER_VERSION = versions[1]

            env.TEMP_TAG = env.WAIVERDB_CONTAINER_VERSION + '-jenkins-' + currentBuild.id

          }

+       }

+     }

+     stage('Update Build Info') {

+       when {

+         expression {

+           return params.PAGURE_URL && params.PAGURE_REPO_NAME

+         }

+       }

+       steps {

+         script {

+           // Set friendly display name and description

+           if (env.PR_NO) { // is pull-request

+             env.PR_URL = "${env.PAGURE_REPO_HOME}/pull-request/${env.PR_NO}"

+             echo "Building PR #${env.PR_NO}: ${env.PR_URL}"

+             // NOTE: Old versions of OpenShift Client Jenkins plugin are buggy to handle arguments

+             // with special bash characters (like whitespaces, #, etc).

+             // https://bugzilla.redhat.com/show_bug.cgi?id=1625518

+             currentBuild.displayName = "PR#${env.PR_NO}"

+             // To enable HTML syntax in build description, go to `Jenkins/Global Security/Markup Formatter` and select 'Safe HTML'.

+             def pagureLink = """<a href="${env.PR_URL}">${currentBuild.displayName}</a>"""

+             try {

+               def prInfo = withPagure {

+                 it.getPR(env.PR_NO)

+               }

+               pagureLink = """<a href="${env.PR_URL}">PR#${env.PR_NO}: ${escapeHtml(prInfo.title)}</a>"""

+               // set PR status to Pending

+               setBuildStatusOnPagurePR(null, 'Pending')

+             } catch (Exception e) {

+               echo "Error using pagure API: ${e}"

+             }

+             currentBuild.description = pagureLink

+           } else {

+             currentBuild.displayName = "${env.WAIVERDB_GIT_REF}: ${env.WAIVERDB_GIT_COMMIT.substring(0, 7)}"

+             currentBuild.description = """<a href="${env.PAGURE_REPO_HOME}/c/${env.WAIVERDB_GIT_COMMIT}">${currentBuild.displayName}</a>"""

+           }

+         }

          sh 'cp conf/settings.py.example conf/settings.py'

        }

      }
@@ -145,7 +195,7 @@ 

              stage('Publish Docs') {

                when {

                  expression {

-                   return "${params.PAGURE_DOC_REPO_NAME}" && (params.WAIVERDB_GIT_REF == "${params.WAIVERDB_MAIN_BRANCH}" || env.FORCE_PUBLISH_DOCS == "true")

+                   return "${params.PAGURE_DOC_REPO_NAME}" && (params.WAIVERDB_GIT_REF == params.WAIVERDB_MAIN_BRANCH || env.FORCE_PUBLISH_DOCS == "true")

                  }

                }

                steps {
@@ -207,7 +257,7 @@ 

      }

      stage('Build container') {

        environment {

-         BUILDCONFIG_INSTANCE_ID = "waiverdb-container-build-${currentBuild.id}"

+         BUILDCONFIG_INSTANCE_ID = "waiverdb-temp-${currentBuild.id}-${UUID.randomUUID().toString().substring(0,7)}"

        }

        steps {

          script {
@@ -220,7 +270,9 @@ 

              def processed = openshift.process(template,

                "-p", "NAME=${env.BUILDCONFIG_INSTANCE_ID}",

                '-p', "WAIVERDB_GIT_REPO=${params.WAIVERDB_GIT_REPO}",

-               '-p', "WAIVERDB_GIT_REF=${params.WAIVERDB_GIT_REF == params.WAIVERDB_MAIN_BRANCH ? env.WAIVERDB_GIT_COMMIT : params.WAIVERDB_GIT_REF}",

+               // A pull-request branch, like pull/123/head, cannot be built with commit ID

+               // because refspec cannot be customized in an OpenShift build .

+               '-p', "WAIVERDB_GIT_REF=${env.PR_NO ? params.WAIVERDB_GIT_REF : env.WAIVERDB_GIT_COMMIT}",

                '-p', "WAIVERDB_IMAGE_TAG=${env.TEMP_TAG}",

                '-p', "WAIVERDB_VERSION=${env.WAIVERDB_VERSION}",

                '-p', "WAIVERDB_IMAGESTREAM_NAME=${params.WAIVERDB_IMAGESTREAM_NAME}",
@@ -230,35 +282,9 @@ 

              def bc = created.narrow('bc')

              echo 'Starting a container build from the created BuildConfig...'

              buildSelector = bc.startBuild()

-             // `buildSelector.logs()` can be dumb when the OpenShift Build is not started.

-             // Let's wait for it to be started or completed (failed).

-             echo 'Waiting for the container build to be started...'

-             timeout(5) { // 5 min

-               buildSelector.watch {

-                 return !(it.object().status.phase in ["New", "Pending", "Unknown"])

-               }

-             }

-             echo 'Following container build logs...'

-             // This function sometimes hangs infinitely.

-             // Not sure it is a problem of OpenShift Jenkins Client plugin

-             // or OpenShift.

-             // FIXME: logs() step may fail with unknown reasons.

-             timeout(time: 15, activity: false) {

-               buildSelector.logs('-f')

-             }

-             // Ensure the build is stopped

-             echo 'Waiting for the container build to be fully stopped...'

-             timeout(5) { // 5 min

-               buildSelector.watch {

-                 return it.object().status.phase != "Running"

-               }

-             }

-             // Assert build result

+             c3i.wait(buildSelector.name())

+             echo 'Container build succeeds.'

              def ocpBuild = buildSelector.object()

-             if (ocpBuild.status.phase != "Complete") {

-               error("Failed to build container image for ${env.TEMP_TAG}, .status.phase=${ocpBuild.status.phase}.")

-             }

-             echo 'Container build is complete.'

              env.RESULTING_IMAGE_REF = ocpBuild.status.outputDockerImageReference

              env.RESULTING_IMAGE_DIGEST = ocpBuild.status.output.to.imageDigest

              def imagestream= created.narrow('is').object()
@@ -268,6 +294,9 @@ 

          }

        }

        post {

+         failure {

+           echo "Failed to build container image ${env.TEMP_TAG}."

+         }

          cleanup {

            script {

              openshift.withCluster() {
@@ -289,31 +318,22 @@ 

                def testBcSelector = openshift.selector('bc', params.WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAME)

                echo 'Starting a functional test for the built container image...'

                def buildSelector = testBcSelector.startBuild(

-                   '-e', "IMAGE=${env.RESULTING_IMAGE_REPO}:${env.RESULTING_TAG}",

                    '-e', "WAIVERDB_GIT_REPO=${params.WAIVERDB_GIT_REPO}",

-                   '-e', "WAIVERDB_GIT_REF=${params.WAIVERDB_GIT_REF == params.WAIVERDB_MAIN_BRANCH ? env.WAIVERDB_GIT_COMMIT : params.WAIVERDB_GIT_REF}",

+                   '-e', "IMAGE=${env.RESULTING_IMAGE_REPO}:${env.RESULTING_TAG}",

+                   '-e', "WAIVERDB_GIT_REF=${env.PR_NO ? env.WAIVERDB_GIT_REF : env.WAIVERDB_GIT_COMMIT}",

                    '-e', "IMAGE_IS_SCRATCH=${params.WAIVERDB_GIT_REF != params.WAIVERDB_MAIN_BRANCH}",

                  )

-               echo 'Waiting for the integration test result...'

-               timeout(time: 20) { // 20 min

-                 buildSelector.watch {

-                   return !(it.object().status.phase in ["New", "Pending", "Unknown"])

-                 }

-                 buildSelector.logs('-f')

-                 buildSelector.watch {

-                   return it.object().status.phase != "Running"

-                 }

-               }

-               // Assert build result

-               def ocpBuild = buildSelector.object()

-               if (ocpBuild.status.phase != "Complete") {

-                 error("Functional test failed for image ${env.TEMP_TAG}, .status.phase=${ocpBuild.status.phase}.")

-               }

-               echo 'Functional test is PASSED.'

+               c3i.wait(buildSelector.name())

+               echo "Functional test passed."

              }

            }

          }

        }

+       post {

+         failure {

+           echo "Functional test failed."

+         }

+       }

      }

      stage('Push container') {

        when {
@@ -348,10 +368,10 @@ 

              // push to registries

              def pushTasks = destinations.collectEntries {

                ["Pushing ${it}" : {

-                 def dest = it

+                 def dest = "${it}:${params.WAIVERDB_DEV_IMAGE_TAG ?: 'latest'}"

                  // Only docker and atomic registries are allowed

-                 if (!it.startsWith('atomic:') && !it.startsWith('docker://')) {

-                   dest = 'docker://' + it

+                 if (!dest.startsWith('atomic:') && !dest.startsWith('docker://')) {

+                   dest = 'docker://' + dest

                  }

                  echo "Pushing container to ${dest}..."

                  withEnv(["DEST_IMAGE_REF=${dest}"]) {
@@ -404,5 +424,89 @@ 

          }

        }

      }

+     success {

+       script {

+         // updating Pagure PR flag and make a comment

+         if (env.PR_NO && params.PAGURE_API_KEY_SECRET_NAME) {

+           try {

+             setBuildStatusOnPagurePR(100, 'Build passed.')

+             echo "Updated PR #${env.PR_NO} status to PASS."

+           } catch (e) {

+             echo "Error updating PR #${env.PR_NO} status to PASS: ${e}"

+           }

+         }

+       }

+     }

+     failure {

+       script {

+         // updating Pagure PR flag

+         if (env.PR_NO && params.PAGURE_API_KEY_SECRET_NAME) {

+           try {

+             setBuildStatusOnPagurePR(0, 'Build failed.')

+             echo "Updated PR #${env.PR_NO} status to FAILURE."

+           } catch (e) {

+             echo "Error updating PR #${env.PR_NO} status to FAILURE: ${e}"

+           }

+           try {

+             commentOnPR("""

+             Build ${env.WAIVERDB_GIT_COMMIT} [FAILED](${env.BUILD_URL})!

+             Rebase or make new commits to rebuild.

+             """.stripIndent())

+             echo "Comment made."

+           } catch (e) {

+             echo "Error making a comment on PR #${env.PR_NO}: ${e}"

+           }

+         }

+         // sending email

+         if (params.MAIL_ADDRESS){

+           try {

+             sendBuildStatusEmail('failed')

+           } catch (e) {

+             echo "Error sending email: ${e}"

+           }

+         }

+       }

+     }

+   }

+ }

+ @NonCPS

+ def getPrNo(branch) {

+   def prMatch = branch =~ /^(?:.+\/)?pull\/(\d+)\/head$/

+   return prMatch ? prMatch[0][1] : ''

+ }

+ def withPagure(args=[:], cl) {

+   args.apiUrl = env.PAGURE_API

+   args.repo = env.PAGURE_REPO_NAME

+   args.isFork = env.PAGURE_REPO_IS_FORK == 'true'

+   def pagureClient = pagure.client(args)

+   return cl(pagureClient)

+ }

+ def withPagureCreds(args=[:], cl) {

+   def pagureClient = null

+   withCredentials([string(credentialsId: "${env.PIPELINE_NAMESPACE}-${env.PAGURE_API_KEY_SECRET_NAME}", variable: 'TOKEN')]) {

+     args.token = env.TOKEN

+     pagureClient = withPagure(args, cl)

+   }

+   return pagureClient

+ }

+ def setBuildStatusOnPagurePR(percent, String comment) {

+   withPagureCreds {

+     it.updatePRStatus(username: 'c3i-jenkins', uid: 'ci-pre-merge',

+       url: env.BUILD_URL, percent: percent, comment: comment, pr: env.PR_NO)

+   }

+ }

+ def commentOnPR(String comment) {

+   withPagureCreds {

+     it.commentOnPR(comment: comment, pr: env.PR_NO)

+   }

+ }

+ def sendBuildStatusEmail(String status) {

+   def recipient = params.MAIL_ADDRESS

+   def subject = "Jenkins job ${env.JOB_NAME} #${env.BUILD_NUMBER} ${status}."

+   def body = "Build URL: ${env.DEV_BUILD_URL}"

+   if (env.PR_NO) {

+     subject = "Jenkins job ${env.JOB_NAME}, PR #${env.PR_NO} ${status}."

+     body += "\nPull Request: ${env.PR_URL}"

    }

+   emailext to: recipient, subject: subject, body: body

  }

@@ -26,6 +26,9 @@ 

    displayName: set to 'true' to poll for PRs, or 'false' for the master branch

    required: true

    value: "false"

+ - name: PAGURE_URL

+   displayName: Pagure URL

+   value: "https://pagure.io"

  - name: PAGURE_POLLING_SCHEDULE

    displayName: Schedule of polling

    description: using cron-style syntax
@@ -35,25 +38,18 @@ 

    displayName: Name of polled branch

    required: true

    value: "master"

- - name: DEV_PIPELINE_BC_NAME

-   displayName: Name of BuildConfig for starting dev pipeline builds

+ - name: PREMERGE_JOB_NAME

+   displayName: Downstream pre-merge job name

    required: true

-   value: waiverdb-dev

- - name: DEV_PIPELINE_BC_NAMESPACE

-   displayName: Namespace of BuildConfig for starting dev pipeline builds

-   required: false

- - name: PAGURE_API_KEY_SECRET_NAME

-   displayName: Name of Secret for updating Pagure pull-requests status

-   value: 'pagure-api-key'

-   required: false

+   value: waiverdb-premerge

+ - name: POSTMERGE_JOB_NAME

+   displayName: Downstream post-merge job name

+   required: true

+   value: waiverdb-postmerge

  - name: PIPELINE_UPDATE_JOBS_DIR

    displayName: location of pipeline job definitions for auto update

    value: jobs

    required: false

- - name: MAIL_ENABLED

-   displayName: Whether to send an email

-   value: 'true'

-   required: true

  - name: JENKINS_AGENT_IMAGE

    displayName: Container image for Jenkins slave pods

    required: true
@@ -129,12 +125,13 @@ 

              }

              environment {

                PIPELINE_NAMESPACE = readFile('/run/secrets/kubernetes.io/serviceaccount/namespace').trim()

-               PAGURE_URL = 'https://pagure.io'

-               PAGURE_API = "${env.PAGURE_URL}/api/0"

+               PAGURE_URL = "${PAGURE_URL}"

                PAGURE_REPO_IS_FORK = "${PAGURE_REPO_IS_FORK}"

                PAGURE_POLLING_FOR_PR = "${PAGURE_POLLING_FOR_PR}"

                PAGURE_REPO_HOME = "${env.PAGURE_URL}${env.PAGURE_REPO_IS_FORK == 'true' ? '/fork' : ''}/${PAGURE_REPO_NAME}"

                GIT_URL = "${env.PAGURE_URL}/${env.PAGURE_REPO_IS_FORK == 'true' ? 'forks/' : ''}${PAGURE_REPO_NAME}.git"

+               PREMERGE_JOB_NAME = "${PREMERGE_JOB_NAME}"

+               POSTMERGE_JOB_NAME = "${POSTMERGE_JOB_NAME}"

              }

              triggers { pollSCM("${PAGURE_POLLING_SCHEDULE}") }

              stages {
@@ -155,35 +152,24 @@ 

                        ],

                        extensions: [[$class: 'CleanBeforeCheckout']],

                      ])

-                     env.GIT_COMMIT = scmVars.GIT_COMMIT

-                     env.GIT_AUTHOR_EMAIL = scmVars.GIT_AUTHOR_EMAIL

+                     env.WAIVERDB_GIT_COMMIT = scmVars.GIT_COMMIT

                      // setting build display name

                      def prefix = 'origin/'

                      def branch = scmVars.GIT_BRANCH.startsWith(prefix) ? scmVars.GIT_BRANCH.substring(prefix.size())

                        : scmVars.GIT_BRANCH // origin/pull/1234/head -> pull/1234/head, origin/master -> master

-                     env.GIT_BRANCH = branch

-                     echo "Build on branch=${env.GIT_BRANCH}, commit=${env.GIT_COMMIT}"

-                     if (env.PAGURE_POLLING_FOR_PR == 'false' && branch == "${PAGURE_POLLED_BRANCH}") {

-                       echo 'Building master'

-                       currentBuild.displayName = "${PAGURE_POLLED_BRANCH}"

+                     env.WAIVERDB_GIT_BRANCH = branch

+                     echo "Build on branch=${env.WAIVERDB_GIT_BRANCH}, commit=${env.WAIVERDB_GIT_COMMIT}"

+                     if (env.PAGURE_POLLING_FOR_PR == 'false') {

+                       currentBuild.displayName = "${env.WAIVERDB_GIT_BRANCH}: ${env.WAIVERDB_GIT_COMMIT.substring(0, 7)}"

+                       currentBuild.description = """<a href="${env.PAGURE_REPO_HOME}/c/${env.WAIVERDB_GIT_COMMIT}">${currentBuild.displayName}</a>"""

                      }

                      else if (env.PAGURE_POLLING_FOR_PR == 'true' && branch ==~ /^pull\/[0-9]+\/head$/) {

                        env.PR_NO = branch.split('/')[1]

                        env.PR_URL = "${env.PAGURE_REPO_HOME}/pull-request/${env.PR_NO}"

                        // To HTML syntax in build description, go to `Jenkins/Global Security/Markup Formatter` and select 'Safe HTML'.

-                       def pagureLink = """<a href="${env.PR_URL}">PR-${env.PR_NO}</a>"""

-                       try {

-                         def prInfo = getPagurePRInfo()

-                         pagureLink = """<a href="${env.PR_URL}">${prInfo.title}</a>"""

-                       } catch (Exception e) {

-                         echo "Error using pagure API: ${e}"

-                         // ignoring this...

-                       }

+                       def pagureLink = """<a href="${env.PR_URL}">PR#${env.PR_NO}</a>"""

                        echo "Building PR #${env.PR_NO}: ${env.PR_URL}"

-                       // FIXME: We are going to pass the display name to the triggered dev pipeline build,

-                       // however OpenShift Pipeline DSL is buggy to handle arguments with special bash characters (like whitespaces, #, etc).

-                       // https://bugzilla.redhat.com/show_bug.cgi?id=1625518

-                       currentBuild.displayName = "PR-${env.PR_NO}"

+                       currentBuild.displayName = "PR#${env.PR_NO}"

                        currentBuild.description = pagureLink

                      } else { // This shouldn't happen.

                        error("Build is aborted due to unexpected polling trigger actions.")
@@ -194,12 +180,12 @@ 

                stage('Update pipeline jobs') {

                  when {

                    expression {

-                     return "${PIPELINE_UPDATE_JOBS_DIR}" && env.PAGURE_POLLING_FOR_PR == 'false' && env.GIT_BRANCH == "${PAGURE_POLLED_BRANCH}"

+                     return "${PIPELINE_UPDATE_JOBS_DIR}" && env.PAGURE_POLLING_FOR_PR == 'false' && env.WAIVERDB_GIT_BRANCH == "${PAGURE_POLLED_BRANCH}"

                    }

                  }

                  steps {

                    checkout([$class: 'GitSCM',

-                     branches: [[name: env.GIT_BRANCH]],

+                     branches: [[name: env.WAIVERDB_GIT_COMMIT]],

                      userRemoteConfigs: [

                        [

                          name: 'origin',
@@ -218,183 +204,26 @@ 

                    }

                  }

                }

-               stage('Run Dev Build') {

+               stage('Build') {

                  steps {

                    script {

                      openshift.withCluster() {

-                       openshift.withProject("${DEV_PIPELINE_BC_NAMESPACE}") {

-                         def bcSelector = openshift.selector('bc', "${DEV_PIPELINE_BC_NAME}")

-                         echo 'Starting a dev pipeline build...'

-                         def devBuild = bcSelector.startBuild(

-                           '-e', "WAIVERDB_GIT_REPO=${env.GIT_URL}",

-                           '-e', "WAIVERDB_GIT_REF=${env.GIT_BRANCH}",

-                           '-e', "WAIVERDB_MAIN_BRANCH=${PAGURE_POLLED_BRANCH}",

-                           '-e', "BUILD_DISPLAY_RENAME_TO=${currentBuild.displayName}",

-                         )

-                         devBuild.watch {

-                           return !(it.object().status.phase in ["New", "Pending"])

-                         }

-                         def devBuildInfo = devBuild.object()

-                         env.DEV_BUILD_NAME = devBuildInfo.metadata.name

-                         env.DEV_BUILD_URL = devBuildInfo.metadata.annotations['openshift.io/jenkins-build-uri'] ?: env.BUILD_URL

-                         if (env.PAGURE_POLLING_FOR_PR == 'true') {

-                           // setting PR status to Pending

-                           setBuildStatusOnPagurePR(null, 'Pending')

-                         }

-                         echo "Waiting for dev build ${devBuildInfo.metadata.name}(${env.DEV_BUILD_URL}) to complete..."

-                         devBuild.watch {

-                           return it.object().status.phase != "Running"

-                         }

-                         devBuildInfo = devBuild.object()

-                         echo "Dev build ${devBuildInfo.metadata.name}(${env.DEV_BUILD_URL}) finished with status ${devBuildInfo.status.phase}."

-                         if (devBuildInfo.status.phase != "Complete") {

-                           error("Dev build ${devBuildInfo.metadata.name}(${env.DEV_BUILD_URL}) failed.")

-                         }

-                       }

-                     }

-                   }

-                 }

-               }

-             }

-             post {

-               success {

-                 script {

-                   // updating Pagure PR flag and make a comment

-                   if (env.PAGURE_POLLING_FOR_PR == 'true' && "${PAGURE_API_KEY_SECRET_NAME}") {

-                     try {

-                       setBuildStatusOnPagurePR(100, 'Build passed.')

-                       echo "Updated PR #${env.PR_NO} status to PASS."

-                     } catch (e) {

-                       echo "Error updating PR #${env.PR_NO} status to PASS: ${e}"

-                     }

-                   }

-                   // sending email

-                   if ("${MAIL_ENABLED}" == 'true' && env.PAGURE_POLLING_FOR_PR == 'true'){

-                     try {

-                       sendBuildStatusEmail(true)

-                     } catch (e) {

-                       echo "Error sending email: ${e}"

-                     }

-                   }

-                 }

-               }

-               failure {

-                 script {

-                   // updating Pagure PR flag

-                   if (env.PAGURE_POLLING_FOR_PR == 'true' && "${PAGURE_API_KEY_SECRET_NAME}") {

-                     try {

-                       setBuildStatusOnPagurePR(0, 'Build failed.')

-                       echo "Updated PR #${env.PR_NO} status to FAILURE."

-                     } catch (e) {

-                       echo "Error updating PR #${env.PR_NO} status to FAILURE: ${e}"

-                     }

-                     if (env.DEV_BUILD_NAME) {

-                       try {

-                         commentOnPR("""

-                         Build ${env.GIT_COMMIT} [FAILED](${env.DEV_BUILD_URL})!

-                         Rebase or make new commits to rebuild.

-                         """.stripIndent())

-                         echo "Comment made."

-                       } catch (e) {

-                         echo "Error making a comment on PR #${env.PR_NO}: ${e}"

+                       def bcSelector = openshift.selector('bc',

+                         env.PAGURE_POLLING_FOR_PR == 'true' ? env.PREMERGE_JOB_NAME : env.POSTMERGE_JOB_NAME)

+                       echo 'Starting a WaiverDB build run...'

+                       def devBuild = bcSelector.startBuild(

+                         '-e', "WAIVERDB_GIT_REF=${env.WAIVERDB_GIT_BRANCH}",

+                       )

+                       devBuild.watch {

+                         return !(it.object().status.phase in ["New", "Pending"])

                        }

-                     }

-                   }

-                   // sending email

-                   if ("${MAIL_ENABLED}" == 'true'){

-                     try {

-                       sendBuildStatusEmail(false)

-                     } catch (e) {

-                       echo "Error sending email: ${e}"

+                       def devBuildInfo = devBuild.object()

+                       def downstreamBuildName = devBuildInfo.metadata.name

+                       def downstreamBuildUrl = devBuildInfo.metadata.annotations['openshift.io/jenkins-build-uri']

+                       echo "Downstream build ${downstreamBuildName}(${downstreamBuildUrl}) started."

                      }

                    }

                  }

                }

              }

            }

-           import java.net.URLEncoder

-           class PagureClient {

-             String pagureApiUrl

-             String token

-             def steps

-             def callApi(String httpMode, String apiPath, Map payload = null) {

-               def headers = []

-               if (token) {

-                 headers << [name: 'Authorization', value: "token ${token}", maskValue: true]

-               }

-               def payloadItems = []

-               if (payload) {

-                 for (it in payload) {

-                   if (it == null || it.value == null) {

-                     continue

-                   }

-                   payloadItems << (URLEncoder.encode(it.key.toString(), 'utf-8') +

-                     '=' +  URLEncoder.encode(it.value.toString(), 'utf-8'))

-                 }

-               }

-               return steps.httpRequest(

-                 httpMode: httpMode,

-                 url: "${pagureApiUrl}/${apiPath}",

-                 acceptType: 'APPLICATION_JSON',

-                 contentType: 'APPLICATION_FORM',

-                 requestBody: payloadItems.join('&'),

-                 customHeaders: headers,

-               )

-             }

-             def getPR(Map args) {

-               def apiPath = "${args.fork?'fork/':''}${args.repo}/pull-request/${args.pr}"

-               def response = callApi('GET', apiPath)

-               return steps.readJSON(text: response.content)

-             }

-             def updatePRStatus(Map args) {

-               def apiPath = "${args.fork?'fork/':''}${args.repo}/pull-request/${args.pr}/flag"

-               def response = callApi('POST', apiPath, [

-                 'username': args.username,

-                 'uid': args.uid,

-                 'percent': args.percent,

-                 'comment': args.comment,

-                 'url': args.url,

-               ])

-               return steps.readJSON(text: response.content)

-             }

-             def commentOnPR(Map args) {

-               def apiPath = "${args.fork?'fork/':''}${args.repo}/pull-request/${args.pr}/comment"

-               def response = callApi('POST', apiPath, [

-                 'comment': args.comment,

-               ])

-               return steps.readJSON(text: response.content)

-             }

-           }

-           def getPagurePRInfo() {

-             def pagureClient = new PagureClient (pagureApiUrl: env.PAGURE_API, steps: steps)

-             return pagureClient.getPR(fork: env.PAGURE_REPO_IS_FORK == 'true', repo: "${PAGURE_REPO_NAME}", pr: env.PR_NO)

-           }

-           def setBuildStatusOnPagurePR(percent, String comment) {

-             withCredentials([string(credentialsId: "${env.PIPELINE_NAMESPACE}-${PAGURE_API_KEY_SECRET_NAME}", variable: 'TOKEN')]) {

-               def pagureClient = new PagureClient (pagureApiUrl: env.PAGURE_API, token: env.TOKEN, steps: steps)

-               pagureClient.updatePRStatus(

-                 username: 'c3i-jenkins', uid: 'ci-pre-merge', url: env.DEV_BUILD_URL?:env.BUILD_URL,

-                 percent: percent, comment: comment, pr: env.PR_NO,

-                 repo: "${PAGURE_REPO_NAME}", fork: env.PAGURE_REPO_IS_FORK == 'true')

-             }

-           }

-           def commentOnPR(String comment) {

-             withCredentials([string(credentialsId: "${env.PIPELINE_NAMESPACE}-${PAGURE_API_KEY_SECRET_NAME}", variable: 'TOKEN')]) {

-               def pagureClient = new PagureClient (pagureApiUrl: env.PAGURE_API, token: env.TOKEN, steps: steps)

-               pagureClient.commentOnPR(

-                 comment: comment, pr: env.PR_NO,

-                 repo: "${PAGURE_REPO_NAME}", fork: env.PAGURE_REPO_IS_FORK == 'true')

-             }

-           }

-           def sendBuildStatusEmail(boolean success) {

-             def status = success ? 'passed' : 'failed'

-             def reciepent = env.PAGURE_POLLING_FOR_PR != 'true' && ownership.job.ownershipEnabled && ownership.job.primaryOwnerEmail ?

-               ownership.job.primaryOwnerEmail : env.GIT_AUTHOR_EMAIL

-             def subject = "Jenkins job ${env.JOB_NAME} #${env.BUILD_NUMBER} ${status}."

-             def body = "Build URL: ${env.DEV_BUILD_URL}"

-             if (env.PAGURE_POLLING_FOR_PR == 'true') {

-               subject = "Jenkins job ${env.JOB_NAME}, PR #${env.PR_NO} ${status}."

-               body += "\nPull Request: ${env.PR_URL}"

-             }

-             emailext to: reciepent, subject: subject, body: body

-           }

Following up https://pagure.io/waiverdb/issue/263.

  1. Split waiverdb-dev job into waiverdb-premerge and waiverdb-postmerge
    for better readability. The new 2 jobs are generated from the same template waiverdb-build.
  2. Move Pagure pull-request flagging and mail sending code from polling job to waiverdb-premerge and waiverdb-postmerge jobs.
  3. Use C3I Library to interact with Pagure API.

rebased onto 3282d97e7aa4eff3348089d5932a4bf89de4720e

5 years ago

You'll want to add the:

changelog: false,

option to the library step, to avoid Jenkins also polling for changes on the library repo. See:

https://github.com/jenkinsci/workflow-cps-global-lib-plugin/pull/34#issuecomment-312108505

for more information.

Isn't PAGURE_URL being passed in as a parameter? This value should come from there.

You could use c3i.wait(buildSelector.name()) here. It handles checking when the build starts, streaming the logs, and raising an error if the build does not end in the Complete state. You can use it for watching the progress of any build, either a container build or another pipeline job.

You don't need the c3i. here, pagure is available as a global variable.

Made a few comments, but overall +1, I really like refactoring, I think it makes it a lot simpler and more manageable.

rebased onto 9d75c931c0fe6030ae507be129bf2c4204c3ccac

5 years ago

Is this left over from testing?

This is actually useless. I just don't know what the default value of WAIVERDB_GIT_REF should be for a premerge job. It should be something like pull/$number/head but it is usually passed from upstream polling job. I finally decided to remove this parameter because it is not intended to be manually triggered.

You'll want to add the:
changelog: false,
option to the library step, to avoid Jenkins also polling for changes on the library repo. See:
https://github.com/jenkinsci/workflow-cps-global-lib-plugin/pull/34#issuecomment-312108505
for more information.

Done.

Should this be displayName:?

Yes, it's a mistake.

Isn't PAGURE_URL being passed in as a parameter? This value should come from there.

You are right. Changed.

You could use c3i.wait(buildSelector.name()) here. It handles checking when the build starts, streaming the logs, and raising an error if the build does not end in the Complete state. You can use it for watching the progress of any build, either a container build or another pipeline job.

It is really awesome to have such a wait function! I've tried it and it indeed reduces the code duplication. But I have some RFEs for those C3I functions. I will open an issue in the c3-library repo later.

You don't need the c3i. here, pagure is available as a global variable.

Done.

Should be recipient.

Done.

Made a few comments, but overall +1, I really like refactoring, I think it makes it a lot simpler and more manageable.

Thanks for the review. I am really excited in introducing the c3i-library to WaiverDB pipeline. The power of that library began to show up!

PAGURE_API and PAGURE_REPO_HOME are already set in the global environment section, they don't need to be set again here.

One comment about duplicate environment variables. Otherwise looks good! +1 to merge it.

rebased onto 67ef28c

5 years ago

Commit 5863efd fixes this pull-request

Pull-Request has been merged by rayson

5 years ago

Pull-Request has been merged by rayson

5 years ago