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
This commit is contained in:
Oliver Nocon
2019-02-13 16:45:35 +01:00
committed by Christopher Fenner
parent 56d3a6ed97
commit 793df723cf
5 changed files with 218 additions and 0 deletions
@@ -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|
+1
View File
@@ -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
@@ -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'
@@ -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 <http://build.url|#1>', 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 <http://build.url|#1>', 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()
}
}
+53
View File
@@ -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)
}
}