From dd9e26042d1fb81a9f5b3a9cd8e917dba9e3860a Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Thu, 25 Oct 2018 08:41:50 +0200 Subject: [PATCH 1/2] Fix trailing whitespaces --- test/groovy/CheckChangeInDevelopmentTest.groovy | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/groovy/CheckChangeInDevelopmentTest.groovy b/test/groovy/CheckChangeInDevelopmentTest.groovy index bf994d7b2..f9214b437 100644 --- a/test/groovy/CheckChangeInDevelopmentTest.groovy +++ b/test/groovy/CheckChangeInDevelopmentTest.groovy @@ -41,7 +41,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest { ChangeManagement cm = getChangeManagementUtils(true) jsr.step.checkChangeInDevelopment( - script: nullScript, + script: nullScript, cmUtils: cm, changeManagement: [endpoint: 'https://example.org/cm'], failIfStatusIsNotInDevelopment: true) @@ -64,7 +64,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest { ChangeManagement cm = getChangeManagementUtils(false) jsr.step.checkChangeInDevelopment( - script: nullScript, + script: nullScript, cmUtils: cm, changeManagement: [endpoint: 'https://example.org/cm']) } @@ -74,7 +74,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest { ChangeManagement cm = getChangeManagementUtils(false) boolean inDevelopment = jsr.step.checkChangeInDevelopment( - script: nullScript, + script: nullScript, cmUtils: cm, changeManagement: [endpoint: 'https://example.org/cm'], failIfStatusIsNotInDevelopment: false) @@ -86,7 +86,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest { ChangeManagement cm = getChangeManagementUtils(true, '0815') jsr.step.checkChangeInDevelopment( - script: nullScript, + script: nullScript, changeDocumentId: '42', cmUtils: cm, changeManagement: [endpoint: 'https://example.org/cm']) @@ -99,7 +99,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest { ChangeManagement cm = getChangeManagementUtils(true, '0815') jsr.step.checkChangeInDevelopment( - script: nullScript, + script: nullScript, cmUtils: cm, changeManagement : [endpoint: 'https://example.org/cm']) @@ -125,7 +125,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest { } jsr.step.checkChangeInDevelopment( - script: nullScript, + script: nullScript, cmUtils: cm, changeManagement: [endpoint: 'https://example.org/cm']) } @@ -140,7 +140,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest { ChangeManagement cm = getChangeManagementUtils(false, null) jsr.step.checkChangeInDevelopment( - script: nullScript, + script: nullScript, cmUtils: cm, changeManagement: [endpoint: 'https://example.org/cm']) } @@ -155,7 +155,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest { ChangeManagement cm = getChangeManagementUtils(false, '') jsr.step.checkChangeInDevelopment( - script: nullScript, + script: nullScript, cmUtils: cm, changeManagement: [endpoint: 'https://example.org/cm']) } From a8a29b753ab7269d2716ee8bf0526433a0bc4ebc Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Thu, 25 Oct 2018 16:56:09 +0200 Subject: [PATCH 2/2] add step githubPublishRelease (#345) This step allows to easily create a new release for your GitHub repository * include PR review feedback - add additional labels * consider return status of api call --- documentation/docs/images/githubRelease.png | Bin 0 -> 53084 bytes .../docs/steps/githubPublishRelease.md | 77 +++++++ documentation/mkdocs.yml | 1 + resources/default_pipeline_environment.yml | 11 + test/groovy/GithubPublishReleaseTest.groovy | 196 ++++++++++++++++++ vars/githubPublishRelease.groovy | 134 ++++++++++++ 6 files changed, 419 insertions(+) create mode 100644 documentation/docs/images/githubRelease.png create mode 100644 documentation/docs/steps/githubPublishRelease.md create mode 100644 test/groovy/GithubPublishReleaseTest.groovy create mode 100644 vars/githubPublishRelease.groovy diff --git a/documentation/docs/images/githubRelease.png b/documentation/docs/images/githubRelease.png new file mode 100644 index 0000000000000000000000000000000000000000..84813ca04c07da5f7159b8f244cfd6321b8e7573 GIT binary patch literal 53084 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 Aw*UYD literal 0 HcmV?d00001 diff --git a/documentation/docs/steps/githubPublishRelease.md b/documentation/docs/steps/githubPublishRelease.md new file mode 100644 index 000000000..7e22fa8bf --- /dev/null +++ b/documentation/docs/steps/githubPublishRelease.md @@ -0,0 +1,77 @@ +# githubPublishRelease + +## Description +This step creates a tag in your GitHub repository together with a release. + +The release can be filled with text plus additional information like: + +* Closed pull request since last release +* Closed issues since last release +* link to delta information showing all commits since last release + +The result looks like + +![Example release](../images/githubRelease.png) + +## Prerequisites +You need to create a personal access token within GitHub and add this to the Jenkins credentials store. + +Please see [GitHub documentation for details about creating the personal access token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/). + +## Example + +Usage of pipeline step: + +```groovy +githubPublishRelease script: this, releaseBodyHeader: "**This is the latest success!**
" +``` + +## Parameters + +| parameter | mandatory | default | possible values | +| ----------|-----------|---------|-----------------| +|script|yes||| +|addClosedIssues|no|`false`|| +|addDeltaToLastRelease|no|`false`|| +|customFilterExtension|no|``|| +|excludeLabels|no|
  • `duplicate`
  • `invalid`
  • `question`
  • `wontfix`
|| +|githubApiUrl|no|`//https://api.github.com`|| +|githubOrg|yes|`script.commonPipelineEnvironment.getGitFolder()`|| +|githubRepo|yes|`script.commonPipelineEnvironment.getGitRepo()`|| +|githubServerUrl|no|`https://github.com`|| +|githubTokenCredentialsId|yes||| +|releaseBodyHeader|no||| +|version|yes|`script.commonPipelineEnvironment.getArtifactVersion()`|| + +### Details: + +* `script` defines the global script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for storing the measured duration. +* All GitHub related properties allow you to overwrite the default behavior of identifying e.g. GitHub organization, GitHub repository. +* `version` defines the version number which will be written as tag as well as release name +* By defining the `releaseBodyHeader` you can specify the content which will appear for the release +* If you set `addClosedIssues` to `true`, a list of all closed issues and merged pull-requests since the last release will added below the `releaseBodyHeader` +* If you set `addDeltaToLastRelease` to `true`, a link will be added to the relese information that brings up all commits since the last release. +* By passing the parameter `customFilterExtension` it is possible to pass additional filter criteria for retrieving closed issues since the last release. Additional criteria could be for example specific `label`, or `filter` according to [GitHub API documentation](https://developer.github.com/v3/issues/). +* It is possible to exclude issues with dedicated labels using parameter `excludeLabels`. Usage is like `excludeLabels: ['label1', 'label2']` + + +## Step configuration + +We recommend to define values of step parameters via [config.yml file](../configuration.md). + +In following sections the configuration is possible: + +| parameter | general | step | stage | +| ----------|-----------|---------|-----------------| +|script|||| +|addClosedIssues||X|X| +|addDeltaToLastRelease||X|X| +|customFilterExtension||X|X| +|excludeLabels||X|X| +|githubApiUrl|X|X|X| +|githubOrg||X|X| +|githubRepo||X|X| +|githubServerUrl|X|X|X| +|githubTokenCredentialsId|X|X|X| +|releaseBodyHeader||X|X| +|version||X|X| diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 0947c2865..cef9c12a4 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -12,6 +12,7 @@ nav: - dockerExecute: steps/dockerExecute.md - dockerExecuteOnKubernetes: steps/dockerExecuteOnKubernetes.md - durationMeasure: steps/durationMeasure.md + - githubPublishRelease: steps/githubPublishRelease.md - gaugeExecuteTests: steps/gaugeExecuteTests.md - handlePipelineStepErrors: steps/handlePipelineStepErrors.md - healthExecuteCheck: steps/healthExecuteCheck.md diff --git a/resources/default_pipeline_environment.yml b/resources/default_pipeline_environment.yml index 2c3d50683..9656e584b 100644 --- a/resources/default_pipeline_environment.yml +++ b/resources/default_pipeline_environment.yml @@ -12,6 +12,8 @@ general: from: 'origin/master' to: 'HEAD' format: '%b' + githubApiUrl: 'https://api.github.com' + githubServerUrl: 'https://github.com' gitSshKeyCredentialsId: '' #needed to allow sshagent to run with local ssh key jenkinsKubernetes: jnlpAgent: 's4sdk/jenkins-agent-k8s:latest' @@ -140,6 +142,15 @@ steps: workspace: '**/*.*' stashExcludes: workspace: 'nohup.out' + githubPublishRelease: + addClosedIssues: false + addDeltaToLastRelease: false + customFilterExtension: '' + excludeLabels: + - 'duplicate' + - 'invalid' + - 'question' + - 'wontfix' gaugeExecuteTests: buildTool: 'maven' dockerEnvVars: diff --git a/test/groovy/GithubPublishReleaseTest.groovy b/test/groovy/GithubPublishReleaseTest.groovy new file mode 100644 index 000000000..7473e8a8c --- /dev/null +++ b/test/groovy/GithubPublishReleaseTest.groovy @@ -0,0 +1,196 @@ +#!groovy +import groovy.json.JsonSlurperClassic +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.junit.rules.RuleChain +import util.BasePiperTest +import util.JenkinsCredentialsRule +import util.JenkinsLoggingRule +import util.JenkinsReadJsonRule +import util.JenkinsReadYamlRule +import util.JenkinsStepRule +import util.Rules + +import static org.hamcrest.Matchers.* +import static org.junit.Assert.assertThat + +class GithubPublishReleaseTest extends BasePiperTest { + private JenkinsStepRule jsr = new JenkinsStepRule(this) + private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this) + private JenkinsReadJsonRule jrjr = new JenkinsReadJsonRule(this) + private ExpectedException thrown = ExpectedException.none() + + @Rule + public RuleChain rules = Rules + .getCommonRules(this) + .around(new JenkinsReadYamlRule(this)) + .around(jlr) + .around(jrjr) + .around(jsr) + .around(thrown) + + def data + def requestList = [] + + @Before + void init() throws Exception { + // register Jenkins commands with mock values + helper.registerAllowedMethod( "deleteDir", [], null ) + helper.registerAllowedMethod("httpRequest", [], null) + helper.registerAllowedMethod('string', [Map], { m -> return m }) + helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c -> + try { + l.each {Map settings -> + binding.setProperty(settings.variable, '********') + } + c() + }finally { + l.each {Map settings -> + binding.setProperty(settings.variable, null) + } + } + }) + + def responseLatestRelease = '{"url":"https://api.github.com/SAP/jenkins-library/releases/26581","assets_url":"https://api.github.com/SAP/jenkins-library/releases/26581/assets","upload_url":"https://github.com/api/uploads/repos/ContinuousDelivery/piper-library/releases/26581/assets{?name,label}","html_url":"https://github.com/ContinuousDelivery/piper-library/releases/tag/1.11.0-20180409-074550","id":26581,"tag_name":"1.11.0-20180409-074550","target_commitish":"master","name":"1.11.0-20180409-074550","draft":false,"author":{"login":"XTEST1","id":1809,"avatar_url":"https://github.com/avatars/u/1809?","gravatar_id":"","url":"https://api.github.com/users/XTEST1","html_url":"https://github.com/XTEST1","followers_url":"https://api.github.com/users/XTEST1/followers","following_url":"https://api.github.com/users/XTEST1/following{/other_user}","gists_url":"https://api.github.com/users/XTEST1/gists{/gist_id}","starred_url":"https://api.github.com/users/XTEST1/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XTEST1/subscriptions","organizations_url":"https://api.github.com/users/XTEST1/orgs","repos_url":"https://api.github.com/users/XTEST1/repos","events_url":"https://api.github.com/users/XTEST1/events{/privacy}","received_events_url":"https://api.github.com/users/XTEST1/received_events","type":"User","site_admin":false},"prerelease":false,"created_at":"2018-04-09T07:45:38Z","published_at":"2018-04-09T07:52:49Z","assets":[],"tarball_url":"https://api.github.com/SAP/jenkins-library/tarball/1.11.0-20180409-074550","zipball_url":"https://api.github.com/SAP/jenkins-library/zipball/1.11.0-20180409-074550","body":"

**List of closed pull-requests since last release**
[# 887](https://github.com/ContinuousDelivery/piper-library/pull/887): Add tests for checkmarx step
[# 907](https://github.com/ContinuousDelivery/piper-library/pull/907): Enable triaging
[# 908](https://github.com/ContinuousDelivery/piper-library/pull/908): SourceClear support reporting into fixed version
[# 909](https://github.com/ContinuousDelivery/piper-library/pull/909): add UserTriggerCause
[# 910](https://github.com/ContinuousDelivery/piper-library/pull/910): deployToKubernetes support of kubectl based deployment
[# 912](https://github.com/ContinuousDelivery/piper-library/pull/912): Speed up tests
[# 914](https://github.com/ContinuousDelivery/piper-library/pull/914): update config usage in writeInflux
[# 915](https://github.com/ContinuousDelivery/piper-library/pull/915): update config usage in executeVulasScan
[# 918](https://github.com/ContinuousDelivery/piper-library/pull/918): switch to new slack channel name
[# 921](https://github.com/ContinuousDelivery/piper-library/pull/921): correct utils object for restartableSteps step
[# 922](https://github.com/ContinuousDelivery/piper-library/pull/922): add default influx server
[# 924](https://github.com/ContinuousDelivery/piper-library/pull/924): update config usage in setupPipelineEnvironment
[# 925](https://github.com/ContinuousDelivery/piper-library/pull/925): Unstash content earlier to avoid FileNotFoundException
[# 927](https://github.com/ContinuousDelivery/piper-library/pull/927): Revert SourceClear support reporting into fixed version
[# 928](https://github.com/ContinuousDelivery/piper-library/pull/928): Fix rolling back mock behavior
[# 929](https://github.com/ContinuousDelivery/piper-library/pull/929): Add post-deploy actions via body
[# 930](https://github.com/ContinuousDelivery/piper-library/pull/930): parameters passed to resolveFortifyCredentialsID
[# 931](https://github.com/ContinuousDelivery/piper-library/pull/931): simplify bower installation for source clear
[# 932](https://github.com/ContinuousDelivery/piper-library/pull/932): use descriptive message for nodeAvailable
[# 934](https://github.com/ContinuousDelivery/piper-library/pull/934): Cease support for fortify technical user
[# 937](https://github.com/ContinuousDelivery/piper-library/pull/937): setVersion - allow extension of maven parameters
[# 938](https://github.com/ContinuousDelivery/piper-library/pull/938): Improve Protecode vulnerability processing
[# 939](https://github.com/ContinuousDelivery/piper-library/pull/939): xMake Docker metadata available in global pipeline environment
[# 940](https://github.com/ContinuousDelivery/piper-library/pull/940): Add missing hand-over of globalPipelineEnvironment
[# 944](https://github.com/ContinuousDelivery/piper-library/pull/944): fix: translate gh issues in traceability reports to proper urls
[# 945](https://github.com/ContinuousDelivery/piper-library/pull/945): Bump Version

**List of closed issues since last release**
[# 382](https://github.com/ContinuousDelivery/piper-library/issues/382): Snyk for security testing
[# 579](https://github.com/ContinuousDelivery/piper-library/issues/579): Evaluate required enhancements for Kubernetes
[# 638](https://github.com/ContinuousDelivery/piper-library/issues/638): Integrate Protecode for Docker scanning
[# 878](https://github.com/ContinuousDelivery/piper-library/issues/878): setVersion - allow parametrization of Maven call
[# 920](https://github.com/ContinuousDelivery/piper-library/issues/920): restartableSteps refers to wrong utils object
[# 941](https://github.com/ContinuousDelivery/piper-library/issues/941): issue in deep config merge

**Changes**
[1.10.0-20180326-070201...1.11.0-20180409-074550](https://github.com/ContinuousDelivery/piper-library/compare/1.10.0-20180326-070201...1.11.0-20180409-074550)
"}' + def responseIssues = '[{"url":"https://api.github.com/SAP/jenkins-library/issues/13","repository_url":"https://api.github.com/SAP/jenkins-library","labels_url":"https://api.github.com/SAP/jenkins-library/issues/13/labels{/name}","comments_url":"https://api.github.com/SAP/jenkins-library/issues/13/comments","events_url":"https://api.github.com/SAP/jenkins-library/issues/13/events","html_url":"https://github.com/ContinuousDelivery/piper-library/issues/13","id":422536,"number":13,"title":"influx: add function to include performance result file (CSV)","user":{"login":"XTEST2","id":6991,"avatar_url":"https://github.com/avatars/u/6991?","gravatar_id":"","url":"https://api.github.com/users/XTEST2","html_url":"https://github.com/XTEST2","followers_url":"https://api.github.com/users/XTEST2/followers","following_url":"https://api.github.com/users/XTEST2/following{/other_user}","gists_url":"https://api.github.com/users/XTEST2/gists{/gist_id}","starred_url":"https://api.github.com/users/XTEST2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XTEST2/subscriptions","organizations_url":"https://api.github.com/users/XTEST2/orgs","repos_url":"https://api.github.com/users/XTEST2/repos","events_url":"https://api.github.com/users/XTEST2/events{/privacy}","received_events_url":"https://api.github.com/users/XTEST2/received_events","type":"User","site_admin":false},"labels":[{"id":541874,"url":"https://api.github.com/SAP/jenkins-library/labels/enhancement","name":"enhancement","color":"84b6eb","default":true}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2017-03-17T10:23:08Z","updated_at":"2017-08-02T08:39:37Z","closed_at":"2017-08-02T08:39:37Z","author_association":"OWNER","body":""},{"url":"https://api.github.com/SAP/jenkins-library/issues/21","repository_url":"https://api.github.com/SAP/jenkins-library","labels_url":"https://api.github.com/SAP/jenkins-library/issues/21/labels{/name}","comments_url":"https://api.github.com/SAP/jenkins-library/issues/21/comments","events_url":"https://api.github.com/SAP/jenkins-library/issues/21/events","html_url":"https://github.com/ContinuousDelivery/piper-library/issues/21","id":422768,"number":21,"title":"environment: provide convenient method to get property as boolean","user":{"login":"XTEST2","id":6991,"avatar_url":"https://github.com/avatars/u/6991?","gravatar_id":"","url":"https://api.github.com/users/XTEST2","html_url":"https://github.com/XTEST2","followers_url":"https://api.github.com/users/XTEST2/followers","following_url":"https://api.github.com/users/XTEST2/following{/other_user}","gists_url":"https://api.github.com/users/XTEST2/gists{/gist_id}","starred_url":"https://api.github.com/users/XTEST2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XTEST2/subscriptions","organizations_url":"https://api.github.com/users/XTEST2/orgs","repos_url":"https://api.github.com/users/XTEST2/repos","events_url":"https://api.github.com/users/XTEST2/events{/privacy}","received_events_url":"https://api.github.com/users/XTEST2/received_events","type":"User","site_admin":false},"labels":[{"id":541874,"url":"https://api.github.com/SAP/jenkins-library/labels/enhancement","name":"enhancement","color":"84b6eb","default":true}],"state":"closed","locked":false,"assignee":{"login":"XTEST2","id":6991,"avatar_url":"https://github.com/avatars/u/6991?","gravatar_id":"","url":"https://api.github.com/users/XTEST2","html_url":"https://github.com/XTEST2","followers_url":"https://api.github.com/users/XTEST2/followers","following_url":"https://api.github.com/users/XTEST2/following{/other_user}","gists_url":"https://api.github.com/users/XTEST2/gists{/gist_id}","starred_url":"https://api.github.com/users/XTEST2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XTEST2/subscriptions","organizations_url":"https://api.github.com/users/XTEST2/orgs","repos_url":"https://api.github.com/users/XTEST2/repos","events_url":"https://api.github.com/users/XTEST2/events{/privacy}","received_events_url":"https://api.github.com/users/XTEST2/received_events","type":"User","site_admin":false},"assignees":[{"login":"XTEST2","id":6991,"avatar_url":"https://github.com/avatars/u/6991?","gravatar_id":"","url":"https://api.github.com/users/XTEST2","html_url":"https://github.com/XTEST2","followers_url":"https://api.github.com/users/XTEST2/followers","following_url":"https://api.github.com/users/XTEST2/following{/other_user}","gists_url":"https://api.github.com/users/XTEST2/gists{/gist_id}","starred_url":"https://api.github.com/users/XTEST2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XTEST2/subscriptions","organizations_url":"https://api.github.com/users/XTEST2/orgs","repos_url":"https://api.github.com/users/XTEST2/repos","events_url":"https://api.github.com/users/XTEST2/events{/privacy}","received_events_url":"https://api.github.com/users/XTEST2/received_events","type":"User","site_admin":false}],"milestone":null,"comments":1,"created_at":"2017-03-17T13:24:21Z","updated_at":"2017-08-03T09:32:45Z","closed_at":"2017-08-03T09:32:45Z","author_association":"OWNER","body":""}]' + def responseRelease = '{"url":"https://api.github.com/SAP/jenkins-library/releases/27149","assets_url":"https://api.github.com/SAP/jenkins-library/releases/27149/assets","upload_url":"https://github.com/api/uploads/repos/ContinuousDelivery/piper-library/releases/27149/assets{?name,label}","html_url":"https://github.com/ContinuousDelivery/piper-library/releases/tag/test","id":27149,"tag_name":"test","target_commitish":"master","name":"v1.0.0","draft":false,"author":{"login":"XTEST2","id":6991,"avatar_url":"https://github.com/avatars/u/6991?","gravatar_id":"","url":"https://api.github.com/users/XTEST2","html_url":"https://github.com/XTEST2","followers_url":"https://api.github.com/users/XTEST2/followers","following_url":"https://api.github.com/users/XTEST2/following{/other_user}","gists_url":"https://api.github.com/users/XTEST2/gists{/gist_id}","starred_url":"https://api.github.com/users/XTEST2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/XTEST2/subscriptions","organizations_url":"https://api.github.com/users/XTEST2/orgs","repos_url":"https://api.github.com/users/XTEST2/repos","events_url":"https://api.github.com/users/XTEST2/events{/privacy}","received_events_url":"https://api.github.com/users/XTEST2/received_events","type":"User","site_admin":false},"prerelease":false,"created_at":"2018-04-18T11:00:17Z","published_at":"2018-04-18T11:32:34Z","assets":[],"tarball_url":"https://api.github.com/SAP/jenkins-library/tarball/test","zipball_url":"https://api.github.com/SAP/jenkins-library/zipball/test","body":"Description of the release"}' + + helper.registerAllowedMethod("httpRequest", [String.class], { s -> + def result = [status: 404] + requestList.push(s.toString()) + if(s.contains('/releases/latest?')) { + result.content = responseLatestRelease + result.status = 200 + } else if(s.contains('/issues?')) { + result.content = responseIssues + result.status = 200 + } + return result + }) + helper.registerAllowedMethod("httpRequest", [Map.class], { m -> + def result = '' + requestList.push(m?.url?.toString()) + if(m?.url?.contains('/releases?')){ + data = new JsonSlurperClassic().parseText(m?.requestBody?.toString()) + result = responseRelease + } + return [content: result] + }) + } + + @Test + void testPublishGithubReleaseWithDefaults() throws Exception { + jsr.step.githubPublishRelease( + script: nullScript, + githubOrg: 'TestOrg', + githubRepo: 'TestRepo', + githubTokenCredentialsId: 'TestCredentials', + version: '1.2.3' + ) + // asserts + assertThat('this is not handled as a first release', jlr.log, not(containsString('[githubPublishRelease] This is the first release - no previous releases available'))) + assertThat('every request starts with the github api url', requestList, everyItem(startsWith('https://api.github.com'))) + assertThat('every request contains the github org & repo', requestList, everyItem(containsString('/TestOrg/TestRepo/'))) + // test githubTokenCredentialsId + assertThat('every request has an access token', requestList, everyItem(containsString('access_token=********'))) + // test releaseBodyHeader + assertThat('the header is not set', data.body, startsWith('')) + // test addClosedIssues + assertThat('the list of closed PR is not present', data.body, not(containsString('**List of closed pull-requests since last release**'))) + assertThat('the list of closed issues is not present', data.body, not(containsString('**List of closed issues since last release**'))) + // test addDeltaToLastRelease + assertThat('the compare link is not present', data.body, not(containsString('[1.11.0-20180409-074550...1.2.3]'))) + + assertThat(data.name, is('1.2.3')) + assertThat(data.tag_name, is('1.2.3')) + assertThat(data.draft, is(false)) + assertThat(data.prerelease, is(false)) + assertJobStatusSuccess() + } + + @Test + void testPublishGithubRelease() throws Exception { + jsr.step.githubPublishRelease( + script: nullScript, + githubOrg: 'TestOrg', + githubRepo: 'TestRepo', + githubTokenCredentialsId: 'TestCredentials', + version: '1.2.3', + releaseBodyHeader: '**TestHeader**', + addClosedIssues: true, + addDeltaToLastRelease: true + ) + // asserts + assertThat('this is not handled as a first release', jlr.log, not(containsString('[githubPublishRelease] This is the first release - no previous releases available'))) + assertThat('every request starts with the github api url', requestList, everyItem(startsWith('https://api.github.com'))) + assertThat('every request contains the github org & repo', requestList, everyItem(containsString('/TestOrg/TestRepo/'))) + // test githubTokenCredentialsId + assertThat('every request has an access token', requestList, everyItem(containsString('access_token=********'))) + // test releaseBodyHeader + assertThat('the header is set', data.body, startsWith('**TestHeader**')) + // test addClosedIssues + assertThat('the list of closed PR is present', data.body, containsString('**List of closed pull-requests since last release**')) + assertThat('the list of closed issues is present', data.body, containsString('**List of closed issues since last release**')) + // test addDeltaToLastRelease + assertThat('the compare link is present', data.body, containsString('[1.11.0-20180409-074550...1.2.3]')) + assertThat('the default github url is used', data.body, containsString('https://github.com')) + + //test fix for https://github.com/ContinuousDelivery/piper-library/issues/1047 + assertThat(requestList[1].toString(), is('https://api.github.com/repos/TestOrg/TestRepo/issues?access_token=********&per_page=100&state=closed&direction=asc&since=2018-04-09T07:52:49Z')) + + assertThat(data.name, is('1.2.3')) + assertThat(data.tag_name, is('1.2.3')) + assertThat(data.draft, is(false)) + assertThat(data.prerelease, is(false)) + assertJobStatusSuccess() + } + + @Test + void testExcludeLabels() throws Exception { + jsr.step.githubPublishRelease( + script: nullScript, + githubOrg: 'TestOrg', + githubRepo: 'TestRepo', + githubTokenCredentialsId: 'TestCredentials', + version: '1.2.3', + releaseBodyHeader: '**TestHeader**', + addClosedIssues: true, + addDeltaToLastRelease: true, + excludeLabels: ['enhancement'] + ) + // asserts + assertThat('issues with excluded labels are not listed', data.body, not(containsString('influx: add function to include performance result file (CSV)'))) + assertJobStatusSuccess() + } + + @Test + void testIsExcluded() throws Exception { + def item = new JsonSlurperClassic().parseText('''{ + "id": 422536, + "number": 13, + "title": "influx: add function to include performance result file (CSV)", + "user": { + "login": "XTEST2", + "id": 6991, + "type": "User", + "site_admin": false + }, + "labels": [{ + "id": 541874, + "url": "https://api.github.com/SAP/jenkins-library/labels/enhancement", + "name": "enhancement", + "color": "84b6eb", + "default": true + }], + "state": "closed", + "locked": false, + "body": "" + }''') + // asserts + assertThat(jsr.step.isExcluded(item, ['enhancement', 'won\'t fix']), is(true)) + assertThat(jsr.step.isExcluded(item, ['won\'t fix']), is(false)) + assertJobStatusSuccess() + } +} diff --git a/vars/githubPublishRelease.groovy b/vars/githubPublishRelease.groovy new file mode 100644 index 000000000..42b13fa46 --- /dev/null +++ b/vars/githubPublishRelease.groovy @@ -0,0 +1,134 @@ +import com.sap.piper.Utils +import com.sap.piper.ConfigurationHelper + +import groovy.transform.Field + +@Field String STEP_NAME = 'githubPublishRelease' +@Field Set GENERAL_CONFIG_KEYS = ['githubApiUrl', 'githubTokenCredentialsId', 'githubServerUrl'] +@Field Set STEP_CONFIG_KEYS = [ + 'addClosedIssues', + 'addDeltaToLastRelease', + 'customFilterExtension', + 'excludeLabels', + 'githubApiUrl', + 'githubTokenCredentialsId', + 'githubOrg', + 'githubRepo', + 'githubServerUrl', + 'releaseBodyHeader', + 'version' +] +@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS + +void call(Map parameters = [:]) { + handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) { + def script = parameters.script ?: [commonPipelineEnvironment: commonPipelineEnvironment] + + // load default & individual configuration + Map config = ConfigurationHelper.newInstance(this) + .loadStepDefaults() + .mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS) + .mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS) + .mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS) + .mixin(parameters, PARAMETER_KEYS) + .addIfEmpty('githubOrg', script.commonPipelineEnvironment.getGithubOrg()) + .addIfEmpty('githubRepo', script.commonPipelineEnvironment.getGithubRepo()) + .addIfEmpty('version', script.commonPipelineEnvironment.getArtifactVersion()) + .withMandatoryProperty('githubOrg') + .withMandatoryProperty('githubRepo') + .withMandatoryProperty('githubTokenCredentialsId') + .withMandatoryProperty('version') + .use() + + new Utils().pushToSWA([step: STEP_NAME], config) + + withCredentials([string(credentialsId: config.githubTokenCredentialsId, variable: 'TOKEN')]) { + def releaseBody = config.releaseBodyHeader?"${config.releaseBodyHeader}
":'' + def content = getLastRelease(config, TOKEN) + if (config.addClosedIssues) + releaseBody += addClosedIssue(config, TOKEN, content.published_at) + if (config.addDeltaToLastRelease) + releaseBody += addDeltaToLastRelease(config, content.tag_name) + postNewRelease(config, TOKEN, releaseBody) + } + } +} + +Map getLastRelease(config, TOKEN){ + def result = [:] + + def response = httpRequest "${config.githubApiUrl}/repos/${config.githubOrg}/${config.githubRepo}/releases/latest?access_token=${TOKEN}" + if (response.status == 200) { + result = readJSON text: response.content + } else { + echo "[${STEP_NAME}] This is the first release - no previous releases available" + config.addDeltaToLastRelease = false + } + return result +} + +String addClosedIssue(config, TOKEN, publishedAt){ + if (config.customFilterExtension) { + config.customFilterExtension = "&${config.customFilterExtension}" + } + + def publishedAtFilter = publishedAt ? "&since=${publishedAt}": '' + + def response = httpRequest "${config.githubApiUrl}/repos/${config.githubOrg}/${config.githubRepo}/issues?access_token=${TOKEN}&per_page=100&state=closed&direction=asc${publishedAtFilter}${config.customFilterExtension}" + def result = '' + + content = readJSON text: response.content + + //list closed pull-requests + result += '
**List of closed pull-requests since last release**
' + for (def item : content) { + if (item.pull_request && !isExcluded(item, config.excludeLabels)) { + result += "[#${item.number}](${item.html_url}): ${item.title}
" + } + } + //list closed issues + result += '
**List of closed issues since last release**
' + for (def item : content) { + if (!item.pull_request && !isExcluded(item, config.excludeLabels)) { + result += "[#${item.number}](${item.html_url}): ${item.title}
" + } + } + return result +} + +String addDeltaToLastRelease(config, latestTag){ + def result = '' + //add delta link to previous release + result += '
**Changes**
' + result += "[${latestTag}...${config.version}](${config.githubServerUrl}/${config.githubOrg}/${config.githubRepo}/compare/${latestTag}...${config.version})
" + return result +} + +void postNewRelease(config, TOKEN, releaseBody){ + releaseBody = releaseBody.replace('"', '\\"') + //write release information + def data = "{\"tag_name\": \"${config.version}\",\"target_commitish\": \"master\",\"name\": \"${config.version}\",\"body\": \"${releaseBody}\",\"draft\": false,\"prerelease\": false}" + try { + httpRequest httpMode: 'POST', requestBody: data, url: "${config.githubApiUrl}/repos/${config.githubOrg}/${config.githubRepo}/releases?access_token=${TOKEN}" + } catch (e) { + echo """[${STEP_NAME}] Error occured when writing release information +--------------------------------------------------------------------- +Request body was: +--------------------------------------------------------------------- +${data} +---------------------------------------------------------------------""" + throw e + } +} + +boolean isExcluded(item, excludeLabels){ + def result = false + excludeLabels.each {labelName -> + item.labels.each { label -> + if (label.name == labelName) { + result = true + } + } + } + return result +}