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:
commit
887e8e93e2
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@ -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
2
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
.idea/
|
||||
bin/
|
||||
/bin
|
||||
.settings
|
||||
logs
|
||||
reports
|
||||
|
@ -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
|
||||
|
@ -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`
|
||||
|
135
consumer-test/TestRunnerThread.groovy
Normal file
135
consumer-test/TestRunnerThread.groovy
Normal 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)
|
||||
}
|
||||
}
|
232
consumer-test/consumerTestController.groovy
Normal file
232
consumer-test/consumerTestController.groovy
Normal 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)
|
||||
}
|
@ -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
|
@ -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"
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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:
|
||||
|
20
documentation/docs/steps/detectExecuteScan.md
Normal file
20
documentation/docs/steps/detectExecuteScan.md
Normal 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}
|
@ -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
|
||||
|
||||
|
27
documentation/docs/steps/kanikoExecute.md
Normal file
27
documentation/docs/steps/kanikoExecute.md
Normal 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}
|
18
documentation/docs/steps/multicloudDeploy.md
Normal file
18
documentation/docs/steps/multicloudDeploy.md
Normal 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'
|
||||
)
|
||||
```
|
@ -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
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
|
||||
# ${docGenStepName}
|
||||
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## ${docGenParameters}
|
||||
|
@ -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
|
||||
|
||||
|
11
documentation/docs/steps/pipelineStashFilesAfterBuild.md
Normal file
11
documentation/docs/steps/pipelineStashFilesAfterBuild.md
Normal file
@ -0,0 +1,11 @@
|
||||
# ${docGenStepName}
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## Prerequsites
|
||||
|
||||
none
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
## ${docGenConfiguration}
|
11
documentation/docs/steps/pipelineStashFilesBeforeBuild.md
Normal file
11
documentation/docs/steps/pipelineStashFilesBeforeBuild.md
Normal file
@ -0,0 +1,11 @@
|
||||
# ${docGenStepName}
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## Prerequsites
|
||||
|
||||
none
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
## ${docGenConfiguration}
|
7
documentation/docs/steps/piperPipelineStagePost.md
Normal file
7
documentation/docs/steps/piperPipelineStagePost.md
Normal file
@ -0,0 +1,7 @@
|
||||
# ${docGenStepName}
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
## ${docGenConfiguration}
|
@ -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
|
||||
|
||||
|
@ -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}
|
||||
|
||||
|
18
documentation/docs/steps/sonarExecuteScan.md
Normal file
18
documentation/docs/steps/sonarExecuteScan.md
Normal 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
|
@ -8,11 +8,8 @@
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
|
||||
## ${docGenConfiguration}
|
||||
|
||||
|
||||
|
||||
The step is configured using a customer configuration file provided as
|
||||
resource in an custom shared library.
|
||||
|
||||
|
@ -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
|
||||
|
8
pom.xml
8
pom.xml
@ -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>
|
||||
|
@ -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'
|
||||
|
@ -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:
|
||||
|
5
src/com/sap/piper/CloudPlatform.groovy
Normal file
5
src/com/sap/piper/CloudPlatform.groovy
Normal file
@ -0,0 +1,5 @@
|
||||
package com.sap.piper
|
||||
|
||||
enum CloudPlatform {
|
||||
NEO, CLOUD_FOUNDRY
|
||||
}
|
@ -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}.")
|
||||
}
|
||||
|
34
src/com/sap/piper/DeploymentType.groovy
Normal file
34
src/com/sap/piper/DeploymentType.groovy
Normal 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}")
|
||||
}
|
||||
}
|
||||
}
|
@ -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}/ }
|
||||
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
* <paranoia>&/ltThe keys are also not copied/cloned, even if they are
|
||||
* Maps or Collections;paranoia>
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 += [
|
||||
|
@ -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.")
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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 = []
|
||||
|
143
test/groovy/DetectExecuteScanTest.groovy
Normal file
143
test/groovy/DetectExecuteScanTest.groovy
Normal 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"))
|
||||
}
|
||||
}
|
143
test/groovy/KanikoExecuteTest.groovy
Normal file
143
test/groovy/KanikoExecuteTest.groovy
Normal 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'),
|
||||
)))
|
||||
}
|
||||
}
|
@ -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>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
256
test/groovy/MulticloudDeployTest.groovy
Normal file
256
test/groovy/MulticloudDeployTest.groovy
Normal 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')
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
122
test/groovy/PiperPipelineStageInitTest.groovy
Normal file
122
test/groovy/PiperPipelineStageInitTest.groovy
Normal 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'
|
||||
))
|
||||
}
|
||||
}
|
69
test/groovy/PiperPipelineStagePostTest.groovy
Normal file
69
test/groovy/PiperPipelineStagePostTest.groovy
Normal 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'))
|
||||
}
|
||||
}
|
237
test/groovy/SonarExecuteTest.groovy
Normal file
237
test/groovy/SonarExecuteTest.groovy
Normal 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()
|
||||
}
|
||||
}
|
@ -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'),
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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 ->
|
||||
|
@ -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'
|
||||
))
|
||||
}
|
||||
}
|
||||
|
55
test/groovy/util/JenkinsMockStepRule.groovy
Normal file
55
test/groovy/util/JenkinsMockStepRule.groovy
Normal 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()
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
8
test/resources/deploy/pom.xml
Normal file
8
test/resources/deploy/pom.xml
Normal 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>
|
8
test/resources/kaniko/config.json
Normal file
8
test/resources/kaniko/config.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"auths": {
|
||||
"docker.my.domain.com:4444": {
|
||||
"auth": "myAuth",
|
||||
"email": "my.user@domain.com"
|
||||
}
|
||||
}
|
||||
}
|
@ -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',
|
||||
/**
|
||||
|
4
vars/buildSetResult.groovy
Normal file
4
vars/buildSetResult.groovy
Normal file
@ -0,0 +1,4 @@
|
||||
void call(currentBuild, result = 'SUCCESS') {
|
||||
echo "Current build result is ${currentBuild.result}, setting it to ${result}."
|
||||
currentBuild.result = result
|
||||
}
|
@ -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
|
||||
|
154
vars/detectExecuteScan.groovy
Normal file
154
vars/detectExecuteScan.groovy
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -42,4 +42,3 @@ def call(Map parameters = [:], body) {
|
||||
|
||||
return duration
|
||||
}
|
||||
|
||||
|
@ -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: ``.*/
|
||||
|
@ -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)
|
||||
|
@ -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
127
vars/kanikoExecute.groovy
Normal 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
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
141
vars/multicloudDeploy.groovy
Normal file
141
vars/multicloudDeploy.groovy
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
]]
|
||||
])
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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') {
|
||||
|
@ -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') {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
51
vars/piperPipelineStagePost.groovy
Normal file
51
vars/piperPipelineStagePost.groovy
Normal 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
|
||||
}
|
||||
}
|
@ -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 */
|
||||
|
193
vars/sonarExecuteScan.groovy
Normal file
193
vars/sonarExecuteScan.groovy
Normal 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
|
||||
"""
|
||||
}
|
@ -184,30 +184,33 @@ void call(parameters = [:]) {
|
||||
try {
|
||||
if(backendType == BackendType.SOLMAN) {
|
||||
transportRequestId = cm.createTransportRequestSOLMAN(
|
||||
configuration.changeManagement.solman.docker,
|
||||
configuration.changeDocumentId,
|
||||
configuration.developmentSystemId,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.changeManagement.clientOpts)
|
||||
configuration.changeManagement.solman.docker,
|
||||
configuration.changeDocumentId,
|
||||
configuration.developmentSystemId,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.changeManagement.clientOpts
|
||||
)
|
||||
} else if(backendType == BackendType.CTS) {
|
||||
transportRequestId = cm.createTransportRequestCTS(
|
||||
configuration.changeManagement.cts.docker,
|
||||
configuration.transportType,
|
||||
configuration.targetSystem,
|
||||
configuration.description,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.changeManagement.clientOpts)
|
||||
configuration.changeManagement.cts.docker,
|
||||
configuration.transportType,
|
||||
configuration.targetSystem,
|
||||
configuration.description,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.changeManagement.clientOpts
|
||||
)
|
||||
} else if (backendType == BackendType.RFC) {
|
||||
transportRequestId = cm.createTransportRequestRFC(
|
||||
configuration.changeManagement.rfc.docker,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.rfc.developmentInstance,
|
||||
configuration.changeManagement.rfc.developmentClient,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.description,
|
||||
configuration.verbose)
|
||||
configuration.changeManagement.rfc.docker,
|
||||
configuration.changeManagement.endpoint,
|
||||
configuration.changeManagement.rfc.developmentInstance,
|
||||
configuration.changeManagement.rfc.developmentClient,
|
||||
configuration.changeManagement.credentialsId,
|
||||
configuration.description,
|
||||
configuration.verbose
|
||||
)
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid backend type: '${backendType}'.")
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrati
|
||||
'transportRequestId',
|
||||
/** @see transportRequestCreate */
|
||||
'verbose',
|
||||
])
|
||||
])
|
||||
|
||||
/** Releases a Transport Request. */
|
||||
@GenerateDocumentation
|
||||
|
@ -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',
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user