mirror of
https://github.com/SAP/jenkins-library.git
synced 2026-06-19 22:58:55 +02:00
Merge branch 'master' into pr/resolvePlugins
This commit is contained in:
@@ -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'))
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
]
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user