mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
Merge remote-tracking branch 'github/master' into HEAD
This commit is contained in:
commit
56b651dbe9
@ -37,7 +37,10 @@ jobs:
|
||||
name: Create Documentation
|
||||
install: docker pull squidfunk/mkdocs-material:3.0.4
|
||||
before_script: documentation/bin/createDocu.sh
|
||||
script: docker run --rm -it -v ${TRAVIS_BUILD_DIR}/documentation:/docs squidfunk/mkdocs-material:3.0.4 build --clean --strict
|
||||
script:
|
||||
- docker run -u `id -u`:`id -g` --rm -it -v ${TRAVIS_BUILD_DIR}/documentation:/docs squidfunk/mkdocs-material:3.0.4 build --clean --strict
|
||||
- mkdir -p documentation/docs-gen/misc
|
||||
- cp target/docuMetaData.json documentation/docs-gen/misc
|
||||
deploy:
|
||||
on:
|
||||
branch: master
|
||||
|
@ -71,11 +71,9 @@ To setup the shared library, you need to perform the following steps:
|
||||
|
||||
1. Login to your Jenkins instance with administration privileges.
|
||||
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. Scroll down to section *Global Pipeline Libraries* and add a new Library by clicking the *Add* button.
|
||||
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 *Default Version* to the branch or tag you want to consume (e.g. `master` or `v0.1`)
|
||||
1. set *Retrieval Method* to `Modern SCM`
|
||||
1. set *Source Code Management* to `Git`
|
||||
1. set *Project Repository* to `https://github.com/SAP/jenkins-library`
|
||||
@ -103,6 +101,7 @@ Feel free to open new issues for feature requests, bugs or general feedback on
|
||||
the [GitHub issues page of this project][piper-library-issues].
|
||||
|
||||
Register to our [google group][google-group] in order to get updates or for asking questions.
|
||||
|
||||
# Contributing
|
||||
|
||||
Read and understand our [contribution guidelines][piper-library-contribution]
|
||||
|
@ -3,6 +3,7 @@ import groovy.json.JsonOutput
|
||||
import org.yaml.snakeyaml.Yaml
|
||||
import org.codehaus.groovy.control.CompilerConfiguration
|
||||
import com.sap.piper.GenerateDocumentation
|
||||
import com.sap.piper.GenerateStageDocumentation
|
||||
import java.util.regex.Matcher
|
||||
import groovy.text.StreamingTemplateEngine
|
||||
|
||||
@ -23,7 +24,7 @@ class TemplateHelper {
|
||||
|
||||
def props = parameters.get(it)
|
||||
|
||||
def defaultValue = isComplexDefault(props.defaultValue) ? renderComplexDefaultValue(props.defaultValue) : "`${props.defaultValue}`"
|
||||
def defaultValue = isComplexDefault(props.defaultValue) ? renderComplexDefaultValue(props.defaultValue) : renderSimpleDefaultValue(props.defaultValue)
|
||||
|
||||
t += "| `${it}` | ${props.mandatory ?: props.required ? 'yes' : 'no'} | ${defaultValue} | ${props.value ?: ''} |\n"
|
||||
}
|
||||
@ -48,6 +49,11 @@ class TemplateHelper {
|
||||
.join('<br />')
|
||||
}
|
||||
|
||||
private static renderSimpleDefaultValue(def _default) {
|
||||
if (_default == null) return ''
|
||||
return "`${_default}`"
|
||||
}
|
||||
|
||||
static createParameterDescriptionSection(Map parameters) {
|
||||
def t = ''
|
||||
parameters.keySet().toSorted().each {
|
||||
@ -78,6 +84,98 @@ class TemplateHelper {
|
||||
|
||||
t.trim()
|
||||
}
|
||||
|
||||
static createStageContentSection(Map stageDescriptions) {
|
||||
def t = 'This stage comprises following steps which are activated depending on your use-case/configuration:\n\n'
|
||||
|
||||
t += '| step | step description |\n'
|
||||
t += '| ---- | ---------------- |\n'
|
||||
|
||||
stageDescriptions.each {step, description ->
|
||||
t += "| [${step}](../steps/${step}.md) | ${description.trim()} |\n"
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
static createStageActivationSection() {
|
||||
def t = '''This stage will be active if any one of the following conditions is met:
|
||||
|
||||
* Stage configuration in [config.yml file](../configuration.md) contains entries for this stage.
|
||||
* Any of the conditions are met which are explained in the section [Step Activation](#step-activation).
|
||||
'''
|
||||
return t.trim()
|
||||
}
|
||||
|
||||
static createStepActivationSection(Map configConditions) {
|
||||
if (!configConditions) return 'For this stage no conditions are assigned to steps.'
|
||||
def t = 'Certain steps will be activated automatically depending on following conditions:\n\n'
|
||||
|
||||
|
||||
t += '| step | config key | config value | file pattern |\n'
|
||||
t += '| ---- | ---------- | ------------ | ------------ |\n'
|
||||
|
||||
configConditions?.each {stepName, conditions ->
|
||||
t += "| ${stepName} "
|
||||
t += "| ${renderValueList(conditions?.configKeys)} "
|
||||
t += "| ${renderValueList(mapToValueList(conditions?.config))} "
|
||||
|
||||
List filePatterns = []
|
||||
if (conditions?.filePattern) filePatterns.add(conditions?.filePattern)
|
||||
if (conditions?.filePatternFromConfig) filePatterns.add(conditions?.filePatternFromConfig)
|
||||
t += "| ${renderValueList(filePatterns)} |\n"
|
||||
}
|
||||
|
||||
t += '''
|
||||
!!! info "Step condition details"
|
||||
There are currently several conditions which can be checked.<br /> This is done in the [Init stage](init.md) of the pipeline shortly after checkout of the source code repository.<br/ >
|
||||
**Important: It will be sufficient that any one condition per step is met.**
|
||||
|
||||
* `config key`: Checks if a defined configuration parameter is set.
|
||||
* `config value`: Checks if a configuration parameter has a defined value.
|
||||
* `file pattern`: Checks if files according a defined pattern exist in the project. Either the pattern is speficified direcly or it is retrieved from a configuration parameter.
|
||||
|
||||
|
||||
!!! note "Overruling step activation conditions"
|
||||
It is possible to overrule the automatically detected step activation status.<br />
|
||||
Just add to your stage configuration `<stepName>: false`, for example `deployToKubernetes: false`.
|
||||
|
||||
For details about the configuration options, please see [Configuration of Piper](../configuration.md).
|
||||
'''
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
private static renderValueList(List valueList) {
|
||||
if (!valueList) return ''
|
||||
if (valueList.size() > 1) {
|
||||
List quotedList = []
|
||||
valueList.each {listItem ->
|
||||
quotedList.add("-`${listItem}`")
|
||||
}
|
||||
return quotedList.join('<br />')
|
||||
} else {
|
||||
return "`${valueList[0]}`"
|
||||
}
|
||||
}
|
||||
|
||||
private static mapToValueList(Map map) {
|
||||
List valueList = []
|
||||
map?.each {key, value ->
|
||||
if (value instanceof List) {
|
||||
value.each {listItem ->
|
||||
valueList.add("${key}: ${listItem}")
|
||||
}
|
||||
} else {
|
||||
valueList.add("${key}: ${value}")
|
||||
}
|
||||
}
|
||||
return valueList
|
||||
}
|
||||
|
||||
static createStageConfigurationSection() {
|
||||
return 'The stage parameters need to be defined in the section `stages` of [config.yml file](../configuration.md).'
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@ -118,6 +216,11 @@ class Helper {
|
||||
prepareDefaultValuesStep
|
||||
}
|
||||
|
||||
static Map getYamlResource(String resource) {
|
||||
def ymlContent = new File(projectRoot,"resources/${resource}").text
|
||||
return new Yaml().load(ymlContent)
|
||||
}
|
||||
|
||||
static getDummyScript(def prepareDefaultValuesStep, def stepName, Map prepareDefaultValuesStepParams) {
|
||||
|
||||
def _prepareDefaultValuesStep = prepareDefaultValuesStep
|
||||
@ -346,6 +449,15 @@ class Helper {
|
||||
return params
|
||||
}
|
||||
|
||||
static getStageStepKeys(def script) {
|
||||
try {
|
||||
return script.STAGE_STEP_KEYS ?: []
|
||||
} catch (groovy.lang.MissingPropertyException ex) {
|
||||
System.err << "[INFO] STAGE_STEP_KEYS not set for: ${script.STEP_NAME}.\n"
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
static getRequiredParameters(File f) {
|
||||
def params = [] as Set
|
||||
f.eachLine {
|
||||
@ -386,7 +498,7 @@ class Helper {
|
||||
def scriptName = (it =~ /vars\${File.separator}(.*)\.groovy/)[0][1]
|
||||
def stepScript = gse.createScript("${scriptName}.groovy", new Binding())
|
||||
for (def method in stepScript.getClass().getMethods()) {
|
||||
if(method.getName() == 'call' && method.getAnnotation(GenerateDocumentation) != null) {
|
||||
if(method.getName() == 'call' && (method.getAnnotation(GenerateDocumentation) != null || method.getAnnotation(GenerateStageDocumentation) != null)) {
|
||||
docuRelevantSteps << scriptName
|
||||
break
|
||||
}
|
||||
@ -395,6 +507,26 @@ class Helper {
|
||||
}
|
||||
docuRelevantSteps
|
||||
}
|
||||
|
||||
static resolveDocuRelevantStages(GroovyScriptEngine gse, File stepsDir) {
|
||||
|
||||
def docuRelevantStages = [:]
|
||||
|
||||
stepsDir.traverse(type: FileType.FILES, maxDepth: 0) {
|
||||
if(it.getName().endsWith('.groovy')) {
|
||||
def scriptName = (it =~ /vars\${File.separator}(.*)\.groovy/)[0][1]
|
||||
def stepScript = gse.createScript("${scriptName}.groovy", new Binding())
|
||||
for (def method in stepScript.getClass().getMethods()) {
|
||||
GenerateStageDocumentation stageDocsAnnotation = method.getAnnotation(GenerateStageDocumentation)
|
||||
if(method.getName() == 'call' && stageDocsAnnotation != null) {
|
||||
docuRelevantStages[scriptName] = stageDocsAnnotation.defaultStageName()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
docuRelevantStages
|
||||
}
|
||||
}
|
||||
|
||||
roots = [
|
||||
@ -404,33 +536,65 @@ roots = [
|
||||
|
||||
stepsDir = null
|
||||
stepsDocuDir = null
|
||||
String customDefaults = null
|
||||
stagesDocuDir = null
|
||||
customDefaults = null
|
||||
|
||||
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\'.'
|
||||
p longOpt: 'docuDirStages', args: 1, argName: 'dir', 'The directory containing the docu stubs for pipeline stages. Defaults to \'documentation/docs/stages\'.'
|
||||
c longOpt: 'customDefaults', args: 1, argName: 'file', 'Additional custom default configuration'
|
||||
i longOpt: 'stageInitFile', args: 1, argName: 'file', 'The file containing initialization data for step piperInitRunStageConfiguration'
|
||||
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){
|
||||
System.err << "[INFO] Using custom step root: ${options.s}.\n"
|
||||
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) {
|
||||
System.err << "[INFO] Using custom doc dir for steps: ${options.d}.\n"
|
||||
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.p) {
|
||||
System.err << "[INFO] Using custom doc dir for stages: ${options.p}.\n"
|
||||
stagesDocuDir = new File(Helper.projectRoot, options.p)
|
||||
}
|
||||
|
||||
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
|
||||
stagesDocuDir = stagesDocuDir ?: new File(Helper.projectRoot, "documentation/docs/stages")
|
||||
|
||||
if(options.c) {
|
||||
System.err << "[INFO] Using custom defaults: ${options.c}.\n"
|
||||
customDefaults = options.c
|
||||
}
|
||||
|
||||
steps.addAll(options.arguments())
|
||||
|
||||
// assign parameters
|
||||
//
|
||||
@ -461,6 +625,16 @@ if( ! steps) {
|
||||
System.err << "[INFO] Generating docu only for step ${steps.size > 1 ? 's' : ''} ${steps}.\n"
|
||||
}
|
||||
|
||||
// find all the stages that we have to document
|
||||
Map stages = Helper.resolveDocuRelevantStages(gse, stepsDir)
|
||||
|
||||
// retrieve default conditions for steps
|
||||
//ToDo: allow passing config file name via parameter
|
||||
Map stageConfig
|
||||
if (options.s) {
|
||||
stageConfig = Helper.getYamlResource(options.s)
|
||||
}
|
||||
|
||||
def prepareDefaultValuesStep = Helper.getPrepareDefaultValuesStep(gse)
|
||||
|
||||
boolean exceptionCaught = false
|
||||
@ -490,6 +664,39 @@ for(step in stepDescriptors) {
|
||||
}
|
||||
}
|
||||
|
||||
//update stepDescriptors: remove stages and put into separate stageDescriptors map
|
||||
def stageDescriptors = [:]
|
||||
stages.each {key, value ->
|
||||
System.err << "[INFO] Processing stage '${key}' ...\n"
|
||||
stageDescriptors."${key}" = [:] << stepDescriptors."${key}"
|
||||
stepDescriptors.remove(key)
|
||||
|
||||
//add stage name to stageDescriptors
|
||||
stageDescriptors."${key}".name = value
|
||||
|
||||
//add stepCondition informmation to stageDescriptors
|
||||
stageDescriptors."${key}".configConditions = stageConfig?.stages?.get(value)?.stepConditions
|
||||
|
||||
//identify step keys in stages
|
||||
def stageStepKeys = Helper.getStageStepKeys(gse.createScript( "${key}.groovy", new Binding() ))
|
||||
|
||||
// prepare step descriptions
|
||||
stageDescriptors."${key}".stepDescriptions = [:]
|
||||
stageDescriptors."${key}".parameters.each {paramKey, paramValue ->
|
||||
|
||||
if (paramKey in stageStepKeys) {
|
||||
stageDescriptors."${key}".stepDescriptions."${paramKey}" = "${paramValue.docu ?: ''}\n"
|
||||
}
|
||||
}
|
||||
|
||||
//remove details from parameter map
|
||||
stageStepKeys.each {stepKey ->
|
||||
stageDescriptors."${key}".parameters.remove(stepKey)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
for(step in stepDescriptors) {
|
||||
try {
|
||||
renderStep(step.key, step.value)
|
||||
@ -500,6 +707,16 @@ for(step in stepDescriptors) {
|
||||
}
|
||||
}
|
||||
|
||||
for (stage in stageDescriptors) {
|
||||
try {
|
||||
renderStage(stage.key, stage.value)
|
||||
System.err << "[INFO] Stage '${stage.key}' has been rendered.\n"
|
||||
} catch(Exception e) {
|
||||
exceptionCaught = true
|
||||
System.err << "${e.getClass().getName()} caught while rendering stage '${stage}': ${e.getMessage()}.\n"
|
||||
}
|
||||
}
|
||||
|
||||
if(exceptionCaught) {
|
||||
System.err << "[ERROR] Exception caught during generating documentation. Check earlier log for details.\n"
|
||||
System.exit(1)
|
||||
@ -532,6 +749,31 @@ void renderStep(stepName, stepProperties) {
|
||||
theStepDocu.withWriter { w -> w.write text }
|
||||
}
|
||||
|
||||
void renderStage(stageName, stageProperties) {
|
||||
|
||||
def stageFileName = stageName.indexOf('Stage') != -1 ? stageName.split('Stage')[1].toLowerCase() : stageFileName
|
||||
File theStageDocu = new File(stagesDocuDir, "${stageFileName}.md")
|
||||
|
||||
if(!theStageDocu.exists()) {
|
||||
System.err << "[WARNING] stage docu input file for stage '${stageName}' is missing.\n"
|
||||
return
|
||||
}
|
||||
|
||||
def binding = [
|
||||
docGenStageName : stageProperties.name,
|
||||
docGenDescription : stageProperties.description,
|
||||
docGenStageContent : 'Stage Content\n\n' + TemplateHelper.createStageContentSection(stageProperties.stepDescriptions),
|
||||
docGenStageActivation: 'Stage Activation\n\n' + TemplateHelper.createStageActivationSection(),
|
||||
docGenStepActivation: 'Step Activation\n\n' + TemplateHelper.createStepActivationSection(stageProperties.configConditions),
|
||||
docGenStageParameters : 'Additional Stage Parameters\n\n' + TemplateHelper.createParametersSection(stageProperties.parameters),
|
||||
docGenStageConfiguration : 'Configuration of Additional Stage Parameters\n\n' + TemplateHelper.createStageConfigurationSection()
|
||||
]
|
||||
def template = new StreamingTemplateEngine().createTemplate(theStageDocu.text)
|
||||
String text = template.make(binding)
|
||||
|
||||
theStageDocu.withWriter { w -> w.write text }
|
||||
}
|
||||
|
||||
def fetchTextFrom(def step, def parameterName, def steps) {
|
||||
try {
|
||||
def docuFromOtherStep = steps[step]?.parameters[parameterName]?.docu
|
||||
@ -561,6 +803,12 @@ def handleStep(stepName, prepareDefaultValuesStep, gse, customDefaults) {
|
||||
File theStep = new File(stepsDir, "${stepName}.groovy")
|
||||
File theStepDocu = new File(stepsDocuDir, "${stepName}.md")
|
||||
|
||||
if (!theStepDocu.exists() && stepName.indexOf('Stage') != -1) {
|
||||
//try to get a corresponding stage documentation
|
||||
def stageName = stepName.split('Stage')[1].toLowerCase()
|
||||
theStepDocu = new File(stagesDocuDir,"${stageName}.md" )
|
||||
}
|
||||
|
||||
if(!theStepDocu.exists()) {
|
||||
System.err << "[WARNING] step docu input file for step '${stepName}' is missing.\n"
|
||||
return
|
||||
@ -620,9 +868,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,
|
||||
|
@ -11,10 +11,10 @@ Your configuration inherits from the default configuration located at [https://g
|
||||
Configuration of the Piper steps as well the Piper templates can be done in a hierarchical manner.
|
||||
|
||||
1. Directly passed step parameters will always take precedence over other configuration values and defaults
|
||||
2. Stage configuration parameters define a Jenkins pipeline stage dependent set of parameters (e.g. deployment options for the `Acceptance` stage)
|
||||
3. Step configuration defines how steps behave in general (e.g. step `cloudFoundryDeploy`)
|
||||
4. General configuration parameters define parameters which are available across step boundaries
|
||||
5. Default configuration comes with the Piper library and is always available
|
||||
1. Stage configuration parameters define a Jenkins pipeline stage dependent set of parameters (e.g. deployment options for the `Acceptance` stage)
|
||||
1. Step configuration defines how steps behave in general (e.g. step `cloudFoundryDeploy`)
|
||||
1. General configuration parameters define parameters which are available across step boundaries
|
||||
1. Default configuration comes with the Piper library and is always available
|
||||
|
||||
![Piper Configuration](images/piper_config.png)
|
||||
|
||||
|
@ -4,4 +4,4 @@
|
||||
|
||||
.md-typeset a:not(.headerlink):hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ node(){
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
mtaBuild
|
||||
mtaBuild:
|
||||
buildTarget: 'CF'
|
||||
cloudFoundryDeploy:
|
||||
cloudFoundry:
|
||||
|
@ -33,10 +33,10 @@ The basic workflow is as follows:
|
||||
|
||||
**Note:** The blank line between message header and message description is mandatory.
|
||||
|
||||
2. To communicate with SAP Solution Manager, the pipeline uses credentials that must be stored on Jenkins using the credential ID `CM`. For more information, see [checkChangeInDevelopment](https://sap.github.io/jenkins-library/steps/checkChangeInDevelopment/).
|
||||
3. The required transport request is created on the fly. **Note:** The change document can contain various components (for example, UI and backend components).
|
||||
4. The changes of your development team trigger the Jenkins pipeline. It builds and validates the changes and attaches them to the respective transport request.
|
||||
5. As soon as the development process is completed, the change document in SAP Solution Manager can be set to status `to be tested` and all components can be transported to the test system.
|
||||
1. To communicate with SAP Solution Manager, the pipeline uses credentials that must be stored on Jenkins using the credential ID `CM`. For more information, see [checkChangeInDevelopment](https://sap.github.io/jenkins-library/steps/checkChangeInDevelopment/).
|
||||
1. The required transport request is created on the fly. **Note:** The change document can contain various components (for example, UI and backend components).
|
||||
1. The changes of your development team trigger the Jenkins pipeline. It builds and validates the changes and attaches them to the respective transport request.
|
||||
1. As soon as the development process is completed, the change document in SAP Solution Manager can be set to status `to be tested` and all components can be transported to the test system.
|
||||
|
||||
![Hybrid Application Development Workflow](../images/Scenario_SolMan.png "Hybrid Application Development Workflow")
|
||||
###### Hybrid Application Development Workflow
|
||||
|
@ -11,12 +11,10 @@ Build an application based on SAPUI5 or SAP Fiori with Jenkins and deploy the bu
|
||||
* You have installed Node.js including node and npm. See [Node.js](https://nodejs.org/en/download/).
|
||||
* You have installed the SAP Cloud Platform Neo Environment SDK. See [SAP Development Tools](https://tools.hana.ondemand.com/#cloud).
|
||||
|
||||
|
||||
### Project Prerequisites
|
||||
|
||||
This scenario requires additional files in your project and in the execution environment on your Jenkins instance.
|
||||
|
||||
|
||||
On the project level, provide and adjust the following template:
|
||||
|
||||
| File Name | Description | Position |
|
||||
@ -26,12 +24,10 @@ On the project level, provide and adjust the following template:
|
||||
| [`package.json`](https://github.com/SAP/jenkins-library/blob/master/documentation/docs/scenarios/ui5-sap-cp/files/package.json) | This file lists the required development dependencies for the build. | Add the content of the `package.json` file to your existing `package.json` file. |
|
||||
| [`Gruntfile.js`](https://github.com/SAP/jenkins-library/blob/master/documentation/docs/scenarios/ui5-sap-cp/files/Gruntfile.js) | This file controls the grunt build. By default the tasks `clean`, `build`, and `lint` are executed. | Place the `Gruntfile.js` in the root directory of your project. |
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
This scenario combines various different steps to create a complete pipeline.
|
||||
|
||||
|
||||
In this scenario, we want to show how to build an application based on SAPUI5 or SAP Fiori by using the multi-target application (MTA) concept and how to deploy the build result into an SAP Cloud Platform account in the Neo environment. This document comprises the [mtaBuild](https://sap.github.io/jenkins-library/steps/mtaBuild/) and the [neoDeploy](https://sap.github.io/jenkins-library/steps/neoDeploy/) steps.
|
||||
|
||||
![This pipeline in Jenkins Blue Ocean](images/pipeline.jpg)
|
||||
@ -73,7 +69,6 @@ steps:
|
||||
| `buildTarget` | The target platform to which the mtar can be deployed. Possible values are: `CF`, `NEO`, `XSA` |
|
||||
| `mtaJarLocation` | The location of the multi-target application archive builder jar file, including file name and extension. |
|
||||
|
||||
|
||||
#### Configuration for the Deployment to SAP Cloud Platform
|
||||
|
||||
| Parameter | Description |
|
||||
@ -83,7 +78,6 @@ steps:
|
||||
| `host` | The SAP Cloud Platform host to deploy to. |
|
||||
| `neoHome` | The path to the `neo-java-web-sdk` tool that is used for the deployment. |
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
For the detailed description of the relevant parameters, see:
|
||||
|
13
documentation/docs/stages/confirm.md
Normal file
13
documentation/docs/stages/confirm.md
Normal file
@ -0,0 +1,13 @@
|
||||
# ${docGenStageName}
|
||||
|
||||
${docGenDescription}
|
||||
|
||||
## ${docGenStageContent}
|
||||
|
||||
## ${docGenStageActivation}
|
||||
|
||||
## ${docGenStepActivation}
|
||||
|
||||
## ${docGenStageParameters}
|
||||
|
||||
## ${docGenStageConfiguration}
|
@ -32,8 +32,7 @@ resource in an custom shared library.
|
||||
|
||||
// inside the shared lib denoted by 'foo' the additional configuration file
|
||||
// needs to be located under 'resources' ('resoures/myConfig.yml')
|
||||
prepareDefaultValues script: this,
|
||||
customDefaults: 'myConfig.yml'
|
||||
prepareDefaultValues script: this, customDefaults: 'myConfig.yml'
|
||||
```
|
||||
|
||||
Example content of `'resources/myConfig.yml'` in branch `'master'` of the repository denoted by
|
||||
@ -79,11 +78,13 @@ The parameters can also be provided when the step is invoked:
|
||||
// explict endpoint provided, we search for changeDocumentId
|
||||
// starting at the previous commit (HEAD~1) rather than on
|
||||
// 'origin/master' (the default).
|
||||
checkChangeInDevelopment script:this
|
||||
changeManagement: [
|
||||
endpoint: 'https:example.org/cm'
|
||||
git: [
|
||||
from: 'HEAD~1'
|
||||
]
|
||||
]
|
||||
checkChangeInDevelopment(
|
||||
script: this
|
||||
changeManagement: [
|
||||
endpoint: 'https:example.org/cm'
|
||||
git: [
|
||||
from: 'HEAD~1'
|
||||
]
|
||||
]
|
||||
)
|
||||
```
|
||||
|
@ -15,8 +15,8 @@ Very basic setup can be done like that (with user "admin" and password "adminPwd
|
||||
|
||||
For more advanced setup please reach out to the respective documentation:
|
||||
|
||||
- https://hub.docker.com/_/influxdb/ (and https://github.com/docker-library/docs/tree/master/influxdb)
|
||||
- https://hub.docker.com/r/grafana/grafana/ (and https://github.com/grafana/grafana-docker)
|
||||
- InfluxDB ([Docker Hub](https://hub.docker.com/_/influxdb/) [GitHub](https://github.com/docker-library/docs/tree/master/influxdb))
|
||||
- Grafana ([Docker Hub](https://hub.docker.com/r/grafana/grafana/) [GitHub](https://github.com/grafana/grafana-docker))
|
||||
|
||||
After you have started your InfluxDB docker you need to create a database:
|
||||
|
||||
@ -43,7 +43,7 @@ Once you have started both docker containers and Influx and Grafana are running
|
||||
To setup your Jenkins you need to do two configuration steps:
|
||||
|
||||
1. Configure Jenkins (via Manage Jenkins)
|
||||
2. Adapt pipeline configuration
|
||||
1. Adapt pipeline configuration
|
||||
|
||||
### Configure Jenkins
|
||||
|
||||
|
@ -10,7 +10,7 @@ Kaniko expects a Docker `config.json` file containing the credential information
|
||||
You can create it like explained in the Docker Success Center in the articale about [How to generate a new auth in the config.json file](https://success.docker.com/article/generate-new-auth-in-config-json-file).
|
||||
|
||||
Please copy this file and upload it to your Jenkins for example<br />
|
||||
via _Jenkins_ -> _Credentials_ -> _System_ -> _Global credentials (unrestricted)_ -> _ Add Credentials_ ->
|
||||
via _Jenkins_ -> _Credentials_ -> _System_ -> _Global credentials (unrestricted)_ -> _Add Credentials_ ->
|
||||
|
||||
* Kind: _Secret file_
|
||||
* File: upload your `config.json` file
|
||||
|
@ -1,7 +1,6 @@
|
||||
|
||||
# ${docGenStepName}
|
||||
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## ${docGenParameters}
|
||||
|
@ -18,7 +18,7 @@ seleniumExecuteTests (script: this) {
|
||||
|
||||
### Example test using WebdriverIO
|
||||
|
||||
Example based on http://webdriver.io/guide/getstarted/modes.html and http://webdriver.io/guide.html
|
||||
Example based on <http://webdriver.io/guide/getstarted/modes.html> and <http://webdriver.io/guide.html>
|
||||
|
||||
#### Configuration for Local Docker Environment
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
* Installed and configured [Slack JenkinsCI integration](https://my.slack.com/services/new/jenkins-ci)
|
||||
* *secret text* Jenkins credentials with the Slack token
|
||||
* Installed and configured [Jenkins Slack plugin](https://github.com/jenkinsci/slack-plugin#install-instructions-for-slack).
|
||||
* Installed and configured [Jenkins Slack plugin](https://github.com/jenkinsci/slack-plugin#install-instructions-for-slack)
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
|
@ -23,8 +23,7 @@ resource in an custom shared library.
|
||||
|
||||
// inside the shared lib denoted by 'foo' the additional configuration file
|
||||
// needs to be located under 'resources' ('resoures/myConfig.yml')
|
||||
prepareDefaultValues script: this,
|
||||
customDefaults: 'myConfig.yml'
|
||||
prepareDefaultValues script: this, customDefaults: 'myConfig.yml'
|
||||
```
|
||||
|
||||
Example content of `'resources/myConfig.yml'` in branch `'master'` of the repository denoted by
|
||||
|
@ -8,11 +8,8 @@
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
|
||||
## ${docGenConfiguration}
|
||||
|
||||
|
||||
|
||||
The step is configured using a customer configuration file provided as
|
||||
resource in an custom shared library.
|
||||
|
||||
|
@ -74,21 +74,25 @@ The parameters can also be provided when the step is invoked. For examples see b
|
||||
|
||||
```groovy
|
||||
// SOLMAN
|
||||
transportRequestUploadFile script:this,
|
||||
changeDocumentId: '001', // typically provided via git commit history
|
||||
transportRequestId: '001', // typically provided via git commit history
|
||||
applicationId: '001',
|
||||
filePath: '/path',
|
||||
changeManagement:[
|
||||
type: 'SOLMAN'
|
||||
endpoint: 'https://example.org/cm'
|
||||
]
|
||||
transportRequestUploadFile(
|
||||
script: this,
|
||||
changeDocumentId: '001', // typically provided via git commit history
|
||||
transportRequestId: '001', // typically provided via git commit history
|
||||
applicationId: '001',
|
||||
filePath: '/path',
|
||||
changeManagement: [
|
||||
type: 'SOLMAN'
|
||||
endpoint: 'https://example.org/cm'
|
||||
]
|
||||
)
|
||||
// CTS
|
||||
transportRequestUploadFile script:this,
|
||||
transportRequestId: '001', // typically provided via git commit history
|
||||
filePath: '/path',
|
||||
changeManagement:[
|
||||
type: 'CTS'
|
||||
endpoint: 'https://example.org/cm'
|
||||
]
|
||||
transportRequestUploadFile(
|
||||
script: this,
|
||||
transportRequestId: '001', // typically provided via git commit history
|
||||
filePath: '/path',
|
||||
changeManagement: [
|
||||
type: 'CTS'
|
||||
endpoint: 'https://example.org/cm'
|
||||
]
|
||||
)
|
||||
```
|
||||
|
14
pom.xml
14
pom.xml
@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>plugin</artifactId>
|
||||
@ -17,10 +19,10 @@
|
||||
<url>https://sap.github.io/jenkins-library/</url>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License 2.0</name>
|
||||
<comments>https://github.com/SAP/jenkins-library/blob/master/LICENSE</comments>
|
||||
</license>
|
||||
<license>
|
||||
<name>Apache License 2.0</name>
|
||||
<comments>https://github.com/SAP/jenkins-library/blob/master/LICENSE</comments>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<repositories>
|
||||
|
@ -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'
|
||||
|
12
src/com/sap/piper/GenerateStageDocumentation.groovy
Normal file
12
src/com/sap/piper/GenerateStageDocumentation.groovy
Normal file
@ -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()
|
||||
}
|
@ -33,25 +33,27 @@ String getGitCommitId() {
|
||||
return sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
|
||||
}
|
||||
|
||||
String[] extractLogLines(String filter = '',
|
||||
String from = 'origin/master',
|
||||
String to = 'HEAD',
|
||||
String format = '%b') {
|
||||
String[] extractLogLines(
|
||||
String filter = '',
|
||||
String from = 'origin/master',
|
||||
String to = 'HEAD',
|
||||
String format = '%b'
|
||||
) {
|
||||
|
||||
// Checks below: there was an value provided from outside, but the value was null.
|
||||
// Throwing an exception is more transparent than making a fallback to the defaults
|
||||
// used in case the paramter is omitted in the signature.
|
||||
if(filter == null) throw new IllegalArgumentException('Parameter \'filter\' not provided.')
|
||||
if(! from?.trim()) throw new IllegalArgumentException('Parameter \'from\' not provided.')
|
||||
if(! to?.trim()) throw new IllegalArgumentException('Parameter \'to\' not provided.')
|
||||
if(! format?.trim()) throw new IllegalArgumentException('Parameter \'format\' not provided.')
|
||||
// Checks below: there was an value provided from outside, but the value was null.
|
||||
// Throwing an exception is more transparent than making a fallback to the defaults
|
||||
// used in case the paramter is omitted in the signature.
|
||||
if(filter == null) throw new IllegalArgumentException('Parameter \'filter\' not provided.')
|
||||
if(! from?.trim()) throw new IllegalArgumentException('Parameter \'from\' not provided.')
|
||||
if(! to?.trim()) throw new IllegalArgumentException('Parameter \'to\' not provided.')
|
||||
if(! format?.trim()) throw new IllegalArgumentException('Parameter \'format\' not provided.')
|
||||
|
||||
sh ( returnStdout: true,
|
||||
script: """#!/bin/bash
|
||||
git log --pretty=format:${format} ${from}..${to}
|
||||
"""
|
||||
)?.split('\n')
|
||||
?.findAll { line -> line ==~ /${filter}/ }
|
||||
script: """#!/bin/bash
|
||||
git log --pretty=format:${format} ${from}..${to}
|
||||
"""
|
||||
)?.split('\n')
|
||||
?.findAll { line -> line ==~ /${filter}/ }
|
||||
|
||||
}
|
||||
|
||||
|
@ -37,4 +37,3 @@ class MtaUtils {
|
||||
if (!script.fileExists(targetMtaDescriptor)) throw new AbortException("'${targetMtaDescriptor}' has not been generated.")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.sap.piper.cm;
|
||||
package com.sap.piper.cm
|
||||
|
||||
public enum BackendType {
|
||||
SOLMAN, CTS, RFC, NONE
|
||||
|
@ -17,32 +17,32 @@ public class ChangeManagement implements Serializable {
|
||||
}
|
||||
|
||||
String getChangeDocumentId(
|
||||
String from = 'origin/master',
|
||||
String to = 'HEAD',
|
||||
String label = 'ChangeDocument\\s?:',
|
||||
String format = '%b'
|
||||
) {
|
||||
String from = 'origin/master',
|
||||
String to = 'HEAD',
|
||||
String label = 'ChangeDocument\\s?:',
|
||||
String format = '%b'
|
||||
) {
|
||||
|
||||
return getLabeledItem('ChangeDocumentId', from, to, label, format)
|
||||
}
|
||||
|
||||
String getTransportRequestId(
|
||||
String from = 'origin/master',
|
||||
String to = 'HEAD',
|
||||
String label = 'TransportRequest\\s?:',
|
||||
String format = '%b'
|
||||
) {
|
||||
String from = 'origin/master',
|
||||
String to = 'HEAD',
|
||||
String label = 'TransportRequest\\s?:',
|
||||
String format = '%b'
|
||||
) {
|
||||
|
||||
return getLabeledItem('TransportRequestId', from, to, label, format)
|
||||
}
|
||||
|
||||
private String getLabeledItem(
|
||||
String name,
|
||||
String from,
|
||||
String to,
|
||||
String label,
|
||||
String format
|
||||
) {
|
||||
String name,
|
||||
String from,
|
||||
String to,
|
||||
String label,
|
||||
String format
|
||||
) {
|
||||
|
||||
if( ! gitUtils.insideWorkTree() ) {
|
||||
throw new ChangeManagementException("Cannot retrieve ${name}. Not in a git work tree. ${name} is extracted from git commit messages.")
|
||||
@ -421,16 +421,16 @@ public class ChangeManagement implements Serializable {
|
||||
String clientOpts = '') {
|
||||
String cmCommandLine = '#!/bin/bash'
|
||||
if(clientOpts) {
|
||||
cmCommandLine += """
|
||||
export CMCLIENT_OPTS="${clientOpts}" """
|
||||
cmCommandLine += """
|
||||
export CMCLIENT_OPTS="${clientOpts}" """
|
||||
}
|
||||
cmCommandLine += """
|
||||
cmclient -e '$endpoint' \
|
||||
-u '$username' \
|
||||
-p '$password' \
|
||||
-t ${type} \
|
||||
${command} ${(args as Iterable).join(' ')}
|
||||
"""
|
||||
cmclient -e '$endpoint' \
|
||||
-u '$username' \
|
||||
-p '$password' \
|
||||
-t ${type} \
|
||||
${command} ${(args as Iterable).join(' ')}
|
||||
"""
|
||||
return cmCommandLine
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.sap.piper.cm;
|
||||
package com.sap.piper.cm
|
||||
|
||||
public class ChangeManagementException extends RuntimeException {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.sap.piper.cm;
|
||||
package com.sap.piper.cm
|
||||
|
||||
import com.cloudbees.groovy.cps.NonCPS
|
||||
|
||||
@ -23,7 +23,7 @@ public class StepHelpers {
|
||||
}
|
||||
|
||||
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}'."
|
||||
" Searching for pattern '${configuration.changeManagement.transportRequestLabel}'. Searching with format '${configuration.changeManagement.git.format}'."
|
||||
|
||||
try {
|
||||
transportRequestId = cm.getTransportRequestId(
|
||||
@ -62,7 +62,7 @@ public class StepHelpers {
|
||||
}
|
||||
|
||||
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}'."
|
||||
"Searching for pattern '${configuration.changeManagement.changeDocumentLabel}'. Searching with format '${configuration.changeManagement.git.format}'."
|
||||
|
||||
try {
|
||||
changeDocumentId = cm.getChangeDocumentId(
|
||||
@ -91,15 +91,15 @@ public class StepHelpers {
|
||||
backendType = configuration.changeManagement.type as BackendType
|
||||
} catch(IllegalArgumentException e) {
|
||||
script.error "Invalid backend type: '${configuration.changeManagement.type}'. " +
|
||||
"Valid values: [${BackendType.values().join(', ')}]. " +
|
||||
"Configuration: 'changeManagement/type'."
|
||||
"Valid values: [${BackendType.values().join(', ')}]. " +
|
||||
"Configuration: 'changeManagement/type'."
|
||||
}
|
||||
|
||||
if (backendType == BackendType.NONE) {
|
||||
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'."
|
||||
"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'."
|
||||
}
|
||||
|
||||
return backendType
|
||||
|
@ -24,6 +24,6 @@ enum DeployMode {
|
||||
throw new IllegalArgumentException("${value} is not in the list of possible values ${stringValues()}")
|
||||
}
|
||||
|
||||
return enumValue;
|
||||
return enumValue
|
||||
}
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ class NeoCommandHelper {
|
||||
def environment = deploymentConfiguration.environment
|
||||
|
||||
if (!(environment in Map)) {
|
||||
step.error("The environment variables for the deployment to Neo have to be defined as a map.");
|
||||
step.error("The environment variables for the deployment to Neo have to be defined as a map.")
|
||||
}
|
||||
|
||||
def keys = environment.keySet()
|
||||
|
@ -21,6 +21,6 @@ enum WarAction {
|
||||
throw new IllegalArgumentException("${value} is not in the list of possible values ${stringValues()}")
|
||||
}
|
||||
|
||||
return enumValue;
|
||||
return enumValue
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import com.lesfurets.jenkins.unit.BasePipelineTest
|
||||
|
||||
import static org.junit.Assert.assertEquals
|
||||
|
@ -1,5 +1,3 @@
|
||||
#!groovy
|
||||
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import com.sap.piper.JenkinsUtils
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
|
@ -4,8 +4,9 @@ import static org.hamcrest.Matchers.equalTo
|
||||
import static org.hamcrest.Matchers.is
|
||||
import static org.junit.Assert.assertThat
|
||||
import static org.junit.Assert.fail
|
||||
import static util.StepHelper.getSteps
|
||||
|
||||
import java.io.File;
|
||||
import java.io.File
|
||||
import java.util.stream.Collectors
|
||||
import java.lang.reflect.Field
|
||||
|
||||
@ -192,7 +193,7 @@ public class CommonStepsTest extends BasePiperTest{
|
||||
continue
|
||||
}
|
||||
|
||||
boolean notAccessible = false;
|
||||
boolean notAccessible = false
|
||||
def fieldName
|
||||
|
||||
if(!stepNameField.isAccessible()) {
|
||||
@ -242,11 +243,4 @@ public class CommonStepsTest extends BasePiperTest{
|
||||
assertThat("Steps with call methods with return types other than void: ${stepsWithCallMethodsOtherThanVoid}",
|
||||
stepsWithCallMethodsOtherThanVoid, is(empty()))
|
||||
}
|
||||
|
||||
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] }
|
||||
return steps
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -207,7 +207,7 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
|
||||
|
||||
@Test
|
||||
void testDockerExecuteOnKubernetesEmptyContainerMapNoDockerImage() throws Exception {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expect(IllegalArgumentException.class)
|
||||
stepRule.step.dockerExecuteOnKubernetes(
|
||||
script: nullScript,
|
||||
juStabUtils: utils,
|
||||
|
@ -1,5 +1,3 @@
|
||||
#!groovy
|
||||
|
||||
import com.sap.piper.analytics.InfluxData
|
||||
|
||||
import org.junit.Rule
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import groovy.json.JsonSlurperClassic
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import hudson.AbortException
|
||||
|
||||
import static org.hamcrest.Matchers.is
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import com.sap.piper.DefaultValueCache
|
||||
import com.sap.piper.analytics.InfluxData
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -40,9 +40,6 @@ class NeoDeployTest extends BasePiperTest {
|
||||
private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
|
||||
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
|
||||
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
|
||||
private JenkinsLockRule lockRule = new JenkinsLockRule(this)
|
||||
private JenkinsFileExistsRule fileExistsRule = new JenkinsFileExistsRule(this, ['warArchive.war', 'archive.mtar', 'war.properties'])
|
||||
|
||||
|
||||
@Rule
|
||||
public RuleChain ruleChain = Rules
|
||||
@ -56,9 +53,9 @@ class NeoDeployTest extends BasePiperTest {
|
||||
.withCredentials('myCredentialsId', 'anonymous', '********')
|
||||
.withCredentials('CI_CREDENTIALS_ID', 'defaultUser', '********'))
|
||||
.around(stepRule)
|
||||
.around(lockRule)
|
||||
.around(new JenkinsLockRule(this))
|
||||
.around(new JenkinsWithEnvRule(this))
|
||||
.around(fileExistsRule)
|
||||
.around(new JenkinsFileExistsRule(this, ['warArchive.war', 'archive.mtar', 'war.properties']))
|
||||
|
||||
|
||||
private static warArchiveName = 'warArchive.war'
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package stages
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package stages
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -1,15 +1,15 @@
|
||||
import org.junit.Before
|
||||
import org.junit.Rule;
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
import org.junit.rules.RuleChain;
|
||||
import org.junit.rules.RuleChain
|
||||
import com.sap.piper.DefaultValueCache
|
||||
|
||||
import util.BasePiperTest
|
||||
import util.JenkinsLoggingRule
|
||||
import util.JenkinsReadYamlRule
|
||||
import util.JenkinsShellCallRule
|
||||
import util.JenkinsStepRule;
|
||||
import util.JenkinsStepRule
|
||||
|
||||
import util.Rules
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -1,5 +1,3 @@
|
||||
#!groovy
|
||||
|
||||
import static org.hamcrest.Matchers.*
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
import com.sap.piper.DescriptorUtils
|
||||
import com.sap.piper.JsonUtils
|
||||
import com.sap.piper.integration.WhitesourceOrgAdminRepository
|
||||
|
@ -22,7 +22,7 @@ class MapUtilsTest {
|
||||
c: [d: '1',
|
||||
e: '2']],
|
||||
b = [b: '2',
|
||||
c: [d: 'x']];
|
||||
c: [d: 'x']]
|
||||
|
||||
Map merged = MapUtils.merge(a, b)
|
||||
|
||||
|
@ -25,7 +25,7 @@ class MtaUtilsTest extends BasePiperTest {
|
||||
private File badJson
|
||||
private mtaUtils
|
||||
|
||||
private ExpectedException thrown= ExpectedException.none();
|
||||
private ExpectedException thrown= ExpectedException.none()
|
||||
|
||||
@ClassRule
|
||||
public static TemporaryFolder tmp = new TemporaryFolder()
|
||||
|
@ -8,7 +8,7 @@ import static org.junit.Assert.assertEquals
|
||||
import static org.junit.Assert.assertNotNull
|
||||
|
||||
class SystemEnvTest {
|
||||
SystemEnv env = null;
|
||||
SystemEnv env = null
|
||||
Map systemEnvironmentMock = [:]
|
||||
@Before
|
||||
void setUp() {
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
79
test/groovy/templates/PiperPipelineStageConfirmTest.groovy
Normal file
79
test/groovy/templates/PiperPipelineStageConfirmTest.groovy
Normal file
@ -0,0 +1,79 @@
|
||||
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'))
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package templates
|
||||
|
||||
import org.junit.Before
|
||||
@ -54,17 +53,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 +174,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 +211,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 +234,7 @@ class PiperPipelineTest extends BasePiperTest {
|
||||
]
|
||||
jsr.step.piperPipeline(script: nullScript)
|
||||
|
||||
assertThat(stepsCalled, not(hasItem('input')))
|
||||
assertThat(stepsCalled, not(hasItem('piperPipelineStageConfirm')))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -232,7 +262,7 @@ class PiperPipelineTest extends BasePiperTest {
|
||||
'piperPipelineStageSecurity',
|
||||
'piperPipelineStagePerformance',
|
||||
'piperPipelineStageCompliance',
|
||||
'input',
|
||||
'piperPipelineStageConfirm',
|
||||
'piperPipelineStagePromote',
|
||||
'piperPipelineStageRelease',
|
||||
'piperPipelineStagePost'
|
||||
|
@ -1,5 +1,3 @@
|
||||
#!groovy
|
||||
|
||||
package util
|
||||
|
||||
import com.lesfurets.jenkins.unit.BasePipelineTest
|
||||
|
@ -1,5 +1,3 @@
|
||||
#!groovy
|
||||
|
||||
package util
|
||||
|
||||
import com.sap.piper.DescriptorUtils
|
||||
|
@ -8,7 +8,7 @@ import org.junit.runner.Description
|
||||
import org.junit.runners.model.Statement
|
||||
|
||||
import static org.hamcrest.Matchers.containsString
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertThat
|
||||
|
||||
import org.hamcrest.Matchers
|
||||
|
||||
|
@ -31,8 +31,8 @@ class JenkinsShellCallRule implements TestRule {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (obj == null || !obj instanceof Command) return false;
|
||||
Command other = (Command) obj;
|
||||
if (obj == null || !obj instanceof Command) return false
|
||||
Command other = (Command) obj
|
||||
return type == other.type && script == other.script
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,7 @@ class LibraryLoadingTestExecutionListener extends AbstractTestExecutionListener
|
||||
@Override
|
||||
void beforeTestClass(TestContext testContext) throws Exception {
|
||||
super.beforeTestClass(testContext)
|
||||
StepTracker.before(testContext.testClass.getSimpleName())
|
||||
def helper = LibraryLoadingTestExecutionListener.getSingletonInstance()
|
||||
registerDefaultAllowedMethods(helper)
|
||||
LibraryLoadingTestExecutionListener.START_CLASS_TRACKING = true
|
||||
@ -87,6 +88,7 @@ class LibraryLoadingTestExecutionListener extends AbstractTestExecutionListener
|
||||
@Override
|
||||
void afterTestClass(TestContext testContext) throws Exception {
|
||||
super.afterTestClass(testContext)
|
||||
StepTracker.after()
|
||||
PipelineTestHelper helper = LibraryLoadingTestExecutionListener.getSingletonInstance()
|
||||
helper.clearAllowedMethodCallbacks(LibraryLoadingTestExecutionListener.TRACKED_ON_CLASS)
|
||||
LibraryLoadingTestExecutionListener.TRACKED_ON_CLASS.clear()
|
||||
@ -112,6 +114,7 @@ class LibraryLoadingTestExecutionListener extends AbstractTestExecutionListener
|
||||
void beforeTestMethod(TestContext testContext) throws Exception {
|
||||
super.beforeTestMethod(testContext)
|
||||
def testInstance = testContext.getTestInstance()
|
||||
StepTracker.before(testInstance.getClass().getSimpleName())
|
||||
testInstance.binding.setVariable('currentBuild', [result: 'SUCCESS', currentResult: 'SUCCESS'])
|
||||
PipelineTestHelper helper = LibraryLoadingTestExecutionListener.getSingletonInstance()
|
||||
LibraryLoadingTestExecutionListener.START_METHOD_TRACKING = true
|
||||
@ -121,6 +124,7 @@ class LibraryLoadingTestExecutionListener extends AbstractTestExecutionListener
|
||||
void afterTestMethod(TestContext testContext) throws Exception {
|
||||
super.afterTestMethod(testContext)
|
||||
def testInstance = testContext.getTestInstance()
|
||||
StepTracker.after()
|
||||
PipelineTestHelper helper = LibraryLoadingTestExecutionListener.getSingletonInstance()
|
||||
|
||||
helper.clearCallStack()
|
||||
@ -181,6 +185,7 @@ class LibraryLoadingTestExecutionListener extends AbstractTestExecutionListener
|
||||
|
||||
static class PipelineTestHelperHook {
|
||||
def helper = new PipelineTestHelper() {
|
||||
|
||||
def clearAllowedMethodCallbacks(Collection c = []) {
|
||||
List itemsToRemove = []
|
||||
c.each {
|
||||
|
@ -5,6 +5,6 @@ import hudson.AbortException
|
||||
class PipelineWhenException extends AbortException{
|
||||
public PipelineWhenException(String message)
|
||||
{
|
||||
super(message);
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!groovy
|
||||
package util
|
||||
|
||||
import com.lesfurets.jenkins.unit.global.lib.SourceRetriever
|
||||
|
15
test/groovy/util/StepHelper.groovy
Normal file
15
test/groovy/util/StepHelper.groovy
Normal file
@ -0,0 +1,15 @@
|
||||
package util
|
||||
|
||||
import java.util.List
|
||||
|
||||
import groovy.io.FileType
|
||||
|
||||
public class StepHelper {
|
||||
|
||||
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] }
|
||||
return steps
|
||||
}
|
||||
}
|
69
test/groovy/util/StepTracker.groovy
Normal file
69
test/groovy/util/StepTracker.groovy
Normal file
@ -0,0 +1,69 @@
|
||||
package util
|
||||
|
||||
import static com.lesfurets.jenkins.unit.MethodSignature.method
|
||||
import static util.StepHelper.getSteps
|
||||
|
||||
import org.codehaus.groovy.runtime.MetaClassHelper
|
||||
import com.lesfurets.jenkins.unit.MethodSignature
|
||||
import com.lesfurets.jenkins.unit.PipelineTestHelper
|
||||
import groovy.json.JsonBuilder
|
||||
|
||||
class StepTracker {
|
||||
|
||||
/*
|
||||
* Contains the piper steps as key (derived from the test name, so this is blurry since it might
|
||||
* contains also other cases than only piper step name) and the observed calls in a collection.
|
||||
*/
|
||||
static Map piperStepCallMapping = [:]
|
||||
static Set piperSteps = StepHelper.getSteps()
|
||||
|
||||
static Set calls
|
||||
|
||||
static {
|
||||
initialize()
|
||||
}
|
||||
|
||||
final static void initialize() {
|
||||
|
||||
PipelineTestHelper.metaClass.getAllowedMethodEntry = {
|
||||
|
||||
// We need to be careful here, in case we switch to another
|
||||
// version of the Les Furets framework we have to check if
|
||||
// this here still works.
|
||||
|
||||
String name, Object[] args ->
|
||||
|
||||
Class[] paramTypes = MetaClassHelper.castArgumentsToClassArray(args)
|
||||
MethodSignature signature = method(name, paramTypes)
|
||||
def intercepted = allowedMethodCallbacks.find { k, v -> k == signature }
|
||||
|
||||
if(intercepted != null)
|
||||
StepTracker.add(name)
|
||||
|
||||
return intercepted
|
||||
}
|
||||
}
|
||||
|
||||
static void before(String stepName) {
|
||||
|
||||
if(piperStepCallMapping[stepName] == null)
|
||||
piperStepCallMapping[stepName] = (Set)[]
|
||||
calls = piperStepCallMapping[stepName]
|
||||
}
|
||||
|
||||
static void after() {
|
||||
calls = null
|
||||
write()
|
||||
}
|
||||
static void add (String call) {
|
||||
calls.add(call)
|
||||
}
|
||||
|
||||
static private void write() {
|
||||
Map root = [
|
||||
piperSteps: piperSteps,
|
||||
calls: piperStepCallMapping.sort()
|
||||
]
|
||||
new File('target/trackedCalls.json').write(new JsonBuilder(root).toPrettyString())
|
||||
}
|
||||
}
|
@ -169,9 +169,9 @@ void call(Map parameters = [:], Closure body = null) {
|
||||
|
||||
try {
|
||||
sh """#!/bin/bash
|
||||
git add .
|
||||
git ${gitConfig} commit -m 'update version ${newVersion}'
|
||||
git tag ${config.tagPrefix}${newVersion}"""
|
||||
git add .
|
||||
git ${gitConfig} commit -m 'update version ${newVersion}'
|
||||
git tag ${config.tagPrefix}${newVersion}"""
|
||||
config.gitCommitId = gitUtils.getGitCommitIdOrNull()
|
||||
} catch (e) {
|
||||
error "[${STEP_NAME}]git commit and tag failed: ${e}"
|
||||
|
@ -12,6 +12,7 @@ class commonPipelineEnvironment implements Serializable {
|
||||
|
||||
//stores the gitCommitId as well as additional git information for the build during pipeline run
|
||||
String gitCommitId
|
||||
String gitCommitMessage
|
||||
String gitSshUrl
|
||||
String gitHttpsUrl
|
||||
String gitBranch
|
||||
@ -46,6 +47,7 @@ class commonPipelineEnvironment implements Serializable {
|
||||
configuration = [:]
|
||||
|
||||
gitCommitId = null
|
||||
gitCommitMessage = null
|
||||
gitSshUrl = null
|
||||
gitHttpsUrl = null
|
||||
gitBranch = null
|
||||
|
@ -42,4 +42,3 @@ def call(Map parameters = [:], body) {
|
||||
|
||||
return duration
|
||||
}
|
||||
|
||||
|
@ -197,7 +197,7 @@ def getCulprits(config, branch, numberOfCommits) {
|
||||
ignoreMissing: true
|
||||
) {
|
||||
def pullRequestID = branch.replaceAll('PR-', '')
|
||||
def localBranchName = "pr" + pullRequestID;
|
||||
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
|
||||
|
@ -54,15 +54,19 @@ void call(Map parameters = [:]) {
|
||||
|
||||
deleteDir()
|
||||
|
||||
checkout([$class: 'GitSCM', branches: [[name: config.branch]],
|
||||
doGenerateSubmoduleConfigurations: false,
|
||||
extensions: [[$class: 'SparseCheckoutPaths',
|
||||
sparseCheckoutPaths: [[path: config.path]]
|
||||
]],
|
||||
submoduleCfg: [],
|
||||
userRemoteConfigs: [[credentialsId: config.credentialsId,
|
||||
url: config.repoUrl
|
||||
]]
|
||||
checkout([
|
||||
$class: 'GitSCM',
|
||||
branches: [[name: config.branch]],
|
||||
doGenerateSubmoduleConfigurations: false,
|
||||
extensions: [[
|
||||
$class: 'SparseCheckoutPaths',
|
||||
sparseCheckoutPaths: [[path: config.path]]
|
||||
]],
|
||||
submoduleCfg: [],
|
||||
userRemoteConfigs: [[
|
||||
credentialsId: config.credentialsId,
|
||||
url: config.repoUrl
|
||||
]]
|
||||
])
|
||||
|
||||
}
|
||||
|
@ -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') {
|
||||
|
80
vars/piperPipelineStageConfirm.groovy
Normal file
80
vars/piperPipelineStageConfirm.groovy
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -40,12 +40,12 @@ import groovy.text.SimpleTemplateEngine
|
||||
*
|
||||
* Notification contains:
|
||||
*
|
||||
* * Build status;
|
||||
* * Repo Owner;
|
||||
* * Repo Name;
|
||||
* * Branch Name;
|
||||
* * Jenkins Build Number;
|
||||
* * Jenkins Build URL.
|
||||
* * Build status
|
||||
* * Repo Owner
|
||||
* * Repo Name
|
||||
* * Branch Name
|
||||
* * Jenkins Build Number
|
||||
* * Jenkins Build URL
|
||||
*/
|
||||
@GenerateDocumentation
|
||||
void call(Map parameters = [:]) {
|
||||
|
@ -160,7 +160,7 @@ void call(Map parameters = [:]) {
|
||||
switch(config.pullRequestProvider){
|
||||
case 'github':
|
||||
config.options.add("sonar.pullrequest.github.repository=${config.githubOrg}/${config.githubRepo}")
|
||||
break;
|
||||
break
|
||||
default: error "Pull-Request provider '${config.pullRequestProvider}' is not supported!"
|
||||
}
|
||||
workerForGithubAuth(config)
|
||||
|
@ -184,30 +184,33 @@ void call(parameters = [:]) {
|
||||
try {
|
||||
if(backendType == BackendType.SOLMAN) {
|
||||
transportRequestId = cm.createTransportRequestSOLMAN(
|
||||
configuration.changeManagement.solman.docker,
|
||||
configuration.changeDocumentId,
|
||||
configuration.developmentSystemId,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.changeManagement.clientOpts)
|
||||
configuration.changeManagement.solman.docker,
|
||||
configuration.changeDocumentId,
|
||||
configuration.developmentSystemId,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.changeManagement.clientOpts
|
||||
)
|
||||
} else if(backendType == BackendType.CTS) {
|
||||
transportRequestId = cm.createTransportRequestCTS(
|
||||
configuration.changeManagement.cts.docker,
|
||||
configuration.transportType,
|
||||
configuration.targetSystem,
|
||||
configuration.description,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.changeManagement.clientOpts)
|
||||
configuration.changeManagement.cts.docker,
|
||||
configuration.transportType,
|
||||
configuration.targetSystem,
|
||||
configuration.description,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.changeManagement.clientOpts
|
||||
)
|
||||
} else if (backendType == BackendType.RFC) {
|
||||
transportRequestId = cm.createTransportRequestRFC(
|
||||
configuration.changeManagement.rfc.docker,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.rfc.developmentInstance,
|
||||
configuration.changeManagement.rfc.developmentClient,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.description,
|
||||
configuration.verbose)
|
||||
configuration.changeManagement.rfc.docker,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.rfc.developmentInstance,
|
||||
configuration.changeManagement.rfc.developmentClient,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.description,
|
||||
configuration.verbose
|
||||
)
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid backend type: '${backendType}'.")
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrati
|
||||
'transportRequestId',
|
||||
/** @see transportRequestCreate */
|
||||
'verbose',
|
||||
])
|
||||
])
|
||||
|
||||
/** Releases a Transport Request. */
|
||||
@GenerateDocumentation
|
||||
|
@ -177,9 +177,9 @@ void call(parameters = [:]) {
|
||||
"Change document id not provided (parameter: \'changeDocumentId\' or via commit history).")
|
||||
}
|
||||
configuration = configHelper
|
||||
.withMandatoryProperty('transportRequestId',
|
||||
"Transport request id not provided (parameter: \'transportRequestId\' or via commit history).")
|
||||
.use()
|
||||
.withMandatoryProperty('transportRequestId',
|
||||
"Transport request id not provided (parameter: \'transportRequestId\' or via commit history).")
|
||||
.use()
|
||||
|
||||
def uploadingMessage = ['[INFO] Uploading file ' +
|
||||
"'${backendType == BackendType.RFC ? configuration.applicationUrl : configuration.filePath}' " +
|
||||
|
Loading…
Reference in New Issue
Block a user