1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-30 05:59:39 +02:00

Merge remote-tracking branch 'github/master' into HEAD

This commit is contained in:
Marcus Holl 2019-05-17 09:58:43 +02:00
commit 887e8e93e2
87 changed files with 3251 additions and 793 deletions

View File

@ -46,7 +46,7 @@ Code shall contain comments to explain the intention of the code when it is uncl
#### EditorConfig
To ensure a common file format, there is a `.editorConfig` file [in place](.editorconfig). To respect this file, [check](http://editorconfig.org/#download) if your editor does support it natively or you need to download a plugin.
To ensure a common file format, there is a `.editorConfig` file [in place](../.editorconfig). To respect this file, [check](http://editorconfig.org/#download) if your editor does support it natively or you need to download a plugin.
### Commit Message Style

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
.idea/
bin/
/bin
.settings
logs
reports

View File

@ -31,10 +31,10 @@ jobs:
- ./cc-test-reporter upload-coverage
- name: Consumer Tests
if: repo = "SAP/jenkins-library" && ( (type != pull_request && branch =~ /^master$|^it\/.*$/) || (type == pull_request && head_repo = "SAP/jenkins-library" && head_branch =~ /^it\/.*$/) )
script: cd consumer-test && chmod +x integrationTestController.sh && ./integrationTestController.sh
script: cd consumer-test && groovy consumerTestController.groovy
- stage: Docs
name: Build & Deploy
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

View File

@ -74,8 +74,7 @@ To setup the shared library, you need to perform the following steps:
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`

View File

@ -0,0 +1,135 @@
@Grab('org.yaml:snakeyaml:1.17')
import org.yaml.snakeyaml.Yaml
class TestRunnerThread extends Thread {
static def workspacesRootDir
static def libraryVersionUnderTest
static def repositoryUnderTest
Process currentProcess
final StringBuilder stdOut = new StringBuilder()
final StringBuilder stdErr = new StringBuilder()
int lastPrintedStdOutLine = -1
public def returnCode = -1
public def lastCommand
def area
def testCase
def uniqueName
def testCaseRootDir
def testCaseWorkspace
def testCaseConfig
TestRunnerThread(File testCaseFile) {
// Regex pattern expects a folder structure such as '/rootDir/areaDir/testCase.extension'
def testCaseMatches = (testCaseFile.toString() =~
/^[\w\-]+\\/([\w\-]+)\\/([\w\-]+)\..*\u0024/)
this.area = testCaseMatches[0][1]
this.testCase = testCaseMatches[0][2]
if (!area || !testCase) {
throw new RuntimeException("Expecting file structure '/rootDir/areaDir/testCase.yml' " +
"but got '${testCaseFile}'.")
}
this.uniqueName = "${area}|${testCase}"
this.testCaseRootDir = new File("${workspacesRootDir}/${area}/${testCase}")
this.testCaseWorkspace = "${testCaseRootDir}/workspace"
this.testCaseConfig = new Yaml().load(testCaseFile.text)
}
void run() {
println "[INFO] Test case '${uniqueName}' launched."
if (testCaseRootDir.exists() || !testCaseRootDir.mkdirs()) {
throw new RuntimeException("Creation of dir '${testCaseRootDir}' failed.")
}
executeShell("git clone -b ${testCase} ${testCaseConfig.referenceAppRepoUrl} " +
"${testCaseWorkspace}")
addJenkinsYmlToWorkspace()
setLibraryVersionInJenkinsfile()
//Commit the changed version because artifactSetVersion expects the git repo not to be dirty
executeShell(["git", "-C", "${testCaseWorkspace}", "commit", "--all",
'--author="piper-testing-bot <piper-testing-bot@example.com>"',
'--message="Set piper lib version for test"'])
executeShell("docker run -v /var/run/docker.sock:/var/run/docker.sock " +
"-v ${System.getenv('PWD')}/${testCaseWorkspace}:/workspace -v /tmp " +
"-e CASC_JENKINS_CONFIG=/workspace/jenkins.yml -e CX_INFRA_IT_CF_USERNAME " +
"-e CX_INFRA_IT_CF_PASSWORD -e BRANCH_NAME=${testCase} ppiper/jenkinsfile-runner")
println "*****[INFO] Test case '${uniqueName}' finished successfully.*****"
printOutput()
}
// Configure path to library-repository under test in Jenkins config
private void addJenkinsYmlToWorkspace() {
def sourceFile = 'jenkins.yml'
def sourceText = new File(sourceFile).text.replaceAll(
'__REPO_SLUG__', repositoryUnderTest)
def target = new File("${testCaseWorkspace}/${sourceFile}")
target.write(sourceText)
}
// Force usage of library version under test by setting it in the Jenkinsfile,
// which is then the first definition and thus has the highest precedence.
private void setLibraryVersionInJenkinsfile() {
def jenkinsfile = new File("${testCaseWorkspace}/Jenkinsfile")
def manipulatedText =
"@Library(\"piper-library-os@${libraryVersionUnderTest}\") _\n" +
jenkinsfile.text
jenkinsfile.write(manipulatedText)
}
private void executeShell(command) {
lastCommand = command
def startOfCommandString = "Shell command: '${command}'\n"
stdOut << startOfCommandString
stdErr << startOfCommandString
currentProcess = command.execute()
currentProcess.waitForProcessOutput(stdOut, stdErr)
returnCode = currentProcess.exitValue()
currentProcess = null
if (returnCode > 0) {
throw new ReturnCodeNotZeroException("Test case: [${uniqueName}]; " +
"shell command '${command} exited with return code '${returnCode}")
}
}
void printOutput() {
println "\n[INFO] stdout output from test case ${uniqueName}:"
stdOut.eachLine { line, i ->
println "${i} [${uniqueName}] ${line}"
lastPrintedStdOutLine = i
}
println "\n[INFO] stderr output from test case ${uniqueName}:"
stdErr.eachLine { line, i ->
println "${i} [${uniqueName}] ${line}"
}
}
public void printRunningStdOut() {
stdOut.eachLine { line, i ->
if (i > lastPrintedStdOutLine) {
println "${i} [${uniqueName}] ${line}"
lastPrintedStdOutLine = i
}
}
}
@Override
public String toString() {
return uniqueName
}
}
class ReturnCodeNotZeroException extends Exception {
ReturnCodeNotZeroException(message) {
super(message)
}
}

View File

@ -0,0 +1,232 @@
import groovy.io.FileType
import static groovy.json.JsonOutput.toJson
COMMIT_HASH = null
RUNNING_LOCALLY = false
AUXILIARY_SLEEP_MS = 10000
START_TIME_MS = System.currentTimeMillis()
WORKSPACES_ROOT = 'workspaces'
TEST_CASES_DIR = 'testCases'
LIBRARY_VERSION_UNDER_TEST = "git log --format=%H -n 1".execute().text.trim()
EXCLUDED_FROM_CONSUMER_TESTING_REGEXES = [
/^documentation\/.*$/,
/^.travis.yml$/,
/^test\/.*$/
]
newEmptyDir(WORKSPACES_ROOT)
TestRunnerThread.workspacesRootDir = WORKSPACES_ROOT
TestRunnerThread.libraryVersionUnderTest = LIBRARY_VERSION_UNDER_TEST
TestRunnerThread.repositoryUnderTest = System.getenv('TRAVIS_REPO_SLUG') ?: 'SAP/jenkins-library'
def testCaseThreads
def cli = new CliBuilder(
usage: 'groovy consumerTestController.groovy [<options>]',
header: 'Options:',
footer: 'If no options are set, all tests are run centrally, i.e. on travisCI.')
cli.with {
h longOpt: 'help', 'Print this help text and exit.'
l longOpt: 'run-locally', 'Run consumer tests locally in Docker, i.e. skip reporting of GitHub status.'
s longOpt: 'single-test', args: 1, argName: 'filePath', 'Run single test.'
}
def options = cli.parse(args)
if (options.h) {
cli.usage()
return
}
if (options.l) {
RUNNING_LOCALLY = true
}
if (!RUNNING_LOCALLY) {
/*
In case the build is performed for a pull request TRAVIS_COMMIT is a merge
commit between the base branch and the PR branch HEAD. That commit is actually built.
But for notifying about a build status we need the commit which is currently
the HEAD of the PR branch.
In case the build is performed for a simple branch (not associated with a PR)
In this case there is no merge commit between any base branch and HEAD of a PR branch.
The commit which we need for notifying about a build status is in this case simply
TRAVIS_COMMIT itself.
*/
COMMIT_HASH = System.getenv('TRAVIS_PULL_REQUEST_SHA') ?: System.getenv('TRAVIS_COMMIT')
if (changeDoesNotNeedConsumerTesting()) {
println 'No consumer tests necessary.'
notifyGithub("success", "No consumer tests necessary.")
return
} else {
notifyGithub("pending", "Consumer tests are in progress.")
}
}
if (!System.getenv('CX_INFRA_IT_CF_USERNAME') || !System.getenv('CX_INFRA_IT_CF_PASSWORD')) {
exitPrematurely('Environment variables CX_INFRA_IT_CF_USERNAME and CX_INFRA_IT_CF_PASSWORD need to be set.')
}
if (options.s) {
def file = new File(options.s)
if (!file.exists()) {
exitPrematurely("Test case configuration file '${file}' does not exist. " +
"Please provide path to a configuration file of structure '/rootDir/areaDir/testCase.yml'.")
}
testCaseThreads = [new TestRunnerThread(file)]
} else {
testCaseThreads = listTestCaseThreads()
}
testCaseThreads.each { it ->
it.start()
}
//The thread below will print to console while the test cases are running.
//Otherwise the job would be canceled after 10 minutes without output.
def done = false
Thread.start {
def outputWasPrintedPrematurely = false
def singleTestCase = (testCaseThreads.size() == 1)
if (singleTestCase) {
AUXILIARY_SLEEP_MS = 1000 //for a single test case we print the running output every second
}
for (; ;) {
if (singleTestCase) {
testCaseThreads[0].printRunningStdOut()
} else {
println "[INFO] Consumer tests are still running."
}
// Build is killed at 50 min, print log to console at minute 45
int MINUTES_SINCE_START = (System.currentTimeMillis() - START_TIME_MS) / (1000 * 60)
if (!singleTestCase && MINUTES_SINCE_START > 44 && !outputWasPrintedPrematurely) {
testCaseThreads.each { thread ->
thread.printOutput()
}
outputWasPrintedPrematurely = true
}
sleep(AUXILIARY_SLEEP_MS)
if (done) {
break
}
}
}
testCaseThreads.each { it ->
it.join()
}
done = true
def failedThreads = testCaseThreads.findAll { thread ->
thread.returnCode != 0
}
def status
def statusMessage
if (failedThreads.size() == 0) {
status = "success"
statusMessage = "All consumer tests finished successfully. Congratulations!"
} else {
failedThreads.each { failedThread ->
println "[ERROR] ${failedThread.uniqueName}: Process execution of command: '${failedThread.lastCommand}' failed. " +
"Return code: ${failedThread.returnCode}."
failedThread.printOutput()
}
status = "failure"
statusMessage "The following consumer test(s) failed: ${failedThreads}"
}
if (!RUNNING_LOCALLY) {
notifyGithub(status, statusMessage)
}
println statusMessage
if (status == "failure") {
System.exit(1)
}
def listTestCaseThreads() {
//Each dir that includes a yml file is a test case
def threads = []
new File(TEST_CASES_DIR).traverse(type: FileType.FILES, nameFilter: ~/^.+\.yml\u0024/) { file ->
threads << new TestRunnerThread(file)
}
return threads
}
def notifyGithub(state, description) {
println "[INFO] Notifying about state '${state}' for commit '${COMMIT_HASH}'."
URL url = new URL("https://api.github.com/repos/SAP/jenkins-library/statuses/${COMMIT_HASH}")
HttpURLConnection con = (HttpURLConnection) url.openConnection()
con.setRequestMethod('POST')
con.setRequestProperty("Content-Type", "application/json; utf-8");
con.setRequestProperty('User-Agent', 'groovy-script')
con.setRequestProperty('Authorization', "token ${System.getenv('INTEGRATION_TEST_VOTING_TOKEN')}")
def postBody = [
state : state,
target_url : System.getenv('TRAVIS_BUILD_WEB_URL'),
description: description,
context : "integration-tests"
]
con.setDoOutput(true)
con.getOutputStream().withStream { os ->
os.write(toJson(postBody).getBytes("UTF-8"))
}
int responseCode = con.getResponseCode()
if (responseCode != HttpURLConnection.HTTP_CREATED) {
exitPrematurely("[ERROR] Posting status to github failed. Expected response code " +
"'${HttpURLConnection.HTTP_CREATED}', but got '${responseCode}'. " +
"Response message: '${con.getResponseMessage()}'",
34) // Error code taken from curl: CURLE_HTTP_POST_ERROR
}
}
def changeDoesNotNeedConsumerTesting() {
if (System.getenv('TRAVIS_BRANCH') == 'master') {
return false
}
def excludesRegex = '(' + EXCLUDED_FROM_CONSUMER_TESTING_REGEXES.join('|') + ')'
"git remote add sap https://github.com/SAP/jenkins-library.git".execute().waitFor()
"git fetch sap".execute().waitFor()
def diff = "git diff --name-only sap/master ${LIBRARY_VERSION_UNDER_TEST}".execute().text.trim()
for (def line : diff.readLines()) {
if (!(line ==~ excludesRegex)) {
return false
}
}
return true
}
static def newEmptyDir(String dirName) {
def dir = new File(dirName)
if (dir.exists()) {
if (!dir.deleteDir()) {
exitPrematurely("Deletion of dir '${dirName}' failed.")
}
}
if (!dir.mkdirs()) {
exitPrematurely("Creation of dir '${dirName}' failed.")
}
}
static def exitPrematurely(String message, int returnCode = 1) {
println message
System.exit(returnCode)
}

View File

@ -1,139 +0,0 @@
#!/bin/bash
function fail() {
local message="$1"
local returnCode=${2:-1}
echo "[ERROR] ${message}" >&2
exit "${returnCode}"
}
function notify() {
local state=${1}
local description=${2}
local hash=${3}
echo "[INFO] Notifying about state \"${state}\" for commit \"${hash}\"."
curl -X POST \
--fail \
--silent \
--output /dev/null \
--data "{\"state\": \"${state}\", \"target_url\": \"${TRAVIS_BUILD_WEB_URL}\", \"description\": \"${description}\", \"context\": \"integration-tests\"}" \
--user "${INTEGRATION_TEST_VOTING_USER}:${INTEGRATION_TEST_VOTING_TOKEN}" \
"https://api.github.com/repos/SAP/jenkins-library/statuses/${hash}" || fail "Cannot send notification. curl return code: $?"
}
function cleanup() {
[[ -z "${notificationThreadPid}" ]] || kill -PIPE "${notificationThreadPid}" &>/dev/null
}
trap cleanup EXIT
#
# In case the build is performed for a pull request TRAVIS_COMMIT is a merge
# commit between the base branch and the PR branch HEAD. That commit is actually built.
# But for notifying about a build status we need the commit which is currently
# the HEAD of the PR branch.
#
# In case the build is performed for a simple branch (not associated with a PR)
# In this case there is no merge commit between any base branch and HEAD of a PR branch.
# The commit which we need for notifying about a build status is in this case simply
# TRAVIS_COMMIT itself.
#
COMMIT_HASH_FOR_STATUS_NOTIFICATIONS="${TRAVIS_PULL_REQUEST_SHA}"
[[ -z "${COMMIT_HASH_FOR_STATUS_NOTIFICATIONS}" ]] && COMMIT_HASH_FOR_STATUS_NOTIFICATIONS="${TRAVIS_COMMIT}"
notify "pending" "Integration tests in progress." "${COMMIT_HASH_FOR_STATUS_NOTIFICATIONS}"
WORKSPACES_ROOT=workspaces
[[ -e "${WORKSPACES_ROOT}" ]] && rm -rf ${WORKSPACES_ROOT}
TEST_CASES=$(find testCases -name '*.yml')
# This auxiliary thread is needed in order to produce some output while the
# test are running. Otherwise the job will be canceled after 10 minutes without
# output.
while true; do sleep 10; echo "[INFO] Integration tests still running."; done &
notificationThreadPid=$!
declare -a processes
i=0
for f in ${TEST_CASES}
do
testCase=$(basename "${f%.*}")
area=$(dirname "${f#*/}")
echo "[INFO] Running test case \"${testCase}\" in area \"${area}\"."
TEST_CASE_ROOT="${WORKSPACES_ROOT}/${area}/${testCase}"
[[ -e "${TEST_CASE_ROOT}" ]] && rm -rf "${TEST_CASE_ROOT}"
mkdir -p "${TEST_CASE_ROOT}" || fail "Cannot create test case root directory for test case \"${testCase}\"." 1
source ./runTest.sh "${testCase}" "${TEST_CASE_ROOT}" &> "${TEST_CASE_ROOT}/log.txt" &
pid=$!
processes[$i]="${area}/${testCase}:${pid}"
echo "[INFO] Test case \"${testCase}\" in area \"${area}\" launched. (PID: \"${pid}\")."
let i=i+1
done
[[ "${i}" == 0 ]] && fail "No tests has been executed." 1
#
# wait for the test cases and cat the log
for p in "${processes[@]}"
do
area=$(dirname "${p%:*}")
testCase=$(basename "${p%:*}")
processId="${p#*:}"
echo "[INFO] Waiting for test case \"${testCase}\" in area \"${area}\" (PID: \"${processId}\")."
wait "${processId}"
echo "[INFO] Test case \"${testCase}\" in area \"${area}\" finished (PID: \"${processId}\")."
done
kill -PIPE "${notificationThreadPid}" &>/dev/null && notificationThreadPid=""
#
# provide the logs
for p in "${processes[@]}"
do
area=$(dirname "${p%:*}")
testCase=$(basename "${p%:*}")
TEST_CASE_ROOT="${WORKSPACES_ROOT}/${area}/${testCase}"
echo "[INFO] === START === Logs for test case \"${testCase}\" ===."
cat "${TEST_CASE_ROOT}/log.txt"
echo "[INFO] === END === Logs for test case \"${testCase}\" ===."
done
#
# list test case status
echo "[INFO] Build status:"
failure="false"
for p in "${processes[@]}"
do
status="UNDEFINED"
area=$(dirname "${p%:*}")
testCase=$(basename "${p%:*}")
TEST_CASE_ROOT="${WORKSPACES_ROOT}/${area}/${testCase}"
if [[ -f "${TEST_CASE_ROOT}/SUCCESS" ]]
then
status="SUCCESS"
else
status="FAILURE"
failure="true"
fi
printf "[INFO] %-30s: %s\n" "${testCase}" "${status}"
done
STATUS_DESCRIPTION="The integration tests failed."
STATUS_STATE="failure"
if [[ "${failure}" == "false" ]]
then
STATUS_DESCRIPTION="The integration tests succeeded."
STATUS_STATE="success"
fi
notify "${STATUS_STATE}" "${STATUS_DESCRIPTION}" "${COMMIT_HASH_FOR_STATUS_NOTIFICATIONS}"
[[ "${failure}" != "false" ]] && fail "Integration tests failed." 1
echo "[INFO] Integration tests succeeded."
exit 0

View File

@ -1,30 +0,0 @@
#!/usr/bin/env bash
TEST_CASE=$1
TEST_CASE_ROOT=$2
TEST_CASE_WORKSPACE="${TEST_CASE_ROOT}/workspace"
LIBRARY_VERSION_UNDER_TEST=$(git log --format="%H" -n 1)
REPOSITORY_UNDER_TEST=${TRAVIS_REPO_SLUG:-SAP/jenkins-library}
git clone -b "${TEST_CASE}" https://github.com/sap/cloud-s4-sdk-book "${TEST_CASE_WORKSPACE}"
cp -f jenkins.yml "${TEST_CASE_WORKSPACE}"
cd "${TEST_CASE_WORKSPACE}" || exit 1
# Configure path to library-repository under test in Jenkins config
sed -i -e "s:__REPO_SLUG__:${REPOSITORY_UNDER_TEST}:g" jenkins.yml
# Force usage of library version under test by setting it in the Jenkinsfile which is then the first definition and thus has the highest precedence
echo "@Library(\"piper-library-os@$LIBRARY_VERSION_UNDER_TEST\") _" | cat - Jenkinsfile > temp && mv temp Jenkinsfile
# Commit the changed version because artifactSetVersion expects the git repo not to be dirty
git commit --all --author="piper-testing-bot <piper-testing-bot@example.com>" --message="Set piper lib version for test"
docker run -v /var/run/docker.sock:/var/run/docker.sock -v "${PWD}":/workspace -v /tmp -e CASC_JENKINS_CONFIG=/workspace/jenkins.yml \
-e CX_INFRA_IT_CF_USERNAME -e CX_INFRA_IT_CF_PASSWORD -e BRANCH_NAME="${TEST_CASE}" ppiper/jenkinsfile-runner
RC=$?
cd - &> /dev/null || { echo "[ERROR] change directory back into integration test root folder failed."; exit 1; }
[[ "${RC}" == 0 ]] && touch "${TEST_CASE_ROOT}/SUCCESS"

View File

@ -1,2 +1,2 @@
# Empty for the moment.
# Might contain test configuration in the future.
# Test case configuration
referenceAppRepoUrl: "https://github.com/sap/cloud-s4-sdk-book"

View File

@ -1,2 +1,2 @@
# Empty for the moment.
# Might contain test configuration in the future.
# Test case configuration
referenceAppRepoUrl: "https://github.com/sap/cloud-s4-sdk-book"

View File

@ -1,10 +1,13 @@
import groovy.io.FileType
import groovy.json.JsonOutput
import org.yaml.snakeyaml.Yaml
import org.codehaus.groovy.control.CompilerConfiguration
import com.sap.piper.GenerateDocumentation
import java.util.regex.Matcher
import groovy.text.StreamingTemplateEngine
import com.sap.piper.MapUtils
//
// Collects helper functions for rendering the documentation
//
@ -19,12 +22,33 @@ class TemplateHelper {
parameters.keySet().toSorted().each {
def props = parameters.get(it)
t += "| `${it}` | ${props.mandatory ?: props.required ? 'yes' : 'no'} | ${(props.defaultValue ? '`' + props.defaultValue + '`' : '') } | ${props.value ?: ''} |\n"
def defaultValue = isComplexDefault(props.defaultValue) ? renderComplexDefaultValue(props.defaultValue) :
props.defaultValue != null ? "`${props.defaultValue}`" : ''
t += "| `${it}` | ${props.mandatory ?: props.required ? 'yes' : 'no'} | ${defaultValue} | ${props.value ?: ''} |\n"
}
t
}
private static boolean isComplexDefault(def _default) {
if(! (_default in Collection)) return false
if(_default.size() == 0) return false
for(def entry in _default) {
if(! (entry in Map)) return false
if(! entry.dependentParameterKey) return false
if(! entry.key) return false
}
return true
}
private static renderComplexDefaultValue(def _default) {
_default
.collect { "${it.dependentParameterKey}=`${it.key ?: '<empty>'}`:`${it.value ?: '<empty>'}`" }
.join('<br />')
}
static createParameterDescriptionSection(Map parameters) {
def t = ''
parameters.keySet().toSorted().each {
@ -67,7 +91,7 @@ class Helper {
static getConfigHelper(classLoader, roots, script) {
def compilerConfig = new CompilerConfiguration()
compilerConfig.setClasspathList( roots )
compilerConfig.setClasspathList( roots )
new GroovyClassLoader(classLoader, compilerConfig, true)
.parseClass(new File(projectRoot, 'src/com/sap/piper/ConfigurationHelper.groovy'))
@ -87,11 +111,15 @@ class Helper {
prepareDefaultValuesStep.metaClass.readYaml {
m -> new Yaml().load(m.text)
}
prepareDefaultValuesStep.metaClass.echo {
m -> println(m)
}
prepareDefaultValuesStep
}
static getDummyScript(def prepareDefaultValuesStep, def stepName) {
static getDummyScript(def prepareDefaultValuesStep, def stepName, Map prepareDefaultValuesStepParams) {
def _prepareDefaultValuesStep = prepareDefaultValuesStep
def _stepName = stepName
@ -101,7 +129,7 @@ class Helper {
def STEP_NAME = _stepName
def prepareDefaultValues() {
_prepareDefaultValuesStep()
_prepareDefaultValuesStep(prepareDefaultValuesStepParams)
}
@ -185,105 +213,114 @@ class Helper {
f.eachLine {
line ->
if(docuEnd) {
docuEnd = false
if(line ==~ /.*dependingOn.*/) {
def dependentConfigKey = (line =~ /.*dependingOn\('(.*)'\).mixin\('(.*)'/)[0][1]
def configKey = (line =~ /.*dependingOn\('(.*)'\).mixin\('(.*)'/)[0][2]
if(! step.dependentConfig[configKey]) {
step.dependentConfig[configKey] = []
}
step.dependentConfig[configKey] << dependentConfigKey
}
if(isHeader(line)) {
def _docu = []
docuLines.each { _docu << it }
_docu = Helper.trim(_docu)
step.description = _docu.join('\n')
} else {
if(docuEnd) {
docuEnd = false
def param = retrieveParameterName(line)
if(isHeader(line)) {
def _docu = []
docuLines.each { _docu << it }
_docu = Helper.trim(_docu)
step.description = _docu.join('\n')
} else {
if(!param) {
throw new RuntimeException('Cannot retrieve parameter for a comment')
def param = retrieveParameterName(line)
if(!param) {
throw new RuntimeException('Cannot retrieve parameter for a comment')
}
def _docu = [], _value = [], _mandatory = [], _parentObject = []
docuLines.each { _docu << it }
valueLines.each { _value << it }
mandatoryLines.each { _mandatory << it }
parentObjectLines.each { _parentObject << it }
_parentObject << param
param = _parentObject*.trim().join('/').trim()
if(step.parameters[param].docu || step.parameters[param].value)
System.err << "[WARNING] There is already some documentation for parameter '${param}. Is this parameter documented twice?'\n"
step.parameters[param].docu = _docu*.trim().join(' ').trim()
step.parameters[param].value = _value*.trim().join(' ').trim()
step.parameters[param].mandatory = _mandatory*.trim().join(' ').trim()
}
docuLines.clear()
valueLines.clear()
mandatoryLines.clear()
parentObjectLines.clear()
}
if( line.trim() ==~ /^\/\*\*.*/ ) {
docu = true
}
if(docu) {
def _line = line
_line = _line.replaceAll('^\\s*', '') // leading white spaces
if(_line.startsWith('/**')) _line = _line.replaceAll('^\\/\\*\\*', '') // start comment
if(_line.startsWith('*/') || _line.trim().endsWith('*/')) _line = _line.replaceAll('^\\*/', '').replaceAll('\\*/\\s*$', '') // end comment
if(_line.startsWith('*')) _line = _line.replaceAll('^\\*', '') // continue comment
if(_line.startsWith(' ')) _line = _line.replaceAll('^\\s', '')
if(_line ==~ /.*@possibleValues.*/) {
mandatory = false // should be something like reset attributes
value = true
parentObject = false
}
// some remark for mandatory e.g. some parameters are only mandatory under certain conditions
if(_line ==~ /.*@mandatory.*/) {
value = false // should be something like reset attributes ...
mandatory = true
parentObject = false
}
// grouping config properties within a parent object for easier readability
if(_line ==~ /.*@parentConfigKey.*/) {
value = false // should be something like reset attributes ...
mandatory = false
parentObject = true
}
def _docu = [], _value = [], _mandatory = [], _parentObject = []
docuLines.each { _docu << it }
valueLines.each { _value << it }
mandatoryLines.each { _mandatory << it }
parentObjectLines.each { _parentObject << it }
_parentObject << param
param = _parentObject*.trim().join('/').trim()
if(value) {
if(_line) {
_line = (_line =~ /.*@possibleValues\s*?(.*)/)[0][1]
valueLines << _line
}
}
if(step.parameters[param].docu || step.parameters[param].value)
System.err << "[WARNING] There is already some documentation for parameter '${param}. Is this parameter documented twice?'\n"
if(mandatory) {
if(_line) {
_line = (_line =~ /.*@mandatory\s*?(.*)/)[0][1]
mandatoryLines << _line
}
}
step.parameters[param].docu = _docu*.trim().join(' ').trim()
step.parameters[param].value = _value*.trim().join(' ').trim()
step.parameters[param].mandatory = _mandatory*.trim().join(' ').trim()
if(parentObject) {
if(_line) {
_line = (_line =~ /.*@parentConfigKey\s*?(.*)/)[0][1]
parentObjectLines << _line
}
}
if(!value && !mandatory && !parentObject) {
docuLines << _line
}
}
docuLines.clear()
valueLines.clear()
mandatoryLines.clear()
parentObjectLines.clear()
}
if( line.trim() ==~ /^\/\*\*.*/ ) {
docu = true
}
if(docu) {
def _line = line
_line = _line.replaceAll('^\\s*', '') // leading white spaces
if(_line.startsWith('/**')) _line = _line.replaceAll('^\\/\\*\\*', '') // start comment
if(_line.startsWith('*/') || _line.trim().endsWith('*/')) _line = _line.replaceAll('^\\*/', '').replaceAll('\\*/\\s*$', '') // end comment
if(_line.startsWith('*')) _line = _line.replaceAll('^\\*', '') // continue comment
if(_line.startsWith(' ')) _line = _line.replaceAll('^\\s', '')
if(_line ==~ /.*@possibleValues.*/) {
mandatory = false // should be something like reset attributes
value = true
parentObject = false
}
// some remark for mandatory e.g. some parameters are only mandatory under certain conditions
if(_line ==~ /.*@mandatory.*/) {
value = false // should be something like reset attributes ...
mandatory = true
parentObject = false
}
// grouping config properties within a parent object for easier readability
if(_line ==~ /.*@parentConfigKey.*/) {
value = false // should be something like reset attributes ...
if(docu && line.trim() ==~ /^.*\*\//) {
docu = false
value = false
mandatory = false
parentObject = true
parentObject = false
docuEnd = true
}
if(value) {
if(_line) {
_line = (_line =~ /.*@possibleValues\s*?(.*)/)[0][1]
valueLines << _line
}
}
if(mandatory) {
if(_line) {
_line = (_line =~ /.*@mandatory\s*?(.*)/)[0][1]
mandatoryLines << _line
}
}
if(parentObject) {
if(_line) {
_line = (_line =~ /.*@parentConfigKey\s*?(.*)/)[0][1]
parentObjectLines << _line
}
}
if(!value && !mandatory && !parentObject) {
docuLines << _line
}
}
if(docu && line.trim() ==~ /^.*\*\//) {
docu = false
value = false
mandatory = false
parentObject = false
docuEnd = true
}
}
}
@ -341,13 +378,6 @@ class Helper {
return mappings
}
static getValue(Map config, def pPath) {
def p =config[pPath.head()]
if(pPath.size() == 1) return p // there is no tail
if(p in Map) getValue(p, pPath.tail())
else return p
}
static resolveDocuRelevantSteps(GroovyScriptEngine gse, File stepsDir) {
def docuRelevantSteps = []
@ -371,10 +401,11 @@ class Helper {
roots = [
new File(Helper.projectRoot, "vars").getAbsolutePath(),
new File(Helper.projectRoot, "src").getAbsolutePath()
]
]
stepsDir = null
stepsDocuDir = null
String customDefaults = null
steps = []
@ -390,6 +421,7 @@ def cli = new CliBuilder(
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.'
}
@ -411,6 +443,10 @@ if(options.d)
stepsDocuDir = stepsDocuDir ?: new File(Helper.projectRoot, "documentation/docs/steps")
if(options.c) {
customDefaults = options.c
}
steps.addAll(options.arguments())
// assign parameters
@ -449,7 +485,7 @@ boolean exceptionCaught = false
def stepDescriptors = [:]
for (step in steps) {
try {
stepDescriptors."${step}" = handleStep(step, prepareDefaultValuesStep, gse)
stepDescriptors."${step}" = handleStep(step, prepareDefaultValuesStep, gse, customDefaults)
} catch(Exception e) {
exceptionCaught = true
System.err << "${e.getClass().getName()} caught while handling step '${step}': ${e.getMessage()}.\n"
@ -464,6 +500,8 @@ for(step in stepDescriptors) {
def otherStep = param.value.docu.replaceAll('@see', '').trim()
param.value.docu = fetchTextFrom(otherStep, param.key, stepDescriptors)
param.value.mandatory = fetchMandatoryFrom(otherStep, param.key, stepDescriptors)
if(! param.value.value)
param.value.value = fetchPossibleValuesFrom(otherStep, param.key, stepDescriptors)
}
}
}
@ -484,6 +522,10 @@ if(exceptionCaught) {
System.exit(1)
}
File docuMetaData = new File('target/docuMetaData.json')
if(docuMetaData.exists()) docuMetaData.delete()
docuMetaData << new JsonOutput().toJson(stepDescriptors)
System.err << "[INFO] done.\n"
void renderStep(stepName, stepProperties) {
@ -527,7 +569,11 @@ def fetchMandatoryFrom(def step, def parameterName, def steps) {
}
}
def handleStep(stepName, prepareDefaultValuesStep, gse) {
def fetchPossibleValuesFrom(def step, def parameterName, def steps) {
return steps[step]?.parameters[parameterName]?.value ?: ''
}
def handleStep(stepName, prepareDefaultValuesStep, gse, customDefaults) {
File theStep = new File(stepsDir, "${stepName}.groovy")
File theStepDocu = new File(stepsDocuDir, "${stepName}.md")
@ -539,9 +585,13 @@ def handleStep(stepName, prepareDefaultValuesStep, gse) {
System.err << "[INFO] Handling step '${stepName}'.\n"
Map prepareDefaultValuesStepParams = [:]
if (customDefaults)
prepareDefaultValuesStepParams.customDefaults = customDefaults
def defaultConfig = Helper.getConfigHelper(getClass().getClassLoader(),
roots,
Helper.getDummyScript(prepareDefaultValuesStep, stepName)).use()
roots,
Helper.getDummyScript(prepareDefaultValuesStep, stepName, prepareDefaultValuesStepParams)).use()
def params = [] as Set
@ -567,32 +617,34 @@ def handleStep(stepName, prepareDefaultValuesStep, gse) {
def compatibleParams = [] as Set
if(parentObjectMappings) {
params.each {
if (parentObjectMappings[it])
compatibleParams.add(parentObjectMappings[it] + '/' + it)
else
compatibleParams.add(it)
if (parentObjectMappings[it])
compatibleParams.add(parentObjectMappings[it] + '/' + it)
else
compatibleParams.add(it)
}
if (compatibleParams)
params = compatibleParams
}
def step = [parameters:[:]]
// 'dependentConfig' is only present here for internal reasons and that entry is removed at
// end of method.
def step = [parameters:[:], dependentConfig: [:]]
//
// START special handling for 'script' parameter
// ... would be better if there is no special handling required ...
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`. ' +
'This allows the function to access the ' +
'commonPipelineEnvironment for retrieving, for example, configuration parameters.',
required: true,
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`. ' +
'This allows the function to access the ' +
'commonPipelineEnvironment for retrieving, for example, configuration parameters.',
required: true,
GENERAL_CONFIG: false,
STEP_CONFIG: false
]
GENERAL_CONFIG: false,
STEP_CONFIG: false
]
// END special handling for 'script' parameter
@ -600,12 +652,12 @@ def handleStep(stepName, prepareDefaultValuesStep, gse) {
it ->
def defaultValue = Helper.getValue(defaultConfig, it.split('/'))
def defaultValue = MapUtils.getByPath(defaultConfig, it)
def parameterProperties = [
defaultValue: defaultValue,
required: requiredParameters.contains((it as String)) && defaultValue == null
]
defaultValue: defaultValue,
required: requiredParameters.contains((it as String)) && defaultValue == null
]
step.parameters.put(it, parameterProperties)
@ -619,5 +671,34 @@ def handleStep(stepName, prepareDefaultValuesStep, gse) {
Helper.scanDocu(theStep, step)
step.parameters.each { k, v ->
if(step.dependentConfig.get(k)) {
def dependentParameterKey = step.dependentConfig.get(k)[0]
def dependentValues = step.parameters.get(dependentParameterKey)?.value
if (dependentValues) {
def the_defaults = []
dependentValues
.replaceAll('[\'"` ]', '')
.split(',').each {possibleValue ->
if (!possibleValue instanceof Boolean && defaultConfig.get(possibleValue)) {
the_defaults <<
[
dependentParameterKey: dependentParameterKey,
key: possibleValue,
value: MapUtils.getByPath(defaultConfig.get(possibleValue), k)
]
}
}
v.defaultValue = the_defaults
}
}
}
//
// 'dependentConfig' is only present for internal purposes and must not be used outside.
step.remove('dependentConfig')
step
}

