diff --git a/documentation/docs/images/githubRelease.png b/documentation/docs/images/githubRelease.png
new file mode 100644
index 000000000..84813ca04
Binary files /dev/null and b/documentation/docs/images/githubRelease.png differ
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/CheckChangeInDevelopmentTest.groovy b/test/groovy/CheckChangeInDevelopmentTest.groovy
index d36ce9e33..a40f03374 100644
--- a/test/groovy/CheckChangeInDevelopmentTest.groovy
+++ b/test/groovy/CheckChangeInDevelopmentTest.groovy
@@ -44,7 +44,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest {
ChangeManagement cm = getChangeManagementUtils(true)
jsr.step.checkChangeInDevelopment(
- script: nullScript,
+ script: nullScript,
cmUtils: cm,
changeManagement: [
type: 'SOLMAN',
@@ -69,7 +69,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest {
ChangeManagement cm = getChangeManagementUtils(false)
jsr.step.checkChangeInDevelopment(
- script: nullScript,
+ script: nullScript,
cmUtils: cm,
changeManagement: [type: 'SOLMAN',
endpoint: 'https://example.org/cm'])
@@ -80,7 +80,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)
@@ -92,7 +92,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest {
ChangeManagement cm = getChangeManagementUtils(true, '0815')
jsr.step.checkChangeInDevelopment(
- script: nullScript,
+ script: nullScript,
changeDocumentId: '42',
cmUtils: cm,
changeManagement: [type: 'SOLMAN',
@@ -106,7 +106,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest {
ChangeManagement cm = getChangeManagementUtils(true, '0815')
jsr.step.checkChangeInDevelopment(
- script: nullScript,
+ script: nullScript,
cmUtils: cm,
changeManagement : [type: 'SOLMAN',
endpoint: 'https://example.org/cm'])
@@ -133,7 +133,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest {
}
jsr.step.checkChangeInDevelopment(
- script: nullScript,
+ script: nullScript,
cmUtils: cm,
changeManagement: [type: 'SOLMAN',
endpoint: 'https://example.org/cm'])
@@ -149,7 +149,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',
type: 'SOLMAN'])
@@ -165,7 +165,7 @@ class CheckChangeInDevelopmentTest extends BasePiperTest {
ChangeManagement cm = getChangeManagementUtils(false, '')
jsr.step.checkChangeInDevelopment(
- script: nullScript,
+ script: nullScript,
cmUtils: cm,
changeManagement: [type: 'SOLMAN',
endpoint: 'https://example.org/cm'])
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
+}