Merge branch 'master' into pr/resolvePlugins

This commit is contained in:
Christopher Fenner
2019-05-22 11:27:58 +02:00
committed by GitHub
8 changed files with 249 additions and 28 deletions
+30 -14
View File
@@ -433,26 +433,42 @@ steps = []
//
// assign parameters
if(args.length >= 1)
stepsDir = new File(args[0])
def cli = new CliBuilder(
usage: 'groovy createDocu [<options>]',
header: 'Options:',
footer: 'Copyright: SAP SE')
cli.with {
s longOpt: 'stepsDir', args: 1, argName: 'dir', 'The directory containing the steps. Defaults to \'vars\'.'
d longOpt: 'docuDir', args: 1, argName: 'dir', 'The directory containing the docu stubs. Defaults to \'documentation/docs/steps\'.'
c longOpt: 'customDefaults', args: 1, argName: 'file', 'Additional custom default configuration'
h longOpt: 'help', 'Prints this help.'
}
def options = cli.parse(args)
if(options.h) {
System.err << "Printing help.\n"
cli.usage()
return
}
if(options.s)
stepsDir = new File(Helper.projectRoot, options.s)
stepsDir = stepsDir ?: new File(Helper.projectRoot, "vars")
if(args.length >= 2)
stepsDocuDir = new File(args[1])
if(options.d)
stepsDocuDir = new File(Helper.projectRoot, options.d)
stepsDocuDir = stepsDocuDir ?: new File(Helper.projectRoot, "documentation/docs/steps")
def argsDrop = 2
if(args.length >= 3 && args[2].contains('.yml')) {
customDefaults = args[2]
argsDrop ++
if(options.c) {
customDefaults = options.c
}
if(args.length >= 3)
steps = (args as List).drop(argsDrop) // the first two entries are stepsDir and docuDir
// the other parts are considered as step names
steps.addAll(options.arguments())
// assign parameters
//
@@ -657,9 +673,9 @@ def handleStep(stepName, prepareDefaultValuesStep, gse, customDefaults) {
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.',
'`commonPipelineEnvironment` for retrieving, e.g. configuration parameters.',
required: true,
GENERAL_CONFIG: false,
@@ -44,6 +44,8 @@ general:
# runAsUser: 1000
# fsGroup: 1000
manualConfirmation: true
manualConfirmationMessage: 'Shall we proceed to Promote & Release?'
manualConfirmationTimeout: 720 # 1 month
productiveBranch: 'master'
whitesource:
serviceUrl: 'https://saas.whitesourcesoftware.com/api'
@@ -0,0 +1,12 @@
package com.sap.piper
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.METHOD, ElementType.TYPE])
public @interface GenerateStageDocumentation {
public String defaultStageName()
}
@@ -0,0 +1,80 @@
#!groovy
package templates
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import util.*
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.is
import static org.junit.Assert.assertThat
class PiperPipelineStageConfirmTest extends BasePiperTest {
private JenkinsStepRule jsr = new JenkinsStepRule(this)
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private Map timeoutSettings
private Map inputSettings
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(jlr)
.around(jsr)
@Before
void init() {
binding.variables.env.STAGE_NAME = 'Confirm'
helper.registerAllowedMethod('timeout', [Map.class, Closure.class], {m, body ->
timeoutSettings = m
return body()
})
helper.registerAllowedMethod('input', [Map.class], {m ->
inputSettings = m
return [reason: 'this is my test reason for failing step 1 and step 3', acknowledgement: true]
})
}
@Test
void testStageDefault() {
jsr.step.piperPipelineStageConfirm(
script: nullScript
)
assertThat(timeoutSettings.unit, is('HOURS'))
assertThat(timeoutSettings.time, is(720))
assertThat(inputSettings.message, is('Shall we proceed to Promote & Release?'))
}
@Test
void testStageBuildUnstable() {
binding.setVariable('currentBuild', [result: 'UNSTABLE'])
nullScript.commonPipelineEnvironment.setValue('unstableSteps', ['step1', 'step3'])
helper.registerAllowedMethod('text', [Map.class], {m ->
assertThat(m.defaultValue, containsString('step1:'))
assertThat(m.defaultValue, containsString('step3:'))
assertThat(m.description, is('Please provide a reason for overruling following failed steps:'))
assertThat(m.name, is('reason'))
})
helper.registerAllowedMethod('booleanParam', [Map.class], {m ->
assertThat(m.description, is('I acknowledge that for traceability purposes the approval reason is stored together with my user name / user id:'))
assertThat(m.name, is('acknowledgement'))
})
jsr.step.piperPipelineStageConfirm(
script: nullScript
)
assertThat(inputSettings.message, is('Approve continuation of pipeline, although some steps failed.'))
assertThat(jlr.log, containsString('this is my test reason'))
assertThat(jlr.log, containsString('Acknowledged:\n-------------\ntrue'))
}
}
+41 -10
View File
@@ -54,17 +54,41 @@ class PiperPipelineTest extends BasePiperTest {
helper.registerAllowedMethod('when', [Closure.class], {cWhen ->
helper.registerAllowedMethod('allOf', [Closure.class], null)
helper.registerAllowedMethod('allOf', [Closure.class], {cAllOf ->
def branchResult = false
helper.registerAllowedMethod('branch', [String.class], {branchName ->
if (!branchResult)
branchResult = (branchName == env.BRANCH_NAME)
if( !branchResult) {
throw new PipelineWhenException("Stage '${stageName}' skipped - expression: '${branchResult}'")
}
})
helper.registerAllowedMethod('expression', [Closure.class], { Closure cExp ->
def result = cExp()
if(!result) {
throw new PipelineWhenException("Stage '${stageName}' skipped - expression: '${result}'")
}
return result
})
return cAllOf()
})
helper.registerAllowedMethod('anyOf', [Closure.class], {cAnyOf ->
def result = false
helper.registerAllowedMethod('branch', [String.class], {branchName ->
if (!result)
result = (branchName == env.BRANCH_NAME)
if( !result) {
throw new PipelineWhenException("Stage '${stageName}' skipped - expression: '${result}'")
}
return result
})
helper.registerAllowedMethod('expression', [Closure.class], { Closure cExp ->
if (!result)
result = cExp()
return result
})
cAnyOf()
if(!result) {
throw new PipelineWhenException("Stage '${stageName}' skipped - anyOf: '${result}'")
}
return cAnyOf()
})
@@ -151,8 +175,8 @@ class PiperPipelineTest extends BasePiperTest {
helper.registerAllowedMethod('piperPipelineStageCompliance', [Map.class], {m ->
stepsCalled.add('piperPipelineStageCompliance')
})
helper.registerAllowedMethod('input', [Map.class], {m ->
stepsCalled.add('input')
helper.registerAllowedMethod('piperPipelineStageConfirm', [Map.class], {m ->
stepsCalled.add('piperPipelineStageConfirm')
})
helper.registerAllowedMethod('piperPipelineStagePromote', [Map.class], {m ->
stepsCalled.add('piperPipelineStagePromote')
@@ -188,10 +212,17 @@ class PiperPipelineTest extends BasePiperTest {
}
@Test
void testConfirm() {
void testConfirmUnstable() {
nullScript.commonPipelineEnvironment.configuration = [
general: [
manualConfirmation: false
]
]
binding.setVariable('currentBuild', [result: 'UNSTABLE'])
jsr.step.piperPipeline(script: nullScript)
assertThat(stepsCalled, hasItem('input'))
assertThat(stepsCalled, hasItem('piperPipelineStageConfirm'))
}
@@ -204,7 +235,7 @@ class PiperPipelineTest extends BasePiperTest {
]
jsr.step.piperPipeline(script: nullScript)
assertThat(stepsCalled, not(hasItem('input')))
assertThat(stepsCalled, not(hasItem('piperPipelineStageConfirm')))
}
@Test
@@ -232,7 +263,7 @@ class PiperPipelineTest extends BasePiperTest {
'piperPipelineStageSecurity',
'piperPipelineStagePerformance',
'piperPipelineStageCompliance',
'input',
'piperPipelineStageConfirm',
'piperPipelineStagePromote',
'piperPipelineStageRelease',
'piperPipelineStagePost'
+2 -2
View File
@@ -11,11 +11,11 @@ import groovy.transform.Field
@Field Set STEP_CONFIG_KEYS = [
/**
* If it is set to true` the step `mailSendNotification` will be triggered in case of an error.
* If it is set to `true` the step `mailSendNotification` will be triggered in case of an error.
*/
'sendMail',
/**
* Defines the time period where the job waits for input. Default is 15 minutes. Once this time is passed the job enters state FAILED.
* Defines the time period where the job waits for input. Default is 15 minutes. Once this time is passed the job enters state `FAILED`.
*/
'timeoutInSeconds'
]
+2 -2
View File
@@ -62,9 +62,9 @@ void call(parameters) {
}
stage('Confirm') {
agent none
when {allOf {branch parameters.script.commonPipelineEnvironment.getStepConfiguration('', '').productiveBranch; expression {return parameters.script.commonPipelineEnvironment.getStepConfiguration('piperInitRunStageConfiguration', env.STAGE_NAME).manualConfirmation}}}
when {allOf {expression { env.BRANCH_NAME ==~ parameters.script.commonPipelineEnvironment.getStepConfiguration('', '').productiveBranch }; anyOf {expression {return (currentBuild.result == 'UNSTABLE')}; expression {return parameters.script.commonPipelineEnvironment.getStepConfiguration('piperInitRunStageConfiguration', env.STAGE_NAME).manualConfirmation}}}}
steps {
input message: 'Shall we proceed to promotion & release?'
piperPipelineStageConfirm script: parameters.script
}
}
stage('Promote') {
+80
View File
@@ -0,0 +1,80 @@
import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateStageDocumentation
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
/**
* Specifies if a manual confirmation is active before running the __Promote__ and __Release__ stages of the pipeline.
* @possibleValues `true`, `false`
*/
'manualConfirmation',
/** Defines message displayed as default manual confirmation. Please note: only used in case pipeline is in state __SUCCESSFUL__ */
'manualConfirmationMessage',
/** Defines how many hours a manual confirmation is possible for a dedicated pipeline. */
'manualConfirmationTimeout'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* In this stage a manual confirmation is requested before processing subsequent stages like __Promote__ and __Release__.
*
* This stage will be active in two scenarios:
* - manual activation of this stage
* - in case of an 'UNSTABLE' build (even when manual confirmation is inactive)
*/
@GenerateStageDocumentation(defaultStageName = 'Confirm')
void call(Map parameters = [:]) {
def script = checkScript(this, parameters) ?: this
def stageName = parameters.stageName?:env.STAGE_NAME
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.use()
String unstableStepNames = script.commonPipelineEnvironment.getValue('unstableSteps') ? "${script.commonPipelineEnvironment.getValue('unstableSteps').join(':\n------\n')}:" : ''
boolean approval = false
def userInput
timeout(
unit: 'HOURS',
time: config.manualConfirmationTimeout
){
if (currentBuild.result == 'UNSTABLE') {
while(!approval) {
userInput = input(
message: 'Approve continuation of pipeline, although some steps failed.',
ok: 'Approve',
parameters: [
text(
defaultValue: unstableStepNames,
description: 'Please provide a reason for overruling following failed steps:',
name: 'reason'
),
booleanParam(
defaultValue: false,
description: 'I acknowledge that for traceability purposes the approval reason is stored together with my user name / user id:',
name: 'acknowledgement'
)
]
)
approval = userInput.acknowledgement && userInput.reason?.length() > (unstableStepNames.length() + 10)
}
echo "Reason:\n-------------\n${userInput.reason}"
echo "Acknowledged:\n-------------\n${userInput.acknowledgement}"
} else {
input message: config.manualConfirmationMessage
}
}
}