View File

@ -15,7 +15,8 @@ Set up an agile development process with Jenkins CI, which automatically feeds c
In many SAP development scenarios, it is vital to synchronize both backend and frontend deliveries. These deliveries are typically an SAP UI5 application and an ABAP backend from which it is served. The SAP UI5 parts are often developed using agile practices and use Continuous Integration pipelines that automatically build, test, and deploy the application.
**Note:** This scenario description is an example. You can apply the process to other scenarios and component sets, as well.
!!! note
This scenario description is an example. You can apply the process to other scenarios and component sets, as well.
In this scenario, we want to show how an agile development process with Jenkins CI can automatically feed changes into SAP Solution Manager. In SAP Solution Manager, all parts of the application stack come together and can be subject to classic change and transport management.

View File

@ -1,6 +1,6 @@
# Build and Deploy SAP UI5 or SAP Fiori Applications on SAP Cloud Platform with Jenkins
# Build and Deploy SAPUI5 or SAP Fiori Applications on SAP Cloud Platform with Jenkins
Build an application based on SAP UI5 or SAP Fiori with Jenkins and deploy the build result into an SAP Cloud Platform account in the Neo environment.
Build an application based on SAPUI5 or SAP Fiori with Jenkins and deploy the build result into an SAP Cloud Platform account in the Neo environment.
## Prerequisites
@ -11,12 +11,10 @@ Build an application based on SAP UI5 or SAP Fiori with Jenkins and deploy the b
* 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,13 +24,11 @@ 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 SAP UI5 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.
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)
###### Screenshot: Build and Deploy Process in Jenkins
@ -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:

View File

