From 793df723cfd49e347ff5ac5983a835e601734776 Mon Sep 17 00:00:00 2001 From: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com> Date: Wed, 13 Feb 2019 16:45:35 +0100 Subject: [PATCH 1/3] add step slackSendNotification (#338) * add step slackSendNotification This step allows to send Slack notifications in case of pipeline failures. * add SWA reporting * remove allowBuildFailure * add GENERAL_CONFIG_KEYS * update STEP_NAME * add missing import * fix MD findings * adjust rule name to be aligned with #455 --- .../docs/steps/slackSendNotification.md | 73 +++++++++++++++ documentation/mkdocs.yml | 1 + resources/default_pipeline_environment.yml | 3 + test/groovy/SlackSendNotificationTest.groovy | 88 +++++++++++++++++++ vars/slackSendNotification.groovy | 53 +++++++++++ 5 files changed, 218 insertions(+) create mode 100644 documentation/docs/steps/slackSendNotification.md create mode 100644 test/groovy/SlackSendNotificationTest.groovy create mode 100644 vars/slackSendNotification.groovy diff --git a/documentation/docs/steps/slackSendNotification.md b/documentation/docs/steps/slackSendNotification.md new file mode 100644 index 000000000..4357d0978 --- /dev/null +++ b/documentation/docs/steps/slackSendNotification.md @@ -0,0 +1,73 @@ +# slackSendNotification + +## Description + +Sends notifications to the Slack channel about the build status. + +Notification contains: + +* Build status; +* Repo Owner; +* Repo Name; +* Branch Name; +* Jenkins Build Number; +* Jenkins Build URL. + +## Prerequisites + +Installed and configured [Jenkins Slack plugin](https://github.com/jenkinsci/slack-plugin). + +## Example + +Usage of pipeline step: + +```groovy +try { + stage('..') {..} + stage('..') {..} + stage('..') {..} + currentBuild.result = 'SUCCESS' +} catch (Throwable err) { + currentBuild.result = 'FAILURE' + throw err +} finally { + stage('report') { + slackSendNotification script: this + } +} +``` + +## Parameters + +| parameter | mandatory | default | possible values | +| ----------|-----------|---------|-----------------| +|script|yes||| +|baseUrl|no||| +|channel|no||| +|color|no|`${buildStatus == 'SUCCESS'?'#008000':'#E60000'}`|| +|credentialsId|no||| +|message|no||| + +### Details + +* `script` defines the global script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for storing the measured duration. +* `baseUrl` allows overriding the Slack Plugin Integration Base Url specified in the global configuration. +* `color` defines the message color. +* If `channel` is defined another than the default channel will be used. +* `credentialsId` defines the Jenkins credentialId which holds the Slack token +* With parameter `message` a custom message can be defined which is sent into the Slack channel. + +## Step configuration + +We recommend to define values of step parameters via [config.yml file](../configuration.md). + +In following sections the configuration is possible: + +| parameter | general | step | stage | +| ----------|-----------|---------|-----------------| +|script|||| +|baseUrl||X|X| +|channel||X|X| +|color||X|X| +|credentialsId||X|X| +|message||X|X| diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml index 4ad088c00..f766e9799 100644 --- a/documentation/mkdocs.yml +++ b/documentation/mkdocs.yml @@ -30,6 +30,7 @@ nav: - prepareDefaultValues: steps/prepareDefaultValues.md - seleniumExecuteTests: steps/seleniumExecuteTests.md - setupCommonPipelineEnvironment: steps/setupCommonPipelineEnvironment.md + - slackSendNotification: steps/slackSendNotification.md - testsPublishResults: steps/testsPublishResults.md - toolValidate: steps/toolValidate.md - transportRequestCreate: steps/transportRequestCreate.md diff --git a/resources/default_pipeline_environment.yml b/resources/default_pipeline_environment.yml index 9c781f17b..2d8d65327 100644 --- a/resources/default_pipeline_environment.yml +++ b/resources/default_pipeline_environment.yml @@ -305,6 +305,9 @@ steps: dockerImage: 'node:8-stretch' dockerName: 'npm' dockerWorkspace: '/home/node' + slackSendNotification: + color: "${buildStatus == 'SUCCESS'?'#008000':'#E60000'}" + defaultMessage: "${buildStatus}: Job ${env.JOB_NAME} <${env.BUILD_URL}|#${env.BUILD_NUMBER}>" snykExecute: buildDescriptorFile: './package.json' dockerImage: 'node:8-stretch' diff --git a/test/groovy/SlackSendNotificationTest.groovy b/test/groovy/SlackSendNotificationTest.groovy new file mode 100644 index 000000000..c59f82b41 --- /dev/null +++ b/test/groovy/SlackSendNotificationTest.groovy @@ -0,0 +1,88 @@ +#!groovy +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.JenkinsReadYamlRule +import util.JenkinsStepRule +import util.Rules + +import static org.junit.Assert.* + +class SlackSendNotificationTest extends BasePiperTest { + def slackCallMap = [:] + + private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this) + private JenkinsStepRule stepRule = new JenkinsStepRule(this) + + @Rule + public RuleChain ruleChain = Rules + .getCommonRules(this) + .around(new JenkinsReadYamlRule(this)) + .around(loggingRule) + .around(stepRule) + + @Before + void init() throws Exception { + helper.registerAllowedMethod("slackSend", [Map.class], {m -> slackCallMap = m}) + } + + @Test + void testNotificationBuildSuccessDefaultChannel() throws Exception { + stepRule.step.slackSendNotification(script: [currentBuild: [result: 'SUCCESS']]) + // asserts + assertEquals('Message not set correctly', 'SUCCESS: Job p ', slackCallMap.message.toString()) + assertNull('Channel not set correctly', slackCallMap.channel) + assertEquals('Color not set correctly', '#008000', slackCallMap.color) + assertJobStatusSuccess() + } + + @Test + void testNotificationBuildSuccessCustomChannel() throws Exception { + stepRule.step.slackSendNotification(script: [currentBuild: [result: 'SUCCCESS']], channel: 'Test') + // asserts + assertEquals('Channel not set correctly', 'Test', slackCallMap.channel) + assertJobStatusSuccess() + } + + @Test + void testNotificationBuildFailed() throws Exception { + stepRule.step.slackSendNotification(script: [currentBuild: [result: 'FAILURE']]) + // asserts + assertEquals('Message not set correctly', 'FAILURE: Job p ', slackCallMap.message.toString()) + assertEquals('Color not set correctly', '#E60000', slackCallMap.color) + } + + @Test + void testNotificationBuildStatusNull() throws Exception { + stepRule.step.slackSendNotification(script: [currentBuild: [:]]) + // asserts + assertTrue('Missing build status not detected', loggingRule.log.contains('currentBuild.result is not set. Skipping Slack notification')) + assertJobStatusSuccess() + } + + @Test + void testNotificationCustomMessageAndColor() throws Exception { + stepRule.step.slackSendNotification(script: [currentBuild: [:]], message: 'Custom Message', color: '#AAAAAA') + // asserts + assertEquals('Custom message not set correctly', 'Custom Message', slackCallMap.message.toString()) + assertEquals('Custom color not set correctly', '#AAAAAA', slackCallMap.color) + assertJobStatusSuccess() + } + + @Test + void testNotificationWithCustomCredentials() throws Exception { + stepRule.step.slackSendNotification( + script: [currentBuild: [:]], + message: 'I am no Message', + baseUrl: 'https://my.base.url', + credentialsId: 'MY_TOKEN_ID' + ) + // asserts + assertEquals('Custom base url not set correctly', 'https://my.base.url', slackCallMap.baseUrl) + assertEquals('Custom token id not set correctly', 'MY_TOKEN_ID', slackCallMap.tokenCredentialId) + assertJobStatusSuccess() + } +} diff --git a/vars/slackSendNotification.groovy b/vars/slackSendNotification.groovy new file mode 100644 index 000000000..33c1a0cee --- /dev/null +++ b/vars/slackSendNotification.groovy @@ -0,0 +1,53 @@ +import static com.sap.piper.Prerequisites.checkScript + +import com.sap.piper.ConfigurationHelper +import com.sap.piper.Utils +import groovy.transform.Field +import groovy.text.SimpleTemplateEngine + +@Field String STEP_NAME = getClass().getName() + +@Field Set GENERAL_CONFIG_KEYS = [] +@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([ + 'baseUrl', + 'channel', + 'color', + 'credentialsId', + 'message' +]) +@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS + +void call(Map parameters = [:]) { + handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) { + def utils = parameters.juStabUtils ?: new Utils() + def script = checkScript(this, parameters) ?: this + // load default & individual configuration + Map config = ConfigurationHelper.newInstance(this) + .loadStepDefaults() + .mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS) + .mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS) + .mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS) + .mixin(parameters, PARAMETER_KEYS) + .use() + + new Utils().pushToSWA([step: STEP_NAME], config) + + def buildStatus = script.currentBuild.result + // resolve templates + config.color = SimpleTemplateEngine.newInstance().createTemplate(config.color).make([buildStatus: buildStatus]).toString() + if (!config?.message){ + if (!buildStatus) { + echo "[${STEP_NAME}] currentBuild.result is not set. Skipping Slack notification" + return + } + config.message = SimpleTemplateEngine.newInstance().createTemplate(config.defaultMessage).make([buildStatus: buildStatus, env: env]).toString() + } + Map options = [:] + if(config.credentialsId) + options.put('tokenCredentialId', config.credentialsId) + for(String entry : STEP_CONFIG_KEYS.minus('credentialsId')) + if(config.get(entry)) + options.put(entry, config.get(entry)) + slackSend(options) + } +} From 523fc067f1a70c7b12aa0f05efbbe80381cff1b3 Mon Sep 17 00:00:00 2001 From: weloli <45266922+weloli@users.noreply.github.com> Date: Thu, 14 Feb 2019 08:45:20 +0100 Subject: [PATCH 2/3] fix general config for param changeManagement (#513) --- vars/checkChangeInDevelopment.groovy | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/vars/checkChangeInDevelopment.groovy b/vars/checkChangeInDevelopment.groovy index fdd8540a6..46a9ae4c0 100644 --- a/vars/checkChangeInDevelopment.groovy +++ b/vars/checkChangeInDevelopment.groovy @@ -16,17 +16,13 @@ import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrati @Field def STEP_NAME = getClass().getName() -@Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS - -@Field Set STEP_CONFIG_KEYS = [ - 'changeManagement', +@Field Set GENERAL_CONFIG_KEYS = ['changeManagement'] +@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus( /** * When set to `false` the step will not fail in case the step is not in status 'in development'. * @possibleValues `true`, `false` */ - 'failIfStatusIsNotInDevelopment' - ] - + 'failIfStatusIsNotInDevelopment') @Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus('changeDocumentId') /** From 58c8063e6de1f9e27446dd91cb8b12eb01570e8f Mon Sep 17 00:00:00 2001 From: Florian Wilhelm <2292245+fwilhe@users.noreply.github.com> Date: Thu, 14 Feb 2019 09:58:04 +0100 Subject: [PATCH 3/3] Add consumer tests for s4sdk pipeline (#506) Additional tests for running an example pipeline with the s4-sdk address manager example application. --- .gitignore | 2 ++ .travis.yml | 4 +++- consumer-test/jenkins.yml | 29 +++++++++++++++++++++++++++++ consumer-test/runTests.sh | 14 ++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 consumer-test/jenkins.yml create mode 100755 consumer-test/runTests.sh diff --git a/.gitignore b/.gitignore index 8e150e0a0..e286faf6a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ hs_err_pid* target/ targets/ documentation/docs-gen + +consumer-test/workspace diff --git a/.travis.yml b/.travis.yml index e0ab22dfd..d5cae0e77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,9 @@ jobs: cp -r documentation/docs documentation/docs-tmp documentation/bin/createDocu.sh vars documentation/docs-tmp/steps docker run --rm -it -v ${TRAVIS_BUILD_DIR}:/docs -w /docs/documentation squidfunk/mkdocs-material:3.0.4 build --clean --verbose --strict - + - name: Consumer Tests for s4sdk pipeline + script: cd consumer-test && chmod +x runTests.sh && ./runTests.sh + - stage: Docs name: Deploy if: repo = "SAP/jenkins-library" AND branch = master AND NOT type = pull_request diff --git a/consumer-test/jenkins.yml b/consumer-test/jenkins.yml new file mode 100644 index 000000000..84eff57ee --- /dev/null +++ b/consumer-test/jenkins.yml @@ -0,0 +1,29 @@ +jenkins: + numExecutors: 10 +unclassified: + globallibraries: + libraries: + - defaultVersion: "master" + name: "s4sdk-pipeline-library" + retriever: + modernSCM: + scm: + git: + remote: "https://github.com/SAP/cloud-s4-sdk-pipeline-lib.git" + - defaultVersion: "master" + name: "piper-library-os" + retriever: + modernSCM: + scm: + git: + remote: "https://github.com/__REPO_SLUG__.git" +credentials: + system: + domainCredentials: + - credentials: + - usernamePassword: + scope: GLOBAL + id: "devops-docker-images-IT-cf" + username: ${CX_INFRA_IT_CF_USERNAME} + password: ${CX_INFRA_IT_CF_PASSWORD} + description: "SAP CP Trail account for test deployment" diff --git a/consumer-test/runTests.sh b/consumer-test/runTests.sh new file mode 100755 index 000000000..9a3095f20 --- /dev/null +++ b/consumer-test/runTests.sh @@ -0,0 +1,14 @@ +#!/bin/bash -e + +LIBRARY_VERSION_UNDER_TEST=$(git log --format="%H" -n 1) +REPOSITORY_UNDER_TEST=${TRAVIS_REPO_SLUG:-SAP/jenkins-library} + +rm -rf workspace +git clone -b consumer-test https://github.com/sap/cloud-s4-sdk-book workspace +cp -f jenkins.yml workspace +cd workspace +sed -i -e "s:__REPO_SLUG__:${REPOSITORY_UNDER_TEST}:g" jenkins.yml +echo "@Library(\"piper-library-os@$LIBRARY_VERSION_UNDER_TEST\") _" | cat - Jenkinsfile > temp && mv temp Jenkinsfile +git commit --all --author="piper-testing-bot " --message="Set piper lib version for test" + +docker run -v /var/run/docker.sock:/var/run/docker.sock -v "${PWD}":/workspace -v /tmp -e CASC_JENKINS_CONFIG=/workspace/jenkins.yml -e CX_INFRA_IT_CF_USERNAME -e CX_INFRA_IT_CF_PASSWORD -e BRANCH_NAME=consumer-test ppiper/jenkinsfile-runner