From 1f7055b82f9b6c34b8cf2d96ba4199549a29754a Mon Sep 17 00:00:00 2001 From: Sayali Gaikawad <61760125+gaiksaya@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:20:20 -0700 Subject: [PATCH] Add standard release pipeline library with generic trigger (#22) Signed-off-by: Sayali Gaikawad --- README.md | 2 + .../TestStandardReleasePipeline.groovy | 2 +- ...dReleasePipelineWithGenericTriggers.groovy | 101 ++++++++++++++++++ .../jenkins/jobs/PublishToNpm_Jenkinsfile.txt | 2 +- ...PipelineWithGenericTriggersTag_Jenkinsfile | 14 +++ ...lineWithGenericTriggersTag_Jenkinsfile.txt | 13 +++ ...asePipelineWithGenericTriggers_Jenkinsfile | 15 +++ ...ipelineWithGenericTriggers_Jenkinsfile.txt | 16 +++ vars/publishToNpm.groovy | 2 +- ...rdReleasePipelineWithGenericTrigger.groovy | 80 ++++++++++++++ 10 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 tests/jenkins/TestStandardReleasePipelineWithGenericTriggers.groovy create mode 100644 tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile create mode 100644 tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile.txt create mode 100644 tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile create mode 100644 tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile.txt create mode 100644 vars/standardReleasePipelineWithGenericTrigger.groovy diff --git a/README.md b/README.md index de6fc4ad5..4d73967de 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,8 @@ lib = library(identifier: 'jenkins@', retriever: modernSCM([ | Name | Description | |--------------------------------------------------------|:-----------------------------------------------------------------------------------------| | [standardReleasePipeline.groovy](./vars/standardReleasePipeline.groovy) | The library sets up the necessary jenkins properties for you such as agent label, docker image to use as well as workflow time out. Check how to use the [default](./tests/jenkins/jobs/StandardReleasePipeline_JenkinsFile) in your workflow and how to [overide](./tests/jenkins/jobs/StandardReleasePipelineWithArgs_JenkinsFile) agent & docker image if you need.| +| [standardReleasePipelineWithGenericTrigger.groovy](./vars/standardReleasePipelineWithGenericTrigger.groovy) | A standard release pipeline for OpenSearch projects including generic triggers. A tag or a draft release can be used as a trigger using this library. The defaults are all set to trigger via a draft release. If the release is successful, the release can be published by using right params.. Check how to use the [default](./tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile) in your workflow and how to [overide](./tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile) values| +| [publishToNpm.groovy](./vars/publishToNpm.groovy) | A library to publish artifacts to NPM registry under @opensearch-project namespace. You can use [PublishToNpmLibTester](./tests/jenkins/lib-testers/PublishToNpmLibTester.groovy) to add tests in your repository. See how to use the lib in your [jenkinsFile](./tests/jenkins/jobs/PublishToNpm_Jenkinsfile)| ## Contributing diff --git a/tests/jenkins/TestStandardReleasePipeline.groovy b/tests/jenkins/TestStandardReleasePipeline.groovy index 6f0fdfa6a..02f2c5706 100644 --- a/tests/jenkins/TestStandardReleasePipeline.groovy +++ b/tests/jenkins/TestStandardReleasePipeline.groovy @@ -46,7 +46,7 @@ class TestStandardReleasePipeline extends BuildPipelineTest { assertThat(echoCommand, hasItem('Executing on agent [docker:[image:test:image, reuseNode:false, stages:[:], args:, alwaysPull:true, containerPerStageRoot:false, label:AL2-X64]]')) } - @Test + @Test void 'check default values'(){ runScript("tests/jenkins/jobs/StandardReleasePipeline_JenkinsFile") def echoCommand = getEchoCommands().findAll{ diff --git a/tests/jenkins/TestStandardReleasePipelineWithGenericTriggers.groovy b/tests/jenkins/TestStandardReleasePipelineWithGenericTriggers.groovy new file mode 100644 index 000000000..8605ff79c --- /dev/null +++ b/tests/jenkins/TestStandardReleasePipelineWithGenericTriggers.groovy @@ -0,0 +1,101 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + + +import jenkins.tests.BuildPipelineTest +import org.junit.Before +import org.junit.Test +import static org.hamcrest.MatcherAssert.assertThat +import static org.hamcrest.CoreMatchers.equalTo +import static com.lesfurets.jenkins.unit.MethodCall.callArgsToString +import static org.hamcrest.CoreMatchers.hasItem + + + +class TestStandardReleasePipelineWithGenericTriggers extends BuildPipelineTest { + + @Before + void setUp() { + helper.registerAllowedMethod("GenericTrigger", [Map.class], null) + binding.setVariable('tag', '1.0.0') + binding.setVariable('release_url', 'https://api.github.com/repos/Codertocat/Hello-World/releases/17372790') + super.setUp() + } + + + @Test + void testStandardReleasePipelineWithGenericTriggers() { + super.testPipeline('tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile') + } + + @Test + void testStandardReleasePipelineWithTagTriggers() { + super.testPipeline('tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile') + } + + @Test + void 'validate override values'() { + runScript("tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile") + def echoCommand = getCommands('echo').findAll{ + command -> command.contains('agent') + } + + assertThat(echoCommand.size(), equalTo(1)) + assertThat(echoCommand, hasItem('Executing on agent [docker:[image:centos:7, reuseNode:false, stages:[:], args:, alwaysPull:true, containerPerStageRoot:false, label:AL2-X64]]')) + } + + @Test + void 'validate default triggers'(){ + runScript("tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile") + def cmd = getCommands('GenericTrigger').findAll{ + c -> c.contains('generic') + } + assertThat(cmd.size(), equalTo(1)) + assertThat(cmd, hasItem('{genericVariables=[{key=ref, value=$.release.tag_name}, {key=isDraft, value=$.release.draft}, {key=release_url, value=$.release.url}], tokenCredentialId=opensearch-ci-webhook-trigger-token, causeString=A tag was cut on opensearch-ci repo, printContributedVariables=false, printPostContent=false, regexpFilterText=$isDraft, regexpFilterExpression=true}')) + } + + @Test + void 'validate release is published'(){ + runScript("tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile") + def cmd = getCommands('sh').findAll{ + c -> c.contains('curl') + } + assertThat(cmd.size(), equalTo(1)) + assertThat(cmd, hasItem("curl -X PATCH -H 'Accept: application/vnd.github+json' -H 'Authorization: Bearer GIT_TOKEN' https://api.github.com/repos/Codertocat/Hello-World/releases/17372790 -d '{\"tag_name\":\"1.0.0\",\"draft\":false,\"prerelease\":false}'")) + + } + + @Test + void 'use tag as trigger'(){ + runScript("tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile") + def cmd = getCommands('GenericTrigger').findAll{ + c -> c.contains('generic') + } + assertThat(cmd.size(), equalTo(1)) + assertThat(cmd, hasItem('{genericVariables=[{key=ref, value=.ref}, {key=isDraft, value=$.release.draft}, {key=release_url, value=$.release.url}], tokenCredentialId=opensearch-ci-webhook-trigger-token, causeString=A tag was cut on opensearch-ci repo, printContributedVariables=false, printPostContent=false, regexpFilterText=$ref, regexpFilterExpression=^refs/tags/.*}')) + } + + @Test + void 'validate release is not published'(){ + runScript("tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile") + def cmd = getCommands('sh').findAll{ + c -> c.contains('curl') + } + assertThat(cmd.size(), equalTo(0)) + } + + def getCommands(String method) { + def echoCommands = helper.callStack.findAll { call -> + call.methodName == method + }.collect { call -> + callArgsToString(call) + } + return echoCommands + } +} diff --git a/tests/jenkins/jobs/PublishToNpm_Jenkinsfile.txt b/tests/jenkins/jobs/PublishToNpm_Jenkinsfile.txt index 4b072a9cc..61e6dc6d0 100644 --- a/tests/jenkins/jobs/PublishToNpm_Jenkinsfile.txt +++ b/tests/jenkins/jobs/PublishToNpm_Jenkinsfile.txt @@ -5,6 +5,6 @@ PublishToNpm_Jenkinsfile.script(groovy.lang.Closure) PublishToNpm_Jenkinsfile.publishToNpm({repository=https://github.com/opensearch-project/opensearch-ci, tag=1.0.0}) publishToNpm.checkout({$class=GitSCM, branches=[{name=1.0.0}], userRemoteConfigs=[{url=https://github.com/opensearch-project/opensearch-ci}]}) - publishToNpm.string({credentialsId=publish-to-npm-token, variable=NPM_TOKEN}) + publishToNpm.string({credentialsId=jenkins-opensearch-publish-to-npm-token, variable=NPM_TOKEN}) publishToNpm.withCredentials([NPM_TOKEN], groovy.lang.Closure) publishToNpm.sh(npm set registry "https://registry.npmjs.org"; npm set //registry.npmjs.org/:_authToken NPM_TOKEN; npm publish --dry-run && npm publish --access public) diff --git a/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile b/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile new file mode 100644 index 000000000..d1cfe7185 --- /dev/null +++ b/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile @@ -0,0 +1,14 @@ +standardReleasePipelineWithGenericTrigger(jsonValue: '.ref', + tokenIdCredential: 'opensearch-ci-webhook-trigger-token', + causeString: 'A tag was cut on opensearch-ci repo', + regexpFilterText: '$ref', + regexpFilterExpression: '^refs/tags/.*') +{ + fakePublishToNpm( + tag: "${tag}" + ) +} + +def fakePublishToNpm(Map args) { + echo "fakePublishToNpm ${args}" +} \ No newline at end of file diff --git a/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile.txt b/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile.txt new file mode 100644 index 000000000..40054e6f2 --- /dev/null +++ b/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile.txt @@ -0,0 +1,13 @@ + StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile.run() + StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile.standardReleasePipelineWithGenericTrigger({jsonValue=.ref, tokenIdCredential=opensearch-ci-webhook-trigger-token, causeString=A tag was cut on opensearch-ci repo, regexpFilterText=$ref, regexpFilterExpression=^refs/tags/.*}, groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.pipeline(groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.timeout({time=1, unit=HOURS}) + standardReleasePipelineWithGenericTrigger.echo(Executing on agent [docker:[image:opensearchstaging/ci-runner:release-centos7-clients-v1, reuseNode:false, stages:[:], args:, alwaysPull:true, containerPerStageRoot:false, label:Jenkins-Agent-AL2-X64-C54xlarge-Docker-Host]]) + standardReleasePipelineWithGenericTrigger.GenericTrigger({genericVariables=[{key=ref, value=.ref}, {key=isDraft, value=$.release.draft}, {key=release_url, value=$.release.url}], tokenCredentialId=opensearch-ci-webhook-trigger-token, causeString=A tag was cut on opensearch-ci repo, printContributedVariables=false, printPostContent=false, regexpFilterText=$ref, regexpFilterExpression=^refs/tags/.*}) + standardReleasePipelineWithGenericTrigger.stage(Release, groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.script(groovy.lang.Closure) + StandardReleasePipelineWithGenericTriggersTag_Jenkinsfile.echo(fakePublishToNpm [tag:1.0.0]) + standardReleasePipelineWithGenericTrigger.script(groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.postCleanup() + postCleanup.cleanWs({disableDeferredWipeout=true, deleteDirs=true}) + standardReleasePipelineWithGenericTrigger.script(groovy.lang.Closure) diff --git a/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile b/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile new file mode 100644 index 000000000..b2fe4e1ca --- /dev/null +++ b/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile @@ -0,0 +1,15 @@ +standardReleasePipelineWithGenericTrigger(overrideAgent: 'AL2-X64', + overrideDockerImage: 'centos:7', + tokenIdCredential: 'opensearch-ci-webhook-trigger-token', + causeString: 'A tag was cut on opensearch-ci repo', + publishRelease: true) +{ + fakePublishToMaven( + mavenArtifactsPath: "/maven", + autoPublish: true + ) +} + +def fakePublishToMaven(Map args) { + echo "fakePublishToMaven ${args}" +} \ No newline at end of file diff --git a/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile.txt b/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile.txt new file mode 100644 index 000000000..4352967cf --- /dev/null +++ b/tests/jenkins/jobs/StandardReleasePipelineWithGenericTriggers_Jenkinsfile.txt @@ -0,0 +1,16 @@ + StandardReleasePipelineWithGenericTriggers_Jenkinsfile.run() + StandardReleasePipelineWithGenericTriggers_Jenkinsfile.standardReleasePipelineWithGenericTrigger({overrideAgent=AL2-X64, overrideDockerImage=centos:7, tokenIdCredential=opensearch-ci-webhook-trigger-token, causeString=A tag was cut on opensearch-ci repo, publishRelease=true}, groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.pipeline(groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.timeout({time=1, unit=HOURS}) + standardReleasePipelineWithGenericTrigger.echo(Executing on agent [docker:[image:centos:7, reuseNode:false, stages:[:], args:, alwaysPull:true, containerPerStageRoot:false, label:AL2-X64]]) + standardReleasePipelineWithGenericTrigger.GenericTrigger({genericVariables=[{key=ref, value=$.release.tag_name}, {key=isDraft, value=$.release.draft}, {key=release_url, value=$.release.url}], tokenCredentialId=opensearch-ci-webhook-trigger-token, causeString=A tag was cut on opensearch-ci repo, printContributedVariables=false, printPostContent=false, regexpFilterText=$isDraft, regexpFilterExpression=true}) + standardReleasePipelineWithGenericTrigger.stage(Release, groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.script(groovy.lang.Closure) + StandardReleasePipelineWithGenericTriggers_Jenkinsfile.echo(fakePublishToMaven [mavenArtifactsPath:/maven, autoPublish:true]) + standardReleasePipelineWithGenericTrigger.script(groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.postCleanup() + postCleanup.cleanWs({disableDeferredWipeout=true, deleteDirs=true}) + standardReleasePipelineWithGenericTrigger.script(groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.string({credentialsId=jenkins-github-bot-token, variable=GIT_TOKEN}) + standardReleasePipelineWithGenericTrigger.withCredentials([GIT_TOKEN], groovy.lang.Closure) + standardReleasePipelineWithGenericTrigger.sh(curl -X PATCH -H 'Accept: application/vnd.github+json' -H 'Authorization: Bearer GIT_TOKEN' https://api.github.com/repos/Codertocat/Hello-World/releases/17372790 -d '{"tag_name":"1.0.0","draft":false,"prerelease":false}') diff --git a/vars/publishToNpm.groovy b/vars/publishToNpm.groovy index a7e0ead6d..9b2ae47c6 100644 --- a/vars/publishToNpm.groovy +++ b/vars/publishToNpm.groovy @@ -16,7 +16,7 @@ void call(Map args = [:]) { checkout([$class: 'GitSCM', branches: [[name: "${args.tag}" ]], userRemoteConfigs: [[url: "${args.repository}" ]]]) - withCredentials([string(credentialsId: 'publish-to-npm-token', variable: 'NPM_TOKEN')]){ + withCredentials([string(credentialsId: 'jenkins-opensearch-publish-to-npm-token', variable: 'NPM_TOKEN')]){ sh """npm set registry "https://registry.npmjs.org"; npm set //registry.npmjs.org/:_authToken ${NPM_TOKEN}; npm publish --dry-run && npm publish --access public""" } } \ No newline at end of file diff --git a/vars/standardReleasePipelineWithGenericTrigger.groovy b/vars/standardReleasePipelineWithGenericTrigger.groovy new file mode 100644 index 000000000..d04a2bbe1 --- /dev/null +++ b/vars/standardReleasePipelineWithGenericTrigger.groovy @@ -0,0 +1,80 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** A standard release pipeline for OpenSearch projects including generic triggers. A tag or a draft release can be used as a trigger using this library. The defaults are all set to trigger via a draft release. If the release is successful, the release can be published by using right params. +@param Map args = [:] args A map of the following parameters +@param body - A closure containing release steps to be executed in release stage. +@param args.tokenIdCredential - Credential id containing token for trigger authentication +@param args.overrideAgent - Jenkins agent label to override the default ('Jenkins-Agent-AL2-X64-C54xlarge-Docker-Host') +@param args.overrideDockerImage - Docker image to override the default ('opensearchstaging/ci-runner:release-centos7-clients-v1') +@param args.jsonValue - Json value retrieved from payload of the webhook. Defaults to '$.release.tag_name' +@param args.causeString - String mentioning why the workflow was triggered. Defaults to 'A tag was cut on GitHub repository causing this workflow to run' +@param args.regexpFilterText - Variable to apply regular expression on. Defaults to '$isDraft' +@param.regexpFilterExpression - Regular expression to test on the evaluated text specified. Defaults to '' +@param.publishRelease - If set to true the release that triggered the job will be published on GitHub. +*/ + +void call(Map args = [:], Closure body) { + pipeline { + agent + { + docker { + label args.overrideAgent ?: 'Jenkins-Agent-AL2-X64-C54xlarge-Docker-Host' + image args.overrideDockerImage ?: 'opensearchstaging/ci-runner:release-centos7-clients-v1' + alwaysPull true + } + } + options { + timeout(time: 1, unit: 'HOURS') + } + triggers { + GenericTrigger( + genericVariables: [ + [key: 'ref', value: (args.jsonValue ?: '$.release.tag_name')], + [key: 'isDraft', value: '$.release.draft'], + [key: 'release_url', value: '$.release.url'] + ], + tokenCredentialId: args.tokenIdCredential, + causeString: args.causeString ?: 'A tag was cut on GitHub repository causing this workflow to run', + printContributedVariables: false, + printPostContent: false, + regexpFilterText: (args.regexpFilterText ?: '$isDraft'), + regexpFilterExpression: (args.regexpFilterExpression ?: 'true') + ) + } + environment { + tag = "$ref" + } + stages{ + stage("Release") { + steps { + script { + body() + } + } + } + } + post { + success { + script { + if (args.publishRelease && release_url!= null){ + withCredentials([string(credentialsId: 'jenkins-github-bot-token', variable: 'GIT_TOKEN')]){ + sh "curl -X PATCH -H 'Accept: application/vnd.github+json' -H 'Authorization: Bearer ${GIT_TOKEN}' ${release_url} -d '{\"tag_name\":\"${tag}\",\"draft\":false,\"prerelease\":false}'" + } + } + } + } + always { + script { + postCleanup() + } + } + } + } + } \ No newline at end of file