@ -0,0 +1,20 @@
# ${docGenStepName}
## ${docGenDescription}
## Prerequsites
You need to store the API token for the Detect service as _'Secret text'_ credential in your Jenkins system.
!!! note "minimum plugin requirement"
This step requires [synopsys-detect-plugin](https://github.com/jenkinsci/synopsys-detect-plugin) with at least version `2.0.0`.
## Example
```groovy
detectExecuteScan script: this, scanProperties: ['--logging.level.com.synopsys.integration=TRACE']
```
## ${docGenParameters}
## ${docGenConfiguration}

View File

@ -1,22 +1,6 @@
# influxWriteData
# ${docGenStepName}
## Description
Since your Continuous Delivery Pipeline in Jenkins provides your productive development and delivery infrastructure you should monitor the pipeline to ensure it runs as expected. How to setup this monitoring is described in the following.
You basically need three components:
- The [InfluxDB Jenkins plugin](https://wiki.jenkins-ci.org/display/JENKINS/InfluxDB+Plugin) which allows you to send build metrics to InfluxDB servers
- The [InfluxDB](https://www.influxdata.com/time-series-platform/influxdb/) to store this data (Docker available)
- A [Grafana](http://grafana.org/) dashboard to visualize the data stored in InfluxDB (Docker available)
!!! note "no InfluxDB available?"
If you don't have an InfluxDB available yet this step will still provide you some benefit.
It will create following files for you and archive them into your build:
* `jenkins_data.json`: This file gives you build-specific information, like e.g. build result, stage where the build failed
* `influx_data.json`: This file gives you detailed information about your pipeline, e.g. stage durations, steps executed, ...
## ${docGenDescription}
## Prerequisites
@ -31,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:
@ -79,37 +63,9 @@ You need to define the influxDB server in your pipeline as it is defined in the
influxDBServer=jenkins
```
## Parameters
## ${docGenParameters}
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
|script|yes|||
|artifactVersion|no|`commonPipelineEnvironment.getArtifactVersion()`||
|customData|no|`InfluxData.getInstance().getFields().jenkins_custom_data`||
|customDataMap|no|`InfluxData.getInstance().getFields()`||
|customDataMapTags|no|`InfluxData.getInstance().getTags()`||
|customDataTags|no|`InfluxData.getInstance().getTags().jenkins_custom_data`||
|influxPrefix|no|||
|influxServer|no|`''`||
|wrapInNode|no|`false`||
## Step configuration
We recommend to define values of step parameters via [config.yml file](../configuration.md).
In following sections the configuration is possible:
| parameter | general | step | stage |
| ----------|-----------|---------|-----------------|
|script||||
|artifactVersion||X|X|
|customData||X|X|
|customDataMap||X|X|
|customDataMapTags||X|X|
|customDataTags||X|X|
|influxPrefix||X|X|
|influxServer||X|X|
|wrapInNode||X|X|
## ${docGenConfiguration}
## Example

View File

@ -0,0 +1,27 @@
# ${docGenStepName}
## ${docGenDescription}
## Prerequsites
When pushing to a container registry, you need to maintain the respective credentials in your Jenkins credentials store:
Kaniko expects a Docker `config.json` file containing the credential information for registries.
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_ ->
* Kind: _Secret file_
* File: upload your `config.json` file
* ID: specify id which you then use for the configuration of `dockerConfigJsonCredentialsId` (see below)
## Example
```groovy
kanikoExecute script:this
```
## ${docGenParameters}
## ${docGenConfiguration}

View File

@ -0,0 +1,18 @@
# ${docGenStepName}
## ${docGenDescription}
## ${docGenParameters}
## ${docGenConfiguration}
## Examples
```groovy
multicloudDeploy(
script: script,
cfTargets: [[apiEndpoint: 'https://test.server.com', appName:'cfAppName', credentialsId: 'cfCredentialsId', manifest: 'cfManifest', org: 'cfOrg', space: 'cfSpace']],
neoTargets: [[credentialsId: 'my-credentials-id', host: hana.example.org, account: 'trialuser1']],
enableZeroDowntimeDeployment: 'true'
)
```

View File

@ -1,12 +1,6 @@
# neoDeploy
# ${docGenStepName}
## Description
Deploys an Application to SAP Cloud Platform (SAP CP) using the SAP Cloud Platform Console Client (Neo Java Web SDK).
Before doing this, validates that SAP Cloud Platform Console Client is installed and the version is compatible.
Note that a version is formed by `major.minor.patch`, and a version is compatible to another version if the minor and patch versions are higher, but the major version is not, e.g. if 3.39.10 is the expected version, 3.39.11 and 3.40.1 would be compatible versions, but 4.0.1 would not be a compatible version.
## ${docGenDescription}
## Prerequisites
@ -20,97 +14,9 @@ Note that a version is formed by `major.minor.patch`, and a version is compatibl
* **Java 8 or compatible version** - needed by the *Neo-Java-Web-SDK*. Java environment needs to be properly configured (JAVA_HOME, java exectutable contained in path).
## Parameters when using MTA deployment method (default - MTA)
## ${docGenParameters}
| parameter | mandatory | default | possible values |
| -------------------|-----------|-------------------------------|-------------------------------------------------|
| `script` | yes | | |
| `neo` | no | | |
| `deployMode` | yes | `'mta'` | `'mta'`, `'warParams'`, `'warPropertiesFile'` |
| `neoHome` | no | | |
| `source` | no | | |
The parameter `neo` is a map which contains the following parameters:
| parameter | mandatory | default | possible values |
| -------------------|-----------|-------------------------------|-------------------------------------------------|
| `account` | no | | |
| `credentialsId` | no | `'CI_CREDENTIALS_ID'` | |
| `host` | no | | |
## Parameters when using WAR file deployment method with .properties file (WAR_PROPERTIESFILE)
| parameter | mandatory | default | possible values |
| -------------------|-----------|-------------------------------|-------------------------------------------------|
| `script` | yes | | |
| `neo` | no | | |
| `deployMode` | yes | `'mta'` | `'mta'`, `'warParams'`, `'warPropertiesFile'` |
| `neoHome` | no | | |
| `source` | no | | |
| `warAction` | yes | `'deploy'` | `'deploy'`, `'rolling-update'` |
The parameter `neo` is a map which contains the following parameters:
| parameter | mandatory | default | possible values |
| -------------------|-----------|-------------------------------|-------------------------------------------------|
| `credentialsId` | no | `'CI_CREDENTIALS_ID'` | |
| `propertiesFile` | yes | | |
## Parameters when using WAR file deployment method without .properties file - with parameters (WAR_PARAMS)
| parameter | mandatory | default | possible values |
| -------------------|-----------|-------------------------------|-------------------------------------------------|
| `script` | yes | | |
| `neo` | no | | |
| `deployMode` | yes | `'mta'` | `'mta'`, `'warParams'`, `'warPropertiesFile'` |
| `neoHome` | no | | |
| `source` | no | | |
| `warAction` | yes | `'deploy'` | `'deploy'`, `'rolling-update'` |
The parameter `neo` is a map which contains the following parameters:
| parameter | mandatory | default | possible values |
| -------------------|-----------|-------------------------------|-------------------------------------------------|
| `account` | yes | | |
| `application` | yes | | |
| `credentialsId` | no | `'CI_CREDENTIALS_ID'` | |
| `environment` | | | |
| `host` | yes | | |
| `runtime` | yes | | |
| `runtimeVersion` | yes | | |
| `size` | no | `'lite'` | `'lite'`, `'pro'`, `'prem'`, `'prem-plus'` |
| `vmArguments` | | | |
* `script` - The common script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for retrieving e.g. configuration parameters.
* `deployMode` - The deployment mode which should be used. Available options are `'mta'` (default), `'warParams'` (deploying WAR file and passing all the deployment parameters via the function call) and `'warPropertiesFile'` (deploying WAR file and putting all the deployment parameters in a .properties file)
* `neoHome` - The path to the `neo-java-web-sdk` tool used for SAP CP deployment. If no parameter is provided, the path is retrieved from the environment variables using the environment variable `NEO_HOME`. If no parameter and no environment variable is provided, the path is retrieved from the step configuration using the step configuration key `neoHome`. If the previous configurations are not provided, the tool is expected on the `PATH`, and if it is not available on the `PATH` an AbortException is thrown.
* `source`- The path to the archive for deployment to SAP CP. If not provided `mtarFilePath` from commom pipeline environment is used instead.
* `warAction` - Action mode when using WAR file mode. Available options are `deploy` (default) and `rolling-update` which performs update of an application without downtime in one go.
The parameters for `neo`:
* `account` - The SAP Cloud Platform account to deploy to.
* `application` - Name of the application you want to manage, configure, or deploy
* `credentialsId` - The Jenkins credentials containing user and password used for SAP CP deployment.
* `environment` - Map of environment variables in the form of KEY: VALUE
* `host` - The SAP Cloud Platform host to deploy to.
* `propertiesFile` - The path to the .properties file in which all necessary deployment properties for the application are defined.
* `runtime` - Name of SAP Cloud Platform application runtime
* `runtimeVersion` - Version of SAP Cloud Platform application runtime
* `size` - Compute unit (VM) size. Acceptable values: lite, pro, prem, prem-plus.
* `vmArguments` - String of VM arguments passed to the JVM
The step is prepared for being executed in docker. The corresponding parameters can be applied. See step `dockerExecute` for details.
## Step configuration
The parameter `neo` including all options can also be specified as a global parameter using the global configuration file.
The following parameters can also be specified as step parameters using the global configuration file:
* `dockerImage`
* `neoHome`
* `source`
## ${docGenConfiguration}
## Side effects
@ -119,15 +25,15 @@ none
## Exceptions
* `Exception`:
* If `source` is not provided.
* If `propertiesFile` is not provided (when using `'WAR_PROPERTIESFILE'` deployment mode).
* If `application` is not provided (when using `'WAR_PARAMS'` deployment mode).
* If `runtime` is not provided (when using `'WAR_PARAMS'` deployment mode).
* If `runtimeVersion` is not provided (when using `'WAR_PARAMS'` deployment mode).
* If `source` is not provided.
* If `propertiesFile` is not provided (when using `'WAR_PROPERTIESFILE'` deployment mode).
* If `application` is not provided (when using `'WAR_PARAMS'` deployment mode).
* If `runtime` is not provided (when using `'WAR_PARAMS'` deployment mode).
* If `runtimeVersion` is not provided (when using `'WAR_PARAMS'` deployment mode).
* `AbortException`:
* If neo-java-web-sdk is not installed, or `neoHome`is wrong.
* If neo-java-web-sdk is not installed, or `neoHome`is wrong.
* `CredentialNotFoundException`:
* If the credentials cannot be resolved.
* If the credentials cannot be resolved.
## Example

View File

@ -1,7 +1,6 @@
# ${docGenStepName}
## ${docGenDescription}
## ${docGenParameters}

View File

@ -1,22 +1,12 @@
# pipelineStashFiles
# ${docGenStepName}
## Description
This step stashes files that are needed in other build steps (on other nodes).
## ${docGenDescription}
## Prerequsites
none
## Parameters
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
| script | yes | | |
| runCheckmarx | no | false | |
| runOpaTests | no | false | |
| stashIncludes | no | see details | |
| stashExcludes | no | see details | |
## ${docGenParameters}
Details:
@ -42,14 +32,7 @@ The step is stashing files before and after the build. This is due to the fact,
* `stashIncludes: [buildDescriptor: '**/mybuild.yml]`
* `stashExcludes: [tests: '**/NOTRELEVANT.*]`
## Step configuration
The following parameters can also be specified as step parameters using the global configuration file:
* runOpaTests
* runCheckmarx
* stashExcludes
* stashIncludes
## ${docGenConfiguration}
## Explanation of pipeline step

View File

@ -0,0 +1,11 @@
# ${docGenStepName}
## ${docGenDescription}
## Prerequsites
none
## ${docGenParameters}
## ${docGenConfiguration}

View File

@ -0,0 +1,11 @@
# ${docGenStepName}
## ${docGenDescription}
## Prerequsites
none
## ${docGenParameters}
## ${docGenConfiguration}

View File

@ -0,0 +1,7 @@
# ${docGenStepName}
## ${docGenDescription}
## ${docGenParameters}
## ${docGenConfiguration}

View File

@ -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

View File

@ -4,7 +4,9 @@
## Prerequisites
* Installed and configured [Jenkins Slack plugin](https://github.com/jenkinsci/slack-plugin).
* 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).
## ${docGenParameters}

View File

@ -0,0 +1,18 @@
# ${docGenStepName}
## ${docGenDescription}
## Prerequsites
- The project needs a `sonar-project.properties` file that describes the project and defines certain settings, see [here](https://docs.sonarqube.org/display/SCAN/Advanced+SonarQube+Scanner+Usages#AdvancedSonarQubeScannerUsages-Multi-moduleProjectStructure).
- A SonarQube instance needs to be defined in the Jenkins.
## ${docGenParameters}
## ${docGenConfiguration}
## Exceptions
none
## Examples

View File

@ -8,11 +8,8 @@
## ${docGenParameters}
## ${docGenConfiguration}
The step is configured using a customer configuration file provided as
resource in an custom shared library.

View File

@ -10,11 +10,12 @@ nav:
- cloudFoundryDeploy: steps/cloudFoundryDeploy.md
- commonPipelineEnvironment: steps/commonPipelineEnvironment.md
- containerExecuteStructureTests: steps/containerExecuteStructureTests.md
- detectExecuteScan: steps/detectExecuteScan.md
- dockerExecute: steps/dockerExecute.md
- dockerExecuteOnKubernetes: steps/dockerExecuteOnKubernetes.md
- durationMeasure: steps/durationMeasure.md
- githubPublishRelease: steps/githubPublishRelease.md
- gaugeExecuteTests: steps/gaugeExecuteTests.md
- githubPublishRelease: steps/githubPublishRelease.md
- handlePipelineStepErrors: steps/handlePipelineStepErrors.md
- healthExecuteCheck: steps/healthExecuteCheck.md
- influxWriteData: steps/influxWriteData.md
@ -22,17 +23,21 @@ nav:
- mailSendNotification: steps/mailSendNotification.md
- mavenExecute: steps/mavenExecute.md
- mtaBuild: steps/mtaBuild.md
- multicloudDeploy: steps/multicloudDeploy.md
- neoDeploy: steps/neoDeploy.md
- newmanExecute: steps/newmanExecute.md
- npmExecute: steps/npmExecute.md
- pipelineExecute: steps/pipelineExecute.md
- pipelineRestartSteps: steps/pipelineRestartSteps.md
- pipelineStashFiles: steps/pipelineStashFiles.md
- pipelineStashFilesAfterBuild: steps/pipelineStashFilesAfterBuild.md
- pipelineStashFilesBeforeBuild: steps/pipelineStashFilesBeforeBuild.md
- prepareDefaultValues: steps/prepareDefaultValues.md
- seleniumExecuteTests: steps/seleniumExecuteTests.md
- setupCommonPipelineEnvironment: steps/setupCommonPipelineEnvironment.md
- slackSendNotification: steps/slackSendNotification.md
- snykExecute: steps/snykExecute.md
- sonarExecuteScan: steps/sonarExecuteScan.md
- testsPublishResults: steps/testsPublishResults.md
- transportRequestCreate: steps/transportRequestCreate.md
- transportRequestRelease: steps/transportRequestRelease.md

View File

@ -17,10 +17,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>

View File

@ -1,4 +1,8 @@
stages:
Init:
stepConditions:
slackSendNotification:
config: 'channel'
'Pull-Request Voting': {}
Build: {}
'Additional Unit Tests': {}
@ -18,3 +22,7 @@ stages:
Compliance: {}
Promote: {}
Release: {}
'Post Actions':
stepConditions:
slackSendNotification:
config: 'channel'

View File

@ -177,6 +177,27 @@ steps:
stashContent:
- 'tests'
testReportFilePath: 'cst-report.json'
detectExecuteScan:
detect:
projectVersion: '1'
scanners:
- signature
scanPaths:
- '.'
scanProperties:
- '--blackduck.signature.scanner.memory=4096'
- '--blackduck.timeout=6000'
- '--blackduck.trust.cert=true'
- '--detect.policy.check.fail.on.severities=BLOCKER,CRITICAL,MAJOR'
- '--detect.report.timeout=4800'
- '--logging.level.com.synopsys.integration=DEBUG'
stashContent:
- 'buildDescriptor'
- 'checkmarx'
# buildTool specific settings
golang:
dockerImage: 'golang:1.12-stretch'
dockerWorkspace: ''
dockerExecute:
dockerPullImage: true
sidecarPullImage: true
@ -221,6 +242,13 @@ steps:
languageRunner: 'js'
runCommand: 'gauge run'
testOptions: 'specs'
bundler:
dockerImage: 'ruby:2.5.3-stretch'
dockerName: 'bundler'
dockerWorkspace: ''
languageRunner: 'ruby'
runCommand: 'bundle install && bundle exec gauge run'
testOptions: 'specs'
handlePipelineStepErrors:
echoDetails: true
failOnError: true
@ -232,6 +260,15 @@ steps:
healthEndpoint: ''
influxWriteData:
influxServer: ''
kanikoExecute:
containerBuildOptions: '--skip-tls-verify-pull'
containerCommand: '/busybox/tail -f /dev/null'
containerPreparationCommand: 'rm /kaniko/.docker/config.json'
containerShell: '/busybox/sh'
customTlsCertificateLinks: []
dockerfile: Dockerfile
dockerImage: 'gcr.io/kaniko-project/executor:debug'
dockerOptions: "-u 0 --entrypoint=''"
karmaExecuteTests:
containerPortMappings:
'node:8-stretch':
@ -334,6 +371,7 @@ steps:
stashContent:
- 'buildDescriptor'
- 'opensourceConfiguration'
- 'checkmarx'
additionalInstallCommand: >-
curl --fail https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
&& mkdir -p \$GOPATH/src/${config.whitesource.projectName.substring(0, config.whitesource.projectName.lastIndexOf('/'))}
@ -407,6 +445,10 @@ steps:
dockerImage: 'node:8-stretch'
dockerName: 'npm'
dockerWorkspace: '/home/node'
bundler:
dockerImage: 'ruby:2.5.3-stretch'
dockerName: 'bundler'
dockerWorkspace: ''
slackSendNotification:
color: "${buildStatus == 'SUCCESS'?'#008000':'#E60000'}"
defaultMessage: "${buildStatus}: Job ${env.JOB_NAME} <${env.BUILD_URL}|#${env.BUILD_NUMBER}>"
@ -421,6 +463,12 @@ steps:
- 'opensourceConfiguration'
toJson: false
toHtml: false
sonarExecuteScan:
dockerImage: 'maven:3.5-jdk-8'
instance: 'SonarCloud'
options: []
pullRequestProvider: 'github'
sonarScannerDownloadUrl: 'https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.3.0.1492-linux.zip'
testsPublishResults:
failOnError: false
junit:

View File

@ -0,0 +1,5 @@
package com.sap.piper
enum CloudPlatform {
NEO, CLOUD_FOUNDRY
}

View File

@ -127,9 +127,11 @@ class ConfigurationHelper implements Serializable {
handleValidationFailures()
MapUtils.traverse(config, { v -> (v instanceof GString) ? v.toString() : v })
if(config.verbose) step.echo "[${name}] Configuration: ${config}"
return config
return MapUtils.deepCopy(config)
}
/* private */ def getConfigPropertyNested(key) {
return getConfigPropertyNested(config, key)
}
@ -184,7 +186,7 @@ class ConfigurationHelper implements Serializable {
ConfigurationHelper withPropertyInValues(String key, Set values){
withMandatoryProperty(key)
def value = config[key]
def value = config[key] instanceof GString ? config[key].toString() : config[key]
if(! (value in values) ) {
throw new IllegalArgumentException("Invalid ${key} = '${value}'. Valid '${key}' values are: ${values}.")
}

View File

@ -0,0 +1,34 @@
package com.sap.piper
enum DeploymentType {
NEO_ROLLING_UPDATE('rolling-update'), CF_BLUE_GREEN('blue-green'), CF_STANDARD('standard'), NEO_DEPLOY('deploy')
private String value
public DeploymentType(String value){
this.value = value
}
@Override
public String toString(){
return value
}
static DeploymentType selectFor(CloudPlatform cloudPlatform, boolean enableZeroDowntimeDeployment) {
switch (cloudPlatform) {
case CloudPlatform.NEO:
if (enableZeroDowntimeDeployment) return NEO_ROLLING_UPDATE
return NEO_DEPLOY
case CloudPlatform.CLOUD_FOUNDRY:
if (enableZeroDowntimeDeployment) return CF_BLUE_GREEN
return CF_STANDARD
default:
throw new RuntimeException("Unknown cloud platform: ${cloudPlatform}")
}
}
}

View File

@ -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}/ }
}

View File

@ -62,4 +62,56 @@ class MapUtils implements Serializable {
}
m.putAll(updates)
}
static private def getByPath(Map m, def key) {
List path = key in CharSequence ? key.tokenize('/') : key
def value = m.get(path.head())
if (path.size() == 1) return value
if (value in Map) return getByPath(value, path.tail())
return null
}
/*
* Provides a new map with the same content like the original map.
* Nested Collections and Maps are copied. Values with are not
* Collections/Maps are not copied/cloned.
* &lt;paranoia&gt;&/ltThe keys are also not copied/cloned, even if they are
* Maps or Collections;paranoia&gt;
*/
static deepCopy(Map original) {
Map copy = [:]
for (def e : original.entrySet()) {
if(e.value == null) {
copy.put(e.key, e.value)
} else {
copy.put(e.key, deepCopy(e.value))
}
}
copy
}
/* private */ static deepCopy(Set original) {
Set copy = []
for(def e : original)
copy << deepCopy(e)
copy
}
/* private */ static deepCopy(List original) {
List copy = []
for(def e : original)
copy << deepCopy(e)
copy
}
/*
* In fact not a copy, but a catch all for everything not matching
* with the other signatures
*/
/* private */ static deepCopy(def original) {
original
}
}

View File

@ -12,7 +12,7 @@ def stash(name, include = '**/*.*', exclude = '', useDefaultExcludes = true) {
echo "Stash content: ${name} (include: ${include}, exclude: ${exclude}, useDefaultExcludes: ${useDefaultExcludes})"
Map stashParams = [
name: name,
name : name,
includes: include,
excludes: exclude
]
@ -23,6 +23,16 @@ def stash(name, include = '**/*.*', exclude = '', useDefaultExcludes = true) {
steps.stash stashParams
}
@NonCPS
def runClosures(Map closures) {
def closuresToRun = closures.values().asList()
Collections.shuffle(closuresToRun) // Shuffle the list so no one tries to rely on the order of execution
for (int i = 0; i < closuresToRun.size(); i++) {
(closuresToRun[i] as Closure).run()
}
}
def stashList(script, List stashes) {
for (def stash : stashes) {
def name = stash.name
@ -68,7 +78,7 @@ def unstashAll(stashContent) {
def unstashedContent = []
if (stashContent) {
for (i = 0; i < stashContent.size(); i++) {
if(stashContent[i]) {
if (stashContent[i]) {
unstashedContent += unstash(stashContent[i])
}
}
@ -88,7 +98,7 @@ void pushToSWA(Map parameters, Map config) {
try {
parameters.actionName = parameters.get('actionName') ?: 'Piper Library OS'
parameters.eventType = parameters.get('eventType') ?: 'library-os'
parameters.jobUrlSha1 = generateSha1(env.JOB_URL)
parameters.jobUrlSha1 = generateSha1(env.JOB_URL)
parameters.buildUrlSha1 = generateSha1(env.BUILD_URL)
Telemetry.notify(this, config, parameters)
@ -98,8 +108,18 @@ void pushToSWA(Map parameters, Map config) {
}
@NonCPS
static String fillTemplate(String templateText, Map binding){
static String fillTemplate(String templateText, Map binding) {
def engine = new SimpleTemplateEngine()
String result = engine.createTemplate(templateText).make(binding)
return result
}
static String downloadSettingsFromUrl(script, String url, String targetFile = 'settings.xml') {
if (script.fileExists(targetFile)) {
throw new RuntimeException("Trying to download settings file to ${targetFile}, but a file with this name already exists. Please specify a unique file name.")
}
def settings = script.httpRequest(url)
script.writeFile(file: targetFile, text: settings.getContent())
return targetFile
}

View File

@ -9,7 +9,7 @@ class WhitesourceConfigurationHelper implements Serializable {
def parsingClosure = { fileReadPath -> return script.readProperties (file: fileReadPath) }
def serializationClosure = { configuration -> serializeUAConfig(configuration) }
def inputFile = config.whitesource.configFilePath.replaceFirst('\\./', '')
def suffix = utils.generateSha1(config.whitesource.configFilePath)
def suffix = utils.generateSha1("${path}${inputFile}")
def targetFile = "${inputFile}.${suffix}"
if(config.whitesource.productName.startsWith('DIST - ')) {
mapping += [

View File

@ -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.")

View File

@ -1,122 +0,0 @@
package com.sap.piper.jenkins
import com.cloudbees.groovy.cps.NonCPS
class JenkinsController implements Serializable {
def script
String jenkinsUrl
def timeout
JenkinsController(script, String jenkinsUrl = "http://localhost:8080", timeout = 3600) {
this.script = script
this.jenkinsUrl = jenkinsUrl
this.timeout = timeout
}
def waitForJenkinsStarted() {
def timeout = 120
def timePerLoop = 5
for (int i = 0; i < timeout; i += timePerLoop) {
script.sleep timePerLoop
try {
if (retrieveJenkinsStatus() == 'NORMAL') {
return true
}
} catch (Exception e) {
script.echo "Could not retrieve status for Jenkins at ${jenkinsUrl}/api/json. Message: ${e.getMessage()}. Retrying..."
e.printStackTrace()
continue
}
return false
}
script.error("Timeout: Jenkins did not start within the expected time frame.")
}
private retrieveJenkinsStatus() {
def apiUrl = "${jenkinsUrl}/api/json"
script.echo "Checking Jenkins Status"
def response = getTextFromUrl(apiUrl)
def result = script.readJSON text: response
return result.mode
}
//Trigger scanning of the multi branch builds
def buildJob(String jobName) {
script.sh "curl -s -X POST ${jenkinsUrl}/job/${URLEncoder.encode(jobName, 'UTF-8')}/build"
}
def waitForSuccess(String jobName, String branch) {
if (this.waitForJobStatus(jobName, branch, 'SUCCESS')) {
this.printConsoleText(jobName, branch)
script.echo "Build was successful"
} else {
this.printConsoleText(jobName, branch)
script.error("Build of ${jobName} ${branch} was not successfull")
}
}
def getBuildUrl(String jobName, String branch) {
return "${jenkinsUrl}/job/${URLEncoder.encode(jobName, 'UTF-8')}/job/${URLEncoder.encode(branch, 'UTF-8')}/lastBuild/"
}
def waitForJobStatus(String jobName, String branch, String status) {
def buildUrl = getBuildUrl(jobName, branch)
def timePerLoop = 10
for (int i = 0; i < timeout; i += timePerLoop) {
script.sleep timePerLoop
try {
script.echo "Checking Build Status of ${jobName} ${branch}"
def buildInformation = retrieveBuildInformation(jobName, branch)
if (buildInformation.building) {
script.echo "Build is still in progress"
continue
}
if (buildInformation.result == status) {
return true
}
} catch (Exception e) {
script.echo "Could not retrieve status for ${buildUrl}. Message: ${e.getMessage()}. Retrying..."
continue
}
return false
}
script.error("Timeout: Build of job ${jobName}, branch ${branch} did not finish in the expected time frame.")
}
def getConsoleText(String jobName, String branch) {
def consoleUrl = this.getBuildUrl(jobName, branch) + "/consoleText"
return getTextFromUrl(consoleUrl)
}
def printConsoleText(String jobName, String branch) {
String consoleOutput = getConsoleText(jobName, branch)
script.echo '***********************************************'
script.echo '** Begin Output of Example Application Build **'
script.echo '***********************************************'
script.echo consoleOutput
script.echo '*********************************************'
script.echo '** End Output of Example Application Build **'
script.echo '*********************************************'
}
def retrieveBuildInformation(String jobName, String branch) {
def buildUrl = getBuildUrl(jobName, branch)
def url = "${buildUrl}/api/json"
script.echo "Checking Build Status of ${jobName} ${branch}"
script.echo "${jenkinsUrl}/job/${URLEncoder.encode(jobName, 'UTF-8')}/job/${URLEncoder.encode(branch, 'UTF-8')}/"
def response = getTextFromUrl(url)
def result = script.readJSON text: response
return result
}
@NonCPS
private static String getTextFromUrl(url) {
return new URL(url).getText()
}
}

View File

@ -46,13 +46,14 @@ public class CommonStepsTest extends BasePiperTest{
// all steps not adopting the usual pattern of working with the script.
def whitelistScriptReference = [
'commonPipelineEnvironment',
'handlePipelineStepErrors',
'pipelineExecute',
'piperPipeline',
'prepareDefaultValues',
'setupCommonPipelineEnvironment'
]
'commonPipelineEnvironment',
'handlePipelineStepErrors',
'pipelineExecute',
'piperPipeline',
'prepareDefaultValues',
'setupCommonPipelineEnvironment',
'buildSetResult'
]
List steps = getSteps().stream()
.filter {! whitelistScriptReference.contains(it)}
@ -102,17 +103,18 @@ public class CommonStepsTest extends BasePiperTest{
}
private static fieldRelatedWhitelist = [
'durationMeasure', // only expects parameters via signature
'prepareDefaultValues', // special step (infrastructure)
'piperPipeline', // special step (infrastructure)
'pipelineStashFilesAfterBuild', // intended to be called from pipelineStashFiles
'pipelineStashFilesBeforeBuild', // intended to be called from pipelineStashFiles
'pipelineStashFiles', // only forwards to before/after step
'pipelineExecute', // special step (infrastructure)
'commonPipelineEnvironment', // special step (infrastructure)
'handlePipelineStepErrors', // special step (infrastructure)
'piperStageWrapper' //intended to be called from within stages
]
'durationMeasure', // only expects parameters via signature
'prepareDefaultValues', // special step (infrastructure)
'piperPipeline', // special step (infrastructure)
'pipelineStashFilesAfterBuild', // intended to be called from pipelineStashFiles
'pipelineStashFilesBeforeBuild', // intended to be called from pipelineStashFiles
'pipelineStashFiles', // only forwards to before/after step
'pipelineExecute', // special step (infrastructure)
'commonPipelineEnvironment', // special step (infrastructure)
'handlePipelineStepErrors', // special step (infrastructure)
'piperStageWrapper', //intended to be called from within stages
'buildSetResult'
]
@Test
public void generalConfigKeysSetPresentTest() {
@ -170,7 +172,8 @@ public class CommonStepsTest extends BasePiperTest{
def whitelist = [
'commonPipelineEnvironment',
'piperPipeline'
'piperPipeline',
'buildSetResult'
]
def stepsWithWrongStepName = []

View File

@ -0,0 +1,143 @@
#!groovy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsCredentialsRule
import util.JenkinsDockerExecuteRule
import util.JenkinsReadYamlRule
import util.JenkinsShellCallRule
import util.JenkinsStepRule
import util.Rules
import static org.hamcrest.CoreMatchers.containsString
import static org.hamcrest.CoreMatchers.is
import static org.hamcrest.Matchers.allOf
import static org.hamcrest.Matchers.hasItem
import static org.hamcrest.Matchers.not
import static org.junit.Assert.assertThat
class DetectExecuteScanTest extends BasePiperTest {
private JenkinsDockerExecuteRule dockerRule = new JenkinsDockerExecuteRule(this)
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private String detectProperties = ''
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(shellRule)
.around(dockerRule)
.around(stepRule)
.around(new JenkinsCredentialsRule(this)
.withCredentials('testCredentials', 'testToken')
)
@Before
void init() {
detectProperties = ''
helper.registerAllowedMethod('synopsys_detect', [String.class], {s ->
detectProperties = s
})
}
@Test
void testDetectDefault() {
stepRule.step.detectExecuteScan([
apiTokenCredentialsId: 'testCredentials',
projectName: 'testProject',
serverUrl: 'https://test.blackducksoftware.com',
juStabUtils: utils,
script: nullScript
])
//ToDo: assert unstashing
assertThat(detectProperties, containsString("--detect.project.name='testProject'"))
assertThat(detectProperties, containsString("--detect.project.version.name='1'"))
assertThat(detectProperties, containsString("--blackduck.url=https://test.blackducksoftware.com"))
assertThat(detectProperties, containsString("--blackduck.api.token=testToken"))
assertThat(detectProperties, containsString("--detect.blackduck.signature.scanner.paths=."))
assertThat(detectProperties, containsString("--blackduck.signature.scanner.memory=4096"))
assertThat(detectProperties, containsString("--blackduck.timeout=6000"))
assertThat(detectProperties, containsString("--blackduck.trust.cert=true"))
assertThat(detectProperties, containsString("--detect.report.timeout=4800"))
}
@Test
void testDetectCustomPaths() {
stepRule.step.detectExecuteScan([
apiTokenCredentialsId: 'testCredentials',
projectName: 'testProject',
scanPaths: ['test1/', 'test2/'],
serverUrl: 'https://test.blackducksoftware.com',
juStabUtils: utils,
script: nullScript
])
assertThat(detectProperties, containsString("--detect.blackduck.signature.scanner.paths=test1/,test2/"))
}
@Test
void testDetectSourceScanOnly() {
stepRule.step.detectExecuteScan([
apiTokenCredentialsId: 'testCredentials',
projectName: 'testProject',
scanners: ['source'],
serverUrl: 'https://test.blackducksoftware.com',
juStabUtils: utils,
script: nullScript
])
assertThat(detectProperties, not(containsString("--detect.blackduck.signature.scanner.paths=.")))
assertThat(detectProperties, containsString("--detect.source.path=."))
}
@Test
void testDetectGolang() {
stepRule.step.detectExecuteScan([
buildTool: 'golang',
apiTokenCredentialsId: 'testCredentials',
projectName: 'testProject',
serverUrl: 'https://test.blackducksoftware.com',
juStabUtils: utils,
script: nullScript
])
assertThat(dockerRule.dockerParams.dockerImage, is('golang:1.12-stretch'))
assertThat(dockerRule.dockerParams.dockerWorkspace, is(''))
assertThat(dockerRule.dockerParams.stashContent, allOf(hasItem('buildDescriptor'),hasItem('checkmarx')))
assertThat(shellRule.shell, hasItem('curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh'))
assertThat(shellRule.shell, hasItem('ln --symbolic $(pwd) $GOPATH/src/hub'))
assertThat(shellRule.shell, hasItem('cd $GOPATH/src/hub && dep ensure'))
}
@Test
void testCustomScanProperties() {
def detectProps = [
'--blackduck.signature.scanner.memory=1024'
]
stepRule.step.detectExecuteScan([
//scanProperties: detectProps,
scanProperties: ['--blackduck.signature.scanner.memory=1024', '--myNewOne'],
apiTokenCredentialsId: 'testCredentials',
projectName: 'testProject',
serverUrl: 'https://test.blackducksoftware.com',
juStabUtils: utils,
script: nullScript
])
assertThat(detectProperties, containsString("--detect.project.name='testProject'"))
assertThat(detectProperties, containsString("--detect.project.version.name='1'"))
assertThat(detectProperties, containsString("--blackduck.signature.scanner.memory=1024"))
assertThat(detectProperties, not(containsString("--blackduck.signature.scanner.memory=4096")))
assertThat(detectProperties, not(containsString("--detect.report.timeout=4800")))
assertThat(detectProperties, containsString("--myNewOne"))
}
}

View File

@ -0,0 +1,143 @@
#!groovy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.*
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
class KanikoExecuteTest extends BasePiperTest {
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
private JenkinsReadFileRule readFileRule = new JenkinsReadFileRule(this, 'test/resources/kaniko/')
private JenkinsWriteFileRule writeFileRule = new JenkinsWriteFileRule(this)
private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(shellRule)
.around(readFileRule)
.around(writeFileRule)
.around(dockerExecuteRule)
.around(stepRule)
def fileMap = [:]
@Before
void init() {
binding.variables.env.WORKSPACE = '/path/to/current/workspace'
helper.registerAllowedMethod('file', [Map], { m ->
fileMap = m
return m
})
helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c ->
binding.setProperty(fileMap.variable, 'config.json')
try {
c()
} finally {
binding.setProperty(fileMap.variable, null)
}
})
UUID.metaClass.static.randomUUID = { -> 1}
}
@Test
void testDefaults() {
stepRule.step.kanikoExecute(
script: nullScript
)
assertThat(shellRule.shell, hasItem('#!/busybox/sh rm /kaniko/.docker/config.json'))
assertThat(shellRule.shell, hasItem(allOf(
startsWith('#!/busybox/sh'),
containsString('mv 1-config.json /kaniko/.docker/config.json'),
containsString('/kaniko/executor'),
containsString('--dockerfile /path/to/current/workspace/Dockerfile'),
containsString('--context /path/to/current/workspace'),
containsString('--skip-tls-verify-pull'),
containsString('--no-push')
)))
assertThat(writeFileRule.files.values()[0], is('{"auths":{}}'))
assertThat(dockerExecuteRule.dockerParams, allOf(
hasEntry('containerCommand', '/busybox/tail -f /dev/null'),
hasEntry('containerShell', '/busybox/sh'),
hasEntry('dockerImage', 'gcr.io/kaniko-project/executor:debug'),
hasEntry('dockerOptions', "-u 0 --entrypoint=''")
))
}
@Test
void testCustomDockerCredentials() {
stepRule.step.kanikoExecute(
script: nullScript,
dockerConfigJsonCredentialsId: 'myDockerConfigJson'
)
assertThat(fileMap.credentialsId, is('myDockerConfigJson'))
assertThat(writeFileRule.files.values()[0], allOf(
containsString('docker.my.domain.com:4444'),
containsString('"auth": "myAuth"'),
containsString('"email": "my.user@domain.com"')
))
}
@Test
void testCustomImage() {
stepRule.step.kanikoExecute(
script: nullScript,
containerImageNameAndTag: 'my.docker.registry/path/myImageName:myTag'
)
assertThat(shellRule.shell, hasItem(allOf(
startsWith('#!/busybox/sh'),
containsString('mv 1-config.json /kaniko/.docker/config.json'),
containsString('/kaniko/executor'),
containsString('--dockerfile /path/to/current/workspace/Dockerfile'),
containsString('--context /path/to/current/workspace'),
containsString('--skip-tls-verify-pull'),
containsString('--destination my.docker.registry/path/myImageName:myTag')
)))
}
@Test
void testPreserveDestination() {
stepRule.step.kanikoExecute(
script: nullScript,
containerBuildOptions: '--destination my.docker.registry/path/myImageName:myTag'
)
assertThat(shellRule.shell, hasItem(allOf(
startsWith('#!/busybox/sh'),
containsString('mv 1-config.json /kaniko/.docker/config.json'),
containsString('/kaniko/executor'),
containsString('--dockerfile /path/to/current/workspace/Dockerfile'),
containsString('--context /path/to/current/workspace'),
containsString('--destination my.docker.registry/path/myImageName:myTag')
)))
}
@Test
void testCustomCertificates() {
stepRule.step.kanikoExecute(
script: nullScript,
customTlsCertificateLinks: ['http://link.one', 'http://link.two']
)
assertThat(shellRule.shell, hasItem(allOf(
startsWith('#!/busybox/sh'),
containsString('rm /kaniko/.docker/config.json'),
containsString('wget http://link.one -O - >> /kaniko/ssl/certs/ca-certificates.crt'),
containsString('wget http://link.two -O - >> /kaniko/ssl/certs/ca-certificates.crt'),
)))
}
}

View File

@ -13,6 +13,7 @@ import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
import util.JenkinsShellCallRule
import util.JenkinsStepRule
import util.JenkinsWriteFileRule
import util.Rules
public class MtaBuildTest extends BasePiperTest {
@ -23,6 +24,7 @@ public class MtaBuildTest extends BasePiperTest {
private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this)
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsReadYamlRule readYamlRule = new JenkinsReadYamlRule(this).registerYaml('mta.yaml', defaultMtaYaml() )
private JenkinsWriteFileRule writeFileRule = new JenkinsWriteFileRule(this)
@Rule
public RuleChain ruleChain = Rules
@ -33,12 +35,15 @@ public class MtaBuildTest extends BasePiperTest {
.around(shellRule)
.around(dockerExecuteRule)
.around(stepRule)
.around(writeFileRule)
@Before
void init() {
helper.registerAllowedMethod('fileExists', [String], { s -> s == 'mta.yaml' })
helper.registerAllowedMethod('httpRequest', [String.class], { s -> new SettingsStub()})
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*\\$MTA_JAR_LOCATION.*', '')
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*\\$JAVA_HOME.*', '')
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*which java.*', 0)
@ -181,6 +186,46 @@ public class MtaBuildTest extends BasePiperTest {
assert 'something' == dockerExecuteRule.dockerParams.dockerOptions
}
@Test
void canConfigureMavenUserSettings() {
stepRule.step.mtaBuild(script: nullScript, projectSettingsFile: 'settings.xml')
assert shellRule.shell.find(){ c -> c.contains('cp settings.xml $HOME/.m2/settings.xml')}
}
@Test
void canConfigureMavenUserSettingsFromRemoteSource() {
stepRule.step.mtaBuild(script: nullScript, projectSettingsFile: 'https://some.host/my-settings.xml')
assert shellRule.shell.find(){ c -> c.contains('cp project-settings.xml $HOME/.m2/settings.xml')}
}
@Test
void canConfigureMavenGlobalSettings() {
stepRule.step.mtaBuild(script: nullScript, globalSettingsFile: 'settings.xml')
assert shellRule.shell.find(){ c -> c.contains('cp settings.xml $M2_HOME/conf/settings.xml')}
}
@Test
void canConfigureNpmRegistry() {
stepRule.step.mtaBuild(script: nullScript, defaultNpmRegistry: 'myNpmRegistry.com')
assert shellRule.shell.find(){ c -> c.contains('npm config set registry myNpmRegistry.com')}
}
@Test
void canConfigureMavenGlobalSettingsFromRemoteSource() {
stepRule.step.mtaBuild(script: nullScript, globalSettingsFile: 'https://some.host/my-settings.xml')
assert shellRule.shell.find(){ c -> c.contains('cp global-settings.xml $M2_HOME/conf/settings.xml')}
}
@Test
void buildTargetFromDefaultStepConfigurationTest() {
@ -274,4 +319,9 @@ public class MtaBuildTest extends BasePiperTest {
'''
}
class SettingsStub {
String getContent() {
return "<xml>sometext</xml>"
}
}
}

View File

@ -0,0 +1,256 @@
import com.sap.piper.JenkinsUtils
import com.sap.piper.Utils
import hudson.AbortException
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.*
class MulticloudDeployTest extends BasePiperTest {
private ExpectedException thrown = new ExpectedException().none()
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsMockStepRule neoDeployRule = new JenkinsMockStepRule(this, 'neoDeploy')
private JenkinsMockStepRule cloudFoundryDeployRule = new JenkinsMockStepRule(this, 'cloudFoundryDeploy')
private JenkinsReadMavenPomRule readMavenPomRule = new JenkinsReadMavenPomRule(this, 'test/resources/deploy')
private Map neo1 = [:]
private Map neo2 = [:]
private Map cloudFoundry1 = [:]
private Map cloudFoundry2 = [:]
@Rule
public RuleChain ruleChain = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(thrown)
.around(stepRule)
.around(neoDeployRule)
.around(cloudFoundryDeployRule)
.around(readMavenPomRule)
private Map neoDeployParameters = [:]
private Map cloudFoundryDeployParameters = [:]
@Before
void init() {
neo1 = [
host: 'test.deploy.host1.com',
account: 'trialuser1',
credentialsId: 'credentialsId1'
]
neo2 = [
host: 'test.deploy.host2.com',
account: 'trialuser2',
credentialsId: 'credentialsId2'
]
cloudFoundry1 = [
appName:'testAppName1',
manifest: 'test.yml',
org: 'testOrg1',
space: 'testSpace1',
credentialsId: 'cfCredentialsId1'
]
cloudFoundry2 = [
appName:'testAppName2',
manifest: 'test.yml',
org: 'testOrg2',
space: 'testSpace2',
credentialsId: 'cfCredentialsId2'
]
nullScript.commonPipelineEnvironment.configuration = [
general: [
neoTargets: [
neo1, neo2
],
cfTargets: [
cloudFoundry1, cloudFoundry2
]
],
stages: [
acceptance: [
org: 'testOrg',
space: 'testSpace',
deployUser: 'testUser'
]
],
steps: [
cloudFoundryDeploy: [
deployTool: 'cf_native',
deployType: 'blue-green',
keepOldInstance: true,
cf_native: [
dockerImage: 's4sdk/docker-cf-cli',
dockerWorkspace: '/home/piper'
]
]
]
]
}
@Test
void errorNoTargetsDefined() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = []
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
thrown.expect(Exception)
thrown.expectMessage('Deployment skipped because no targets defined!')
stepRule.step.multicloudDeploy(
script: nullScript,
stage: 'test'
)
}
@Test
void errorNoSourceForNeoDeploymentTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = [neo1]
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
thrown.expect(Exception)
thrown.expectMessage('ERROR - NO VALUE AVAILABLE FOR source')
stepRule.step.multicloudDeploy(
script: nullScript,
stage: 'test'
)
}
@Test
void neoDeploymentTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = [neo1]
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
stepRule.step.multicloudDeploy(
script: nullScript,
stage: 'test',
source: 'file.mtar'
)
assert neoDeployRule.hasParameter('script', nullScript)
assert neoDeployRule.hasParameter('warAction', 'deploy')
assert neoDeployRule.hasParameter('source', 'file.mtar')
assert neoDeployRule.hasParameter('neo', neo1)
}
@Test
void neoRollingUpdateTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = []
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
def neoParam = [
host: 'test.param.deploy.host.com',
account: 'trialparamNeoUser',
credentialsId: 'paramNeoCredentialsId'
]
stepRule.step.multicloudDeploy(
script: nullScript,
stage: 'test',
neoTargets: [neoParam],
source: 'file.mtar',
enableZeroDowntimeDeployment: true
)
assert neoDeployRule.hasParameter('script', nullScript)
assert neoDeployRule.hasParameter('warAction', 'rolling-update')
assert neoDeployRule.hasParameter('source', 'file.mtar')
assert neoDeployRule.hasParameter('neo', neoParam)
}
@Test
void cfDeploymentTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = []
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
def cloudFoundry = [
appName:'paramTestAppName',
manifest: 'test.yml',
org: 'paramTestOrg',
space: 'paramTestSpace',
credentialsId: 'paramCfCredentialsId'
]
stepRule.step.multicloudDeploy([
script: nullScript,
stage: 'acceptance',
cfTargets: [cloudFoundry]
])
assert cloudFoundryDeployRule.hasParameter('script', nullScript)
assert cloudFoundryDeployRule.hasParameter('deployType', 'standard')
assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry)
assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath)
assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native')
}
@Test
void cfBlueGreenDeploymentTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = []
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = [cloudFoundry1]
stepRule.step.multicloudDeploy([
script: nullScript,
stage: 'acceptance',
enableZeroDowntimeDeployment: true
])
assert cloudFoundryDeployRule.hasParameter('script', nullScript)
assert cloudFoundryDeployRule.hasParameter('deployType', 'blue-green')
assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry1)
assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath)
assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native')
}
@Test
void multicloudDeploymentTest() {
stepRule.step.multicloudDeploy([
script: nullScript,
stage: 'acceptance',
enableZeroDowntimeDeployment: true,
source: 'file.mtar'
])
assert neoDeployRule.hasParameter('script', nullScript)
assert neoDeployRule.hasParameter('warAction', 'rolling-update')
assert neoDeployRule.hasParameter('source', 'file.mtar')
assert neoDeployRule.hasParameter('neo', neo1)
assert neoDeployRule.hasParameter('script', nullScript)
assert neoDeployRule.hasParameter('warAction', 'rolling-update')
assert neoDeployRule.hasParameter('source', 'file.mtar')
assert neoDeployRule.hasParameter('neo', neo2)
assert cloudFoundryDeployRule.hasParameter('script', nullScript)
assert cloudFoundryDeployRule.hasParameter('deployType', 'blue-green')
assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry1)
assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath)
assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native')
assert cloudFoundryDeployRule.hasParameter('script', nullScript)
assert cloudFoundryDeployRule.hasParameter('deployType', 'blue-green')
assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry2)
assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath)
assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native')
}
}

View File

@ -386,11 +386,7 @@ class NeoDeployTest extends BasePiperTest {
deployMode: 'warPropertiesFile',
warAction: 'deploy',
neo: [
propertiesFile: warPropertiesFileName,
application: 'testApp',
runtime: 'neo-javaee6-wp',
runtimeVersion: '2.125',
size: 'lite'
propertiesFile: warPropertiesFileName
]
)
@ -412,11 +408,7 @@ class NeoDeployTest extends BasePiperTest {
deployMode: 'warPropertiesFile',
warAction: 'rolling-update',
neo: [
propertiesFile: warPropertiesFileName,
application: 'testApp',
runtime: 'neo-javaee6-wp',
runtimeVersion: '2.125',
size: 'lite'
propertiesFile: warPropertiesFileName
])
Assert.assertThat(shellRule.shell,
@ -515,4 +507,17 @@ class NeoDeployTest extends BasePiperTest {
utils: utils,
)
}
@Test
void deployModeAsGStringTest() {
Map deployProps = [deployMode: 'warPropertiesFile']
stepRule.step.neoDeploy(script: nullScript,
utils: utils,
neo: [credentialsId: 'myCredentialsId',
propertiesFile: warPropertiesFileName],
deployMode: "$deployProps.deployMode",
source: archiveName)
}
}

View File

@ -0,0 +1,122 @@
#!groovy
package stages
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
import util.JenkinsStepRule
import util.Rules
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
class PiperPipelineStageInitTest extends BasePiperTest {
private JenkinsStepRule jsr = new JenkinsStepRule(this)
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private ExpectedException thrown = ExpectedException.none()
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(thrown)
.around(jlr)
.around(jsr)
private List stepsCalled = []
@Before
void init() {
binding.variables.env.STAGE_NAME = 'Init'
binding.setVariable('scm', {})
helper.registerAllowedMethod('deleteDir', [], null)
helper.registerAllowedMethod("findFiles", [Map.class], { map ->
switch (map.glob) {
case 'pom.xml':
return [new File('pom.xml')].toArray()
default:
return [].toArray()
}
})
helper.registerAllowedMethod('piperStageWrapper', [Map.class, Closure.class], {m, body ->
assertThat(m.stageName, is('Init'))
return body()
})
helper.registerAllowedMethod('checkout', [Closure.class], {c ->
stepsCalled.add('checkout')
return [
GIT_COMMIT: 'abcdef12345',
GIT_URL: 'some.url'
]
})
helper.registerAllowedMethod('setupCommonPipelineEnvironment', [Map.class], {m -> stepsCalled.add('setupCommonPipelineEnvironment')})
helper.registerAllowedMethod('piperInitRunStageConfiguration', [Map.class], {m -> stepsCalled.add('piperInitRunStageConfiguration')})
helper.registerAllowedMethod('slackSendNotification', [Map.class], {m -> stepsCalled.add('slackSendNotification')})
helper.registerAllowedMethod('artifactSetVersion', [Map.class], {m -> stepsCalled.add('artifactSetVersion')})
helper.registerAllowedMethod('pipelineStashFilesBeforeBuild', [Map.class], {m -> stepsCalled.add('pipelineStashFilesBeforeBuild')})
}
@Test
void testInitNoBuildTool() {
thrown.expectMessage('ERROR - NO VALUE AVAILABLE FOR buildTool')
jsr.step.piperPipelineStageInit(script: nullScript, juStabUtils: utils)
}
@Test
void testInitBuildToolDoesNotMatchProject() {
thrown.expect(hudson.AbortException)
thrown.expectMessage(containsString("buildTool configuration 'npm' does not fit to your project"))
jsr.step.piperPipelineStageInit(script: nullScript, juStabUtils: utils, buildTool: 'npm')
}
@Test
void testInitDefault() {
jsr.step.piperPipelineStageInit(script: nullScript, juStabUtils: utils, buildTool: 'maven')
assertThat(stepsCalled, hasItems(
'checkout',
'setupCommonPipelineEnvironment',
'piperInitRunStageConfiguration',
'artifactSetVersion',
'pipelineStashFilesBeforeBuild'
))
assertThat(stepsCalled, not(hasItems('slackSendNotification')))
}
@Test
void testInitNotOnProductiveBranch() {
binding.variables.env.BRANCH_NAME = 'anyOtherBranch'
jsr.step.piperPipelineStageInit(script: nullScript, juStabUtils: utils, buildTool: 'maven')
assertThat(stepsCalled, hasItems(
'checkout',
'setupCommonPipelineEnvironment',
'piperInitRunStageConfiguration',
'pipelineStashFilesBeforeBuild'
))
assertThat(stepsCalled, not(hasItems('artifactSetVersion')))
}
@Test
void testInitWithSlackNotification() {
nullScript.commonPipelineEnvironment.configuration = [runStep: [Init: [slackSendNotification: true]]]
jsr.step.piperPipelineStageInit(script: nullScript, juStabUtils: utils, buildTool: 'maven')
assertThat(stepsCalled, hasItems(
'checkout',
'setupCommonPipelineEnvironment',
'piperInitRunStageConfiguration',
'artifactSetVersion',
'slackSendNotification',
'pipelineStashFilesBeforeBuild'
))
}
}

View File

@ -0,0 +1,69 @@
#!groovy
package stages
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsReadYamlRule
import util.JenkinsStepRule
import util.Rules
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
class PiperPipelineStagePostTest extends BasePiperTest {
private JenkinsStepRule jsr = new JenkinsStepRule(this)
private ExpectedException thrown = ExpectedException.none()
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(thrown)
.around(jsr)
private List stepsCalled = []
@Before
void init() {
binding.variables.env.STAGE_NAME = 'Release'
helper.registerAllowedMethod('piperStageWrapper', [Map.class, Closure.class], {m, body ->
assertThat(m.stageName, is('Release'))
return body()
})
helper.registerAllowedMethod('influxWriteData', [Map.class], {m -> stepsCalled.add('influxWriteData')})
helper.registerAllowedMethod('slackSendNotification', [Map.class], {m -> stepsCalled.add('slackSendNotification')})
helper.registerAllowedMethod('mailSendNotification', [Map.class], {m -> stepsCalled.add('mailSendNotification')})
}
@Test
void testPostDefault() {
jsr.step.piperPipelineStagePost(script: nullScript, juStabUtils: utils)
assertThat(stepsCalled, hasItems('influxWriteData','mailSendNotification'))
assertThat(stepsCalled, not(hasItems('slackSendNotification')))
}
@Test
void testPostNotOnProductiveBranch() {
binding.variables.env.BRANCH_NAME = 'anyOtherBranch'
jsr.step.piperPipelineStagePost(script: nullScript, juStabUtils: utils)
assertThat(stepsCalled, hasItems('influxWriteData','mailSendNotification'))
assertThat(stepsCalled, not(hasItems('slackSendNotification')))
}
@Test
void testPostWithSlackNotification() {
nullScript.commonPipelineEnvironment.configuration = [runStep: ['Post Actions': [slackSendNotification: true]]]
jsr.step.piperPipelineStagePost(script: nullScript, juStabUtils: utils)
assertThat(stepsCalled, hasItems('influxWriteData','mailSendNotification','slackSendNotification'))
}
}

View File

@ -0,0 +1,237 @@
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.hasItem
import static org.hamcrest.Matchers.is
import static org.hamcrest.Matchers.allOf
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.ExpectedException
import static org.junit.Assert.assertThat
import util.BasePiperTest
import util.JenkinsDockerExecuteRule
import util.JenkinsShellCallRule
import util.JenkinsReadYamlRule
import util.JenkinsStepRule
import util.JenkinsLoggingRule
import util.Rules
class SonarExecuteScanTest extends BasePiperTest {
private ExpectedException thrown = ExpectedException.none()
private JenkinsReadYamlRule readYamlRule = new JenkinsReadYamlRule(this)
private JenkinsStepRule jsr = new JenkinsStepRule(this)
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private JenkinsShellCallRule jscr = new JenkinsShellCallRule(this)
private JenkinsDockerExecuteRule jedr = new JenkinsDockerExecuteRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(readYamlRule)
.around(thrown)
.around(jedr)
.around(jscr)
.around(jlr)
.around(jsr)
def sonarInstance
@Before
void init() throws Exception {
sonarInstance = null
helper.registerAllowedMethod("withSonarQubeEnv", [String.class, Closure.class], { string, closure ->
sonarInstance = string
return closure()
})
helper.registerAllowedMethod("unstash", [String.class], { stashInput -> return []})
helper.registerAllowedMethod("fileExists", [String.class], { file -> return file })
helper.registerAllowedMethod('string', [Map], { m -> m })
helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c ->
try {
binding.setProperty(l[0].variable, 'TOKEN_'+l[0].credentialsId)
c()
} finally {
binding.setProperty(l[0].variable, null)
}
})
nullScript.commonPipelineEnvironment.setArtifactVersion('1.2.3-20180101')
}
@Test
void testWithDefaults() throws Exception {
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils
)
// asserts
assertThat('Sonar instance is not set to the default value', sonarInstance, is('SonarCloud'))
assertThat('Sonar project version is not set to the default value', jscr.shell, hasItem(containsString('sonar-scanner -Dsonar.projectVersion=1')))
assertThat('Docker image is not set to the default value', jedr.dockerParams.dockerImage, is('maven:3.5-jdk-8'))
assertJobStatusSuccess()
}
@Test
void testWithCustomVersion() throws Exception {
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
projectVersion: '2'
)
// asserts
assertThat('Sonar project version is not set to the custom value', jscr.shell, hasItem(containsString('sonar-scanner -Dsonar.projectVersion=2')))
assertJobStatusSuccess()
}
@Test
void testWithCustomOptions() throws Exception {
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
options: '-Dsonar.host.url=localhost'
)
// asserts
assertThat('Sonar options are not set to the custom value', jscr.shell, hasItem(containsString('sonar-scanner -Dsonar.host.url=localhost')))
assertJobStatusSuccess()
}
@Test
void testWithCustomOptionsList() throws Exception {
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
options: ['sonar.host.url=localhost']
)
// asserts
assertThat('Sonar options are not set to the custom value', jscr.shell, hasItem(containsString('sonar-scanner -Dsonar.host.url=localhost')))
assertJobStatusSuccess()
}
@Test
void testWithCustomInstance() throws Exception {
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
instance: 'MySonarInstance'
)
// asserts
assertThat('Sonar instance is not set to the custom value', sonarInstance.toString(), is('MySonarInstance'))
assertJobStatusSuccess()
}
@Test
void testWithPRHandling() throws Exception {
binding.setVariable('env', [
'CHANGE_ID': '42',
'CHANGE_TARGET': 'master',
'BRANCH_NAME': 'feature/anything'
])
nullScript.commonPipelineEnvironment.setGithubOrg('testOrg')
//nullScript.commonPipelineEnvironment.setGithubRepo('testRepo')
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
//githubOrg: 'testOrg',
githubRepo: 'testRepo'
)
// asserts
assertThat(jscr.shell, hasItem(allOf(
containsString('-Dsonar.pullrequest.key=42'),
containsString('-Dsonar.pullrequest.base=master'),
containsString('-Dsonar.pullrequest.branch=feature/anything'),
containsString('-Dsonar.pullrequest.provider=github'),
containsString('-Dsonar.pullrequest.github.repository=testOrg/testRepo')
)))
assertJobStatusSuccess()
}
@Test
void testWithPRHandlingWithoutMandatory() throws Exception {
thrown.expect(Exception)
thrown.expectMessage('ERROR - NO VALUE AVAILABLE FOR githubRepo')
binding.setVariable('env', ['CHANGE_ID': '42'])
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
githubOrg: 'testOrg'
)
// asserts
assertJobStatusFailure()
}
@Test
void testWithLegacyPRHandling() throws Exception {
binding.setVariable('env', ['CHANGE_ID': '42'])
nullScript.commonPipelineEnvironment.setGithubOrg('testOrg')
//nullScript.commonPipelineEnvironment.setGithubRepo('testRepo')
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
legacyPRHandling: true,
githubTokenCredentialsId: 'githubId',
//githubOrg: 'testOrg',
githubRepo: 'testRepo'
)
// asserts
assertThat(jscr.shell, hasItem(allOf(
containsString('-Dsonar.analysis.mode=preview'),
containsString('-Dsonar.github.pullRequest=42'),
containsString('-Dsonar.github.oauth=TOKEN_githubId'),
containsString('-Dsonar.github.repository=testOrg/testRepo')
)))
assertJobStatusSuccess()
}
@Test
void testWithLegacyPRHandlingWithoutMandatory() throws Exception {
thrown.expect(Exception)
thrown.expectMessage('ERROR - NO VALUE AVAILABLE FOR githubTokenCredentialsId')
binding.setVariable('env', ['CHANGE_ID': '42'])
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
legacyPRHandling: true,
githubOrg: 'testOrg',
githubRepo: 'testRepo'
)
// asserts
assertJobStatusFailure()
}
@Test
void testWithSonarAuth() throws Exception {
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
sonarTokenCredentialsId: 'githubId'
)
// asserts
assertThat(jscr.shell, hasItem(containsString('-Dsonar.login=TOKEN_githubId')))
assertJobStatusSuccess()
}
@Test
void testWithSonarCloudOrganization() throws Exception {
jsr.step.sonarExecuteScan(
script: nullScript,
juStabUtils: utils,
organization: 'TestOrg-github'
)
// asserts
assertThat(jscr.shell, hasItem(containsString('-Dsonar.organization=TestOrg-github')))
assertJobStatusSuccess()
}
}

View File

@ -449,18 +449,18 @@ class WhitesourceExecuteScanTest extends BasePiperTest {
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'golang:1.12-stretch'))
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/dep'))
assertThat(dockerExecuteRule.dockerParams, hasEntry('stashContent', ['buildDescriptor', 'opensourceConfiguration', 'modified whitesource config d3aa80454919391024374ba46b4df082d15ab9a3']))
assertThat(dockerExecuteRule.dockerParams, hasEntry('stashContent', ['buildDescriptor', 'opensourceConfiguration', 'checkmarx', 'modified whitesource config 7d1c90ed46c66061fc8ea45dd96e209bf767f038']))
assertThat(shellRule.shell, Matchers.hasItems(
assertThat(shellRule.shell, Matchers.hasItems(
is('curl --location --output wss-unified-agent.jar https://github.com/whitesource/unified-agent-distribution/raw/master/standAlone/wss-unified-agent.jar'),
is('./bin/java -jar wss-unified-agent.jar -c \'./myProject/wss-unified-agent.config.d3aa80454919391024374ba46b4df082d15ab9a3\' -apiKey \'testOrgToken\' -userKey \'token-0815\' -product \'testProductName\'')
is('./bin/java -jar wss-unified-agent.jar -c \'./myProject/wss-unified-agent.config.7d1c90ed46c66061fc8ea45dd96e209bf767f038\' -apiKey \'testOrgToken\' -userKey \'token-0815\' -product \'testProductName\'')
))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.d3aa80454919391024374ba46b4df082d15ab9a3'], containsString('apiKey=testOrgToken'))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.d3aa80454919391024374ba46b4df082d15ab9a3'], containsString('productName=testProductName'))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.d3aa80454919391024374ba46b4df082d15ab9a3'], containsString('userKey=token-0815'))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.d3aa80454919391024374ba46b4df082d15ab9a3'], containsString('productVersion=1'))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.d3aa80454919391024374ba46b4df082d15ab9a3'], containsString('projectName=github.wdf.sap.corp/test/golang.myProject'))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.7d1c90ed46c66061fc8ea45dd96e209bf767f038'], containsString('apiKey=testOrgToken'))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.7d1c90ed46c66061fc8ea45dd96e209bf767f038'], containsString('productName=testProductName'))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.7d1c90ed46c66061fc8ea45dd96e209bf767f038'], containsString('userKey=token-0815'))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.7d1c90ed46c66061fc8ea45dd96e209bf767f038'], containsString('productVersion=1'))
assertThat(writeFileRule.files['./myProject/wss-unified-agent.config.7d1c90ed46c66061fc8ea45dd96e209bf767f038'], containsString('projectName=github.wdf.sap.corp/test/golang.myProject'))
}
@Test
@ -506,7 +506,7 @@ class WhitesourceExecuteScanTest extends BasePiperTest {
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'golang:1.12-stretch'))
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/dep'))
assertThat(dockerExecuteRule.dockerParams, hasEntry('stashContent', ['buildDescriptor', 'opensourceConfiguration', 'modified whitesource config d3aa80454919391024374ba46b4df082d15ab9a3']))
assertThat(dockerExecuteRule.dockerParams, hasEntry('stashContent', ['buildDescriptor', 'opensourceConfiguration', 'checkmarx', 'modified whitesource config d3aa80454919391024374ba46b4df082d15ab9a3']))
assertThat(shellRule.shell, Matchers.hasItems(
is('curl --location --output wss-unified-agent.jar https://github.com/whitesource/unified-agent-distribution/raw/master/standAlone/wss-unified-agent.jar'),

View File

@ -420,4 +420,34 @@ class ConfigurationHelperTest {
.use()
}
@Test
public void testWithPropertyInValuesString() {
Map config = ['key1':'value1']
Set possibleValues = ['value1', 'value2', 'value3']
ConfigurationHelper.newInstance(mockScript, config).collectValidationFailures()
.withPropertyInValues('key1', possibleValues)
.use()
}
@Test
public void testWithPropertyInValuesGString() {
String value = 'value1'
Map config = ['key1':"$value"]
Set possibleValues = ['value1', 'value2', 'value3']
ConfigurationHelper.newInstance(mockScript, config).collectValidationFailures()
.withPropertyInValues('key1', possibleValues)
.use()
}
@Test
public void testWithPropertyInValuesInt() {
Map config = ['key1':3]
Set possibleValues = [1, 2, 3]
ConfigurationHelper.newInstance(mockScript, config).collectValidationFailures()
.withPropertyInValues('key1', possibleValues)
.use()
}
}

View File

@ -3,6 +3,9 @@ package com.sap.piper
import org.junit.Assert
import org.junit.Test
import static org.hamcrest.Matchers.is
import static org.junit.Assert.assertThat
class MapUtilsTest {
@Test
@ -50,4 +53,44 @@ class MapUtilsTest {
MapUtils.traverse(m, { s -> (s.startsWith('x')) ? "replaced" : s})
assert m == [a: 'replaced', m: [b: 'replaced', c: 'otherString']]
}
@Test
void testGetByPath() {
Map m = [trees: [oak: 5, beech :1], flowers:[rose: 23]]
assertThat(MapUtils.getByPath(m, 'flowers'), is([rose: 23]))
assertThat(MapUtils.getByPath(m, 'trees/oak'), is(5))
assertThat(MapUtils.getByPath(m, 'trees/palm'), is(null))
}
@Test
void testDeepCopy() {
List l = ['a', 'b', 'c']
def original = [
list: l,
set: (Set)['1', '2'],
nextLevel: [
list: ['x', 'y'],
duplicate: l,
set: (Set)[9, 8, 7]
]
]
def copy = MapUtils.deepCopy(original)
assert ! copy.is(original)
assert ! copy.list.is(original.list)
assert ! copy.set.is(original.set)
assert ! copy.nextLevel.list.is(original.nextLevel.list)
assert ! copy.nextLevel.set.is(original.nextLevel.set)
assert ! copy.nextLevel.duplicate.is(original.nextLevel.duplicate)
// Within the original identical list is used twice, but the
// assuption is that there are different lists in the copy.
assert ! copy.nextLevel.duplicate.is(copy.list)
assert copy == original
}
}

View File

@ -56,7 +56,7 @@ class WhitesourceConfigurationHelperTest extends BasePiperTest {
void testExtendConfigurationFileUnifiedAgentConfigDeeper() {
helper.registerAllowedMethod('readProperties', [Map], { m -> if (!m.file.contains('testModule')) return new Properties() else return null })
WhitesourceConfigurationHelper.extendUAConfigurationFile(nullScript, utils, [scanType: 'none', whitesource: [configFilePath: './config',serviceUrl: "http://some.host.whitesource.com/api/", orgToken: 'abcd', productName: 'DIST - name1', productToken: '1234', userKey: '0000']], "./testModule/")
assertThat(jwfr.files['./testModule/config.847f9aec2f93de9000d5fa4e6eaace2283ae6377'],
assertThat(jwfr.files['./testModule/config.13954509c7675edfce373138f51c68464d1abcac'],
allOf(
not(containsString("log.level=debug")),
containsString("apiKey=abcd"),

View File

@ -99,7 +99,7 @@ stages:
stepConditions:
firstStep:
config: testGeneral
testStage2:
testStage2:
stepConditions:
secondStep:
config: testStage
@ -107,7 +107,7 @@ stages:
stepConditions:
thirdStep:
config: testStep
'''
} else {
return '''
@ -155,23 +155,23 @@ stages:
testStage1:
stepConditions:
firstStep:
config:
config:
testGeneral:
- myValx
- myVal1
testStage2:
- myVal1
testStage2:
stepConditions:
secondStep:
config:
testStage:
config:
testStage:
- maValXyz
testStage3:
stepConditions:
thirdStep:
config:
config:
testStep:
- myVal3
'''
} else {
return '''
@ -209,6 +209,66 @@ steps: {}
}
@Test
void testConditionConfigKeys() {
helper.registerAllowedMethod('libraryResource', [String.class], {s ->
if(s == 'testDefault.yml') {
return '''
stages:
testStage1:
stepConditions:
firstStep:
configKeys:
- myKey1_1
- myKey1_2
testStage2:
stepConditions:
secondStep:
configKeys:
- myKey2_1
testStage3:
stepConditions:
thirdStep:
configKeys:
- myKey3_1
'''
} else {
return '''
general: {}
steps: {}
'''
}
})
nullScript.commonPipelineEnvironment.configuration = [
general: [myKey1_1: 'myVal1_1'],
stages: [:],
steps: [thirdStep: [myKey3_1: 'myVal3_1']]
]
jsr.step.piperInitRunStageConfiguration(
script: nullScript,
juStabUtils: utils,
stageConfigResource: 'testDefault.yml'
)
assertThat(nullScript.commonPipelineEnvironment.configuration.runStage.keySet(),
allOf(
containsInAnyOrder(
'testStage1',
'testStage3'
),
hasSize(2)
)
)
assertThat(nullScript.commonPipelineEnvironment.configuration.runStep.testStage1.firstStep, is(true))
assertThat(nullScript.commonPipelineEnvironment.configuration.runStep.testStage2?.secondStep, is(false))
assertThat(nullScript.commonPipelineEnvironment.configuration.runStep.testStage3.thirdStep, is(true))
}
@Test
void testConditionFilePattern() {
helper.registerAllowedMethod('libraryResource', [String.class], {s ->

View File

@ -108,8 +108,12 @@ class PiperPipelineTest extends BasePiperTest {
})
helper.registerAllowedMethod('steps', [Closure], null)
helper.registerAllowedMethod('post', [Closure], null)
helper.registerAllowedMethod('always', [Closure], null)
helper.registerAllowedMethod('post', [Closure], {c -> c()})
helper.registerAllowedMethod('success', [Closure], {c -> c()})
helper.registerAllowedMethod('failure', [Closure], {c -> c()})
helper.registerAllowedMethod('aborted', [Closure], {c -> c()})
helper.registerAllowedMethod('unstable', [Closure], {c -> c()})
helper.registerAllowedMethod('cleanup', [Closure], {c -> c()})
helper.registerAllowedMethod('input', [Map], {m -> return null})
@ -156,6 +160,9 @@ class PiperPipelineTest extends BasePiperTest {
helper.registerAllowedMethod('piperPipelineStageRelease', [Map.class], {m ->
stepsCalled.add('piperPipelineStageRelease')
})
helper.registerAllowedMethod('piperPipelineStagePost', [Map.class], {m ->
stepsCalled.add('piperPipelineStagePost')
})
nullScript.prepareDefaultValues(script: nullScript)
@ -227,7 +234,8 @@ class PiperPipelineTest extends BasePiperTest {
'piperPipelineStageCompliance',
'input',
'piperPipelineStagePromote',
'piperPipelineStageRelease'
'piperPipelineStageRelease',
'piperPipelineStagePost'
))
}
}

View File

@ -0,0 +1,55 @@
package util
import com.lesfurets.jenkins.unit.BasePipelineTest
import java.beans.Introspector
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class JenkinsMockStepRule implements TestRule {
final BasePipelineTest testInstance
final String stepName
def callsIndex = 0
def callsParameters = [:]
JenkinsMockStepRule(BasePipelineTest testInstance, String stepName) {
this.testInstance = testInstance
this.stepName = stepName
}
boolean hasParameter(def key, def value){
for ( def parameters : callsParameters) {
for ( def parameter : parameters.value.entrySet()) {
if (parameter.key.equals(key) && parameter.value.equals(value)) return true
}
}
return false
}
@Override
Statement apply(Statement base, Description description) {
return new Statement() {
@Override
void evaluate() throws Throwable {
testInstance.helper.registerAllowedMethod(this.stepName, [Map], { Map m ->
this.callsIndex += 1
this.callsParameters.put(callsIndex, m)
})
base.evaluate()
}
}
}
@Override
String toString() {
return callsParameters.toString()
}
}

View File

@ -41,7 +41,8 @@ class JenkinsSetupRule implements TestRule {
JOB_NAME : 'p',
BUILD_NUMBER: '1',
BUILD_URL : 'http://build.url',
BRANCH_NAME: 'master'
BRANCH_NAME: 'master',
WORKSPACE: 'any/path'
])
base.evaluate()

View File

@ -8,7 +8,9 @@ import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class JenkinsStepRule implements TestRule {
final BasePipelineTest testInstance
def step
@ -22,9 +24,11 @@ class JenkinsStepRule implements TestRule {
return new Statement() {
@Override
void evaluate() throws Throwable {
def testClassName = testInstance.getClass().getSimpleName()
def stepName = Introspector.decapitalize(testClassName.replaceAll('Test$', ''))
this.step = testInstance.loadScript("${stepName}.groovy")
base.evaluate()
}
}

View File

@ -0,0 +1,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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sap.piper</groupId>
<artifactId>library-test</artifactId>
<packaging>war</packaging>
<version>1.2.3</version>
<name>library-test</name>
</project>

View File

@ -0,0 +1,8 @@
{
"auths": {
"docker.my.domain.com:4444": {
"auth": "myAuth",
"email": "my.user@domain.com"
}
}
}

View File

@ -22,7 +22,7 @@ import groovy.text.SimpleTemplateEngine
'artifactType',
/**
* Defines the tool which is used for building the artifact.
* @possibleValues docker, dlang, golang, maven, mta, npm, pip, sbt
* @possibleValues `dlang`, `docker`, `golang`, `maven`, `mta`, `npm`, `pip`, `sbt`
*/
'buildTool',
/**

View File

@ -0,0 +1,4 @@
void call(currentBuild, result = 'SUCCESS') {
echo "Current build result is ${currentBuild.result}, setting it to ${result}."
currentBuild.result = result
}

View File

@ -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

View File

@ -0,0 +1,154 @@
import com.sap.piper.GenerateDocumentation
import com.sap.piper.Utils
import com.sap.piper.ConfigurationHelper
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
'detect',
/**
* Jenkins 'Secret text' credentials ID containing the API token used to authenticate with the Synopsis Detect (formerly BlackDuck) Server.
* @parentConfigKey detect
*/
'apiTokenCredentialsId',
/**
* Defines the tool which is used for building the artifact.<br />
* Currently, it is possible to select two behaviors of the step:
* <br />
* 1. Golang-specific behavior (`buildTool: golang`). Assumption here is that project uses the dependency management tool _dep_<br />
* 2. Custom-specific behavior for all other values of `buildTool`
*
* @possibleValues `golang`, any other build tool
*/
'buildTool',
/**
* Name of the Synopsis Detect (formerly BlackDuck) project.
* @parentConfigKey detect
*/
'projectName',
/**
* Version of the Synopsis Detect (formerly BlackDuck) project.
* @parentConfigKey detect
*/
'projectVersion',
/**
* List of paths which should be scanned by the Synopsis Detect (formerly BlackDuck) scan.
* @parentConfigKey detect
*/
'scanPaths',
/**
* Properties passed to the Synopsis Detect (formerly BlackDuck) scan. You can find details in the [Synopsis Detect documentation](https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/622846/Using+Synopsys+Detect+Properties)
* @parentConfigKey detect
*/
'scanProperties',
/**
* List of scanners to be used for Synopsis Detect (formerly BlackDuck) scan.
* @possibleValues `['signature']`
* @parentConfigKey detect
*/
'scanners',
/**
* Server url to the Synopsis Detect (formerly BlackDuck) Server.
* @parentConfigKey detect
*/
'serverUrl'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/** @see dockerExecute */
'dockerImage',
/** @see dockerExecute */
'dockerWorkspace',
/** If specific stashes should be considered for the scan, their names need to be passed via the parameter `stashContent`. */
'stashContent'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
@Field Map CONFIG_KEY_COMPATIBILITY = [
detect: [
apiTokenCredentialsId: 'apiTokenCredentialsId',
projectName: 'projectName',
projectVersion: 'projectVersion',
scanners: 'scanners',
scanPaths: 'scanPaths',
scanProperties: 'scanProperties',
serverUrl: 'serverUrl'
]
]
/**
* This step executes [Synopsis Detect](https://synopsys.atlassian.net/wiki/spaces/INTDOCS/pages/62423113/Synopsys+Detect) scans.
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
def script = checkScript(this, parameters) ?: this
def utils = parameters.juStabUtils ?: new Utils()
// load default & individual configuration
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS,CONFIG_KEY_COMPATIBILITY)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
.mixin(parameters, PARAMETER_KEYS, CONFIG_KEY_COMPATIBILITY)
.dependingOn('buildTool').mixin('dockerImage')
.dependingOn('buildTool').mixin('dockerWorkspace')
.withMandatoryProperty('detect/apiTokenCredentialsId')
.withMandatoryProperty('detect/projectName')
.withMandatoryProperty('detect/projectVersion')
.use()
config.stashContent = utils.unstashAll(config.stashContent)
script.commonPipelineEnvironment.setInfluxStepData('detect', false)
utils.pushToSWA([
step: STEP_NAME,
stepParamKey1: 'buildTool',
stepParam1: config.buildTool ?: 'default'
], config)
//prepare Hub Detect execution using package manager
switch (config.buildTool) {
case 'golang':
dockerExecute(script: script, dockerImage: config.dockerImage, dockerWorkspace: config.dockerWorkspace, stashContent: config.stashContent) {
sh 'curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh'
sh 'ln --symbolic $(pwd) $GOPATH/src/hub'
sh 'cd $GOPATH/src/hub && dep ensure'
}
break
default:
//no additional tasks are performed
echo "[${STEP_NAME}] No preparation steps performed for scan. Please make sure to properly set configuration for `detect.scanProperties`"
}
withCredentials ([string(
credentialsId: config.detect.apiTokenCredentialsId,
variable: 'detectApiToken'
)]) {
def authentication = "--blackduck.api.token=${detectApiToken}"
config.detect.scanProperties += [
"--detect.project.name='${config.detect.projectName}'",
"--detect.project.version.name='${config.detect.projectVersion}'",
"--detect.code.location.name='${config.detect.projectName}/${config.detect.projectVersion}'",
"--blackduck.url=${config.detect.serverUrl}",
]
if ('signature' in config.detect.scanners) [
config.detect.scanProperties.add("--detect.blackduck.signature.scanner.paths=${config.detect.scanPaths.join(',')}")
]
if ('source' in config.detect.scanners) [
config.detect.scanProperties.add("--detect.source.path=${config.detect.scanPaths[0]}")
]
def detectProperties = config.detect.scanProperties.join(' ') + " ${authentication}"
echo "[${STEP_NAME}] Running with following Detect configuration: ${detectProperties}"
synopsys_detect detectProperties
script.commonPipelineEnvironment.setInfluxStepData('detect', true)
}
}
}

View File

@ -162,8 +162,10 @@ void executeOnPod(Map config, utils, Closure body) {
* In case third case, we need to create the 'container' stash to bring the modified content back to the host.
*/
try {
if (config.containerName && config.stashContent.isEmpty()){
config.stashContent.add(stashWorkspace(config, 'workspace'))
def stashContent = config.stashContent
if (config.containerName && stashContent.isEmpty()){
stashContent = [stashWorkspace(config, 'workspace')]
}
podTemplate(getOptions(config)) {
node(config.uniqueId) {
@ -175,7 +177,7 @@ void executeOnPod(Map config, utils, Closure body) {
echo "ContainerConfig: ${containerParams}"
container(containerParams){
try {
utils.unstashAll(config.stashContent)
utils.unstashAll(stashContent)
body()
} finally {
stashWorkspace(config, 'container', true)

View File

@ -42,4 +42,3 @@ def call(Map parameters = [:], body) {
return duration
}

View File

@ -13,7 +13,10 @@ import groovy.transform.Field
@Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = [
/** Defines the build tool to be used for the test execution.*/
/**
* Defines the build tool to be used for the test execution.
* @possibleValues `maven`, `npm`, `bundler`
*/
'buildTool',
/** @see dockerExecute*/
'dockerEnvVars',
@ -25,7 +28,7 @@ import groovy.transform.Field
'dockerWorkspace',
/**
* Defines the behavior in case tests fail. When this is set to `true` test results cannot be recorded using the `publishTestResults` step afterwards.
* @possibleValues true, false
* @possibleValues `true`, `false`
*/
'failOnError',
/** Defines the command for installing Gauge. In case the `dockerImage` already contains Gauge it can be set to empty: ``.*/

View File

@ -51,7 +51,7 @@ import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException
@GenerateDocumentation
void call(Map parameters = [:], body) {
// load default & individual configuration
def cpe = parameters.stepParameters?.script?.commonPipelineEnvironment ?: commonPipelineEnvironment
def cpe = parameters.stepParameters?.script?.commonPipelineEnvironment ?: null
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(cpe, GENERAL_CONFIG_KEYS)
@ -81,12 +81,25 @@ void call(Map parameters = [:], body) {
if (config.failOnError || config.stepName in config.mandatorySteps) {
throw ex
}
if (config.stepParameters?.script) {
config.stepParameters?.script.currentBuild.result = 'UNSTABLE'
} else {
currentBuild.result = 'UNSTABLE'
}
echo "[${STEP_NAME}] Error in step ${config.stepName} - Build result set to 'UNSTABLE'"
List unstableSteps = cpe?.getValue('unstableSteps') ?: []
if(!unstableSteps) {
unstableSteps = []
}
// add information about unstable steps to pipeline environment
// this helps to bring this information to users in a consolidated manner inside a pipeline
unstableSteps.add(config.stepName)
cpe?.setValue('unstableSteps', unstableSteps)
} catch (Throwable error) {
if (config.echoDetails)
message += formatErrorMessage(config, error)

View File

@ -1,5 +1,6 @@
import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.GenerateDocumentation
import com.sap.piper.ConfigurationHelper
import com.sap.piper.JsonUtils
import com.sap.piper.Utils
@ -11,17 +12,61 @@ import groovy.transform.Field
@Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* Defines the version of the current artifact. Defaults to `commonPipelineEnvironment.getArtifactVersion()`
*/
'artifactVersion',
/**
* Defines custom data (map of key-value pairs) to be written to Influx into measurement `jenkins_custom_data`. Defaults to `commonPipelineEnvironment.getInfluxCustomData()`
*/
'customData',
/**
* Defines tags (map of key-value pairs) to be written to Influx into measurement `jenkins_custom_data`. Defaults to `commonPipelineEnvironment.getInfluxCustomDataTags()`
*/
'customDataTags',
/**
* Defines a map of measurement names containing custom data (map of key-value pairs) to be written to Influx. Defaults to `commonPipelineEnvironment.getInfluxCustomDataMap()`
*/
'customDataMap',
/**
* Defines a map of measurement names containing tags (map of key-value pairs) to be written to Influx. Defaults to `commonPipelineEnvironment.getInfluxCustomDataTags()`
*/
'customDataMapTags',
/**
* Defines the name of the Influx server as configured in Jenkins global configuration.
*/
'influxServer',
/**
* Defines a custom prefix.
* For example in multi branch pipelines, where every build is named after the branch built and thus you have different builds called 'master' that report different metrics.
*/
'influxPrefix',
/**
* Defines if a dedicated node/executor should be created in the pipeline run.
* This is especially relevant when running the step in a declarative `POST` stage where by default no executor is available.
*/
'wrapInNode'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* Since your Continuous Delivery Pipeline in Jenkins provides your productive development and delivery infrastructure you should monitor the pipeline to ensure it runs as expected. How to setup this monitoring is described in the following.
*
* You basically need three components:
*
* - The [InfluxDB Jenkins plugin](https://wiki.jenkins-ci.org/display/JENKINS/InfluxDB+Plugin) which allows you to send build metrics to InfluxDB servers
* - The [InfluxDB](https://www.influxdata.com/time-series-platform/influxdb/) to store this data (Docker available)
* - A [Grafana](http://grafana.org/) dashboard to visualize the data stored in InfluxDB (Docker available)
*
* !!! note "no InfluxDB available?"
* If you don't have an InfluxDB available yet this step will still provide you some benefit.
*
* It will create following files for you and archive them into your build:
*
* * `jenkins_data.json`: This file gives you build-specific information, like e.g. build result, stage where the build failed
* * `influx_data.json`: This file gives you detailed information about your pipeline, e.g. stage durations, steps executed, ...
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters, allowBuildFailure: true) {

127
vars/kanikoExecute.groovy Normal file
View File

@ -0,0 +1,127 @@
import groovy.text.GStringTemplateEngine
import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.GenerateDocumentation
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import groovy.transform.Field
@Field def STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = [
/**
* Defines the build options for the [kaniko](https://github.com/GoogleContainerTools/kaniko) build.
*/
'containerBuildOptions',
/** @see dockerExecute */
'containerCommand',
/** Defines the full name of the Docker image to be created including registry, image name and tag like `my.docker.registry/path/myImageName:myTag`.*/
'containerImageNameAndTag',
/** @see dockerExecute */
'containerShell',
/**
* Defines the command to prepare the Kaniko container.
* By default the contained credentials are removed in order to allow anonymous access to container registries.
*/
'containerPreparationCommand',
/**
* List containing download links of custom TLS certificates. This is required to ensure trusted connections to registries with custom certificates.
*/
'customTlsCertificateLinks',
/**
* Defines the location of the Dockerfile relative to the Jenkins workspace.
*/
'dockerfile',
/**
* Defines the id of the file credentials in your Jenkins credentials store which contain the file `.docker/config.json`.
* You can find more details about the Docker credentials in the [Docker documentation](https://docs.docker.com/engine/reference/commandline/login/).
*/
'dockerConfigJsonCredentialsId',
/** @see dockerExecute */
'dockerEnvVars',
/** @see dockerExecute */
'dockerOptions',
/** @see dockerExecute */
'dockerImage'
]
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* Executes a [Kaniko](https://github.com/GoogleContainerTools/kaniko) build for creating a Docker container.
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
final script = checkScript(this, parameters) ?: this
// load default & individual configuration
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.use()
new Utils().pushToSWA([
step: STEP_NAME
], config)
def buildOptions = new GStringTemplateEngine().createTemplate(config.containerBuildOptions).make([config: config, env: env]).toString()
if (!buildOptions.contains('--destination')) {
if (config.containerImageNameAndTag) {
buildOptions += " --destination ${config.containerImageNameAndTag}"
} else {
buildOptions += " --no-push"
}
}
dockerExecute(
script: script,
containerCommand: config.containerCommand,
containerShell: config.containerShell,
dockerEnvVars: config.dockerEnvVars,
dockerImage: config.dockerImage,
dockerOptions: config.dockerOptions
) {
// prepare kaniko container for running with proper Docker config.json and custom certificates
// custom certificates will be downloaded and appended to ca-certificates.crt file used in container
sh """#!${config.containerShell}
${config.containerPreparationCommand}
${getCertificateUpdate(config.customTlsCertificateLinks)}
"""
def uuid = UUID.randomUUID().toString()
if (config.dockerConfigJsonCredentialsId) {
// write proper config.json with credentials
withCredentials([file(credentialsId: config.dockerConfigJsonCredentialsId, variable: 'dockerConfigJson')]) {
writeFile file: "${uuid}-config.json", text: readFile(dockerConfigJson)
}
} else {
// empty config.json to allow anonymous authentication
writeFile file: "${uuid}-config.json", text: '{"auths":{}}'
}
// execute Kaniko
sh """#!${config.containerShell}
mv ${uuid}-config.json /kaniko/.docker/config.json
/kaniko/executor --dockerfile ${env.WORKSPACE}/${config.dockerfile} --context ${env.WORKSPACE} ${buildOptions}"""
}
}
}
private String getCertificateUpdate(List certLinks) {
String certUpdate = ''
if (!certLinks) return certUpdate
certLinks.each {link ->
certUpdate += "wget ${link} -O - >> /kaniko/ssl/certs/ca-certificates.crt\n"
}
return certUpdate
}

View File

@ -6,6 +6,8 @@ import com.sap.piper.Utils
import groovy.transform.Field
import static com.sap.piper.Utils.downloadSettingsFromUrl
@Field def STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = []
@ -63,35 +65,33 @@ void call(Map parameters = [:]) {
String command = "mvn"
def globalSettingsFile = configuration.globalSettingsFile
if (globalSettingsFile?.trim()) {
if(globalSettingsFile.trim().startsWith("http")){
downloadSettingsFromUrl(globalSettingsFile)
globalSettingsFile = "settings.xml"
String globalSettingsFile = configuration.globalSettingsFile?.trim()
if (globalSettingsFile) {
if (globalSettingsFile.startsWith("http")) {
globalSettingsFile = downloadSettingsFromUrl(this, globalSettingsFile, 'global-settings.xml')
}
command += " --global-settings '${globalSettingsFile}'"
}
def m2Path = configuration.m2Path
String m2Path = configuration.m2Path
if(m2Path?.trim()) {
command += " -Dmaven.repo.local='${m2Path}'"
}
def projectSettingsFile = configuration.projectSettingsFile
if (projectSettingsFile?.trim()) {
if(projectSettingsFile.trim().startsWith("http")){
downloadSettingsFromUrl(projectSettingsFile)
projectSettingsFile = "settings.xml"
String projectSettingsFile = configuration.projectSettingsFile?.trim()
if (projectSettingsFile) {
if (projectSettingsFile.startsWith("http")) {
projectSettingsFile = downloadSettingsFromUrl(this, projectSettingsFile, 'project-settings.xml')
}
command += " --settings '${projectSettingsFile}'"
}
def pomPath = configuration.pomPath
String pomPath = configuration.pomPath
if(pomPath?.trim()){
command += " --file '${pomPath}'"
}
def mavenFlags = configuration.flags
String mavenFlags = configuration.flags
if (mavenFlags?.trim()) {
command += " ${mavenFlags}"
}
@ -122,9 +122,3 @@ void call(Map parameters = [:]) {
}
}
}
private downloadSettingsFromUrl(String url){
def settings = httpRequest url
writeFile file: 'settings.xml', text: settings.getContent()
}

View File

@ -4,9 +4,10 @@ import com.sap.piper.GenerateDocumentation
import com.sap.piper.ConfigurationHelper
import com.sap.piper.MtaUtils
import com.sap.piper.Utils
import groovy.transform.Field
import static com.sap.piper.Utils.downloadSettingsFromUrl
@Field def STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = []
@ -26,11 +27,17 @@ import groovy.transform.Field
* The location of the SAP Multitarget Application Archive Builder jar file, including file name and extension.
* If it is not provided, the SAP Multitarget Application Archive Builder is expected on PATH.
*/
'mtaJarLocation'
'mtaJarLocation',
/** Path or url to the mvn settings file that should be used as global settings file.*/
'globalSettingsFile',
/** Path or url to the mvn settings file that should be used as project settings file.*/
'projectSettingsFile'
]
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([
/** @see dockerExecute */
'dockerOptions'
'dockerOptions',
/** Url to the npm registry that should be used for installing npm dependencies.*/
'defaultNpmRegistry'
])
/**
@ -59,6 +66,28 @@ void call(Map parameters = [:]) {
dockerExecute(script: script, dockerImage: configuration.dockerImage, dockerOptions: configuration.dockerOptions) {
String projectSettingsFile = configuration.projectSettingsFile?.trim()
if (projectSettingsFile) {
if (projectSettingsFile.startsWith("http")) {
projectSettingsFile = downloadSettingsFromUrl(this, projectSettingsFile, 'project-settings.xml')
}
sh 'mkdir -p $HOME/.m2'
sh "cp ${projectSettingsFile} \$HOME/.m2/settings.xml"
}
String globalSettingsFile = configuration.globalSettingsFile?.trim()
if (globalSettingsFile) {
if (globalSettingsFile.startsWith("http")) {
globalSettingsFile = downloadSettingsFromUrl(this, globalSettingsFile, 'global-settings.xml')
}
sh "cp ${globalSettingsFile} \$M2_HOME/conf/settings.xml"
}
String defaultNpmRegistry = configuration.defaultNpmRegistry?.trim()
if (defaultNpmRegistry) {
sh "npm config set registry $defaultNpmRegistry"
}
def mtaYamlName = "mta.yaml"
def applicationName = configuration.applicationName

View File

@ -0,0 +1,141 @@
import com.sap.piper.GenerateDocumentation
import com.sap.piper.CloudPlatform
import com.sap.piper.DeploymentType
import com.sap.piper.k8s.ContainerMap
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import com.sap.piper.JenkinsUtils
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
/** Defines the targets to deploy on cloudFoundry.*/
'cfTargets',
/** Defines the targets to deploy on neo.*/
'neoTargets'
]
@Field Set STEP_CONFIG_KEYS = []
@Field Set PARAMETER_KEYS = GENERAL_CONFIG_KEYS.plus([
/** The stage name. If the stage name is not provided, it will be taken from the environment variable 'STAGE_NAME'.*/
'stage',
/** Defines the deployment type.*/
'enableZeroDowntimeDeployment',
/** The source file to deploy to the SAP Cloud Platform.*/
'source'
])
/**
* Deploys an application to multiple platforms (cloudFoundry, SAP Cloud Platform) or to multiple instances of multiple platforms or the same platform.
*/
@GenerateDocumentation
void call(parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
def stageName = parameters.stage ?: env.STAGE_NAME
def enableZeroDowntimeDeployment = parameters.enableZeroDowntimeDeployment ?: false
def script = checkScript(this, parameters) ?: this
def utils = parameters.utils ?: new Utils()
def jenkinsUtils = parameters.jenkinsUtils ?: new JenkinsUtils()
ConfigurationHelper configHelper = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
Map config = configHelper.use()
configHelper
.withMandatoryProperty('source', null, { config.neoTargets })
utils.pushToSWA([
step: STEP_NAME,
stepParamKey1: 'stage',
stepParam1: stageName,
stepParamKey2: 'enableZeroDowntimeDeployment',
stepParam2: enableZeroDowntimeDeployment
], config)
def index = 1
def deployments = [:]
def deploymentType
def deployTool
if (config.cfTargets) {
deploymentType = DeploymentType.selectFor(CloudPlatform.CLOUD_FOUNDRY, enableZeroDowntimeDeployment).toString()
deployTool = script.commonPipelineEnvironment.configuration.isMta ? 'mtaDeployPlugin' : 'cf_native'
for (int i = 0; i < config.cfTargets.size(); i++) {
def target = config.cfTargets[i]
Closure deployment = {
cloudFoundryDeploy(
script: script,
juStabUtils: utils,
jenkinsUtilsStub: jenkinsUtils,
deployType: deploymentType,
cloudFoundry: target,
mtaPath: script.commonPipelineEnvironment.mtarFilePath,
deployTool: deployTool
)
}
setDeployment(deployments, deployment, index, script, stageName)
index++
}
utils.runClosures(deployments)
}
if (config.neoTargets) {
deploymentType = DeploymentType.selectFor(CloudPlatform.NEO, enableZeroDowntimeDeployment)
for (int i = 0; i < config.neoTargets.size(); i++) {
def target = config.neoTargets[i]
Closure deployment = {
neoDeploy (
script: script,
warAction: deploymentType.toString(),
source: config.source,
neo: target
)
}
setDeployment(deployments, deployment, index, script, stageName)
index++
}
utils.runClosures(deployments)
}
if (!config.cfTargets && !config.neoTargets) {
error "Deployment skipped because no targets defined!"
}
}
}
void setDeployment(deployments, deployment, index, script, stageName) {
deployments["Deployment ${index > 1 ? index : ''}"] = {
if (env.POD_NAME) {
dockerExecuteOnKubernetes(script: script, containerMap: ContainerMap.instance.getMap().get(stageName) ?: [:]) {
deployment.run()
}
} else {
node(env.NODE_NAME) {
deployment.run()
}
}
}
}

View File

@ -1,3 +1,4 @@
import com.sap.piper.GenerateDocumentation
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import com.sap.piper.StepAssertions
@ -9,22 +10,106 @@ import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
'neo'
'neo',
/**
* The SAP Cloud Platform account to deploy to.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'account',
/**
* Name of the application you want to manage, configure, or deploy.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'application',
/**
* The Jenkins credentials containing user and password used for SAP CP deployment.
* @parentConfigKey neo
*/
'credentialsId',
/**
* Map of environment variables in the form of KEY: VALUE.
* @parentConfigKey neo
*/
'environment',
/**
* The SAP Cloud Platform host to deploy to.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'host',
/**
* The path to the .properties file in which all necessary deployment properties for the application are defined.
* @parentConfigKey neo
* @mandatory for deployMode=warPropertiesFile
*/
'propertiesFile',
/**
* Name of SAP Cloud Platform application runtime.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'runtime',
/**
* Version of SAP Cloud Platform application runtime.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'runtimeVersion',
/**
* Compute unit (VM) size. Acceptable values: lite, pro, prem, prem-plus.
* @parentConfigKey neo
*/
'size',
/**
* String of VM arguments passed to the JVM.
* @parentConfigKey neo
*/
'vmArguments'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* @see dockerExecute
*/
'dockerEnvVars',
/**
* @see dockerExecute
*/
'dockerImage',
/**
* @see dockerExecute
*/
'dockerOptions',
'neoHome',
/**
* The path to the archive for deployment to SAP CP. If not provided `mtarFilePath` from commom pipeline environment is used instead.
*/
'source'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([
/**
* The deployment mode which should be used. Available options are:
* *`'mta'` - default,
* *`'warParams'` - deploying WAR file and passing all the deployment parameters via the function call,
* *`'warPropertiesFile'` - deploying WAR file and putting all the deployment parameters in a .properties file.
* @possibleValues 'mta', 'warParams', 'warPropertiesFile'
*/
'deployMode',
/**
* Action mode when using WAR file mode. Available options are `deploy` (default) and `rolling-update` which performs update of an application without downtime in one go.
* @possibleValues 'deploy', 'rolling-update'
*/
'warAction'
])
/**
* Deploys an Application to SAP Cloud Platform (SAP CP) using the SAP Cloud Platform Console Client (Neo Java Web SDK).
*/
@GenerateDocumentation
void call(parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
@ -151,14 +236,12 @@ private deploy(script, utils, Map configuration, NeoCommandHelper neoCommandHelp
}
catch (Exception ex) {
if (dockerImage) {
echo "Error while deploying to SAP Cloud Platform. Here are the neo.sh logs:"
try {
sh "cat logs/neo/*"
} catch(Exception e) {
echo "Unable to provide the logs."
ex.addSuppressed(e)
}
echo "Error while deploying to SAP Cloud Platform. Here are the neo.sh logs:"
try {
sh "cat logs/neo/*"
} catch(Exception e) {
echo "Unable to provide the logs."
ex.addSuppressed(e)
}
throw ex
}

View File

@ -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
]]
])
}

View File

@ -1,7 +1,31 @@
import com.sap.piper.GenerateDocumentation
import groovy.transform.Field
@Field STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = []
@Field Set PARAMETER_KEYS = [
/**
* Can be used to overwrite the default behavior of existing stashes as well as to define additional stashes.
* This parameter handles the _includes_ and can be defined as a map of stash name and include patterns.
* Include pattern has to be a string with comma separated patterns as per [Pipeline basic step `stash`](https://jenkins.io/doc/pipeline/steps/workflow-basic-steps/#stash-stash-some-files-to-be-used-later-in-the-build)
*/
'stashIncludes',
/**
* Can be used to overwrite the default behavior of existing stashes as well as to define additional stashes.
* This parameter handles the _excludes_ and can be defined as a map of stash name and exclude patterns.
* Exclude pattern has to be a string with comma separated patterns as per [Pipeline basic step `stash`](https://jenkins.io/doc/pipeline/steps/workflow-basic-steps/#stash-stash-some-files-to-be-used-later-in-the-build)
*/
'stashExcludes'
]
/**
* This step stashes files that are needed in other build steps (on other nodes).
*/
@GenerateDocumentation
void call(Map parameters = [:], body) {
handlePipelineStepErrors (stepName: 'pipelineStashFiles', stepParameters: parameters) {

View File

@ -1,13 +1,34 @@
import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.GenerateDocumentation
import com.sap.piper.Utils
import com.sap.piper.ConfigurationHelper
import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field Set STEP_CONFIG_KEYS = ['noDefaultExludes', 'stashIncludes', 'stashExcludes']
@Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = [
/**
* By default certain files are excluded from stashing (e.g. `.git` folder).
* Details can be found as per [Pipeline basic step `stash](https://jenkins.io/doc/pipeline/steps/workflow-basic-steps/#stash-stash-some-files-to-be-used-later-in-the-build).
* This parameter allows to provide a list of stash names for which the standard exclude behavior should be switched off.
* This will allow you to also stash directories like `.git`.
*/
'noDefaultExludes',
/** @see pipelineStashFiles */
'stashIncludes',
/** @see pipelineStashFiles */
'stashExcludes'
]
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* This step stashes files that are needed in other build steps (on other nodes).
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters, stepNameDoc: 'stashFiles') {

View File

@ -1,13 +1,34 @@
import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.GenerateDocumentation
import com.sap.piper.Utils
import com.sap.piper.ConfigurationHelper
import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field Set STEP_CONFIG_KEYS = ['noDefaultExludes', 'stashIncludes', 'stashExcludes']
@Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = [
/**
* By default certain files are excluded from stashing (e.g. `.git` folder).
* Details can be found as per [Pipeline basic step `stash](https://jenkins.io/doc/pipeline/steps/workflow-basic-steps/#stash-stash-some-files-to-be-used-later-in-the-build).
* This parameter allows to provide a list of stash names for which the standard exclude behavior should be switched off.
* This will allow you to also stash directories like `.git`.
*/
'noDefaultExludes',
/** @see pipelineStashFiles */
'stashIncludes',
/** @see pipelineStashFiles */
'stashExcludes'
]
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* This step stashes files that are needed in other build steps (on other nodes).
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters, stepNameDoc: 'stashFiles') {

View File

@ -3,6 +3,7 @@ import com.sap.piper.ConfigurationLoader
import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.ConfigurationHelper
import com.sap.piper.MapUtils
import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@ -60,20 +61,32 @@ void call(Map parameters = [:]) {
stage.getValue().stepConditions.each {step ->
def stepActive = false
step.getValue().each {condition ->
Map stepConfig = script.commonPipelineEnvironment.getStepConfiguration(step.getKey(), currentStage)
switch(condition.getKey()) {
case 'config':
if (condition.getValue() instanceof Map) {
condition.getValue().each {configCondition ->
if (script.commonPipelineEnvironment.getStepConfiguration(step.getKey(), currentStage)?.get(configCondition.getKey()) in configCondition.getValue()) {
if (MapUtils.getByPath(stepConfig, configCondition.getKey()) in configCondition.getValue()) {
stepActive = true
}
}
} else if (script.commonPipelineEnvironment.getStepConfiguration(step.getKey(), currentStage)?.get(condition.getValue())) {
} else if (MapUtils.getByPath(stepConfig, condition.getValue())) {
stepActive = true
}
break
case 'configKeys':
if (condition.getValue() instanceof List) {
condition.getValue().each {configKey ->
if (MapUtils.getByPath(stepConfig, configKey)) {
stepActive = true
}
}
} else if (MapUtils.getByPath(stepConfig, condition.getValue())) {
stepActive = true
}
break
case 'filePatternFromConfig':
def conditionValue=script.commonPipelineEnvironment.getStepConfiguration(step.getKey(), currentStage)?.get(condition.getValue())
def conditionValue = MapUtils.getByPath(stepConfig, condition.getValue())
if (conditionValue && findFiles(glob: conditionValue)) {
stepActive = true
}

View File

@ -81,9 +81,13 @@ void call(parameters) {
}
}
post {
always {
influxWriteData script: parameters.script, wrapInNode: true
mailSendNotification script: parameters.script, wrapInNode: true
/* https://jenkins.io/doc/book/pipeline/syntax/#post */
success {buildSetResult(currentBuild)}
aborted {buildSetResult(currentBuild, 'ABORTED')}
failure {buildSetResult(currentBuild, 'FAILURE')}
unstable {buildSetResult(currentBuild, 'UNSTABLE')}
cleanup {
piperPipelineStagePost script: parameters.script
}
}
}

View File

@ -55,11 +55,12 @@ void call(Map parameters = [:]) {
piperInitRunStageConfiguration script: script, stageConfigResource: config.stageConfigResource
if (env.BRANCH_NAME == config.productiveBranch) {
if (parameters.script.commonPipelineEnvironment.configuration.runStep?.get('Init')?.slackSendNotification) {
slackSendNotification script: script, message: "STARTED: Job <${env.BUILD_URL}|${URLDecoder.decode(env.JOB_NAME, java.nio.charset.StandardCharsets.UTF_8.name())} ${env.BUILD_DISPLAY_NAME}>", color: 'WARNING'
}
artifactSetVersion script: script
}
pipelineStashFilesBeforeBuild script: script
}
}

View File

@ -0,0 +1,51 @@
import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateDocumentation
import com.sap.piper.Utils
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* In this stage reporting actions like mail notification or telemetry reporting are executed.
*
* This stage contains following steps:
* - [influxWriteData](./influxWriteData.md)
* - [mailSendNotification](./mailSendNotification.md)
*
* !!! note
* This stage is meant to be used in a [post](https://jenkins.io/doc/book/pipeline/syntax/#post) section of a pipeline.
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
def script = checkScript(this, parameters) ?: this
def utils = parameters.juStabUtils ?: new Utils()
def stageName = parameters.stageName?:env.STAGE_NAME
// ease handling extension
stageName = stageName.replace('Declarative: ', '')
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.use()
piperStageWrapper (script: script, stageName: stageName, stageLocking: false) {
// telemetry reporting
utils.pushToSWA([step: STEP_NAME], config)
influxWriteData script: script
if(env.BRANCH_NAME == parameters.script.commonPipelineEnvironment.getStepConfiguration('', '').productiveBranch) {
if(parameters.script.commonPipelineEnvironment.configuration.runStep?.get('Post Actions')?.slackSendNotification) {
slackSendNotification script: parameters.script
}
}
mailSendNotification script: script
}
}

View File

@ -14,7 +14,7 @@ import groovy.text.SimpleTemplateEngine
@Field Set GENERAL_CONFIG_KEYS = [
/**
* Defines the tool which is used for executing the tests
* @possibleValues `'maven'`, `'npm'`
* @possibleValues `maven`, `npm`, `bundler`
*/
'buildTool',
/** @see dockerExecute */

View File

@ -0,0 +1,193 @@
import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateDocumentation
import com.sap.piper.Utils
import static com.sap.piper.Prerequisites.checkScript
import groovy.transform.Field
import groovy.text.SimpleTemplateEngine
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
/**
* Pull-Request voting only:
* The URL to the Github API. see https://docs.sonarqube.org/display/PLUG/GitHub+Plugin#GitHubPlugin-Usage
* deprecated: only supported in LTS / < 7.2
*/
'githubApiUrl',
/**
* Pull-Request voting only:
* The Github organization.
* @default: `commonPipelineEnvironment.getGithubOrg()`
*/
'githubOrg',
/**
* Pull-Request voting only:
* The Github repository.
* @default: `commonPipelineEnvironment.getGithubRepo()`
*/
'githubRepo',
/**
* Pull-Request voting only:
* The Jenkins credentialId for a Github token. It is needed to report findings back to the pull-request.
* deprecated: only supported in LTS / < 7.2
* @possibleValues Jenkins credential id
*/
'githubTokenCredentialsId',
/**
* The Jenkins credentialsId for a SonarQube token. It is needed for non-anonymous analysis runs. see https://sonarcloud.io/account/security
* @possibleValues Jenkins credential id
*/
'sonarTokenCredentialsId',
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* Pull-Request voting only:
* Disables the pull-request decoration with inline comments.
* deprecated: only supported in LTS / < 7.2
* @possibleValues `true`, `false`
*/
'disableInlineComments',
/**
* Name of the docker image that should be used. If empty, Docker is not used and the command is executed directly on the Jenkins system.
* see dockerExecute
*/
'dockerImage',
/**
* The name of the SonarQube instance defined in the Jenkins settings.
*/
'instance',
/**
* Pull-Request voting only:
* Activates the pull-request handling using the [GitHub Plugin](https://docs.sonarqube.org/display/PLUG/GitHub+Plugin) (deprecated).
* deprecated: only supported in LTS / < 7.2
* @possibleValues `true`, `false`
*/
'legacyPRHandling',
/**
* A list of options which are passed to the `sonar-scanner`.
*/
'options',
/**
* Organization that the project will be assigned to in SonarCloud.io.
*/
'organization',
/**
* The project version that is reported to SonarQube.
* @default: major number of `commonPipelineEnvironment.getArtifactVersion()`
*/
'projectVersion'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* The step executes the [sonar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) cli command to scan the defined sources and publish the results to a SonarQube instance.
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
def utils = parameters.juStabUtils ?: new Utils()
def script = checkScript(this, parameters) ?: this
// load default & individual configuration
Map configuration = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, GENERAL_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.addIfEmpty('projectVersion', script.commonPipelineEnvironment.getArtifactVersion()?.tokenize('.')?.get(0))
.addIfEmpty('githubOrg', script.commonPipelineEnvironment.getGithubOrg())
.addIfEmpty('githubRepo', script.commonPipelineEnvironment.getGithubRepo())
// check mandatory parameters
.withMandatoryProperty('githubTokenCredentialsId', null, { config -> config.legacyPRHandling && isPullRequest() })
.withMandatoryProperty('githubOrg', null, { isPullRequest() })
.withMandatoryProperty('githubRepo', null, { isPullRequest() })
.use()
if(configuration.options instanceof String)
configuration.options = [].plus(configuration.options)
def worker = { config ->
withSonarQubeEnv(config.instance) {
loadSonarScanner(config)
if(config.organization) config.options.add("sonar.organization=${config.organization}")
if(config.projectVersion) config.options.add("sonar.projectVersion=${config.projectVersion}")
// prefix options
config.options = config.options.collect { it.startsWith('-D') ? it : "-D${it}" }
sh "PATH=\$PATH:${env.WORKSPACE}/.sonar-scanner/bin sonar-scanner ${config.options.join(' ')}"
}
}
if(configuration.sonarTokenCredentialsId){
def workerForSonarAuth = worker
worker = { config ->
withCredentials([string(
credentialsId: config.sonarTokenCredentialsId,
variable: 'SONAR_TOKEN'
)]){
config.options.add("sonar.login=$SONAR_TOKEN")
workerForSonarAuth(config)
}
}
}
if(isPullRequest()){
def workerForGithubAuth = worker
worker = { config ->
if(config.legacyPRHandling) {
withCredentials([string(
credentialsId: config.githubTokenCredentialsId,
variable: 'GITHUB_TOKEN'
)]){
// support for https://docs.sonarqube.org/display/PLUG/GitHub+Plugin
config.options.add('sonar.analysis.mode=preview')
config.options.add("sonar.github.oauth=$GITHUB_TOKEN")
config.options.add("sonar.github.pullRequest=${env.CHANGE_ID}")
config.options.add("sonar.github.repository=${config.githubOrg}/${config.githubRepo}")
if(config.githubApiUrl) config.options.add("sonar.github.endpoint=${config.githubApiUrl}")
if(config.disableInlineComments) config.options.add("sonar.github.disableInlineComments=${config.disableInlineComments}")
workerForGithubAuth(config)
}
} else {
// see https://sonarcloud.io/documentation/analysis/pull-request/
config.options.add("sonar.pullrequest.key=${env.CHANGE_ID}")
config.options.add("sonar.pullrequest.base=${env.CHANGE_TARGET}")
config.options.add("sonar.pullrequest.branch=${env.BRANCH_NAME}")
config.options.add("sonar.pullrequest.provider=${config.pullRequestProvider}")
switch(config.pullRequestProvider){
case 'github':
config.options.add("sonar.pullrequest.github.repository=${config.githubOrg}/${config.githubRepo}")
break;
default: error "Pull-Request provider '${config.pullRequestProvider}' is not supported!"
}
workerForGithubAuth(config)
}
}
}
dockerExecute(
script: script,
dockerImage: configuration.dockerImage
){
worker(configuration)
}
}
}
private Boolean isPullRequest(){
return env.CHANGE_ID
}
private void loadSonarScanner(config){
def filename = new File(config.sonarScannerDownloadUrl).getName()
def foldername = filename.replace('.zip', '').replace('cli-', '')
sh """
curl --remote-name --remote-header-name --location --silent --show-error ${config.sonarScannerDownloadUrl}
unzip -q ${filename}
mv ${foldername} .sonar-scanner
"""
}

View File

@ -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}'.")
}

View File

@ -70,7 +70,7 @@ import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrati
'transportRequestId',
/** @see transportRequestCreate */
'verbose',
])
])
/** Releases a Transport Request. */
@GenerateDocumentation

View File

@ -63,7 +63,7 @@ import static com.sap.piper.Prerequisites.checkScript
'userTokenCredentialsId',
/**
* Type of development stack used to implement the solution.
* @possibleValues `maven`, `mta`, `npm`, `pip`, `sbt`
* @possibleValues `golang`, `maven`, `mta`, `npm`, `pip`, `sbt`
*/
'scanType',
/**