From 5863efdfb99f4f249fe586465b7a461505f772ae Mon Sep 17 00:00:00 2001 From: Yuxiang Zhu Date: Mar 06 2019 04:01:41 +0000 Subject: Merge #292 `CI/CD: Job refactoring` --- diff --git a/openshift/pipelines/jobs/waiverdb-dev.env b/openshift/pipelines/jobs/waiverdb-dev.env index 6bbf49b..ceb9e64 100644 --- a/openshift/pipelines/jobs/waiverdb-dev.env +++ b/openshift/pipelines/jobs/waiverdb-dev.env @@ -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. diff --git a/openshift/pipelines/jobs/waiverdb-dev.tmpl b/openshift/pipelines/jobs/waiverdb-dev.tmpl index 1c2538d..316b89d 100644 --- a/openshift/pipelines/jobs/waiverdb-dev.tmpl +++ b/openshift/pipelines/jobs/waiverdb-dev.tmpl @@ -1 +1 @@ -waiverdb-dev-template.yaml +waiverdb-build-template.yaml diff --git a/openshift/pipelines/jobs/waiverdb-polling-for-master.env b/openshift/pipelines/jobs/waiverdb-polling-for-master.env index 63d1edc..77db732 100644 --- a/openshift/pipelines/jobs/waiverdb-polling-for-master.env +++ b/openshift/pipelines/jobs/waiverdb-polling-for-master.env @@ -1,4 +1,3 @@ NAME=waiverdb-polling-for-master PAGURE_POLLING_SCHEDULE="H/5 * * * *" PAGURE_POLLED_BRANCH=master -DEV_PIPELINE_BC_NAME=waiverdb-dev diff --git a/openshift/pipelines/jobs/waiverdb-polling-for-prs.env b/openshift/pipelines/jobs/waiverdb-polling-for-prs.env index 702e71a..907d8d3 100644 --- a/openshift/pipelines/jobs/waiverdb-polling-for-prs.env +++ b/openshift/pipelines/jobs/waiverdb-polling-for-prs.env @@ -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 diff --git a/openshift/pipelines/jobs/waiverdb-postmerge.env b/openshift/pipelines/jobs/waiverdb-postmerge.env new file mode 100644 index 0000000..c59617a --- /dev/null +++ b/openshift/pipelines/jobs/waiverdb-postmerge.env @@ -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. diff --git a/openshift/pipelines/jobs/waiverdb-postmerge.tmpl b/openshift/pipelines/jobs/waiverdb-postmerge.tmpl new file mode 100644 index 0000000..316b89d --- /dev/null +++ b/openshift/pipelines/jobs/waiverdb-postmerge.tmpl @@ -0,0 +1 @@ +waiverdb-build-template.yaml diff --git a/openshift/pipelines/jobs/waiverdb-premerge.env b/openshift/pipelines/jobs/waiverdb-premerge.env new file mode 100644 index 0000000..9c43e62 --- /dev/null +++ b/openshift/pipelines/jobs/waiverdb-premerge.env @@ -0,0 +1 @@ +NAME=waiverdb-premerge diff --git a/openshift/pipelines/jobs/waiverdb-premerge.tmpl b/openshift/pipelines/jobs/waiverdb-premerge.tmpl new file mode 100644 index 0000000..316b89d --- /dev/null +++ b/openshift/pipelines/jobs/waiverdb-premerge.tmpl @@ -0,0 +1 @@ +waiverdb-build-template.yaml diff --git a/openshift/pipelines/templates/waiverdb-build-template.yaml b/openshift/pipelines/templates/waiverdb-build-template.yaml new file mode 100644 index 0000000..a2fd8cb --- /dev/null +++ b/openshift/pipelines/templates/waiverdb-build-template.yaml @@ -0,0 +1,233 @@ +# Template to produce a new WaiverDB build job in OpenShift. +# +# WaiverDB build job is a part of the WaiverDB C3I Pipeline, covering the following steps: +# +# - Run Flake8 and Pylint checks +# - Run unit tests +# - Build Docs +# - Publish Docs +# - Build SRPM +# - Build RPM +# - Invoke Rpmlint +# - Build container +# - Run functional tests +# - Push container +# +# Required Jenkins Plugins: +# - Openshift Sync plugin +# - Openshift Client plugin +# - Kubernetes plugin +# - SSH Agent plugin +# - Timestamper plugin +# +--- +apiVersion: v1 +kind: Template +metadata: + 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-build +- name: WAIVERDB_GIT_REPO + displayName: WaiverDB Git repo URL + description: Default WaiverDB Git repo URL in which to run dev tests against + required: true + value: "https://pagure.io/waiverdb.git" +- name: WAIVERDB_GIT_REF + displayName: WaiverDB Git repo ref + description: Default WaiverDB Git repo ref in which to run dev tests against + required: true + value: master +- name: WAIVERDB_MAIN_BRANCH + displayName: Name of the main branch. + 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 + displayName: Name of OpenShift cloud in Jenkins master configuration + required: true + value: openshift +- name: JENKINS_AGENT_IMAGE + displayName: Container image for Jenkins slave pods + required: true + 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, 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 + displayName: Name of the OpenShift SSH secret for publishing docs to Pagure. + required: false + value: pagure-doc-secret +- name: WAIVERDB_DEV_IMAGE_DESTINATIONS + 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: "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 + required: false +- name: WAIVERDB_DEV_IMAGE_TAG + displayName: Tag name of the resulting container image for development environment + value: "latest" + required: true +- name: WAIVERDB_IMAGESTREAM_NAME + displayName: Name of ImageStream for WaiverDB container images + required: true + value: waiverdb +- name: WAIVERDB_IMAGESTREAM_NAMESPACE + displayName: Namespace of ImageStream for WaiverDB container images + required: false +- name: WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAME + displayName: Name of BuildConfig for running integration tests + required: true + value: waiverdb-dev-integration-test +- name: WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAMESPACE + displayName: Namespace of BuildConfig for running integration tests + required: false +- name: FORCE_PUBLISH_IMAGE + displayName: Whether to push the resulting image regardless of the Git branch + value: "false" + required: true +- name: FORCE_PUBLISH_DOCS + displayName: Whether to publish docs regardless of the Git branch + value: "false" + required: true +- name: TAG_INTO_IMAGESTREAM + 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-build +objects: +- kind: "BuildConfig" + apiVersion: "v1" + metadata: + name: "${NAME}-jenkins-slave" + labels: + app: "${NAME}" + spec: + runPolicy: "Serial" + completionDeadlineSeconds: 1800 + strategy: + dockerStrategy: + forcePull: true + dockerfilePath: openshift/containers/jenkins-slave/Dockerfile + resources: + requests: + memory: "512Mi" + cpu: "300m" + limits: + memory: "768Mi" + cpu: "500m" + source: + git: + uri: "${WAIVERDB_GIT_REPO}" + ref: "${WAIVERDB_GIT_REF}" + output: + to: + kind: "DockerImage" + name: "${JENKINS_AGENT_IMAGE}" + pushSecret: + name: "${CONTAINER_REGISTRY_CREDENTIALS}" + +- kind: ServiceAccount + apiVersion: v1 + metadata: + name: "${NAME}-jenkins-slave" + labels: + app: "${NAME}" + +- kind: RoleBinding + apiVersion: v1 + metadata: + name: "${NAME}-jenkins-slave_edit" + labels: + app: "${NAME}" + subjects: + - kind: ServiceAccount + name: "${NAME}-jenkins-slave" + roleRef: + name: edit + +- kind: "BuildConfig" + apiVersion: "v1" + metadata: + name: "${NAME}" + labels: + app: "${NAME}" + spec: + runPolicy: "Serial" + completionDeadlineSeconds: 1800 + source: + git: + uri: "${WAIVERDB_GIT_REPO}" + ref: "${WAIVERDB_MAIN_BRANCH}" + strategy: + type: JenkinsPipeline + jenkinsPipelineStrategy: + env: + - name: "WAIVERDB_GIT_REPO" + value: "${WAIVERDB_GIT_REPO}" + - name: "WAIVERDB_GIT_REF" + value: "${WAIVERDB_GIT_REF}" + - name: "JENKINS_AGENT_CLOUD_NAME" + value: "${JENKINS_AGENT_CLOUD_NAME}" + - name: "JENKINS_AGENT_IMAGE" + value: "${JENKINS_AGENT_IMAGE}" + - name: "JENKINS_AGENT_SERVICE_ACCOUNT" + value: "${NAME}-jenkins-slave" + - name: "WAIVERDB_DEV_IMAGE_DESTINATIONS" + value: "${WAIVERDB_DEV_IMAGE_DESTINATIONS}" + - name: "CONTAINER_REGISTRY_CREDENTIALS" + value: "${CONTAINER_REGISTRY_CREDENTIALS}" + - name: "FORCE_PUBLISH_IMAGE" + value: "${FORCE_PUBLISH_IMAGE}" + - name: "TAG_INTO_IMAGESTREAM" + value: "${TAG_INTO_IMAGESTREAM}" + - name: "WAIVERDB_DEV_IMAGE_TAG" + value: "${WAIVERDB_DEV_IMAGE_TAG}" + - name: "WAIVERDB_IMAGESTREAM_NAME" + value: "${WAIVERDB_IMAGESTREAM_NAME}" + - name: "WAIVERDB_IMAGESTREAM_NAMESPACE" + value: "${WAIVERDB_IMAGESTREAM_NAMESPACE}" + - name: "FORCE_PUBLISH_DOCS" + value: "${FORCE_PUBLISH_DOCS}" + - name: "PAGURE_DOC_REPO_NAME" + value: "${PAGURE_DOC_REPO_NAME}" + - name: "PAGURE_DOC_SECRET" + value: "${PAGURE_DOC_SECRET}" + - name: "WAIVERDB_MAIN_BRANCH" + value: "${WAIVERDB_MAIN_BRANCH}" + - name: "WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAME" + value: "${WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAME}" + - name: "WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAMESPACE" + value: "${WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAMESPACE}" + - 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 diff --git a/openshift/pipelines/templates/waiverdb-build.Jenkinsfile b/openshift/pipelines/templates/waiverdb-build.Jenkinsfile new file mode 100644 index 0000000..244d254 --- /dev/null +++ b/openshift/pipelines/templates/waiverdb-build.Jenkinsfile @@ -0,0 +1,513 @@ +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 { + cloud params.JENKINS_AGENT_CLOUD_NAME + label "jenkins-slave-${UUID.randomUUID().toString()}" + serviceAccount params.JENKINS_AGENT_SERVICE_ACCOUNT + defaultContainer 'jnlp' + yaml """ + apiVersion: v1 + kind: Pod + metadata: + labels: + app: "jenkins-${env.JOB_BASE_NAME}" + factory2-pipeline-kind: "waiverdb-build-pipeline" + factory2-pipeline-build-number: "${env.BUILD_NUMBER}" + spec: + containers: + - name: jnlp + image: "${params.JENKINS_AGENT_IMAGE}" + imagePullPolicy: Always + tty: true + env: + - name: REGISTRY_CREDENTIALS + valueFrom: + secretKeyRef: + name: "${params.CONTAINER_REGISTRY_CREDENTIALS}" + key: '.dockerconfigjson' + # Required by unit tests: Set up NSS Wrapper to generate a fake user name for the random UID assigned by OpenShift + - name: LD_PRELOAD + value: '/usr/lib64/libnss_wrapper.so' + - name: NSS_WRAPPER_PASSWD + value: '/tmp/passwd' + - name: NSS_WRAPPER_GROUP + value: '/etc/group' + volumeMounts: + - name: postgresql-socket + mountPath: /var/run/postgresql + resources: + requests: + memory: 768Mi + cpu: 300m + limits: + memory: 1Gi + cpu: 500m + - name: db + image: registry.access.redhat.com/rhscl/postgresql-95-rhel7:latest + imagePullPolicy: Always + env: + - name: POSTGRESQL_USER + value: waiverdb + - name: POSTGRESQL_PASSWORD + value: waiverdb + - name: POSTGRESQL_DATABASE + value: waiverdb + volumeMounts: + - name: postgresql-socket + mountPath: /var/run/postgresql + resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 384Mi + cpu: 200m + volumes: + - name: postgresql-socket + emptyDir: {} + """ + } + } + options { + timestamps() + timeout(time: 30, unit: 'MINUTES') + } + 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 { + // 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']], + ]) + + // 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 = """${currentBuild.displayName}""" + try { + def prInfo = withPagure { + it.getPR(env.PR_NO) + } + pagureLink = """PR#${env.PR_NO}: ${escapeHtml(prInfo.title)}""" + // 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 = """${currentBuild.displayName}""" + } + } + sh 'cp conf/settings.py.example conf/settings.py' + sh 'pip3 install --user -r ./requirements.txt' + } + } + stage('Run checks') { + failFast false + parallel { + stage('Invoke Flake8') { + steps { + sh 'flake8' + } + } + stage('Invoke Pylint') { + steps { + sh 'pylint-3 --reports=n waiverdb' + } + } + } + } + stage('Run unit tests') { + steps { + // wait for the test datebase to come up + sh 'wait-for-it -s -t 300 127.0.0.1:5432' + // create a database role + sh 'psql -h 127.0.0.1 -U "postgres" -q -d "waiverdb" -c "CREATE ROLE \"$PIPELINE_USERNAME\" WITH LOGIN SUPERUSER;"' + // run unit tests + sh 'py.test-3 -v --junitxml=junit-tests.xml tests' + } + post { + always { + junit 'junit-tests.xml' + } + } + } + stage('Build Artifacts') { + failFast false + parallel { + stage('Branch Docs') { + stages { + stage('Build Docs') { + steps { + sh 'make -C docs html' + } + post { + always { + archiveArtifacts artifacts: 'docs/_build/html/**' + } + } + } + stage('Publish Docs') { + when { + expression { + return "${params.PAGURE_DOC_REPO_NAME}" && (params.WAIVERDB_GIT_REF == params.WAIVERDB_MAIN_BRANCH || env.FORCE_PUBLISH_DOCS == "true") + } + } + steps { + sshagent (credentials: ["${env.PIPELINE_NAMESPACE}-${params.PAGURE_DOC_SECRET}"]) { + sh ''' + mkdir -p ~/.ssh/ + touch ~/.ssh/known_hosts + ssh-keygen -R pagure.io + echo 'pagure.io ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC198DWs0SQ3DX0ptu+8Wq6wnZMrXUCufN+wdSCtlyhHUeQ3q5B4Hgto1n2FMj752vToCfNTn9mWO7l2rNTrKeBsELpubl2jECHu4LqxkRVihu5UEzejfjiWNDN2jdXbYFY27GW9zymD7Gq3u+T/Mkp4lIcQKRoJaLobBmcVxrLPEEJMKI4AJY31jgxMTnxi7KcR+U5udQrZ3dzCn2BqUdiN5dMgckr4yNPjhl3emJeVJ/uhAJrEsgjzqxAb60smMO5/1By+yF85Wih4TnFtF4LwYYuxgqiNv72Xy4D/MGxCqkO/nH5eRNfcJ+AJFE7727F7Tnbo4xmAjilvRria/+l' >>~/.ssh/known_hosts + rm -rf docs-on-pagure + git clone ssh://git@pagure.io/docs/${PAGURE_DOC_REPO_NAME}.git docs-on-pagure + rm -rf docs-on-pagure/* + cp -r docs/_build/html/* docs-on-pagure/ + cd docs-on-pagure + git config user.name 'Pipeline Bot' + git config user.email "pipeline-bot@localhost.localdomain" + git add -A . + if [[ "$(git diff --cached --numstat | wc -l)" -eq 0 ]] ; then + exit 0 # No changes, nothing to commit + fi + git commit -m "Automatic commit of docs built by Jenkins job ${JOB_NAME} #${BUILD_NUMBER}" + git push origin master + ''' + } + } + } + } + } + stage('Build SRPM') { + steps { + sh './rpmbuild.sh -bs' + } + post { + success { + archiveArtifacts artifacts: 'rpmbuild-output/*.src.rpm' + } + } + } + stage('Branch RPM') { + stages { + stage('Build RPM') { + steps { + sh './rpmbuild.sh -bb' + } + post { + success { + archiveArtifacts artifacts: 'rpmbuild-output/*/*.rpm' + } + } + } + stage('Invoke Rpmlint') { + steps { + sh 'rpmlint -f rpmlint-config.py rpmbuild-output/*/*.rpm' + } + } + } + } + } + } + stage('Build container') { + environment { + BUILDCONFIG_INSTANCE_ID = "waiverdb-temp-${currentBuild.id}-${UUID.randomUUID().toString().substring(0,7)}" + } + steps { + script { + openshift.withCluster() { + // OpenShift BuildConfig doesn't support specifying a tag name at build time. + // We have to create a new BuildConfig for each container build. + // Create a BuildConfig from a seperated Template. + echo 'Creating a BuildConfig for container build...' + def template = readYaml file: 'openshift/waiverdb-container-template.yaml' + def processed = openshift.process(template, + "-p", "NAME=${env.BUILDCONFIG_INSTANCE_ID}", + '-p', "WAIVERDB_GIT_REPO=${params.WAIVERDB_GIT_REPO}", + // 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}", + '-p', "WAIVERDB_IMAGESTREAM_NAMESPACE=${params.WAIVERDB_IMAGESTREAM_NAMESPACE}", + ) + def created = openshift.apply(processed) + def bc = created.narrow('bc') + echo 'Starting a container build from the created BuildConfig...' + buildSelector = bc.startBuild() + c3i.wait(buildSelector.name()) + echo 'Container build succeeds.' + def ocpBuild = buildSelector.object() + env.RESULTING_IMAGE_REF = ocpBuild.status.outputDockerImageReference + env.RESULTING_IMAGE_DIGEST = ocpBuild.status.output.to.imageDigest + def imagestream= created.narrow('is').object() + env.RESULTING_IMAGE_REPO = imagestream.status.dockerImageRepository + env.RESULTING_TAG = env.TEMP_TAG + } + } + } + post { + failure { + echo "Failed to build container image ${env.TEMP_TAG}." + } + cleanup { + script { + openshift.withCluster() { + echo 'Tearing down...' + openshift.selector('bc', [ + 'app': env.BUILDCONFIG_INSTANCE_ID, + 'template': 'waiverdb-container-template', + ]).delete() + } + } + } + } + } + stage('Run functional tests') { + steps { + script { + openshift.withCluster() { + openshift.withProject(params.WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAMESPACE) { + 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', "WAIVERDB_GIT_REPO=${params.WAIVERDB_GIT_REPO}", + '-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}", + ) + c3i.wait(buildSelector.name()) + echo "Functional test passed." + } + } + } + } + post { + failure { + echo "Functional test failed." + } + } + } + stage('Push container') { + when { + expression { + return params.FORCE_PUBLISH_IMAGE == 'true' || + params.WAIVERDB_GIT_REF == params.WAIVERDB_MAIN_BRANCH + } + } + steps { + script { + def destinations = env.WAIVERDB_DEV_IMAGE_DESTINATIONS ? + env.WAIVERDB_DEV_IMAGE_DESTINATIONS.split(',') : [] + openshift.withCluster() { + def sourceImage = env.RESULTING_IMAGE_REPO + ":" + env.RESULTING_TAG + if (env.REGISTRY_CREDENTIALS) { + dir ("${env.HOME}/.docker") { + writeFile file:'config.json', text: env.REGISTRY_CREDENTIALS + } + } + // pull the built image from imagestream + echo "Pulling container from ${sourceImage}..." + def registryToken = readFile(file: '/var/run/secrets/kubernetes.io/serviceaccount/token') + withEnv(["SOURCE_IMAGE_REF=${sourceImage}", "TOKEN=${registryToken}"]) { + sh '''set -e +x # hide the token from Jenkins console + mkdir -p _build + skopeo copy \ + --src-cert-dir=/var/run/secrets/kubernetes.io/serviceaccount/ \ + --src-creds=serviceaccount:"$TOKEN" \ + docker://"$SOURCE_IMAGE_REF" dir:_build/waiverdb_container + ''' + } + // push to registries + def pushTasks = destinations.collectEntries { + ["Pushing ${it}" : { + def dest = "${it}:${params.WAIVERDB_DEV_IMAGE_TAG ?: 'latest'}" + // Only docker and atomic registries are allowed + if (!dest.startsWith('atomic:') && !dest.startsWith('docker://')) { + dest = 'docker://' + dest + } + echo "Pushing container to ${dest}..." + withEnv(["DEST_IMAGE_REF=${dest}"]) { + /* Pushes to the internal registry can sometimes randomly fail + * with "unknown blob" due to a known issue with the registry + * storage configuration. So we retry up to 5 times. */ + retry(5) { + sh 'skopeo copy dir:_build/waiverdb_container "$DEST_IMAGE_REF"' + } + } + }] + } + parallel pushTasks + } + } + } + } + stage('Tag into image stream') { + when { + expression { + return "${params.WAIVERDB_DEV_IMAGE_TAG}" && params.TAG_INTO_IMAGESTREAM == "true" && + (params.FORCE_PUBLISH_IMAGE == 'true' || params.WAIVERDB_GIT_REF == params.WAIVERDB_MAIN_BRANCH) + } + } + steps { + script { + openshift.withCluster() { + openshift.withProject("${params.WAIVERDB_IMAGESTREAM_NAMESPACE}") { + def sourceRef = "${params.WAIVERDB_IMAGESTREAM_NAME}:${env.RESULTING_TAG}" + def destRef = "${params.WAIVERDB_IMAGESTREAM_NAME}:${params.WAIVERDB_DEV_IMAGE_TAG}" + echo "Tagging ${sourceRef} as ${destRef}..." + openshift.tag("${sourceRef}", "${destRef}") + } + } + } + } + } + } + post { + cleanup { + script { + if (env.RESULTING_TAG) { + echo "Removing tag ${env.RESULTING_TAG} from the ImageStream..." + openshift.withCluster() { + openshift.withProject("${params.WAIVERDB_IMAGESTREAM_NAMESPACE}") { + openshift.tag("${params.WAIVERDB_IMAGESTREAM_NAME}:${env.RESULTING_TAG}", + "-d") + } + } + } + } + } + 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 +} diff --git a/openshift/pipelines/templates/waiverdb-dev-template.yaml b/openshift/pipelines/templates/waiverdb-dev-template.yaml deleted file mode 100644 index 7fc9e52..0000000 --- a/openshift/pipelines/templates/waiverdb-dev-template.yaml +++ /dev/null @@ -1,213 +0,0 @@ -# Template to produce a new WaiverDB dev CI/CD pipeline in OpenShift. -# -# Dev pipeline is a part of the WaiverDB Pipeline, covers the following steps: -# -# - Run Flake8 and Pylint checks -# - Run unit tests -# - Build Docs -# - Publish Docs -# - Build SRPM -# - Build RPM -# - Invoke Rpmlint -# - Build container -# - Run functional tests -# - Push container -# -# Required Jenkins Plugins: -# - Openshift Sync plugin -# - Openshift Client plugin -# - Kubernetes plugin -# - SSH Agent plugin -# - Timestamper plugin -# ---- -apiVersion: v1 -kind: Template -metadata: - name: waiverdb-dev-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 -- name: WAIVERDB_GIT_REPO - displayName: WaiverDB Git repo URL - description: Default WaiverDB Git repo URL in which to run dev tests against - required: true - value: "https://pagure.io/waiverdb.git" -- name: WAIVERDB_GIT_REF - displayName: WaiverDB Git repo ref - description: Default WaiverDB Git repo ref in which to run dev tests against - required: true - 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. - value: master - required: true -- name: JENKINS_AGENT_CLOUD_NAME - displayName: Name of OpenShift cloud in Jenkins master configuration - required: true - value: openshift -- name: JENKINS_AGENT_IMAGE - displayName: Container image for Jenkins slave pods - required: true - 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 - required: false - value: waiverdb -- name: PAGURE_DOC_SECRET - displayName: Name of the OpenShift SSH secret for publishing docs to Pagure. - 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 - 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" -- name: CONTAINER_REGISTRY_CREDENTIALS - displayName: Secret name of container registries used for pulling and pushing images - value: factory2-pipeline-registry-credentials - required: false -- name: WAIVERDB_DEV_IMAGE_TAG - displayName: Tag name of the resulting container image for development environment - value: "latest" - required: true -- name: WAIVERDB_IMAGESTREAM_NAME - displayName: Name of ImageStream for WaiverDB container images - required: true - value: waiverdb -- name: WAIVERDB_IMAGESTREAM_NAMESPACE - displayName: Namespace of ImageStream for WaiverDB container images - required: false -- name: WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAME - displayName: Name of BuildConfig for running integration tests - required: true - value: waiverdb-dev-integration-test -- name: WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAMESPACE - displayName: Namespace of BuildConfig for running integration tests - required: false -- name: FORCE_PUBLISH_IMAGE - displayName: Whether to push the resulting image regardless of the Git branch - value: "false" - required: true -- name: FORCE_PUBLISH_DOCS - displayName: Whether to publish docs regardless of the Git branch - value: "false" - required: true -- name: TAG_INTO_IMAGESTREAM - displayName: Whether to tag the pushed image as dev - value: "true" - required: true -labels: - template: waiverdb-dev -objects: -- kind: "BuildConfig" - apiVersion: "v1" - metadata: - name: "${NAME}-jenkins-slave" - labels: - app: "${NAME}" - spec: - runPolicy: "Serial" - completionDeadlineSeconds: 1800 - strategy: - dockerStrategy: - forcePull: true - dockerfilePath: openshift/containers/jenkins-slave/Dockerfile - resources: - requests: - memory: "512Mi" - cpu: "300m" - limits: - memory: "768Mi" - cpu: "500m" - source: - git: - uri: "${WAIVERDB_GIT_REPO}" - ref: "${WAIVERDB_GIT_REF}" - output: - to: - kind: "DockerImage" - name: "${JENKINS_AGENT_IMAGE}" - pushSecret: - name: "${CONTAINER_REGISTRY_CREDENTIALS}" - -- kind: ServiceAccount - apiVersion: v1 - metadata: - name: "${NAME}-jenkins-slave" - labels: - app: "${NAME}" - -- kind: RoleBinding - apiVersion: v1 - metadata: - name: "${NAME}-jenkins-slave_edit" - labels: - app: "${NAME}" - subjects: - - kind: ServiceAccount - name: "${NAME}-jenkins-slave" - roleRef: - name: edit - -- kind: "BuildConfig" - apiVersion: "v1" - metadata: - name: "${NAME}" - labels: - app: "${NAME}" - spec: - runPolicy: "Serial" - completionDeadlineSeconds: 1800 - source: - git: - uri: "${WAIVERDB_GIT_REPO}" - ref: "${WAIVERDB_GIT_REF}" - strategy: - type: JenkinsPipeline - jenkinsPipelineStrategy: - env: - - name: "WAIVERDB_GIT_REPO" - value: "${WAIVERDB_GIT_REPO}" - - name: "WAIVERDB_GIT_REF" - value: "${WAIVERDB_GIT_REF}" - - name: "JENKINS_AGENT_CLOUD_NAME" - value: "${JENKINS_AGENT_CLOUD_NAME}" - - name: "JENKINS_AGENT_IMAGE" - value: "${JENKINS_AGENT_IMAGE}" - - name: "JENKINS_AGENT_SERVICE_ACCOUNT" - value: "${NAME}-jenkins-slave" - - name: "WAIVERDB_DEV_IMAGE_DESTINATIONS" - value: "${WAIVERDB_DEV_IMAGE_DESTINATIONS}" - - name: "CONTAINER_REGISTRY_CREDENTIALS" - value: "${CONTAINER_REGISTRY_CREDENTIALS}" - - name: "FORCE_PUBLISH_IMAGE" - value: "${FORCE_PUBLISH_IMAGE}" - - name: "TAG_INTO_IMAGESTREAM" - value: "${TAG_INTO_IMAGESTREAM}" - - name: "WAIVERDB_DEV_IMAGE_TAG" - value: "${WAIVERDB_DEV_IMAGE_TAG}" - - name: "WAIVERDB_IMAGESTREAM_NAME" - value: "${WAIVERDB_IMAGESTREAM_NAME}" - - name: "WAIVERDB_IMAGESTREAM_NAMESPACE" - value: "${WAIVERDB_IMAGESTREAM_NAMESPACE}" - - name: "FORCE_PUBLISH_DOCS" - value: "${FORCE_PUBLISH_DOCS}" - - name: "PAGURE_DOC_REPO_NAME" - value: "${PAGURE_DOC_REPO_NAME}" - - name: "PAGURE_DOC_SECRET" - value: "${PAGURE_DOC_SECRET}" - - name: "WAIVERDB_MAIN_BRANCH" - value: "${WAIVERDB_MAIN_BRANCH}" - - name: "WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAME" - 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 diff --git a/openshift/pipelines/templates/waiverdb-dev.Jenkinsfile b/openshift/pipelines/templates/waiverdb-dev.Jenkinsfile deleted file mode 100644 index b53faad..0000000 --- a/openshift/pipelines/templates/waiverdb-dev.Jenkinsfile +++ /dev/null @@ -1,409 +0,0 @@ -pipeline { - agent { - kubernetes { - cloud params.JENKINS_AGENT_CLOUD_NAME - label "jenkins-slave-${UUID.randomUUID().toString()}" - serviceAccount params.JENKINS_AGENT_SERVICE_ACCOUNT - defaultContainer 'jnlp' - yaml """ - apiVersion: v1 - kind: Pod - metadata: - labels: - app: "jenkins-${env.JOB_BASE_NAME}" - factory2-pipeline-kind: "waiverdb-dev-pipeline" - factory2-pipeline-build-number: "${env.BUILD_NUMBER}" - spec: - containers: - - name: jnlp - image: "${params.JENKINS_AGENT_IMAGE}" - imagePullPolicy: Always - tty: true - env: - - name: REGISTRY_CREDENTIALS - valueFrom: - secretKeyRef: - name: "${params.CONTAINER_REGISTRY_CREDENTIALS}" - key: '.dockerconfigjson' - # Required by unit tests: Set up NSS Wrapper to generate a fake user name for the random UID assigned by OpenShift - - name: LD_PRELOAD - value: '/usr/lib64/libnss_wrapper.so' - - name: NSS_WRAPPER_PASSWD - value: '/tmp/passwd' - - name: NSS_WRAPPER_GROUP - value: '/etc/group' - volumeMounts: - - name: postgresql-socket - mountPath: /var/run/postgresql - resources: - requests: - memory: 768Mi - cpu: 300m - limits: - memory: 1Gi - cpu: 500m - - name: db - image: registry.access.redhat.com/rhscl/postgresql-95-rhel7:latest - imagePullPolicy: Always - env: - - name: POSTGRESQL_USER - value: waiverdb - - name: POSTGRESQL_PASSWORD - value: waiverdb - - name: POSTGRESQL_DATABASE - value: waiverdb - volumeMounts: - - name: postgresql-socket - mountPath: /var/run/postgresql - resources: - requests: - memory: 256Mi - cpu: 100m - limits: - memory: 384Mi - cpu: 200m - volumes: - - name: postgresql-socket - emptyDir: {} - """ - } - } - options { - timestamps() - timeout(time: 30, unit: 'MINUTES') - } - environment { - PIPELINE_NAMESPACE = readFile('/run/secrets/kubernetes.io/serviceaccount/namespace').trim() - PIPELINE_USERNAME = sh(returnStdout: true, script: 'id -un').trim() - } - stages { - stage('Prepare') { - steps { - script { - if (params.BUILD_DISPLAY_RENAME_TO) { - currentBuild.displayName = params.BUILD_DISPLAY_RENAME_TO - } - 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 - // 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 - } - sh 'cp conf/settings.py.example conf/settings.py' - sh 'pip3 install --user -r ./requirements.txt' - } - } - stage('Run checks') { - failFast false - parallel { - stage('Invoke Flake8') { - steps { - sh 'flake8' - } - } - stage('Invoke Pylint') { - steps { - sh 'pylint-3 --reports=n waiverdb' - } - } - } - } - stage('Run unit tests') { - steps { - // wait for the test datebase to come up - sh 'wait-for-it -s -t 300 127.0.0.1:5432' - // create a database role - sh 'psql -h 127.0.0.1 -U "postgres" -q -d "waiverdb" -c "CREATE ROLE \"$PIPELINE_USERNAME\" WITH LOGIN SUPERUSER;"' - // run unit tests - sh 'py.test-3 -v --junitxml=junit-tests.xml tests' - } - post { - always { - junit 'junit-tests.xml' - } - } - } - stage('Build Artifacts') { - failFast false - parallel { - stage('Branch Docs') { - stages { - stage('Build Docs') { - steps { - sh 'make -C docs html' - } - post { - always { - archiveArtifacts artifacts: 'docs/_build/html/**' - } - } - } - stage('Publish Docs') { - when { - expression { - return "${params.PAGURE_DOC_REPO_NAME}" && (params.WAIVERDB_GIT_REF == "${params.WAIVERDB_MAIN_BRANCH}" || env.FORCE_PUBLISH_DOCS == "true") - } - } - steps { - sshagent (credentials: ["${env.PIPELINE_NAMESPACE}-${params.PAGURE_DOC_SECRET}"]) { - sh ''' - mkdir -p ~/.ssh/ - touch ~/.ssh/known_hosts - ssh-keygen -R pagure.io - echo 'pagure.io ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC198DWs0SQ3DX0ptu+8Wq6wnZMrXUCufN+wdSCtlyhHUeQ3q5B4Hgto1n2FMj752vToCfNTn9mWO7l2rNTrKeBsELpubl2jECHu4LqxkRVihu5UEzejfjiWNDN2jdXbYFY27GW9zymD7Gq3u+T/Mkp4lIcQKRoJaLobBmcVxrLPEEJMKI4AJY31jgxMTnxi7KcR+U5udQrZ3dzCn2BqUdiN5dMgckr4yNPjhl3emJeVJ/uhAJrEsgjzqxAb60smMO5/1By+yF85Wih4TnFtF4LwYYuxgqiNv72Xy4D/MGxCqkO/nH5eRNfcJ+AJFE7727F7Tnbo4xmAjilvRria/+l' >>~/.ssh/known_hosts - rm -rf docs-on-pagure - git clone ssh://git@pagure.io/docs/${PAGURE_DOC_REPO_NAME}.git docs-on-pagure - rm -rf docs-on-pagure/* - cp -r docs/_build/html/* docs-on-pagure/ - cd docs-on-pagure - git config user.name 'Pipeline Bot' - git config user.email "pipeline-bot@localhost.localdomain" - git add -A . - if [[ "$(git diff --cached --numstat | wc -l)" -eq 0 ]] ; then - exit 0 # No changes, nothing to commit - fi - git commit -m "Automatic commit of docs built by Jenkins job ${JOB_NAME} #${BUILD_NUMBER}" - git push origin master - ''' - } - } - } - } - } - stage('Build SRPM') { - steps { - sh './rpmbuild.sh -bs' - } - post { - success { - archiveArtifacts artifacts: 'rpmbuild-output/*.src.rpm' - } - } - } - stage('Branch RPM') { - stages { - stage('Build RPM') { - steps { - sh './rpmbuild.sh -bb' - } - post { - success { - archiveArtifacts artifacts: 'rpmbuild-output/*/*.rpm' - } - } - } - stage('Invoke Rpmlint') { - steps { - sh 'rpmlint -f rpmlint-config.py rpmbuild-output/*/*.rpm' - } - } - } - } - } - } - stage('Build container') { - environment { - BUILDCONFIG_INSTANCE_ID = "waiverdb-container-build-${currentBuild.id}" - } - steps { - script { - openshift.withCluster() { - // OpenShift BuildConfig doesn't support specifying a tag name at build time. - // We have to create a new BuildConfig for each container build. - // Create a BuildConfig from a seperated Template. - echo 'Creating a BuildConfig for container build...' - def template = readYaml file: 'openshift/waiverdb-container-template.yaml' - 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}", - '-p', "WAIVERDB_IMAGE_TAG=${env.TEMP_TAG}", - '-p', "WAIVERDB_VERSION=${env.WAIVERDB_VERSION}", - '-p', "WAIVERDB_IMAGESTREAM_NAME=${params.WAIVERDB_IMAGESTREAM_NAME}", - '-p', "WAIVERDB_IMAGESTREAM_NAMESPACE=${params.WAIVERDB_IMAGESTREAM_NAMESPACE}", - ) - def created = openshift.apply(processed) - 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 - 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() - env.RESULTING_IMAGE_REPO = imagestream.status.dockerImageRepository - env.RESULTING_TAG = env.TEMP_TAG - } - } - } - post { - cleanup { - script { - openshift.withCluster() { - echo 'Tearing down...' - openshift.selector('bc', [ - 'app': env.BUILDCONFIG_INSTANCE_ID, - 'template': 'waiverdb-container-template', - ]).delete() - } - } - } - } - } - stage('Run functional tests') { - steps { - script { - openshift.withCluster() { - openshift.withProject(params.WAIVERDB_INTEGRATION_TEST_BUILD_CONFIG_NAMESPACE) { - 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_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.' - } - } - } - } - } - stage('Push container') { - when { - expression { - return params.FORCE_PUBLISH_IMAGE == 'true' || - params.WAIVERDB_GIT_REF == params.WAIVERDB_MAIN_BRANCH - } - } - steps { - script { - def destinations = env.WAIVERDB_DEV_IMAGE_DESTINATIONS ? - env.WAIVERDB_DEV_IMAGE_DESTINATIONS.split(',') : [] - openshift.withCluster() { - def sourceImage = env.RESULTING_IMAGE_REPO + ":" + env.RESULTING_TAG - if (env.REGISTRY_CREDENTIALS) { - dir ("${env.HOME}/.docker") { - writeFile file:'config.json', text: env.REGISTRY_CREDENTIALS - } - } - // pull the built image from imagestream - echo "Pulling container from ${sourceImage}..." - def registryToken = readFile(file: '/var/run/secrets/kubernetes.io/serviceaccount/token') - withEnv(["SOURCE_IMAGE_REF=${sourceImage}", "TOKEN=${registryToken}"]) { - sh '''set -e +x # hide the token from Jenkins console - mkdir -p _build - skopeo copy \ - --src-cert-dir=/var/run/secrets/kubernetes.io/serviceaccount/ \ - --src-creds=serviceaccount:"$TOKEN" \ - docker://"$SOURCE_IMAGE_REF" dir:_build/waiverdb_container - ''' - } - // push to registries - def pushTasks = destinations.collectEntries { - ["Pushing ${it}" : { - def dest = it - // Only docker and atomic registries are allowed - if (!it.startsWith('atomic:') && !it.startsWith('docker://')) { - dest = 'docker://' + it - } - echo "Pushing container to ${dest}..." - withEnv(["DEST_IMAGE_REF=${dest}"]) { - /* Pushes to the internal registry can sometimes randomly fail - * with "unknown blob" due to a known issue with the registry - * storage configuration. So we retry up to 5 times. */ - retry(5) { - sh 'skopeo copy dir:_build/waiverdb_container "$DEST_IMAGE_REF"' - } - } - }] - } - parallel pushTasks - } - } - } - } - stage('Tag into image stream') { - when { - expression { - return "${params.WAIVERDB_DEV_IMAGE_TAG}" && params.TAG_INTO_IMAGESTREAM == "true" && - (params.FORCE_PUBLISH_IMAGE == 'true' || params.WAIVERDB_GIT_REF == params.WAIVERDB_MAIN_BRANCH) - } - } - steps { - script { - openshift.withCluster() { - openshift.withProject("${params.WAIVERDB_IMAGESTREAM_NAMESPACE}") { - def sourceRef = "${params.WAIVERDB_IMAGESTREAM_NAME}:${env.RESULTING_TAG}" - def destRef = "${params.WAIVERDB_IMAGESTREAM_NAME}:${params.WAIVERDB_DEV_IMAGE_TAG}" - echo "Tagging ${sourceRef} as ${destRef}..." - openshift.tag("${sourceRef}", "${destRef}") - } - } - } - } - } - } - post { - cleanup { - script { - if (env.RESULTING_TAG) { - echo "Removing tag ${env.RESULTING_TAG} from the ImageStream..." - openshift.withCluster() { - openshift.withProject("${params.WAIVERDB_IMAGESTREAM_NAMESPACE}") { - openshift.tag("${params.WAIVERDB_IMAGESTREAM_NAME}:${env.RESULTING_TAG}", - "-d") - } - } - } - } - } - } -} diff --git a/openshift/pipelines/templates/waiverdb-polling-pagure.yaml b/openshift/pipelines/templates/waiverdb-polling-pagure.yaml index 765d833..c1694bc 100644 --- a/openshift/pipelines/templates/waiverdb-polling-pagure.yaml +++ b/openshift/pipelines/templates/waiverdb-polling-pagure.yaml @@ -26,6 +26,9 @@ parameters: 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 @@ parameters: 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 @@ objects: } 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 @@ objects: ], 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 = """${currentBuild.displayName}""" } 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 = """PR-${env.PR_NO}""" - try { - def prInfo = getPagurePRInfo() - pagureLink = """${prInfo.title}""" - } catch (Exception e) { - echo "Error using pagure API: ${e}" - // ignoring this... - } + def pagureLink = """PR#${env.PR_NO}""" 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 @@ objects: 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 @@ objects: } } } - 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 - }