From b8c0c8e6b3eaa4526fc2e1f23bb855664db9d1bc Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Tue, 6 Nov 2018 13:47:32 +0100 Subject: [PATCH 01/32] Cache transport request Id in common pipeline environment --- src/com/sap/piper/cm/StepHelpers.groovy | 47 ++++--- .../groovy/TransportRequestReleaseTest.groovy | 4 + .../TransportRequestUploadFileTest.groovy | 4 + .../com/sap/piper/cm/StepHelpersTest.groovy | 127 ++++++++++++++++++ vars/commonPipelineEnvironment.groovy | 4 + vars/transportRequestCreate.groovy | 4 +- 6 files changed, 169 insertions(+), 21 deletions(-) create mode 100644 test/groovy/com/sap/piper/cm/StepHelpersTest.groovy diff --git a/src/com/sap/piper/cm/StepHelpers.groovy b/src/com/sap/piper/cm/StepHelpers.groovy index f0604d98f..be8f86d38 100644 --- a/src/com/sap/piper/cm/StepHelpers.groovy +++ b/src/com/sap/piper/cm/StepHelpers.groovy @@ -12,27 +12,36 @@ public class StepHelpers { if(transportRequestId?.trim()) { step.echo "[INFO] Transport request id '${transportRequestId}' retrieved from parameters." + return transportRequestId - } else { - - step.echo "[INFO] Retrieving transport request id from commit history [from: ${configuration.changeManagement.git.from}, to: ${configuration.changeManagement.git.to}]." + - " Searching for pattern '${configuration.changeManagement.transportRequestLabel}'. Searching with format '${configuration.changeManagement.git.format}'." - - try { - transportRequestId = cm.getTransportRequestId( - configuration.changeManagement.git.from, - configuration.changeManagement.git.to, - configuration.changeManagement.transportRequestLabel, - configuration.changeManagement.git.format - ) - - step.echo "[INFO] Transport request id '${transportRequestId}' retrieved from commit history" - - } catch(ChangeManagementException ex) { - step.echo "[WARN] Cannot retrieve transportRequestId from commit history: ${ex.getMessage()}." - } } - return transportRequestId + + transportRequestId = step.commonPipelineEnvironment.getTransportRequestId() + + if(transportRequestId?.trim()) { + step.echo "[INFO] Transport request id '${transportRequestId}' retrieved from common pipeline environment." + return transportRequestId + } + + step.echo "[INFO] Retrieving transport request id from commit history [from: ${configuration.changeManagement.git.from}, to: ${configuration.changeManagement.git.to}]." + + " Searching for pattern '${configuration.changeManagement.transportRequestLabel}'. Searching with format '${configuration.changeManagement.git.format}'." + + try { + transportRequestId = cm.getTransportRequestId( + configuration.changeManagement.git.from, + configuration.changeManagement.git.to, + configuration.changeManagement.transportRequestLabel, + configuration.changeManagement.git.format + ) + + step.commonPipelineEnvironment.setTransportRequestId(transportRequestId) + step.echo "[INFO] Transport request id '${transportRequestId}' retrieved from commit history" + + } catch(ChangeManagementException ex) { + step.echo "[WARN] Cannot retrieve transportRequestId from commit history: ${ex.getMessage()}." + } + + transportRequestId } @NonCPS diff --git a/test/groovy/TransportRequestReleaseTest.groovy b/test/groovy/TransportRequestReleaseTest.groovy index 4042f978e..e445265b9 100644 --- a/test/groovy/TransportRequestReleaseTest.groovy +++ b/test/groovy/TransportRequestReleaseTest.groovy @@ -108,6 +108,10 @@ public class TransportRequestReleaseTest extends BasePiperTest { @Test public void releaseTransportRequestSuccessTest() { + // Here we test only the case where the transportRequestId is + // provided via parameters. The other cases are tested by + // corresponding tests for StepHelpers#getTransportRequestId(./.) + jlr.expect("[INFO] Closing transport request '002' for change document '001'.") jlr.expect("[INFO] Transport Request '002' has been successfully closed.") diff --git a/test/groovy/TransportRequestUploadFileTest.groovy b/test/groovy/TransportRequestUploadFileTest.groovy index b17156f32..99d5aa6ac 100644 --- a/test/groovy/TransportRequestUploadFileTest.groovy +++ b/test/groovy/TransportRequestUploadFileTest.groovy @@ -194,6 +194,10 @@ public class TransportRequestUploadFileTest extends BasePiperTest { @Test public void uploadFileToTransportRequestSOLMANSuccessTest() { + // Here we test only the case where the transportRequestId is + // provided via parameters. The other cases are tested by + // corresponding tests for StepHelpers#getTransportRequestId(./.) + jlr.expect("[INFO] Uploading file '/path' to transport request '002' of change document '001'.") jlr.expect("[INFO] File '/path' has been successfully uploaded to transport request '002' of change document '001'.") diff --git a/test/groovy/com/sap/piper/cm/StepHelpersTest.groovy b/test/groovy/com/sap/piper/cm/StepHelpersTest.groovy new file mode 100644 index 000000000..6f9397b3e --- /dev/null +++ b/test/groovy/com/sap/piper/cm/StepHelpersTest.groovy @@ -0,0 +1,127 @@ +package com.sap.piper.cm + +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain + +import util.BasePiperTest +import util.JenkinsLoggingRule +import util.Rules + +class StepHelpersTest extends BasePiperTest { + + // Configuration is not checked by the tests here. + // We simply assume it fits. It is the duty of the + // step related tests to ensure the configuration is valid. + def params = [changeManagement: + [git: [ + from: 'HEAD~1', + to: 'HEAD', + format: '%b' + ], + transportRequestLabel: "TransportRequest:" + ] + ] + + private Map getTransportRequestIdReceivedParameters = [:] + + @Before + public void setup() { + getTransportRequestIdReceivedParameters.clear() + } + + JenkinsLoggingRule jlr = new JenkinsLoggingRule(this) + + @Rule + public RuleChain rules = Rules.getCommonRules(this) + .around(jlr) + + private ChangeManagement cm = new ChangeManagement(nullScript) { + String getTransportRequestId( + String from, + String to, + String label, + String format + ) { + getTransportRequestIdReceivedParameters['from'] = from + getTransportRequestIdReceivedParameters['to'] = to + getTransportRequestIdReceivedParameters['label'] = label + getTransportRequestIdReceivedParameters['format'] = format + return '097' + } + } + + @Test + public void transportRequestIdViaCommitHistoryTest() { + + def transportRequestId = StepHelpers.getTransportRequestId(cm, nullScript, params) + + assert transportRequestId == '097' + assert getTransportRequestIdReceivedParameters == + [ + from: 'HEAD~1', + to: 'HEAD', + label: 'TransportRequest:', + format: '%b' + ] + + // We cache the value. Otherwise we have to retrieve it each time from the + // commit history. + assert nullScript.commonPipelineEnvironment.getTransportRequestId() == '097' + + } + + @Test + public void transportRequestIdViaCommonPipelineEnvironmentTest() { + + nullScript.commonPipelineEnvironment.setTransportRequestId('098') + def transportRequestId = StepHelpers.getTransportRequestId(cm, nullScript, params) + + assert transportRequestId == '098' + + // getTransportRequestId gets not called on ChangeManagement util class + // in this case. + assert getTransportRequestIdReceivedParameters == [:] + } + + @Test + public void transportRequestIdViaParametersTest() { + + params << [transportRequestId: '099'] + + def transportRequestId = StepHelpers.getTransportRequestId(cm, nullScript, params) + + assert transportRequestId == '099' + + // In case we get the transport request id via parameters we do not cache it + // Caller knows the transport request id anyway. So the caller can provide it with + // each call. + assert nullScript.commonPipelineEnvironment.getTransportRequestId() == null + + // getTransportRequestId gets not called on ChangeManagement util class + // in this case. + assert getTransportRequestIdReceivedParameters == [:] + } + + @Test + public void transportRequestIdNotProvidedTest() { + + jlr.expect('Cannot retrieve transportRequestId from commit history') + + def cm = new ChangeManagement(nullScript) { + String getTransportRequestId( + String from, + String to, + String label, + String format + ) { + throw new ChangeManagementException('Cannot retrieve transport request id') + } + } + + def transportRequestId = StepHelpers.getTransportRequestId(cm, nullScript, params) + + assert transportRequestId == null + } +} diff --git a/vars/commonPipelineEnvironment.groovy b/vars/commonPipelineEnvironment.groovy index 5685c2302..3b2f8f075 100644 --- a/vars/commonPipelineEnvironment.groovy +++ b/vars/commonPipelineEnvironment.groovy @@ -30,6 +30,8 @@ class commonPipelineEnvironment implements Serializable { String mtarFilePath + String transportRequestId + def reset() { appContainerProperties = [:] artifactVersion = null @@ -49,6 +51,8 @@ class commonPipelineEnvironment implements Serializable { influxCustomDataMap = [pipeline_data: [:], step_data: [:]] mtarFilePath = null + + transportRequestId = null } def setAppContainerProperty(property, value) { diff --git a/vars/transportRequestCreate.groovy b/vars/transportRequestCreate.groovy index 1fec7d580..c697fa5e0 100644 --- a/vars/transportRequestCreate.groovy +++ b/vars/transportRequestCreate.groovy @@ -110,7 +110,7 @@ def call(parameters = [:]) { echo "[INFO] Transport Request '$transportRequestId' has been successfully created." + script.commonPipelineEnvironment.setTransportRequestId(transportRequestId) } - - return transportRequestId + transportRequestId } From 420746463eb5dfa9cfd94dc5d800d8c94b59821b Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Tue, 6 Nov 2018 15:10:54 +0100 Subject: [PATCH 02/32] Remove return value from transportRequestCreate it is contained in the common pipeline environment. --- test/groovy/CommonStepsTest.groovy | 1 - test/groovy/TransportRequestCreateTest.groovy | 18 +++++++++--------- vars/transportRequestCreate.groovy | 3 +-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index da24a3c8f..77ac6137d 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -216,7 +216,6 @@ public class CommonStepsTest extends BasePiperTest{ def stepsWithCallMethodsOtherThanVoid = [] def whitelist = [ - 'transportRequestCreate', 'durationMeasure', ] diff --git a/test/groovy/TransportRequestCreateTest.groovy b/test/groovy/TransportRequestCreateTest.groovy index a7170699e..93ae975f5 100644 --- a/test/groovy/TransportRequestCreateTest.groovy +++ b/test/groovy/TransportRequestCreateTest.groovy @@ -127,9 +127,9 @@ public class TransportRequestCreateTest extends BasePiperTest { } } - def transportId = jsr.step.call(script: nullScript, changeDocumentId: '001', developmentSystemId: '001', cmUtils: cm) + jsr.step.call(script: nullScript, changeDocumentId: '001', developmentSystemId: '001', cmUtils: cm) - assert transportId == '001' + assert nullScript.commonPipelineEnvironment.getTransportRequestId() == '001' assert result == [changeId: '001', developmentSystemId: '001', cmEndpoint: 'https://example.org/cm', @@ -166,14 +166,14 @@ public class TransportRequestCreateTest extends BasePiperTest { } } - def transportId = jsr.step.call(script: nullScript, - transportType: 'W', - targetSystem: 'XYZ', - description: 'desc', - changeManagement: [type: 'CTS'], - cmUtils: cm) + jsr.step.call(script: nullScript, + transportType: 'W', + targetSystem: 'XYZ', + description: 'desc', + changeManagement: [type: 'CTS'], + cmUtils: cm) - assert transportId == '001' + assert nullScript.commonPipelineEnvironment.getTransportRequestId() == '001' assert result == [transportType: 'W', targetSystemId: 'XYZ', description: 'desc', diff --git a/vars/transportRequestCreate.groovy b/vars/transportRequestCreate.groovy index c697fa5e0..edc0f908d 100644 --- a/vars/transportRequestCreate.groovy +++ b/vars/transportRequestCreate.groovy @@ -27,7 +27,7 @@ import hudson.AbortException @Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus(['changeDocumentId']) -def call(parameters = [:]) { +void call(parameters = [:]) { def transportRequestId @@ -112,5 +112,4 @@ def call(parameters = [:]) { echo "[INFO] Transport Request '$transportRequestId' has been successfully created." script.commonPipelineEnvironment.setTransportRequestId(transportRequestId) } - transportRequestId } From b3c5cba7070379cd65da684360261e14311ba3b0 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Thu, 8 Nov 2018 09:44:11 +0100 Subject: [PATCH 03/32] Notify about old config framework from neo deploy. --- test/groovy/NeoDeployTest.groovy | 29 +++++++++++++++++++++++++++-- vars/neoDeploy.groovy | 5 +++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/test/groovy/NeoDeployTest.groovy b/test/groovy/NeoDeployTest.groovy index 31f02581f..f5dae61eb 100644 --- a/test/groovy/NeoDeployTest.groovy +++ b/test/groovy/NeoDeployTest.groovy @@ -1,3 +1,4 @@ +import com.sap.piper.Utils import hudson.AbortException import org.junit.rules.TemporaryFolder @@ -6,6 +7,8 @@ import org.junit.BeforeClass import org.junit.ClassRule import org.junit.Ignore +import java.util.Map + import org.hamcrest.BaseMatcher import org.hamcrest.Description import org.jenkinsci.plugins.credentialsbinding.impl.CredentialNotFoundException @@ -81,13 +84,22 @@ class NeoDeployTest extends BasePiperTest { @Test void straightForwardTestConfigViaConfigProperties() { + boolean notifyOldConfigFrameworkUsed = false + nullScript.commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'test.deploy.host.com') nullScript.commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'trialuser123') nullScript.commonPipelineEnvironment.configuration = [:] + def utils = new Utils() { + void pushToSWA(Map parameters, Map config) { + notifyOldConfigFrameworkUsed = parameters.stepParam4 + } + } + jsr.step.neoDeploy(script: nullScript, archivePath: archiveName, - neoCredentialsId: 'myCredentialsId' + neoCredentialsId: 'myCredentialsId', + utils: utils ) Assert.assertThat(jscr.shell, @@ -98,14 +110,25 @@ class NeoDeployTest extends BasePiperTest { .hasSingleQuotedOption('user', 'anonymous') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasDoubleQuotedOption('source', '.*')) + + assert notifyOldConfigFrameworkUsed } @Test void straightForwardTestConfigViaConfiguration() { + boolean notifyOldConfigFrameworkUsed = true + + def utils = new Utils() { + void pushToSWA(Map parameters, Map config) { + notifyOldConfigFrameworkUsed = parameters.stepParam4 + } + } + jsr.step.neoDeploy(script: nullScript, archivePath: archiveName, - neoCredentialsId: 'myCredentialsId' + neoCredentialsId: 'myCredentialsId', + utils: utils, ) Assert.assertThat(jscr.shell, @@ -116,6 +139,8 @@ class NeoDeployTest extends BasePiperTest { .hasSingleQuotedOption('user', 'anonymous') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasDoubleQuotedOption('source', '.*')) + + assert !notifyOldConfigFrameworkUsed } @Test diff --git a/vars/neoDeploy.groovy b/vars/neoDeploy.groovy index 75cabb565..6f1e3fac3 100644 --- a/vars/neoDeploy.groovy +++ b/vars/neoDeploy.groovy @@ -36,7 +36,7 @@ void call(parameters = [:]) { def script = checkScript(this, parameters) ?: this - def utils = new Utils() + def utils = parameters.utils ?: new Utils() prepareDefaultValues script: script @@ -89,7 +89,8 @@ void call(parameters = [:]) { step: STEP_NAME, stepParam1: configuration.deployMode == 'mta'?'mta':'war', // ['mta', 'warParams', 'warPropertiesFile'] stepParam2: configuration.warAction == 'rolling-update'?'blue-green':'standard', // ['deploy', 'deploy-mta', 'rolling-update'] - stepParam3: parameters?.script == null + stepParam3: parameters?.script == null, + stepParam4: ! stepCompatibilityConfiguration.isEmpty(), ], configuration) def archivePath = configuration.archivePath From 1b8e9d0e30965daa7731fbea62d21cb59b46a223 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Thu, 8 Nov 2018 10:29:30 +0100 Subject: [PATCH 04/32] Emit more explicit warning in case old config framework is is use --- vars/neoDeploy.groovy | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/vars/neoDeploy.groovy b/vars/neoDeploy.groovy index 6f1e3fac3..39be14b86 100644 --- a/vars/neoDeploy.groovy +++ b/vars/neoDeploy.groovy @@ -72,6 +72,23 @@ void call(parameters = [:]) { echo "[WARNING][${STEP_NAME}] Deprecated parameter 'neoCredentialsId' from old configuration framework is used. This will not work anymore in future versions." parameters.put('neoCredentialsId', credId) } + + if(! stepCompatibilityConfiguration.isEmpty()) { + echo "[WARNING][$STEP_NAME] You are using a deprecated configuration framework. This will be removed in " + + 'futureVersions.\nAdd snippet below to \'./pipeline/config.yml\' and remove ' + + 'file \'.pipeline/configuration.properties\'.\n' + + """|steps: + | neoDeploy: + | host: ${stepCompatibilityConfiguration.get('host', '')} + | account: ${stepCompatibilityConfiguration.get('account', '')} + """.stripMargin() + + if(Boolean.getBoolean('com.sap.piper.featureFlag.buildUnstableWhenOldConfigFrameworkIsUsedByNeoDeploy')) { + script.currentBuild.setResult('UNSTABLE') + echo "[WARNING][$STEP_NAME] Build has been set to unstable since old config framework is used." + } + } + // Backward compatibility end // load default & individual configuration From 7063aa12498eaff5eee63cb0b198da41a28703d4 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Thu, 8 Nov 2018 10:31:00 +0100 Subject: [PATCH 05/32] set build to unstable in case old config framework is used. protected by feature flag (so we can test it manually already). --- test/groovy/NeoDeployTest.groovy | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/groovy/NeoDeployTest.groovy b/test/groovy/NeoDeployTest.groovy index f5dae61eb..3041998d0 100644 --- a/test/groovy/NeoDeployTest.groovy +++ b/test/groovy/NeoDeployTest.groovy @@ -84,12 +84,15 @@ class NeoDeployTest extends BasePiperTest { @Test void straightForwardTestConfigViaConfigProperties() { + boolean buildStatusHasBeenSet = false boolean notifyOldConfigFrameworkUsed = false nullScript.commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'test.deploy.host.com') nullScript.commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'trialuser123') nullScript.commonPipelineEnvironment.configuration = [:] + nullScript.currentBuild = [setResult: {buildStatusHasBeenSet = true}] + def utils = new Utils() { void pushToSWA(Map parameters, Map config) { notifyOldConfigFrameworkUsed = parameters.stepParam4 @@ -111,9 +114,37 @@ class NeoDeployTest extends BasePiperTest { .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasDoubleQuotedOption('source', '.*')) + assert !buildStatusHasBeenSet assert notifyOldConfigFrameworkUsed } + @Test + void testConfigViaConfigPropertiesSetsBuildToUnstable() { + + def buildStatus = 'SUCCESS' + + nullScript.commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'test.deploy.host.com') + nullScript.commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'trialuser123') + nullScript.commonPipelineEnvironment.configuration = [:] + + nullScript.currentBuild = [setResult: { r -> buildStatus = r}] + + System.setProperty('com.sap.piper.featureFlag.buildUnstableWhenOldConfigFrameworkIsUsedByNeoDeploy', + Boolean.TRUE.toString()) + + try { + jsr.step.neoDeploy(script: nullScript, + archivePath: archiveName, + neoCredentialsId: 'myCredentialsId', + utils: utils + ) + } finally { + System.clearProperty('com.sap.piper.featureFlag.buildUnstableWhenOldConfigFrameworkIsUsedByNeoDeploy') + } + + assert buildStatus == 'UNSTABLE' + } + @Test void straightForwardTestConfigViaConfiguration() { From d1e33d6b2fb16f083822015045505c8d3e59c3e5 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Thu, 8 Nov 2018 13:40:13 +0100 Subject: [PATCH 06/32] Disable check for Boolean.getBoolean() Is it IMO completly normal to use Boolean.getBoolean(./.). What is stated in the rule description | It is often mistakenly used to attempt to read user input or parse is not a cool reason for having that rule. With such an approach we can knock out a lot of APIs which can be misunderstood. It is the duty of the developer to understand the API s?he is using. The alternate way would be 'Boolean.parseBoolean(System.getProperty('xxx'))' which is not that convenient like Boolean.getBoolean('xxx'). --- .codeclimate.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index 9b68739e4..f16213b9a 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,6 +1,9 @@ plugins: codenarc: enabled: true + checks: + BooleanGetBoolean: + enabled: false editorconfig: enabled: true checks: From 8e7352cdb48f0cf6e92d8bfdad509b7d27149606 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Thu, 8 Nov 2018 16:05:35 +0100 Subject: [PATCH 07/32] Adjust docu there is no return value anymore. --- documentation/docs/steps/transportRequestCreate.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/docs/steps/transportRequestCreate.md b/documentation/docs/steps/transportRequestCreate.md index a5f44a865..a4b7fb2e3 100644 --- a/documentation/docs/steps/transportRequestCreate.md +++ b/documentation/docs/steps/transportRequestCreate.md @@ -7,6 +7,8 @@ Creates * a Transport Request for a Change Document on the Solution Manager (type `SOLMAN`) or * a Transport Request inside an ABAP system (type`CTS`) +The id of the transport request is availabe via [commonPipelineEnvironment.getTransportRequestId()](commonPipelineEnvironment.md) + ## Prerequisites * **[Change Management Client 2.0.0 or compatible version](http://central.maven.org/maven2/com/sap/devops/cmclient/dist.cli/)** - available for download on Maven Central. @@ -97,7 +99,7 @@ The parameters can also be provided when the step is invoked. For examples see b ## Return value -The id of the Transport Request that has been created. +none ## Exceptions From 0bbbb560028264970fb1b18269a137e7ddb26e68 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Thu, 8 Nov 2018 16:16:42 +0100 Subject: [PATCH 08/32] Do not hand over reference to step itself needs to be the reference as it is available by script --- vars/checkChangeInDevelopment.groovy | 2 +- vars/transportRequestCreate.groovy | 2 +- vars/transportRequestRelease.groovy | 4 ++-- vars/transportRequestUploadFile.groovy | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vars/checkChangeInDevelopment.groovy b/vars/checkChangeInDevelopment.groovy index e63b489be..51413f232 100644 --- a/vars/checkChangeInDevelopment.groovy +++ b/vars/checkChangeInDevelopment.groovy @@ -97,7 +97,7 @@ void call(parameters = [:]) { new Utils().pushToSWA([step: STEP_NAME, stepParam1: parameters?.script == null], configuration) - def changeId = getChangeDocumentId(cm, this, configuration) + def changeId = getChangeDocumentId(cm, script, configuration) configuration = configHelper.mixin([changeDocumentId: changeId?.trim() ?: null], ['changeDocumentId'] as Set) diff --git a/vars/transportRequestCreate.groovy b/vars/transportRequestCreate.groovy index edc0f908d..cfa9ba023 100644 --- a/vars/transportRequestCreate.groovy +++ b/vars/transportRequestCreate.groovy @@ -68,7 +68,7 @@ void call(parameters = [:]) { if(backendType == BackendType.SOLMAN) { - changeDocumentId = getChangeDocumentId(cm, this, configuration) + changeDocumentId = getChangeDocumentId(cm, script, configuration) configHelper.mixin([changeDocumentId: changeDocumentId?.trim() ?: null], ['changeDocumentId'] as Set) .withMandatoryProperty('developmentSystemId') diff --git a/vars/transportRequestRelease.groovy b/vars/transportRequestRelease.groovy index 9c28d7dcd..d11903f3e 100644 --- a/vars/transportRequestRelease.groovy +++ b/vars/transportRequestRelease.groovy @@ -62,11 +62,11 @@ void call(parameters = [:]) { stepParam1: parameters?.script == null], configuration) def changeDocumentId = null - def transportRequestId = getTransportRequestId(cm, this, configuration) + def transportRequestId = getTransportRequestId(cm, script, configuration) if(backendType == BackendType.SOLMAN) { - changeDocumentId = getChangeDocumentId(cm, this, configuration) + changeDocumentId = getChangeDocumentId(cm, script, configuration) configHelper.mixin([changeDocumentId: changeDocumentId?.trim() ?: null], ['changeDocumentId'] as Set) .withMandatoryProperty('changeDocumentId', diff --git a/vars/transportRequestUploadFile.groovy b/vars/transportRequestUploadFile.groovy index 420b59592..34eef2ff7 100644 --- a/vars/transportRequestUploadFile.groovy +++ b/vars/transportRequestUploadFile.groovy @@ -68,10 +68,10 @@ void call(parameters = [:]) { def changeDocumentId = null if(backendType == BackendType.SOLMAN) { - changeDocumentId = getChangeDocumentId(cm, this, configuration) + changeDocumentId = getChangeDocumentId(cm, script, configuration) } - def transportRequestId = getTransportRequestId(cm, this, configuration) + def transportRequestId = getTransportRequestId(cm, script, configuration) configHelper .mixin([changeDocumentId: changeDocumentId?.trim() ?: null, From 6252b18e4907cfd1a37e9516e5c67576da8db67a Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Fri, 9 Nov 2018 14:20:01 +0100 Subject: [PATCH 09/32] Remove cps annotation since otherwise we get executed not all the code. It returns too early. --- src/com/sap/piper/cm/StepHelpers.groovy | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/com/sap/piper/cm/StepHelpers.groovy b/src/com/sap/piper/cm/StepHelpers.groovy index 53dea51d5..4b44a8b5c 100644 --- a/src/com/sap/piper/cm/StepHelpers.groovy +++ b/src/com/sap/piper/cm/StepHelpers.groovy @@ -4,7 +4,6 @@ import com.cloudbees.groovy.cps.NonCPS public class StepHelpers { - @NonCPS public static def getTransportRequestId(ChangeManagement cm, def step, Map configuration) { def transportRequestId = configuration.transportRequestId @@ -44,7 +43,6 @@ public class StepHelpers { transportRequestId } - @NonCPS public static getChangeDocumentId(ChangeManagement cm, def step, Map configuration) { def changeDocumentId = configuration.changeDocumentId From 79fc8b1b85c586ec4581ee9ccdc9f18dd9a1c1bd Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Fri, 9 Nov 2018 15:20:13 +0100 Subject: [PATCH 10/32] Improve naming step -> script We cannot say if it is a step, but it is a script, this is essential. --- src/com/sap/piper/cm/StepHelpers.groovy | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/com/sap/piper/cm/StepHelpers.groovy b/src/com/sap/piper/cm/StepHelpers.groovy index 4b44a8b5c..7b0c21eef 100644 --- a/src/com/sap/piper/cm/StepHelpers.groovy +++ b/src/com/sap/piper/cm/StepHelpers.groovy @@ -4,25 +4,25 @@ import com.cloudbees.groovy.cps.NonCPS public class StepHelpers { - public static def getTransportRequestId(ChangeManagement cm, def step, Map configuration) { + public static def getTransportRequestId(ChangeManagement cm, def script, Map configuration) { def transportRequestId = configuration.transportRequestId if(transportRequestId?.trim()) { - step.echo "[INFO] Transport request id '${transportRequestId}' retrieved from parameters." + script.echo "[INFO] Transport request id '${transportRequestId}' retrieved from parameters." return transportRequestId } - transportRequestId = step.commonPipelineEnvironment.getTransportRequestId() + transportRequestId = script.commonPipelineEnvironment.getTransportRequestId() if(transportRequestId?.trim()) { - step.echo "[INFO] Transport request id '${transportRequestId}' retrieved from common pipeline environment." + script.echo "[INFO] Transport request id '${transportRequestId}' retrieved from common pipeline environment." return transportRequestId } - step.echo "[INFO] Retrieving transport request id from commit history [from: ${configuration.changeManagement.git.from}, to: ${configuration.changeManagement.git.to}]." + + script.echo "[INFO] Retrieving transport request id from commit history [from: ${configuration.changeManagement.git.from}, to: ${configuration.changeManagement.git.to}]." + " Searching for pattern '${configuration.changeManagement.transportRequestLabel}'. Searching with format '${configuration.changeManagement.git.format}'." try { @@ -33,35 +33,35 @@ public class StepHelpers { configuration.changeManagement.git.format ) - step.commonPipelineEnvironment.setTransportRequestId(transportRequestId) - step.echo "[INFO] Transport request id '${transportRequestId}' retrieved from commit history" + script.commonPipelineEnvironment.setTransportRequestId(transportRequestId) + script.echo "[INFO] Transport request id '${transportRequestId}' retrieved from commit history" } catch(ChangeManagementException ex) { - step.echo "[WARN] Cannot retrieve transportRequestId from commit history: ${ex.getMessage()}." + script.echo "[WARN] Cannot retrieve transportRequestId from commit history: ${ex.getMessage()}." } transportRequestId } - public static getChangeDocumentId(ChangeManagement cm, def step, Map configuration) { + public static getChangeDocumentId(ChangeManagement cm, def script, Map configuration) { def changeDocumentId = configuration.changeDocumentId if(changeDocumentId?.trim()) { - step.echo "[INFO] ChangeDocumentId '${changeDocumentId}' retrieved from parameters." + script.echo "[INFO] ChangeDocumentId '${changeDocumentId}' retrieved from parameters." return changeDocumentId } - changeDocumentId = step.commonPipelineEnvironment.getChangeDocumentId() + changeDocumentId = script.commonPipelineEnvironment.getChangeDocumentId() if(changeDocumentId?.trim()) { - step.echo "[INFO] ChangeDocumentId '${changeDocumentId}' retrieved from common pipeline environment." + script.echo "[INFO] ChangeDocumentId '${changeDocumentId}' retrieved from common pipeline environment." return changeDocumentId } - step.echo "[INFO] Retrieving ChangeDocumentId from commit history [from: ${configuration.changeManagement.git.from}, to: ${configuration.changeManagement.git.to}]." + + script.echo "[INFO] Retrieving ChangeDocumentId from commit history [from: ${configuration.changeManagement.git.from}, to: ${configuration.changeManagement.git.to}]." + "Searching for pattern '${configuration.changeManagement.changeDocumentLabel}'. Searching with format '${configuration.changeManagement.git.format}'." try { @@ -72,31 +72,31 @@ public class StepHelpers { configuration.changeManagement.git.format ) - step.echo "[INFO] ChangeDocumentId '${changeDocumentId}' retrieved from commit history" - step.commonPipelineEnvironment.setChangeDocumentId(changeDocumentId) + script.echo "[INFO] ChangeDocumentId '${changeDocumentId}' retrieved from commit history" + script.commonPipelineEnvironment.setChangeDocumentId(changeDocumentId) } catch(ChangeManagementException ex) { - step.echo "[WARN] Cannot retrieve changeDocumentId from commit history: ${ex.getMessage()}." + script.echo "[WARN] Cannot retrieve changeDocumentId from commit history: ${ex.getMessage()}." } return changeDocumentId } @NonCPS - static BackendType getBackendTypeAndLogInfoIfCMIntegrationDisabled(def step, Map configuration) { + static BackendType getBackendTypeAndLogInfoIfCMIntegrationDisabled(def script, Map configuration) { BackendType backendType try { backendType = configuration.changeManagement.type as BackendType } catch(IllegalArgumentException e) { - step.error "Invalid backend type: '${configuration.changeManagement.type}'. " + + script.error "Invalid backend type: '${configuration.changeManagement.type}'. " + "Valid values: [${BackendType.values().join(', ')}]. " + "Configuration: 'changeManagement/type'." } if (backendType == BackendType.NONE) { - step.echo "[INFO] Change management integration intentionally switched off. " + + script.echo "[INFO] Change management integration intentionally switched off. " + "In order to enable it provide 'changeManagement/type with one of " + "[${BackendType.values().minus(BackendType.NONE).join(', ')}] and maintain " + "other required properties like 'endpoint', 'credentialsId'." From 7446b1e93d5dafb8ca36721a1b4ed1610eb53d29 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Fri, 9 Nov 2018 16:09:29 +0100 Subject: [PATCH 11/32] Remove return value from step documentation In the meantime the steps do not have a return value anymore. --- documentation/docs/steps/checksPublishResults.md | 4 ---- documentation/docs/steps/dockerExecute.md | 4 ---- documentation/docs/steps/dockerExecuteOnKubernetes.md | 4 ---- documentation/docs/steps/handlePipelineStepErrors.md | 4 ---- documentation/docs/steps/karmaExecuteTests.md | 4 ---- documentation/docs/steps/mailSendNotification.md | 4 ---- documentation/docs/steps/mtaBuild.md | 4 ---- documentation/docs/steps/neoDeploy.md | 4 ---- documentation/docs/steps/pipelineExecute.md | 4 ---- documentation/docs/steps/pipelineRestartSteps.md | 4 ---- documentation/docs/steps/seleniumExecuteTests.md | 4 ---- documentation/docs/steps/setupCommonPipelineEnvironment.md | 4 ---- documentation/docs/steps/testsPublishResults.md | 4 ---- documentation/docs/steps/toolValidate.md | 4 ---- documentation/docs/steps/transportRequestRelease.md | 4 ---- documentation/docs/steps/transportRequestUploadFile.md | 4 ---- 16 files changed, 64 deletions(-) diff --git a/documentation/docs/steps/checksPublishResults.md b/documentation/docs/steps/checksPublishResults.md index ac9fea128..032da43af 100644 --- a/documentation/docs/steps/checksPublishResults.md +++ b/documentation/docs/steps/checksPublishResults.md @@ -144,10 +144,6 @@ checksPublishResults( ![StaticChecks Thresholds](../images/StaticChecks_Threshold.png) -## Return value - -none - ## Side effects If both ESLint and PyLint results are published, they are not correctly aggregated in the aggregator plugin. diff --git a/documentation/docs/steps/dockerExecute.md b/documentation/docs/steps/dockerExecute.md index 1a1529f3d..64d98a3cb 100644 --- a/documentation/docs/steps/dockerExecute.md +++ b/documentation/docs/steps/dockerExecute.md @@ -69,10 +69,6 @@ In following sections the configuration is possible: |sidecarVolumeBind||X|X| |sidecarWorkspace||X|X| -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/dockerExecuteOnKubernetes.md b/documentation/docs/steps/dockerExecuteOnKubernetes.md index 9e0dbce92..1243b6228 100644 --- a/documentation/docs/steps/dockerExecuteOnKubernetes.md +++ b/documentation/docs/steps/dockerExecuteOnKubernetes.md @@ -64,10 +64,6 @@ In following sections the configuration is possible: |stashExcludes||X|X| |stashIncludes||X|X| -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/handlePipelineStepErrors.md b/documentation/docs/steps/handlePipelineStepErrors.md index 67bea9c07..df2b7fd95 100644 --- a/documentation/docs/steps/handlePipelineStepErrors.md +++ b/documentation/docs/steps/handlePipelineStepErrors.md @@ -46,10 +46,6 @@ none none -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/karmaExecuteTests.md b/documentation/docs/steps/karmaExecuteTests.md index 764b7ed3a..856e09787 100644 --- a/documentation/docs/steps/karmaExecuteTests.md +++ b/documentation/docs/steps/karmaExecuteTests.md @@ -79,10 +79,6 @@ In following sections the configuration is possible: |sidecarVolumeBind|X|X|X| |stashContent|X|X|X| -## Return value - -none - ## Side effects Step uses `seleniumExecuteTest` & `dockerExecute` inside. diff --git a/documentation/docs/steps/mailSendNotification.md b/documentation/docs/steps/mailSendNotification.md index 6c7a2650c..394bae5a6 100644 --- a/documentation/docs/steps/mailSendNotification.md +++ b/documentation/docs/steps/mailSendNotification.md @@ -77,10 +77,6 @@ In following sections the configuration is possible: |projectName||X|X| |wrapInNode||X|X| -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/mtaBuild.md b/documentation/docs/steps/mtaBuild.md index 206ee3ff6..393d702dd 100644 --- a/documentation/docs/steps/mtaBuild.md +++ b/documentation/docs/steps/mtaBuild.md @@ -48,10 +48,6 @@ The following parameters can also be specified as step parameters using the glob * `mtaJarLocation` * `applicationName` -## Return value - -none - ## Side effects 1. The file name of the resulting archive is written to the `commonPipelineEnvironment` with variable name `mtarFileName`. diff --git a/documentation/docs/steps/neoDeploy.md b/documentation/docs/steps/neoDeploy.md index 3dd37032c..252e78be2 100644 --- a/documentation/docs/steps/neoDeploy.md +++ b/documentation/docs/steps/neoDeploy.md @@ -95,10 +95,6 @@ The following parameters can also be specified as step parameters using the glob * `neoCredentialsId` * `neoHome` -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/pipelineExecute.md b/documentation/docs/steps/pipelineExecute.md index 5aabc3129..a4b6e90b7 100644 --- a/documentation/docs/steps/pipelineExecute.md +++ b/documentation/docs/steps/pipelineExecute.md @@ -30,10 +30,6 @@ none none -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/pipelineRestartSteps.md b/documentation/docs/steps/pipelineRestartSteps.md index 8a13ed6eb..5e6212b3d 100644 --- a/documentation/docs/steps/pipelineRestartSteps.md +++ b/documentation/docs/steps/pipelineRestartSteps.md @@ -63,10 +63,6 @@ In following sections the configuration is possible: |sendMail|X|X|X| |timeoutInSeconds|X|X|X| -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/seleniumExecuteTests.md b/documentation/docs/steps/seleniumExecuteTests.md index 5a92d737a..94e78166c 100644 --- a/documentation/docs/steps/seleniumExecuteTests.md +++ b/documentation/docs/steps/seleniumExecuteTests.md @@ -137,10 +137,6 @@ In following sections the configuration is possible: |stashContent|X|X|X| |testRepository|X|X|X| -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/setupCommonPipelineEnvironment.md b/documentation/docs/steps/setupCommonPipelineEnvironment.md index 60b2d37fe..c79ca8d80 100644 --- a/documentation/docs/steps/setupCommonPipelineEnvironment.md +++ b/documentation/docs/steps/setupCommonPipelineEnvironment.md @@ -26,10 +26,6 @@ Initializes the [`commonPipelineEnvironment`](commonPipelineEnvironment.md), whi none -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/testsPublishResults.md b/documentation/docs/steps/testsPublishResults.md index 6e0e9d012..17f083978 100644 --- a/documentation/docs/steps/testsPublishResults.md +++ b/documentation/docs/steps/testsPublishResults.md @@ -108,10 +108,6 @@ Following parameters can also be specified as step parameters using the global c * `cobertura` * `jmeter` -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/toolValidate.md b/documentation/docs/steps/toolValidate.md index 7fe71667f..ae32a1eb4 100644 --- a/documentation/docs/steps/toolValidate.md +++ b/documentation/docs/steps/toolValidate.md @@ -23,10 +23,6 @@ none none -## Return value - -none - ## Side effects none diff --git a/documentation/docs/steps/transportRequestRelease.md b/documentation/docs/steps/transportRequestRelease.md index 59afc7f72..0cfded3be 100644 --- a/documentation/docs/steps/transportRequestRelease.md +++ b/documentation/docs/steps/transportRequestRelease.md @@ -86,10 +86,6 @@ The properties can also be configured on a per-step basis: The parameters can also be provided when the step is invoked. For examples see below. -## Return value - -None. - ## Exceptions * `IllegalArgumentException`: diff --git a/documentation/docs/steps/transportRequestUploadFile.md b/documentation/docs/steps/transportRequestUploadFile.md index 88eee8a44..b0b0b28b3 100644 --- a/documentation/docs/steps/transportRequestUploadFile.md +++ b/documentation/docs/steps/transportRequestUploadFile.md @@ -92,10 +92,6 @@ The properties can also be configured on a per-step basis: The parameters can also be provided when the step is invoked. For examples see below. -## Return value - -None. - ## Exceptions * `IllegalArgumentException`: From d1500dce0017410ca1184dfcc47deba6d6fd5b1f Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Mon, 12 Nov 2018 15:50:38 +0100 Subject: [PATCH 12/32] Log mta call which get executed. --- vars/mtaBuild.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vars/mtaBuild.groovy b/vars/mtaBuild.groovy index c0161b83b..8a4bea5a8 100644 --- a/vars/mtaBuild.groovy +++ b/vars/mtaBuild.groovy @@ -77,6 +77,8 @@ void call(Map parameters = [:]) { if (configuration.extension) mtaCall += " --extension=$configuration.extension" mtaCall += ' build' + echo "[INFO] Executing mta build call: '${mtaCall}'." + sh """#!/bin/bash export PATH=./node_modules/.bin:${PATH} $mtaCall From 69058d0f806ef95fa8840805ef36e85a55d3cc88 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Tue, 13 Nov 2018 08:44:38 +0100 Subject: [PATCH 13/32] Streamline code: Inline variable in mtaBuild step --- vars/mtaBuild.groovy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vars/mtaBuild.groovy b/vars/mtaBuild.groovy index c0161b83b..54f13cce0 100644 --- a/vars/mtaBuild.groovy +++ b/vars/mtaBuild.groovy @@ -82,8 +82,7 @@ void call(Map parameters = [:]) { $mtaCall """ - def mtarFilePath = "${mtarFileName}" - script?.commonPipelineEnvironment?.setMtarFilePath(mtarFilePath) + script?.commonPipelineEnvironment?.setMtarFilePath(mtarFileName) } } } From d65a0101583ae8a5bc1bf856088ecabfc5170992 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Fri, 16 Nov 2018 08:57:09 +0100 Subject: [PATCH 14/32] testsPublishResults: Make jUnit pattern more universal (#386) * Make jUnit pattern more universal * update documentation & test --- documentation/docs/steps/testsPublishResults.md | 2 +- resources/default_pipeline_environment.yml | 2 +- test/groovy/TestsPublishResultsTest.groovy | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/docs/steps/testsPublishResults.md b/documentation/docs/steps/testsPublishResults.md index 6e0e9d012..fbe858060 100644 --- a/documentation/docs/steps/testsPublishResults.md +++ b/documentation/docs/steps/testsPublishResults.md @@ -54,7 +54,7 @@ Each of the parameters `junit`, `jacoco`, `cobertura` and `jmeter` can be set to | parameter | mandatory | default | possible values | | ----------|-----------|---------|-----------------| -| pattern | no | `'**/target/surefire-reports/*.xml'` | | +| pattern | no | `'**/TEST-*.xml'` | | | archive | no | `false` | true, false | | updateResults | no | `false` | true, false | | allowEmptyResults | no | `true` | true, false | diff --git a/resources/default_pipeline_environment.yml b/resources/default_pipeline_environment.yml index 562d88537..ec3c6fbbb 100644 --- a/resources/default_pipeline_environment.yml +++ b/resources/default_pipeline_environment.yml @@ -287,7 +287,7 @@ steps: toHtml: false testsPublishResults: junit: - pattern: '**/target/surefire-reports/*.xml' + pattern: '**/TEST-*.xml' updateResults: false allowEmptyResults: true archive: false diff --git a/test/groovy/TestsPublishResultsTest.groovy b/test/groovy/TestsPublishResultsTest.groovy index 1955a8fc0..4b5d71092 100644 --- a/test/groovy/TestsPublishResultsTest.groovy +++ b/test/groovy/TestsPublishResultsTest.groovy @@ -75,7 +75,7 @@ class TestsPublishResultsTest extends BasePiperTest { assertTrue('JUnit options are empty', publisherStepOptions.junit != null) // ensure default patterns are set assertEquals('JUnit default pattern not set correct', - '**/target/surefire-reports/*.xml', publisherStepOptions.junit.testResults) + '**/TEST-*.xml', publisherStepOptions.junit.testResults) // ensure nothing else is published assertTrue('JaCoCo options are not empty', publisherStepOptions.jacoco == null) assertTrue('Cobertura options are not empty', publisherStepOptions.cobertura == null) From ddc40044a2df0a11425f3d9a52219d3daeda92e1 Mon Sep 17 00:00:00 2001 From: Christopher Fenner Date: Mon, 19 Nov 2018 10:54:14 +0100 Subject: [PATCH 15/32] karmaExecuteTests: extend default proxy excludes and stashes (#389) * extend default proxy excludes * Update karmaExecuteTests.md --- documentation/docs/steps/karmaExecuteTests.md | 6 +++--- resources/default_pipeline_environment.yml | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/documentation/docs/steps/karmaExecuteTests.md b/documentation/docs/steps/karmaExecuteTests.md index 764b7ed3a..712642b8f 100644 --- a/documentation/docs/steps/karmaExecuteTests.md +++ b/documentation/docs/steps/karmaExecuteTests.md @@ -25,7 +25,7 @@ In the Docker network, the containers can be referenced by the values provided i | ----------|-----------|---------|-----------------| |script|yes||| |containerPortMappings|no|`[node:8-stretch: [[containerPort: 9876, hostPort: 9876]]]`|| -|dockerEnvVars|no||| +|dockerEnvVars|no|`[ NO_PROXY: 'localhost,karma,$NO_PROXY', no_proxy: 'localhost,karma,$no_proxy']`|| |dockerImage|no|`node:8-stretch`|| |dockerName|no|`karma`|| |dockerWorkspace|no|`/home/node`|| @@ -33,11 +33,11 @@ In the Docker network, the containers can be referenced by the values provided i |installCommand|no|`npm install --quiet`|| |modules|no|`['.']`|| |runCommand|no|`npm run karma`|| -|sidecarEnvVars|no||| +|sidecarEnvVars|no|`[ NO_PROXY: 'localhost,selenium,$NO_PROXY', no_proxy: 'localhost,selenium,$no_proxy']`|| |sidecarImage|no||| |sidecarName|no||| |sidecarVolumeBind|no||| -|stashContent|no||| +|stashContent|no|`['buildDescriptor', 'tests']`|| - `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. - `containerPortMappings` - see step [dockerExecute](dockerExecute.md) diff --git a/resources/default_pipeline_environment.yml b/resources/default_pipeline_environment.yml index ec3c6fbbb..c7f6c2149 100644 --- a/resources/default_pipeline_environment.yml +++ b/resources/default_pipeline_environment.yml @@ -187,6 +187,9 @@ steps: 'node:8-stretch': - containerPort: 9876 hostPort: 9876 + dockerEnvVars: + NO_PROXY: 'localhost,selenium,$NO_PROXY' + no_proxy: 'localhost,selenium,$no_proxy' dockerImage: 'node:8-stretch' dockerName: 'karma' dockerWorkspace: '/home/node' @@ -194,6 +197,12 @@ steps: modules: - '.' runCommand: 'npm run karma' + sidecarEnvVars: + NO_PROXY: 'localhost,karma,$NO_PROXY' + no_proxy: 'localhost,karma,$no_proxy' + stashContent: + - buildDescriptor + - tests mailSendNotification: notificationAttachment: true notifyCulprits: true From db4e184967823bdaa5834514c95ba9d3164c6922 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Mon, 19 Nov 2018 11:35:37 +0100 Subject: [PATCH 16/32] cloudFoundryDeploy - adapt to changes in dockerExecute (#390) pass script to call `dockerExecute` --- vars/cloudFoundryDeploy.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vars/cloudFoundryDeploy.groovy b/vars/cloudFoundryDeploy.groovy index a206ee6af..1098ea49d 100644 --- a/vars/cloudFoundryDeploy.groovy +++ b/vars/cloudFoundryDeploy.groovy @@ -65,7 +65,7 @@ void call(Map parameters = [:]) { .addIfEmpty('mtaPath', config.mtaPath?:findMtar()) .use() - dockerExecute(dockerImage: config.dockerImage, dockerWorkspace: config.dockerWorkspace, stashContent: config.stashContent) { + dockerExecute(script: script, dockerImage: config.dockerImage, dockerWorkspace: config.dockerWorkspace, stashContent: config.stashContent) { deployMta(config) } return @@ -84,6 +84,7 @@ void call(Map parameters = [:]) { echo "[${STEP_NAME}] CF native deployment (${config.deployType}) with cfAppName=${config.cloudFoundry.appName}, cfManifest=${config.cloudFoundry.manifest}, smokeTestScript=${config.smokeTestScript}" dockerExecute ( + script: script, dockerImage: config.dockerImage, dockerWorkspace: config.dockerWorkspace, stashContent: config.stashContent, From 8d141a71832c0a53274e0433185ae04f937b83e6 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Tue, 20 Nov 2018 15:56:00 +0100 Subject: [PATCH 17/32] sendNotificationMail - add resilience (#392) Do not create failure in case `gitSshKeyCredentialsId` is not configured. Only provide warning message. --- vars/mailSendNotification.groovy | 57 +++++++++++++++++--------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/vars/mailSendNotification.groovy b/vars/mailSendNotification.groovy index 5e56c74ae..9f6edeaa7 100644 --- a/vars/mailSendNotification.groovy +++ b/vars/mailSendNotification.groovy @@ -134,42 +134,47 @@ def getCulpritCommitters(config, currentBuild) { } def getCulprits(config, branch, numberOfCommits) { - if (branch?.startsWith('PR-')) { - //special GitHub Pull Request handling - deleteDir() - sshagent( - credentials: [config.gitSshKeyCredentialsId], - ignoreMissing: true - ) { - def pullRequestID = branch.replaceAll('PR-', '') - def localBranchName = "pr" + pullRequestID; - sh """git init -git fetch ${config.gitUrl} pull/${pullRequestID}/head:${localBranchName} > /dev/null 2>&1 -git checkout -f ${localBranchName} > /dev/null 2>&1 -""" - } - } else { - //standard git/GitHub handling - if (config.gitCommitId) { + try { + if (branch?.startsWith('PR-')) { + //special GitHub Pull Request handling deleteDir() sshagent( credentials: [config.gitSshKeyCredentialsId], ignoreMissing: true ) { - sh """git clone ${config.gitUrl} . -git checkout ${config.gitCommitId} > /dev/null 2>&1""" + def pullRequestID = branch.replaceAll('PR-', '') + def localBranchName = "pr" + pullRequestID; + sh """git init + git fetch ${config.gitUrl} pull/${pullRequestID}/head:${localBranchName} > /dev/null 2>&1 + git checkout -f ${localBranchName} > /dev/null 2>&1 + """ } } else { - def retCode = sh(returnStatus: true, script: 'git log > /dev/null 2>&1') - if (retCode != 0) { - echo "[${STEP_NAME}] No git context available to retrieve culprits" - return '' + //standard git/GitHub handling + if (config.gitCommitId) { + deleteDir() + sshagent( + credentials: [config.gitSshKeyCredentialsId], + ignoreMissing: true + ) { + sh """git clone ${config.gitUrl} . + git checkout ${config.gitCommitId} > /dev/null 2>&1""" + } + } else { + def retCode = sh(returnStatus: true, script: 'git log > /dev/null 2>&1') + if (retCode != 0) { + echo "[${STEP_NAME}] No git context available to retrieve culprits" + return '' + } } } - } - def recipients = sh(returnStdout: true, script: "git log -${numberOfCommits} --pretty=format:'%ae %ce'") - return getDistinctRecipients(recipients) + def recipients = sh(returnStdout: true, script: "git log -${numberOfCommits} --pretty=format:'%ae %ce'") + return getDistinctRecipients(recipients) + } catch(err) { + echo "[${STEP_NAME}] Culprit retrieval from git failed with '${err.getMessage()}'. Please make sure to configure gitSshKeyCredentialsId. So far, only fixed list of recipients is used." + return '' + } } def getDistinctRecipients(recipients){ From aadba46083d4b501cd76ad114456ad753686b9fb Mon Sep 17 00:00:00 2001 From: Christopher Fenner Date: Tue, 20 Nov 2018 16:03:07 +0100 Subject: [PATCH 18/32] pipelineStashFiles: resolve STEP_NAME in log message (#391) * resolve STEP_NAME in log message * resolve STEP_NAME in log message --- vars/pipelineStashFilesAfterBuild.groovy | 4 ++-- vars/pipelineStashFilesBeforeBuild.groovy | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vars/pipelineStashFilesAfterBuild.groovy b/vars/pipelineStashFilesAfterBuild.groovy index 0e3ad303b..edcd54d9d 100644 --- a/vars/pipelineStashFilesAfterBuild.groovy +++ b/vars/pipelineStashFilesAfterBuild.groovy @@ -48,14 +48,14 @@ void call(Map parameters = [:]) { utils.stashWithMessage( 'classFiles', - '[${STEP_NAME}] Failed to stash class files.', + "[${STEP_NAME}] Failed to stash class files.", config.stashIncludes.classFiles, config.stashExcludes.classFiles ) utils.stashWithMessage( 'sonar', - '[${STEP_NAME}] Failed to stash sonar files.', + "[${STEP_NAME}] Failed to stash sonar files.", config.stashIncludes.sonar, config.stashExcludes.sonar ) diff --git a/vars/pipelineStashFilesBeforeBuild.groovy b/vars/pipelineStashFilesBeforeBuild.groovy index 161c3f892..dd90ba498 100644 --- a/vars/pipelineStashFilesBeforeBuild.groovy +++ b/vars/pipelineStashFilesBeforeBuild.groovy @@ -48,7 +48,7 @@ void call(Map parameters = [:]) { //store deployment descriptor files depending on technology, e.g. *.mtaext.yml utils.stashWithMessage( 'deployDescriptor', - '[${STEP_NAME}] no deployment descriptor files provided: ', + "[${STEP_NAME}] no deployment descriptor files provided: ", config.stashIncludes.deployDescriptor, config.stashExcludes.deployDescriptor ) @@ -58,34 +58,34 @@ void call(Map parameters = [:]) { sh "chmod -R u+w gitmetadata" utils.stashWithMessage( 'git', - '[${STEP_NAME}] no git repo files detected: ', + "[${STEP_NAME}] no git repo files detected: ", config.stashIncludes.git, config.stashExcludes.git ) //store nsp & retire exclusion file for future use utils.stashWithMessage( 'opensourceConfiguration', - '[${STEP_NAME}] no opensourceConfiguration files provided: ', + "[${STEP_NAME}] no opensourceConfiguration files provided: ", config.stashIncludes.get('opensourceConfiguration'), config.stashExcludes.get('opensourceConfiguration') ) //store pipeline configuration including additional groovy test scripts for future use utils.stashWithMessage( 'pipelineConfigAndTests', - '[${STEP_NAME}] no pipeline configuration and test files found: ', + "[${STEP_NAME}] no pipeline configuration and test files found: ", config.stashIncludes.pipelineConfigAndTests, config.stashExcludes.pipelineConfigAndTests ) utils.stashWithMessage( 'securityDescriptor', - '[${STEP_NAME}] no security descriptor found: ', + "[${STEP_NAME}] no security descriptor found: ", config.stashIncludes.securityDescriptor, config.stashExcludes.securityDescriptor ) //store files required for tests, e.g. Gauge, SUT, ... utils.stashWithMessage( 'tests', - '[${STEP_NAME}] no files for tests provided: ', + "[${STEP_NAME}] no files for tests provided: ", config.stashIncludes.tests, config.stashExcludes.tests ) From 6333530c9d952873df18b479dc212127dd6e6716 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Mon, 19 Nov 2018 16:50:09 +0100 Subject: [PATCH 19/32] Insert newlines before param config layers table --- createDocu.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/createDocu.groovy b/createDocu.groovy index 5b1f52e87..7fc683cef 100644 --- a/createDocu.groovy +++ b/createDocu.groovy @@ -65,7 +65,7 @@ class TemplateHelper { def t = '''| |We recommend to define values of step parameters via [config.yml file](../configuration.md). | - |In following sections the configuration is possible:'''.stripMargin() + |In following sections the configuration is possible:\n\n'''.stripMargin() t += '| parameter | general | step | stage |\n' t += '|-----------|---------|------|-------|\n' From ddbdc034fc6869448c3d0325c2c26c570c6d0f77 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Tue, 20 Nov 2018 16:51:55 +0100 Subject: [PATCH 20/32] Handle annotations without docu generator on call method --- createDocu.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/createDocu.groovy b/createDocu.groovy index 7fc683cef..7b0ecb20a 100644 --- a/createDocu.groovy +++ b/createDocu.groovy @@ -264,8 +264,8 @@ class Helper { } private static isHeader(line) { - Matcher headerMatcher = (line =~ /(def|void)\s*call\s*\(/ ) - return headerMatcher.size() == 1 && headerMatcher[0].size() == 2 + Matcher headerMatcher = (line =~ /(?:(?:def|void)\s*call\s*\()|(?:@.*)/ ) + return headerMatcher.size() == 1 } private static retrieveParameterName(line) { From b921aa8d2a783f2f82d457ec1a05de855ae61c21 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Fri, 23 Nov 2018 09:15:46 +0100 Subject: [PATCH 21/32] [fix] load defaults --- createDocu.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/createDocu.groovy b/createDocu.groovy index 7b0ecb20a..1413fe8c4 100644 --- a/createDocu.groovy +++ b/createDocu.groovy @@ -91,7 +91,7 @@ class Helper { new GroovyClassLoader(classLoader, compilerConfig, true) .parseClass(new File('src/com/sap/piper/ConfigurationHelper.groovy')) - .newInstance(script, [:]) + .newInstance(script, [:]).loadStepDefaults() } static getPrepareDefaultValuesStep(def gse) { From c9359b63a126209643032ceb85ef45b1bd4ecb94 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Fri, 23 Nov 2018 12:38:15 +0100 Subject: [PATCH 22/32] Support mandatory flag when generating docu --- createDocu.groovy | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/createDocu.groovy b/createDocu.groovy index 1413fe8c4..6ebd5e5b1 100644 --- a/createDocu.groovy +++ b/createDocu.groovy @@ -44,7 +44,7 @@ class TemplateHelper { parameters.keySet().toSorted().each { def props = parameters.get(it) - t += "| `${it}` | ${props.required ? 'yes' : 'no'} | ${(props.defaultValue ? '`' + props.defaultValue + '`' : '') } | ${props.value ?: ''} |\n" + t += "| `${it}` | ${props.mandatory ?: props.required ? 'yes' : 'no'} | ${(props.defaultValue ? '`' + props.defaultValue + '`' : '') } | ${props.value ?: ''} |\n" } t @@ -195,9 +195,10 @@ class Helper { boolean docu = false, value = false, + mandatory = false, docuEnd = false - def docuLines = [], valueLines = [] + def docuLines = [], valueLines = [], mandatoryLines = [] f.eachLine { line -> @@ -221,14 +222,17 @@ class Helper { if(step.parameters[param].docu || step.parameters[param].value) System.err << "[WARNING] There is already some documentation for parameter '${param}. Is this parameter documented twice?'\n" - def _docu = [], _value = [] + def _docu = [], _value = [], _mandatory = [] docuLines.each { _docu << it } valueLines.each { _value << it} + mandatoryLines.each { _mandatory << it} step.parameters[param].docu = _docu*.trim().join(' ').trim() step.parameters[param].value = _value*.trim().join(' ').trim() + step.parameters[param].mandatory = _mandatory*.trim().join(' ').trim() } docuLines.clear() valueLines.clear() + mandatoryLines.clear() } if( line.trim() ==~ /^\/\*\*/ ) { @@ -242,15 +246,30 @@ class Helper { if(_line.startsWith('*/')) _line = _line.replaceAll('^\\*/', '') // end comment if(_line.startsWith('*')) _line = _line.replaceAll('^\\*', '') // continue comment if(_line ==~ /.*@possibleValues.*/) { + mandatory = false // should be something like reset attributes value = true } + // some remark for mandatory e.g. some parameters are only mandatory under certain conditions + if(_line ==~ /.*@mandatory.*/) { + value = false // should be something like reset attributes ... + mandatory = true + } if(value) { if(_line) { _line = (_line =~ /.*@possibleValues\s*?(.*)/)[0][1] valueLines << _line } - } else { + } + + if(mandatory) { + if(_line) { + _line = (_line =~ /.*@mandatory\s*?(.*)/)[0][1] + mandatoryLines << _line + } + } + + if(! value && ! mandatory) { docuLines << _line.trim() } } @@ -258,6 +277,7 @@ class Helper { if(docu && line.trim() ==~ /^\*\//) { docu = false value = false + mandatory = false docuEnd = true } } From 36a282a8f2d6c8772902863a14761746ca51acb8 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Fri, 23 Nov 2018 14:27:12 +0100 Subject: [PATCH 23/32] docu generator: script:this case code --- createDocu.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/createDocu.groovy b/createDocu.groovy index 6ebd5e5b1..2d76e9536 100644 --- a/createDocu.groovy +++ b/createDocu.groovy @@ -485,7 +485,7 @@ def handleStep(stepName, prepareDefaultValuesStep, gse) { step.parameters['script'] = [ docu: 'The common script environment of the Jenkinsfile running. ' + 'Typically the reference to the script calling the pipeline ' + - 'step is provided with the this parameter, as in script: this. ' + + 'step is provided with the this parameter, as in `script: this`. ' + 'This allows the function to access the ' + 'commonPipelineEnvironment for retrieving, for example, configuration parameters.', required: true, From 32e20a72936616b4aebeb5aed5513fbe6102e99d Mon Sep 17 00:00:00 2001 From: Florian Geckeler <43751896+fgeckeler@users.noreply.github.com> Date: Tue, 27 Nov 2018 11:47:44 +0100 Subject: [PATCH 24/32] Provide option to Stop and not delete old instance on BlueGreenDeploy (#394) * Provide option to Stop and not delete old instance on BlueGreenDeploy * Default is still delete old instance Closes #323 --- .../docs/steps/cloudFoundryDeploy.md | 2 + resources/default_pipeline_environment.yml | 1 + test/groovy/CloudFoundryDeployTest.groovy | 84 ++++++++++++++++++- vars/cloudFoundryDeploy.groovy | 36 +++++++- 4 files changed, 119 insertions(+), 4 deletions(-) diff --git a/documentation/docs/steps/cloudFoundryDeploy.md b/documentation/docs/steps/cloudFoundryDeploy.md index 78c0feb12..bf0e58db2 100644 --- a/documentation/docs/steps/cloudFoundryDeploy.md +++ b/documentation/docs/steps/cloudFoundryDeploy.md @@ -29,6 +29,7 @@ Deployment can be done | cloudFoundry | yes | | | | deployTool | no | cf_native | cf_native, mtaDeployPlugin | | deployType | no | standard | standard, blue-green | +| keepOldInstance | no | false | true, false | | dockerImage | no | s4sdk/docker-cf-cli | | | dockerWorkspace | no | /home/piper | | | mtaDeployParameters | | -f | | @@ -65,6 +66,7 @@ Deployment can be done * `deployTool` defines the tool which should be used for deployment. * `deployType` defines the type of deployment, either `standard` deployment which results in a system downtime or a zero-downtime `blue-green` deployment. +* `keepOldInstance` in case of a `blue-green` deployment the old instance will be deleted by default. If this option is set to true the old instance will remain stopped in the Cloud Foundry space. * `dockerImage` defines the Docker image containing the deployment tools (like cf cli, ...) and `dockerWorkspace` defines the home directory of the default user of the `dockerImage` * `smokeTestScript` allows to specify a script which performs a check during blue-green deployment. The script gets the FQDN as parameter and returns `exit code 0` in case check returned `smokeTestStatusCode`. More details can be found [here](https://github.com/bluemixgaragelondon/cf-blue-green-deploy#how-to-use)
Currently this option is only considered for deployTool `cf_native`. * `stashContent` defines the stash names which should be unstashed at the beginning of the step. This makes the files available in case the step is started on an empty node. diff --git a/resources/default_pipeline_environment.yml b/resources/default_pipeline_environment.yml index c7f6c2149..340a6b072 100644 --- a/resources/default_pipeline_environment.yml +++ b/resources/default_pipeline_environment.yml @@ -123,6 +123,7 @@ steps: apiEndpoint: 'https://api.cf.eu10.hana.ondemand.com' deployTool: 'cf_native' deployType: 'standard' + keepOldInstance: false mtaDeployParameters: '-f' mtaExtensionDescriptor: '' mtaPath: '' diff --git a/test/groovy/CloudFoundryDeployTest.groovy b/test/groovy/CloudFoundryDeployTest.groovy index a0ce33db2..ccfc9b6e7 100644 --- a/test/groovy/CloudFoundryDeployTest.groovy +++ b/test/groovy/CloudFoundryDeployTest.groovy @@ -19,6 +19,7 @@ import util.Rules import static org.junit.Assert.assertThat import static org.hamcrest.Matchers.hasItem +import static org.hamcrest.Matchers.not import static org.hamcrest.Matchers.hasEntry import static org.hamcrest.Matchers.containsString @@ -126,6 +127,7 @@ class CloudFoundryDeployTest extends BasePiperTest { assertThat(jedr.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}")) assertThat(jscr.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))) assertThat(jscr.shell, hasItem(containsString("cf push testAppName -f 'test.yml'"))) + assertThat(jscr.shell, hasItem(containsString("cf logout"))) } @Test @@ -175,6 +177,7 @@ class CloudFoundryDeployTest extends BasePiperTest { assertThat(jedr.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}")) assertThat(jscr.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))) assertThat(jscr.shell, hasItem(containsString("cf push testAppName -f 'test.yml'"))) + assertThat(jscr.shell, hasItem(containsString("cf logout"))) } @Test @@ -198,6 +201,7 @@ class CloudFoundryDeployTest extends BasePiperTest { // asserts assertThat(jscr.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))) assertThat(jscr.shell, hasItem(containsString("cf push -f 'test.yml'"))) + assertThat(jscr.shell, hasItem(containsString("cf logout"))) } @Test @@ -223,7 +227,7 @@ class CloudFoundryDeployTest extends BasePiperTest { } @Test - void testCfNativeBlueGreen() { + void testCfNativeBlueGreenDefaultDeleteOldInstance() { jryr.registerYaml('test.yml', "applications: [[]]") @@ -244,8 +248,85 @@ class CloudFoundryDeployTest extends BasePiperTest { assertThat(jscr.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))) assertThat(jscr.shell, hasItem(containsString("cf blue-green-deploy testAppName --delete-old-apps -f 'test.yml'"))) + assertThat(jscr.shell, hasItem(containsString("cf logout"))) + } + @Test + void testCfNativeBlueGreenExplicitDeleteOldInstance() { + + jryr.registerYaml('test.yml', "applications: [[]]") + + jsr.step.cloudFoundryDeploy([ + script: nullScript, + juStabUtils: utils, + deployTool: 'cf_native', + deployType: 'blue-green', + keepOldInstance: false, + cfOrg: 'testOrg', + cfSpace: 'testSpace', + cfCredentialsId: 'test_cfCredentialsId', + cfAppName: 'testAppName', + cfManifest: 'test.yml' + ]) + + assertThat(jedr.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli')) + assertThat(jedr.dockerParams, hasEntry('dockerWorkspace', '/home/piper')) + + assertThat(jscr.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))) + assertThat(jscr.shell, hasItem(containsString("cf blue-green-deploy testAppName --delete-old-apps -f 'test.yml'"))) + assertThat(jscr.shell, not(hasItem(containsString("cf stop testAppName-old")))) + assertThat(jscr.shell, hasItem(containsString("cf logout"))) + + } + + @Test + void testCfNativeBlueGreenKeepOldInstance() { + + jryr.registerYaml('test.yml', "applications: [[]]") + + jsr.step.cloudFoundryDeploy([ + script: nullScript, + juStabUtils: utils, + deployTool: 'cf_native', + deployType: 'blue-green', + keepOldInstance: true, + cfOrg: 'testOrg', + cfSpace: 'testSpace', + cfCredentialsId: 'test_cfCredentialsId', + cfAppName: 'testAppName', + cfManifest: 'test.yml' + ]) + + assertThat(jedr.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli')) + assertThat(jedr.dockerParams, hasEntry('dockerWorkspace', '/home/piper')) + + assertThat(jscr.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))) + assertThat(jscr.shell, hasItem(containsString("cf blue-green-deploy testAppName -f 'test.yml'"))) + assertThat(jscr.shell, hasItem(containsString("cf stop testAppName-old"))) + assertThat(jscr.shell, hasItem(containsString("cf logout"))) + } + + @Test + void testCfNativeStandardShouldNotStopInstance() { + + jryr.registerYaml('test.yml', "applications: [[]]") + + jsr.step.cloudFoundryDeploy([ + script: nullScript, + juStabUtils: utils, + deployTool: 'cf_native', + deployType: 'standard', + keepOldInstance: true, + cfOrg: 'testOrg', + cfSpace: 'testSpace', + cfCredentialsId: 'test_cfCredentialsId', + cfAppName: 'testAppName', + cfManifest: 'test.yml' + ]) + + assertThat(jscr.shell, not(hasItem(containsString("cf stop testAppName-old")))) + } @Test void testCfNativeWithoutAppNameBlueGreen() { @@ -285,5 +366,6 @@ class CloudFoundryDeployTest extends BasePiperTest { assertThat(jedr.dockerParams, hasEntry('dockerWorkspace', '/home/piper')) assertThat(jscr.shell, hasItem(containsString('cf login -u test_cf -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))) assertThat(jscr.shell, hasItem(containsString('cf deploy target/test.mtar -f'))) + assertThat(jscr.shell, hasItem(containsString('cf logout'))) } } diff --git a/vars/cloudFoundryDeploy.groovy b/vars/cloudFoundryDeploy.groovy index 1098ea49d..68b076775 100644 --- a/vars/cloudFoundryDeploy.groovy +++ b/vars/cloudFoundryDeploy.groovy @@ -15,6 +15,7 @@ import groovy.transform.Field 'deployUser', 'deployTool', 'deployType', + 'keepOldInstance', 'dockerImage', 'dockerWorkspace', 'mtaDeployParameters', @@ -117,14 +118,16 @@ def deployCfNative (config) { passwordVariable: 'password', usernameVariable: 'username' )]) { - def deployCommand = 'push' + def deployCommand = selectCfDeployCommandForDeployType(config) + if (config.deployType == 'blue-green') { - deployCommand = 'blue-green-deploy' handleLegacyCfManifest(config) } else { config.smokeTest = '' } + def blueGreenDeployOptions = deleteOptionIfRequired(config) + // check if appName is available if (config.cloudFoundry.appName == null || config.cloudFoundry.appName == '') { if (config.deployType == 'blue-green') { @@ -145,11 +148,38 @@ def deployCfNative (config) { export HOME=${config.dockerWorkspace} cf login -u \"${username}\" -p '${password}' -a ${config.cloudFoundry.apiEndpoint} -o \"${config.cloudFoundry.org}\" -s \"${config.cloudFoundry.space}\" cf plugins - cf ${deployCommand} ${config.cloudFoundry.appName?:''} ${config.deployType == 'blue-green'?'--delete-old-apps':''} -f '${config.cloudFoundry.manifest}' ${config.smokeTest}""" + cf ${deployCommand} ${config.cloudFoundry.appName ?: ''} ${blueGreenDeployOptions} -f '${config.cloudFoundry.manifest}' ${config.smokeTest} + ${stopOldAppIfRequired(config)} + """ sh "cf logout" } } +private String selectCfDeployCommandForDeployType(Map config) { + if (config.deployType == 'blue-green') { + return 'blue-green-deploy' + } else { + return 'push' + } +} + +private String deleteOptionIfRequired(Map config) { + boolean deleteOldInstance = !config.keepOldInstance + if (deleteOldInstance && config.deployType == 'blue-green') { + return '--delete-old-apps' + } else { + return '' + } +} + +private String stopOldAppIfRequired(Map config) { + if (config.keepOldInstance && config.deployType == 'blue-green') { + return "cf stop ${config.cloudFoundry.appName}-old" + } else { + return '' + } +} + def deployMta (config) { if (config.mtaExtensionDescriptor == null) config.mtaExtensionDescriptor = '' if (!config.mtaExtensionDescriptor.isEmpty() && !config.mtaExtensionDescriptor.startsWith('-e ')) config.mtaExtensionDescriptor = "-e ${config.mtaExtensionDescriptor}" From d1d07f241d3f7592585e9adb1c10a7ec25bc0740 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Tue, 27 Nov 2018 16:02:06 +0100 Subject: [PATCH 25/32] cloudFoundryDeploy - fix stashing behavior (#396) This change fixes an error which only occurs in a Kubernetes landscape. When initial stashes are filled, the deployment will fail since it misses the artifact to be deployed. When providing an empty stash as default, Docker execution on Kubernetes will respect all content in the current workspace. --- vars/cloudFoundryDeploy.groovy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vars/cloudFoundryDeploy.groovy b/vars/cloudFoundryDeploy.groovy index 68b076775..ff5ea7c34 100644 --- a/vars/cloudFoundryDeploy.groovy +++ b/vars/cloudFoundryDeploy.groovy @@ -58,7 +58,10 @@ void call(Map parameters = [:]) { echo "[${STEP_NAME}] General parameters: deployTool=${config.deployTool}, deployType=${config.deployType}, cfApiEndpoint=${config.cloudFoundry.apiEndpoint}, cfOrg=${config.cloudFoundry.org}, cfSpace=${config.cloudFoundry.space}, cfCredentialsId=${config.cloudFoundry.credentialsId}, deployUser=${config.deployUser}" - config.stashContent = utils.unstashAll(config.stashContent) + //make sure that all relevant descriptors, are available in workspace + utils.unstashAll(config.stashContent) + //make sure that for further execution whole workspace, e.g. also downloaded artifacts are considered + config.stashContent = [:] if (config.deployTool == 'mtaDeployPlugin') { // set default mtar path From 6dc3ebe38c9feb6b69f553fa4427748161a0138b Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Tue, 27 Nov 2018 16:29:38 +0100 Subject: [PATCH 26/32] cloudFoundryDeploy - fix stashing (#398) --- vars/cloudFoundryDeploy.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vars/cloudFoundryDeploy.groovy b/vars/cloudFoundryDeploy.groovy index ff5ea7c34..b02735eac 100644 --- a/vars/cloudFoundryDeploy.groovy +++ b/vars/cloudFoundryDeploy.groovy @@ -61,7 +61,7 @@ void call(Map parameters = [:]) { //make sure that all relevant descriptors, are available in workspace utils.unstashAll(config.stashContent) //make sure that for further execution whole workspace, e.g. also downloaded artifacts are considered - config.stashContent = [:] + config.stashContent = [] if (config.deployTool == 'mtaDeployPlugin') { // set default mtar path From 28818773222333d0def2d58403234bc5092750e7 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Wed, 28 Nov 2018 09:25:34 +0100 Subject: [PATCH 27/32] newmanExecute - adapt to changes (#400) * script now mandatory, thus passing it to dockerExecute * html reporter needs to be installed separately --- resources/default_pipeline_environment.yml | 3 ++- test/groovy/NewmanExecuteTest.groovy | 5 +++-- vars/newmanExecute.groovy | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/resources/default_pipeline_environment.yml b/resources/default_pipeline_environment.yml index 340a6b072..877a1461c 100644 --- a/resources/default_pipeline_environment.yml +++ b/resources/default_pipeline_environment.yml @@ -227,7 +227,8 @@ steps: newmanCollection: '**/*.postman_collection.json' newmanEnvironment: '' newmanGlobals: '' - newmanRunCommand: "run ${config.newmanCollection} --environment '${config.newmanEnvironment}' --globals '${config.newmanGlobals}' --reporters junit,html --reporter-junit-export target/newman/TEST-${collectionDisplayName}.xml --reporter-html-export target/newman/TEST-${collectionDisplayName}.html" + newmanInstallCommand: 'npm install newman newman-reporter-html --global --quiet' + newmanRunCommand: "run '${config.newmanCollection}' --environment '${config.newmanEnvironment}' --globals '${config.newmanGlobals}' --reporters junit,html --reporter-junit-export 'target/newman/TEST-${collectionDisplayName}.xml' --reporter-html-export 'target/newman/TEST-${collectionDisplayName}.html'" stashContent: - 'tests' pipelineRestartSteps: diff --git a/test/groovy/NewmanExecuteTest.groovy b/test/groovy/NewmanExecuteTest.groovy index df76cb9f2..b4a9dc37f 100644 --- a/test/groovy/NewmanExecuteTest.groovy +++ b/test/groovy/NewmanExecuteTest.groovy @@ -68,7 +68,8 @@ class NewmanExecuteTest extends BasePiperTest { newmanGlobals: 'testGlobals' ) // asserts - assertThat(jscr.shell, hasItem('newman run testCollection --environment \'testEnvironment\' --globals \'testGlobals\' --reporters junit,html --reporter-junit-export target/newman/TEST-testCollection.xml --reporter-html-export target/newman/TEST-testCollection.html')) + assertThat(jscr.shell, hasItem('npm install newman newman-reporter-html --global --quiet')) + assertThat(jscr.shell, hasItem('newman run \'testCollection\' --environment \'testEnvironment\' --globals \'testGlobals\' --reporters junit,html --reporter-junit-export \'target/newman/TEST-testCollection.xml\' --reporter-html-export \'target/newman/TEST-testCollection.html\'')) assertThat(jedr.dockerParams.dockerImage, is('node:8-stretch')) assertThat(jlr.log, containsString('[newmanExecute] Found files [testCollection]')) assertJobStatusSuccess() @@ -102,7 +103,7 @@ class NewmanExecuteTest extends BasePiperTest { // asserts assertThat(jedr.dockerParams.dockerImage, is('testImage')) assertThat(gitMap.url, is('testRepo')) - assertThat(jscr.shell, hasItem('newman run testCollection --environment \'testEnvironment\' --globals \'testGlobals\' --reporters junit,html --reporter-junit-export target/newman/TEST-testCollection.xml --reporter-html-export target/newman/TEST-testCollection.html --suppress-exit-code')) + assertThat(jscr.shell, hasItem('newman run \'testCollection\' --environment \'testEnvironment\' --globals \'testGlobals\' --reporters junit,html --reporter-junit-export \'target/newman/TEST-testCollection.xml\' --reporter-html-export \'target/newman/TEST-testCollection.html\' --suppress-exit-code')) assertJobStatusSuccess() } diff --git a/vars/newmanExecute.groovy b/vars/newmanExecute.groovy index ab43895db..8ed3d5a57 100644 --- a/vars/newmanExecute.groovy +++ b/vars/newmanExecute.groovy @@ -18,6 +18,7 @@ import groovy.transform.Field 'newmanCollection', 'newmanEnvironment', 'newmanGlobals', + 'newmanInstallCommand', 'newmanRunCommand', 'stashContent', 'testRepository' @@ -56,10 +57,11 @@ void call(Map parameters = [:]) { } dockerExecute( + script: script, dockerImage: config.dockerImage, stashContent: config.stashContent ) { - sh 'npm install newman --global --quiet' + sh "${config.newmanInstallCommand}" for(String collection : collectionList){ def collectionDisplayName = collection.toString().replace(File.separatorChar,(char)'_').tokenize('.').first() // resolve templates From 90765697aae534e713643915d252b5750d632d0c Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Wed, 28 Nov 2018 10:46:47 +0100 Subject: [PATCH 28/32] Docker execution - make sure that script is passed (#403) --- vars/batsExecuteTests.groovy | 2 +- vars/snykExecute.groovy | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vars/batsExecuteTests.groovy b/vars/batsExecuteTests.groovy index f4f08470f..d5b81f5ad 100644 --- a/vars/batsExecuteTests.groovy +++ b/vars/batsExecuteTests.groovy @@ -70,7 +70,7 @@ void call(Map parameters = [:]) { } finally { sh "cat 'TEST-${config.testPackage}.tap'" if (config.outputFormat == 'junit') { - dockerExecute(dockerImage: config.dockerImage, dockerWorkspace: config.dockerWorkspace, stashContent: config.stashContent) { + dockerExecute(script: script, dockerImage: config.dockerImage, dockerWorkspace: config.dockerWorkspace, stashContent: config.stashContent) { sh "npm install tap-xunit -g" sh "cat 'TEST-${config.testPackage}.tap' | tap-xunit --package='${config.testPackage}' > TEST-${config.testPackage}.xml" } diff --git a/vars/snykExecute.groovy b/vars/snykExecute.groovy index 909bb060a..a4dd8834b 100644 --- a/vars/snykExecute.groovy +++ b/vars/snykExecute.groovy @@ -62,6 +62,7 @@ void call(Map parameters = [:]) { variable: 'token' )]) { dockerExecute( + script: script, dockerImage: config.dockerImage, stashContent: config.stashContent, dockerEnvVars: ['SNYK_TOKEN': token] From dc780b124878c76fb8b33e047fb72399ec227004 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Wed, 28 Nov 2018 11:52:09 +0100 Subject: [PATCH 29/32] [fix] Make regex in test Operating System independent. We searched for the slash, but on win like operating system we have of course a backslash. --- test/groovy/CommonStepsTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/groovy/CommonStepsTest.groovy b/test/groovy/CommonStepsTest.groovy index 77ac6137d..1a950da76 100644 --- a/test/groovy/CommonStepsTest.groovy +++ b/test/groovy/CommonStepsTest.groovy @@ -237,7 +237,7 @@ public class CommonStepsTest extends BasePiperTest{ private static getSteps() { List steps = [] new File('vars').traverse(type: FileType.FILES, maxDepth: 0) - { if(it.getName().endsWith('.groovy')) steps << (it =~ /vars\/(.*)\.groovy/)[0][1] } + { if(it.getName().endsWith('.groovy')) steps << (it =~ /vars[\\\/](.*)\.groovy/)[0][1] } return steps } } From b7468a7ae4d80ace230038a4ba9a0bc65f886c3f Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Thu, 29 Nov 2018 09:54:05 +0100 Subject: [PATCH 30/32] Step name is not a string literal anymore Having the step name always the same like the file name, which is in turn the class name is redundant. --- vars/artifactSetVersion.groovy | 2 +- vars/batsExecuteTests.groovy | 2 +- vars/checkChangeInDevelopment.groovy | 2 +- vars/checksPublishResults.groovy | 2 +- vars/cloudFoundryDeploy.groovy | 2 +- vars/dockerExecute.groovy | 2 +- vars/dockerExecuteOnKubernetes.groovy | 2 +- vars/durationMeasure.groovy | 2 +- vars/gaugeExecuteTests.groovy | 2 +- vars/githubPublishRelease.groovy | 2 +- vars/handlePipelineStepErrors.groovy | 2 +- vars/healthExecuteCheck.groovy | 2 +- vars/influxWriteData.groovy | 2 +- vars/karmaExecuteTests.groovy | 2 +- vars/mailSendNotification.groovy | 2 +- vars/mavenExecute.groovy | 2 +- vars/mtaBuild.groovy | 2 +- vars/neoDeploy.groovy | 2 +- vars/newmanExecute.groovy | 2 +- vars/pipelineExecute.groovy | 2 +- vars/pipelineRestartSteps.groovy | 2 +- vars/pipelineStashFiles.groovy | 2 +- vars/pipelineStashFilesAfterBuild.groovy | 2 +- vars/pipelineStashFilesBeforeBuild.groovy | 2 +- vars/prepareDefaultValues.groovy | 2 +- vars/seleniumExecuteTests.groovy | 2 +- vars/setupCommonPipelineEnvironment.groovy | 2 +- vars/snykExecute.groovy | 2 +- vars/testsPublishResults.groovy | 2 +- vars/toolValidate.groovy | 2 +- vars/transportRequestCreate.groovy | 2 +- vars/transportRequestRelease.groovy | 2 +- vars/transportRequestUploadFile.groovy | 2 +- 33 files changed, 33 insertions(+), 33 deletions(-) diff --git a/vars/artifactSetVersion.groovy b/vars/artifactSetVersion.groovy index 52a9337c5..0c66d816e 100644 --- a/vars/artifactSetVersion.groovy +++ b/vars/artifactSetVersion.groovy @@ -8,7 +8,7 @@ import com.sap.piper.versioning.ArtifactVersioning import groovy.transform.Field import groovy.text.SimpleTemplateEngine -@Field String STEP_NAME = 'artifactSetVersion' +@Field String STEP_NAME = getClass().getName() @Field Map CONFIG_KEY_COMPATIBILITY = [gitSshKeyCredentialsId: 'gitCredentialsId'] @Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS diff --git a/vars/batsExecuteTests.groovy b/vars/batsExecuteTests.groovy index d5b81f5ad..16a39334d 100644 --- a/vars/batsExecuteTests.groovy +++ b/vars/batsExecuteTests.groovy @@ -6,7 +6,7 @@ import com.sap.piper.Utils import groovy.text.SimpleTemplateEngine import groovy.transform.Field -@Field String STEP_NAME = 'batsExecuteTests' +@Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS diff --git a/vars/checkChangeInDevelopment.groovy b/vars/checkChangeInDevelopment.groovy index 51413f232..f6521f015 100644 --- a/vars/checkChangeInDevelopment.groovy +++ b/vars/checkChangeInDevelopment.groovy @@ -14,7 +14,7 @@ import com.sap.piper.cm.ChangeManagementException import static com.sap.piper.cm.StepHelpers.getChangeDocumentId import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrationDisabled -@Field def STEP_NAME = 'checkChangeInDevelopment' +@Field def STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS diff --git a/vars/checksPublishResults.groovy b/vars/checksPublishResults.groovy index dc7b07c82..8ec884252 100644 --- a/vars/checksPublishResults.groovy +++ b/vars/checksPublishResults.groovy @@ -8,7 +8,7 @@ import com.sap.piper.Utils import groovy.transform.Field -@Field def STEP_NAME = 'checksPublishResults' +@Field def STEP_NAME = getClass().getName() @Field Set TOOLS = [ 'aggregation', 'tasks', 'pmd', 'cpd', 'findbugs', 'checkstyle', 'eslint', 'pylint' diff --git a/vars/cloudFoundryDeploy.groovy b/vars/cloudFoundryDeploy.groovy index b02735eac..b33d25ba8 100644 --- a/vars/cloudFoundryDeploy.groovy +++ b/vars/cloudFoundryDeploy.groovy @@ -6,7 +6,7 @@ import com.sap.piper.CfManifestUtils import groovy.transform.Field -@Field String STEP_NAME = 'cloudFoundryDeploy' +@Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS diff --git a/vars/dockerExecute.groovy b/vars/dockerExecute.groovy index 4e2c1a559..46e7401bc 100644 --- a/vars/dockerExecute.groovy +++ b/vars/dockerExecute.groovy @@ -9,7 +9,7 @@ import com.sap.piper.k8s.ContainerMap import groovy.transform.Field -@Field def STEP_NAME = 'dockerExecute' +@Field def STEP_NAME = getClass().getName() @Field def PLUGIN_ID_DOCKER_WORKFLOW = 'docker-workflow' @Field Set GENERAL_CONFIG_KEYS = ['jenkinsKubernetes'] diff --git a/vars/dockerExecuteOnKubernetes.groovy b/vars/dockerExecuteOnKubernetes.groovy index ccc1f98c8..e4eb2de89 100644 --- a/vars/dockerExecuteOnKubernetes.groovy +++ b/vars/dockerExecuteOnKubernetes.groovy @@ -7,7 +7,7 @@ import com.sap.piper.k8s.SystemEnv import groovy.transform.Field import hudson.AbortException -@Field def STEP_NAME = 'dockerExecuteOnKubernetes' +@Field def STEP_NAME = getClass().getName() @Field def PLUGIN_ID_KUBERNETES = 'kubernetes' @Field Set GENERAL_CONFIG_KEYS = ['jenkinsKubernetes'] @Field Set PARAMETER_KEYS = [ diff --git a/vars/durationMeasure.groovy b/vars/durationMeasure.groovy index e2bcdc787..2f254dd2c 100644 --- a/vars/durationMeasure.groovy +++ b/vars/durationMeasure.groovy @@ -1,7 +1,7 @@ import static com.sap.piper.Prerequisites.checkScript import groovy.transform.Field -@Field STEP_NAME = 'durationMeasure' +@Field STEP_NAME = getClass().getName() def call(Map parameters = [:], body) { diff --git a/vars/gaugeExecuteTests.groovy b/vars/gaugeExecuteTests.groovy index 2ac46bd4a..e2664ccd5 100644 --- a/vars/gaugeExecuteTests.groovy +++ b/vars/gaugeExecuteTests.groovy @@ -6,7 +6,7 @@ import com.sap.piper.GitUtils import groovy.text.SimpleTemplateEngine import groovy.transform.Field -@Field String STEP_NAME = 'gaugeExecuteTests' +@Field String STEP_NAME = getClass().getName() @Field Set STEP_CONFIG_KEYS = [ 'buildTool', 'dockerEnvVars', diff --git a/vars/githubPublishRelease.groovy b/vars/githubPublishRelease.groovy index d6bcc347b..2ec89bb3c 100644 --- a/vars/githubPublishRelease.groovy +++ b/vars/githubPublishRelease.groovy @@ -5,7 +5,7 @@ import com.sap.piper.ConfigurationHelper import groovy.transform.Field -@Field String STEP_NAME = 'githubPublishRelease' +@Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = ['githubApiUrl', 'githubTokenCredentialsId', 'githubServerUrl'] @Field Set STEP_CONFIG_KEYS = [ 'addClosedIssues', diff --git a/vars/handlePipelineStepErrors.groovy b/vars/handlePipelineStepErrors.groovy index 00bb0ab5d..c304ac7f0 100644 --- a/vars/handlePipelineStepErrors.groovy +++ b/vars/handlePipelineStepErrors.groovy @@ -1,7 +1,7 @@ import groovy.text.SimpleTemplateEngine import groovy.transform.Field -@Field STEP_NAME = 'handlePipelineStepErrors' +@Field STEP_NAME = getClass().getName() void call(Map parameters = [:], body) { def stepParameters = parameters.stepParameters //mandatory diff --git a/vars/healthExecuteCheck.groovy b/vars/healthExecuteCheck.groovy index 723b83cc5..98472d2e2 100644 --- a/vars/healthExecuteCheck.groovy +++ b/vars/healthExecuteCheck.groovy @@ -4,7 +4,7 @@ import com.sap.piper.ConfigurationHelper import com.sap.piper.Utils import groovy.transform.Field -@Field String STEP_NAME = 'healthExecuteCheck' +@Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS diff --git a/vars/influxWriteData.groovy b/vars/influxWriteData.groovy index e1c323f69..e6ed78673 100644 --- a/vars/influxWriteData.groovy +++ b/vars/influxWriteData.groovy @@ -8,7 +8,7 @@ import com.sap.piper.Utils import groovy.transform.Field -@Field def STEP_NAME = 'influxWriteData' +@Field def STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = [] @Field Set STEP_CONFIG_KEYS = [ diff --git a/vars/karmaExecuteTests.groovy b/vars/karmaExecuteTests.groovy index 8dc16752b..e12b00f47 100644 --- a/vars/karmaExecuteTests.groovy +++ b/vars/karmaExecuteTests.groovy @@ -7,7 +7,7 @@ import com.sap.piper.Utils import groovy.text.SimpleTemplateEngine import groovy.transform.Field -@Field String STEP_NAME = 'karmaExecuteTests' +@Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = [ /** port mappings required for containers. This will only take effect inside a Kubernetes pod, format [[containerPort: 1111, hostPort: 1111]] */ 'containerPortMappings', diff --git a/vars/mailSendNotification.groovy b/vars/mailSendNotification.groovy index 9f6edeaa7..14fc3333c 100644 --- a/vars/mailSendNotification.groovy +++ b/vars/mailSendNotification.groovy @@ -5,7 +5,7 @@ import com.sap.piper.Utils import groovy.text.SimpleTemplateEngine import groovy.transform.Field -@Field String STEP_NAME = 'mailSendNotification' +@Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = ['gitSshKeyCredentialsId'] @Field Set STEP_CONFIG_KEYS = [ 'projectName', diff --git a/vars/mavenExecute.groovy b/vars/mavenExecute.groovy index 704bdbc8c..c91137cc4 100644 --- a/vars/mavenExecute.groovy +++ b/vars/mavenExecute.groovy @@ -5,7 +5,7 @@ import com.sap.piper.Utils import groovy.transform.Field -@Field def STEP_NAME = 'mavenExecute' +@Field def STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = [] @Field Set STEP_CONFIG_KEYS = [ diff --git a/vars/mtaBuild.groovy b/vars/mtaBuild.groovy index c0161b83b..bb76607fb 100644 --- a/vars/mtaBuild.groovy +++ b/vars/mtaBuild.groovy @@ -8,7 +8,7 @@ import com.sap.piper.tools.ToolDescriptor import groovy.transform.Field -@Field def STEP_NAME = 'mtaBuild' +@Field def STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = [] @Field Set STEP_CONFIG_KEYS = [ diff --git a/vars/neoDeploy.groovy b/vars/neoDeploy.groovy index 01c4128fb..66d4bf134 100644 --- a/vars/neoDeploy.groovy +++ b/vars/neoDeploy.groovy @@ -7,7 +7,7 @@ import com.sap.piper.tools.ToolDescriptor import groovy.transform.Field -@Field String STEP_NAME = 'neoDeploy' +@Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = [] @Field Set STEP_CONFIG_KEYS = [ 'account', diff --git a/vars/newmanExecute.groovy b/vars/newmanExecute.groovy index 8ed3d5a57..cd2e2019b 100644 --- a/vars/newmanExecute.groovy +++ b/vars/newmanExecute.groovy @@ -6,7 +6,7 @@ import com.sap.piper.Utils import groovy.text.SimpleTemplateEngine import groovy.transform.Field -@Field String STEP_NAME = 'newmanExecute' +@Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS diff --git a/vars/pipelineExecute.groovy b/vars/pipelineExecute.groovy index 2607c5c8d..85f95aab8 100644 --- a/vars/pipelineExecute.groovy +++ b/vars/pipelineExecute.groovy @@ -3,7 +3,7 @@ import com.sap.piper.Utils import groovy.transform.Field -@Field STEP_NAME = 'pipelineExecute' +@Field STEP_NAME = getClass().getName() /** diff --git a/vars/pipelineRestartSteps.groovy b/vars/pipelineRestartSteps.groovy index b90d2c110..d94a34415 100644 --- a/vars/pipelineRestartSteps.groovy +++ b/vars/pipelineRestartSteps.groovy @@ -4,7 +4,7 @@ import com.sap.piper.JenkinsUtils import com.sap.piper.ConfigurationHelper import groovy.transform.Field -@Field String STEP_NAME = 'pipelineRestartSteps' +@Field String STEP_NAME = getClass().getName() @Field Set STEP_CONFIG_KEYS = [ 'sendMail', 'timeoutInSeconds' diff --git a/vars/pipelineStashFiles.groovy b/vars/pipelineStashFiles.groovy index cb58e7e91..286562293 100644 --- a/vars/pipelineStashFiles.groovy +++ b/vars/pipelineStashFiles.groovy @@ -1,6 +1,6 @@ import groovy.transform.Field -@Field STEP_NAME = 'pipelineStashFiles' +@Field STEP_NAME = getClass().getName() void call(Map parameters = [:], body) { handlePipelineStepErrors (stepName: 'pipelineStashFiles', stepParameters: parameters) { diff --git a/vars/pipelineStashFilesAfterBuild.groovy b/vars/pipelineStashFilesAfterBuild.groovy index edcd54d9d..7ca2a56ad 100644 --- a/vars/pipelineStashFilesAfterBuild.groovy +++ b/vars/pipelineStashFilesAfterBuild.groovy @@ -4,7 +4,7 @@ import com.sap.piper.Utils import com.sap.piper.ConfigurationHelper import groovy.transform.Field -@Field String STEP_NAME = 'pipelineStashFilesAfterBuild' +@Field String STEP_NAME = getClass().getName() @Field Set STEP_CONFIG_KEYS = ['runCheckmarx', 'stashIncludes', 'stashExcludes'] @Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS diff --git a/vars/pipelineStashFilesBeforeBuild.groovy b/vars/pipelineStashFilesBeforeBuild.groovy index dd90ba498..86657feaf 100644 --- a/vars/pipelineStashFilesBeforeBuild.groovy +++ b/vars/pipelineStashFilesBeforeBuild.groovy @@ -4,7 +4,7 @@ import com.sap.piper.Utils import com.sap.piper.ConfigurationHelper import groovy.transform.Field -@Field String STEP_NAME = 'pipelineStashFilesBeforeBuild' +@Field String STEP_NAME = getClass().getName() @Field Set STEP_CONFIG_KEYS = ['runOpaTests', 'stashIncludes', 'stashExcludes'] @Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS diff --git a/vars/prepareDefaultValues.groovy b/vars/prepareDefaultValues.groovy index 9c049065c..c1e80d323 100644 --- a/vars/prepareDefaultValues.groovy +++ b/vars/prepareDefaultValues.groovy @@ -3,7 +3,7 @@ import com.sap.piper.MapUtils import groovy.transform.Field -@Field STEP_NAME = 'prepareDefaultValues' +@Field STEP_NAME = getClass().getName() void call(Map parameters = [:]) { handlePipelineStepErrors (stepName: 'prepareDefaultValues', stepParameters: parameters) { diff --git a/vars/seleniumExecuteTests.groovy b/vars/seleniumExecuteTests.groovy index 62a637e71..93823db2d 100644 --- a/vars/seleniumExecuteTests.groovy +++ b/vars/seleniumExecuteTests.groovy @@ -7,7 +7,7 @@ import com.sap.piper.k8s.ContainerMap import groovy.transform.Field import groovy.text.SimpleTemplateEngine -@Field String STEP_NAME = 'seleniumExecuteTests' +@Field String STEP_NAME = getClass().getName() @Field GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS diff --git a/vars/setupCommonPipelineEnvironment.groovy b/vars/setupCommonPipelineEnvironment.groovy index c9298e14c..ea6d1f704 100644 --- a/vars/setupCommonPipelineEnvironment.groovy +++ b/vars/setupCommonPipelineEnvironment.groovy @@ -4,7 +4,7 @@ import com.sap.piper.ConfigurationHelper import com.sap.piper.Utils import groovy.transform.Field -@Field String STEP_NAME = 'setupCommonPipelineEnvironment' +@Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = ['collectTelemetryData'] void call(Map parameters = [:]) { diff --git a/vars/snykExecute.groovy b/vars/snykExecute.groovy index a4dd8834b..f555f04b2 100644 --- a/vars/snykExecute.groovy +++ b/vars/snykExecute.groovy @@ -6,7 +6,7 @@ import com.sap.piper.mta.MtaMultiplexer import groovy.transform.Field -@Field def STEP_NAME = 'snykExecute' +@Field def STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = ['snykCredentialsId'] @Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([ diff --git a/vars/testsPublishResults.groovy b/vars/testsPublishResults.groovy index 03875bb96..881aa9b05 100644 --- a/vars/testsPublishResults.groovy +++ b/vars/testsPublishResults.groovy @@ -11,7 +11,7 @@ import groovy.transform.Field 'junit','jacoco','cobertura','jmeter' ] -@Field def STEP_NAME = 'testsPublishResults' +@Field def STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = TOOLS @Field Set STEP_CONFIG_KEYS = TOOLS @Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS diff --git a/vars/toolValidate.groovy b/vars/toolValidate.groovy index 2813764d7..7168809d6 100644 --- a/vars/toolValidate.groovy +++ b/vars/toolValidate.groovy @@ -8,7 +8,7 @@ import groovy.transform.Field import hudson.AbortException -@Field STEP_NAME = 'toolValidate' +@Field STEP_NAME = getClass().getName() void call(Map parameters = [:]) { diff --git a/vars/transportRequestCreate.groovy b/vars/transportRequestCreate.groovy index cfa9ba023..91b27e5bb 100644 --- a/vars/transportRequestCreate.groovy +++ b/vars/transportRequestCreate.groovy @@ -13,7 +13,7 @@ import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrati import static com.sap.piper.cm.StepHelpers.getChangeDocumentId import hudson.AbortException -@Field def STEP_NAME = 'transportRequestCreate' +@Field def STEP_NAME = getClass().getName() @Field GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS diff --git a/vars/transportRequestRelease.groovy b/vars/transportRequestRelease.groovy index d11903f3e..938a3c6f0 100644 --- a/vars/transportRequestRelease.groovy +++ b/vars/transportRequestRelease.groovy @@ -14,7 +14,7 @@ import static com.sap.piper.cm.StepHelpers.getTransportRequestId import static com.sap.piper.cm.StepHelpers.getChangeDocumentId import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrationDisabled -@Field def STEP_NAME = 'transportRequestRelease' +@Field def STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS diff --git a/vars/transportRequestUploadFile.groovy b/vars/transportRequestUploadFile.groovy index 34eef2ff7..10a6a7d71 100644 --- a/vars/transportRequestUploadFile.groovy +++ b/vars/transportRequestUploadFile.groovy @@ -14,7 +14,7 @@ import static com.sap.piper.cm.StepHelpers.getTransportRequestId import static com.sap.piper.cm.StepHelpers.getChangeDocumentId import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrationDisabled -@Field def STEP_NAME = 'transportRequestUploadFile' +@Field def STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = [ 'changeManagement' From 74193bd98d8b0221c990fc43746b81381d5df412 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Wed, 5 Dec 2018 13:37:15 +0100 Subject: [PATCH 31/32] Documentation Update - streamline library alias (#402) Change name to how vast majority of current users is using it. Library alias can also be defined differently by users. Then `@Library` statement just needs to reflect the different name. --- README.md | 6 +++--- documentation/docs/images/setupInJenkins.png | Bin 22093 -> 35572 bytes .../docs/steps/checkChangeInDevelopment.md | 2 +- .../docs/steps/transportRequestCreate.md | 2 +- .../docs/steps/transportRequestRelease.md | 2 +- .../docs/steps/transportRequestUploadFile.md | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 047fce3f5..d832e97f2 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ To setup the shared library, you need to perform the following steps: 1. Open the system configuration page (*Manage Jenkins > Configure System*). 1. Scroll down to section *Global Pipeline Libraries* and add a new Library by clicking the *Add* button. - 1. set *Library Name* to `piper-library-os` + 1. set *Library Name* to `piper-lib-os` 1. set *Default Version* to the branch or tag you want to consume (e.g. `master` or `v0.1`) 1. set *Retrieval Method* to `Modern SCM` @@ -82,11 +82,11 @@ To setup the shared library, you need to perform the following steps: ![Library Setup](./documentation/docs/images/setupInJenkins.png) -Now the library is available as `piper-library-os` and can be used in any +Now the library is available as `piper-lib-os` and can be used in any `Jenkinsfile` by adding this line: ```groovy -@Library('piper-library-os') _ +@Library('piper-lib-os') _ ``` Jenkins will download the library during execution of the `Jenkinsfile`. diff --git a/documentation/docs/images/setupInJenkins.png b/documentation/docs/images/setupInJenkins.png index f19ac41ca3557460b71719c504be03f1acb87704..c283fee7b5bae08045676c4eed5ffde4aba5cc14 100755 GIT binary patch literal 35572 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

jS#8cbu*zM`c{?y77zADR@ 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 E2fi}nga7~l literal 22093 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 XJ$|R6%=QOBw=j6R`njxgN@xNAqs4E+ diff --git a/documentation/docs/steps/checkChangeInDevelopment.md b/documentation/docs/steps/checkChangeInDevelopment.md index a850cfa07..f3562bc3e 100644 --- a/documentation/docs/steps/checkChangeInDevelopment.md +++ b/documentation/docs/steps/checkChangeInDevelopment.md @@ -42,7 +42,7 @@ The step is configured using a customer configuration file provided as resource in an custom shared library. ```groovy -@Library('piper-library-os@master') _ +@Library('piper-lib-os@master') _ // the shared lib containing the additional configuration // needs to be configured in Jenkins diff --git a/documentation/docs/steps/transportRequestCreate.md b/documentation/docs/steps/transportRequestCreate.md index a4b7fb2e3..31ec813c1 100644 --- a/documentation/docs/steps/transportRequestCreate.md +++ b/documentation/docs/steps/transportRequestCreate.md @@ -51,7 +51,7 @@ The step is configured using a customer configuration file provided as resource in an custom shared library. ```groovy -@Library('piper-library-os@master') _ +@Library('piper-lib-os@master') _ // the shared lib containing the additional configuration // needs to be configured in Jenkins diff --git a/documentation/docs/steps/transportRequestRelease.md b/documentation/docs/steps/transportRequestRelease.md index 59afc7f72..659ac023e 100644 --- a/documentation/docs/steps/transportRequestRelease.md +++ b/documentation/docs/steps/transportRequestRelease.md @@ -41,7 +41,7 @@ The step is configured using a customer configuration file provided as resource in an custom shared library. ```groovy -@Library('piper-library-os@master') _ +@Library('piper-lib-os@master') _ // the shared lib containing the additional configuration // needs to be configured in Jenkins diff --git a/documentation/docs/steps/transportRequestUploadFile.md b/documentation/docs/steps/transportRequestUploadFile.md index 88eee8a44..6b72c7f81 100644 --- a/documentation/docs/steps/transportRequestUploadFile.md +++ b/documentation/docs/steps/transportRequestUploadFile.md @@ -46,7 +46,7 @@ The step is configured using a customer configuration file provided as resource in an custom shared library. ```groovy -@Library('piper-library-os@master') _ +@Library('piper-lib-os@master') _ // the shared lib containing the additional configuration // needs to be configured in Jenkins From f82267a680aa8361279e1e3b4594e7b837755304 Mon Sep 17 00:00:00 2001 From: Marcus Holl Date: Fri, 7 Dec 2018 09:02:31 +0100 Subject: [PATCH 32/32] Update mail recipients --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 836447266..713d24ebf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,7 +13,7 @@ node { } catch (Throwable err) { echo "Error occured: ${err}" currentBuild.result = 'FAILURE' - mail subject: '[Build failed] SAP/jenkins-library', body: 'Fix the build.', to: 'k.arnold@sap.com,marcus.holl@sap.com,oliver.nocon@sap.com' + mail subject: '[Build failed] SAP/jenkins-library', body: 'Fix the build.', to: 'marcus.holl@sap.com,oliver.nocon@sap.com' throw err } }