1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00

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

This commit is contained in:
Marcus Holl 2019-03-14 13:07:30 +01:00
commit d4b0549f74
40 changed files with 1744 additions and 642 deletions

2
.gitignore vendored
View File

@ -16,4 +16,4 @@ target/
targets/
documentation/docs-gen
consumer-test/workspace
consumer-test/**/workspace

View File

@ -1,6 +1,7 @@
branches:
only:
- master
- /^it\/.*$/
language: groovy
sudo: required
services:
@ -35,8 +36,9 @@ jobs:
cp -r documentation/docs documentation/docs-tmp
documentation/bin/createDocu.sh vars documentation/docs-tmp/steps
docker run --rm -it -v ${TRAVIS_BUILD_DIR}:/docs -w /docs/documentation squidfunk/mkdocs-material:3.0.4 build --clean --verbose --strict
- name: Consumer Tests for s4sdk pipeline
script: cd consumer-test && chmod +x runTests.sh && ./runTests.sh
- 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
- stage: Docs
name: Deploy

19
Jenkinsfile vendored
View File

@ -1,19 +0,0 @@
node {
try {
lock(resource: "sap-jenkins-library/10", inversePrecedence: true) {
milestone 10
deleteDir()
stage ('Checkout'){
checkout scm
}
stage ('Test') {
sh "mvn clean test --batch-mode"
}
}
} catch (Throwable err) {
echo "Error occured: ${err}"
currentBuild.result = 'FAILURE'
mail subject: '[Build failed] SAP/jenkins-library', body: 'Fix the build.', to: 'marcus.holl@sap.com,oliver.nocon@sap.com'
throw err
}
}

View File

@ -108,7 +108,7 @@ Register to our [google group][google-group] in order to get updates or for aski
Read and understand our [contribution guidelines][piper-library-contribution]
before opening a pull request.
# [License][piper-library-license]
# License
Copyright (c) 2017 SAP SE or an SAP affiliate company. All rights reserved.
This file is licensed under the Apache Software License, v. 2 except as noted

View File

@ -0,0 +1,138 @@
#!/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 currenty
# 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 auxiliar 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%:*}")
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

30
consumer-test/runTest.sh Executable file
View File

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

View File

@ -1,14 +0,0 @@
#!/bin/bash -e
LIBRARY_VERSION_UNDER_TEST=$(git log --format="%H" -n 1)
REPOSITORY_UNDER_TEST=${TRAVIS_REPO_SLUG:-SAP/jenkins-library}
rm -rf workspace
git clone -b consumer-test https://github.com/sap/cloud-s4-sdk-book workspace
cp -f jenkins.yml workspace
cd workspace
sed -i -e "s:__REPO_SLUG__:${REPOSITORY_UNDER_TEST}:g" jenkins.yml
echo "@Library(\"piper-library-os@$LIBRARY_VERSION_UNDER_TEST\") _" | cat - Jenkinsfile > temp && mv temp Jenkinsfile
git commit --all --author="piper-testing-bot <null@null.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=consumer-test ppiper/jenkinsfile-runner

View File

@ -0,0 +1,2 @@
# Empty for the moment.
# Might contain test configuration in the future.

View File

@ -0,0 +1,2 @@
# Empty for the moment.
# Might contain test configuration in the future.

View File

@ -2,7 +2,7 @@
## Description
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Prerequisites
@ -10,11 +10,11 @@ Content here is generated from corresponnding step, see `vars`.
## Parameters
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Step configuration
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Exceptions

View File

@ -2,11 +2,11 @@
## Description
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Parameters
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Kubernetes support
@ -14,7 +14,7 @@ If the Jenkins is setup on a Kubernetes cluster, then you can execute the closur
## Step configuration
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Side effects

View File

@ -2,7 +2,7 @@
## Description
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Prerequisites
@ -13,11 +13,11 @@ Content here is generated from corresponnding step, see `vars`.
## Parameters
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Step configuration
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Side effects

View File

@ -2,82 +2,20 @@
## Description
In this step the ([Karma test runner](http://karma-runner.github.io)) is executed.
The step is using the `seleniumExecuteTest` step to spins up two containers in a Docker network:
- a Selenium/Chrome container (`selenium/standalone-chrome`)
- a NodeJS container (`node:8-stretch`)
In the Docker network, the containers can be referenced by the values provided in `dockerName` and `sidecarName`, the default values are `karma` and `selenium`. These values must be used in the `hostname` properties of the test configuration ([Karma](https://karma-runner.github.io/1.0/config/configuration-file.html) and [WebDriver](https://github.com/karma-runner/karma-webdriver-launcher#usage)).
!!! note
In a Kubernetes environment, the containers both need to be referenced with `localhost`.
Content here is generated from corresponnding step, see `vars`.
## Prerequisites
- **running Karma tests** - have a NPM module with running tests executed with Karma
- **configured WebDriver** - have the [`karma-webdriver-launcher`](https://github.com/karma-runner/karma-webdriver-launcher) package installed and a custom, WebDriver-based browser configured in Karma
* **running Karma tests** - have a NPM module with running tests executed with Karma
* **configured WebDriver** - have the [`karma-webdriver-launcher`](https://github.com/karma-runner/karma-webdriver-launcher) package installed and a custom, WebDriver-based browser configured in Karma
## Parameters
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
|script|yes|||
|containerPortMappings|no|`[node:8-stretch: [[containerPort: 9876, hostPort: 9876]]]`||
|dockerEnvVars|no|`[ NO_PROXY: 'localhost,karma,$NO_PROXY', no_proxy: 'localhost,karma,$no_proxy']`||
|dockerImage|no|`node:8-stretch`||
|dockerName|no|`karma`||
|dockerWorkspace|no|`/home/node`||
|failOnError|no|||
|installCommand|no|`npm install --quiet`||
|modules|no|`['.']`||
|runCommand|no|`npm run karma`||
|sidecarEnvVars|no|`[ NO_PROXY: 'localhost,selenium,$NO_PROXY', no_proxy: 'localhost,selenium,$no_proxy']`||
|sidecarImage|no|||
|sidecarName|no|||
|sidecarVolumeBind|no|||
|stashContent|no|`['buildDescriptor', 'tests']`||
- `script` - defines the global script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for storing the measured duration.
- `containerPortMappings` - see step [dockerExecute](dockerExecute.md)
- `dockerEnvVars` - see step [dockerExecute](dockerExecute.md)
- `dockerImage` - see step [dockerExecute](dockerExecute.md)
- `dockerName` - see step [dockerExecute](dockerExecute.md)
- `dockerWorkspace` - see step [dockerExecute](dockerExecute.md)
- `failOnError` - see step [seleniumExecuteTests](seleniumExecuteTests.md)
- `installCommand` - the command that is executed to install dependencies
- `modules` - define the paths of the modules to execute tests on
- `runCommand` - the command that is executed to start the tests
- `sidecarEnvVars` - see step [dockerExecute](dockerExecute.md)
- `sidecarImage` - see step [dockerExecute](dockerExecute.md)
- `sidecarName` - see step [dockerExecute](dockerExecute.md)
- `sidecarVolumeBind` - see step [dockerExecute](dockerExecute.md)
- `stashContent` - pass specific stashed that should be considered for the tests
Content here is generated from corresponnding step, see `vars`.
## 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||||
|containerPortMappings|X|X|X|
|dockerEnvVars|X|X|X|
|dockerImage|X|X|X|
|dockerName|X|X|X|
|dockerWorkspace|X|X|X|
|failOnError|X|X|X|
|installCommand|X|X|X|
|modules|X|X|X|
|runCommand|X|X|X|
|sidecarEnvVars|X|X|X|
|sidecarImage|X|X|X|
|sidecarName|X|X|X|
|sidecarVolumeBind|X|X|X|
|stashContent|X|X|X|
Content here is generated from corresponnding step, see `vars`.
## Side effects

View File

@ -0,0 +1,23 @@
# npmExecute
## Description
Content here is generated from corresponding step, see `vars`.
## Parameters
Content here is generated from corresponding step, see `vars`.
## Step configuration
Content here is generated from corresponding step, see `vars`.
## Exceptions
None
## Examples
```groovy
npmExecute script: this, dockerImage: 'node:8-stretch', npmCommand: 'run build'
```

View File

@ -2,18 +2,21 @@
## Description
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Prerequisites
## Parameters
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Step configuration
Content here is generated from corresponnding step, see `vars`.
Content here is generated from corresponding step, see `vars`.
## Exceptions
If you see an error like `fatal: Not a git repository (or any parent up to mount point /home/jenkins)` it is likely that your test description cannot be found.<br />
Please make sure to point parameter `testOptions` to your `conf.js` file like `testOptions: './path/to/my/tests/conf.js'`
## Examples

View File

@ -24,6 +24,7 @@ nav:
- mtaBuild: steps/mtaBuild.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

View File

@ -16,6 +16,12 @@ general:
from: 'origin/master'
to: 'HEAD'
format: '%b'
rfc:
docker:
image: 'rfc'
options: []
envVars: {}
pullImage: true
githubApiUrl: 'https://api.github.com'
githubServerUrl: 'https://github.com'
gitSshKeyCredentialsId: '' #needed to allow sshagent to run with local ssh key
@ -252,6 +258,8 @@ steps:
newmanRunCommand: "run '${config.newmanCollection}' --environment '${config.newmanEnvironment}' --globals '${config.newmanGlobals}' --reporters junit,html --reporter-junit-export 'target/newman/TEST-${collectionDisplayName}.xml' --reporter-html-export 'target/newman/TEST-${collectionDisplayName}.html'"
stashContent:
- 'tests'
npmExecute:
dockerImage: 'node:8-stretch'
pipelineRestartSteps:
sendMail: true
timeoutInSeconds: 900
@ -361,8 +369,14 @@ steps:
failIfStatusIsNotInDevelopment: true
transportRequestCreate:
developmentSystemId: null
verbose: false
transportRequestUploadFile:
acceptUnixStyleLineEndings: true
codePage: 'UTF-8'
failOnWarning: true
verbose: false
transportRequestRelease:
verbose: false
uiVeri5ExecuteTests:
failOnError: false
dockerEnvVars: {}

View File

@ -2,6 +2,7 @@ package com.sap.piper
import com.cloudbees.groovy.cps.NonCPS
import com.sap.piper.analytics.Telemetry
import groovy.text.SimpleTemplateEngine
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
@ -98,3 +99,10 @@ void pushToSWA(Map parameters, Map config) {
// some error occured in telemetry reporting. This should not break anything though.
}
}
@NonCPS
static String fillTemplate(String templateText, Map binding){
def engine = new SimpleTemplateEngine()
String result = engine.createTemplate(templateText).make(binding)
return result
}

View File

@ -1,5 +1,5 @@
package com.sap.piper.cm;
public enum BackendType {
SOLMAN, CTS, NONE
SOLMAN, CTS, RFC, NONE
}

View File

@ -2,6 +2,7 @@ package com.sap.piper.cm
import com.sap.piper.GitUtils
import groovy.json.JsonSlurper
import hudson.AbortException
@ -63,7 +64,7 @@ public class ChangeManagement implements Serializable {
}
boolean isChangeInDevelopment(String changeId, String endpoint, String credentialsId, String clientOpts = '') {
int rc = executeWithCredentials(BackendType.SOLMAN, endpoint, credentialsId, 'is-change-in-development', ['-cID', "'${changeId}'", '--return-code'],
int rc = executeWithCredentials(BackendType.SOLMAN, [:], endpoint, credentialsId, 'is-change-in-development', ['-cID', "'${changeId}'", '--return-code'],
false,
clientOpts) as int
@ -78,7 +79,7 @@ public class ChangeManagement implements Serializable {
String createTransportRequestCTS(String transportType, String targetSystemId, String description, String endpoint, String credentialsId, String clientOpts = '') {
try {
def transportRequest = executeWithCredentials(BackendType.CTS, endpoint, credentialsId, 'create-transport',
def transportRequest = executeWithCredentials(BackendType.CTS, [:], endpoint, credentialsId, 'create-transport',
['-tt', transportType, '-ts', targetSystemId, '-d', "\"${description}\""],
true,
clientOpts)
@ -91,7 +92,7 @@ public class ChangeManagement implements Serializable {
String createTransportRequestSOLMAN(String changeId, String developmentSystemId, String endpoint, String credentialsId, String clientOpts = '') {
try {
def transportRequest = executeWithCredentials(BackendType.SOLMAN, endpoint, credentialsId, 'create-transport', ['-cID', changeId, '-dID', developmentSystemId],
def transportRequest = executeWithCredentials(BackendType.SOLMAN, [:], endpoint, credentialsId, 'create-transport', ['-cID', changeId, '-dID', developmentSystemId],
true,
clientOpts)
return (transportRequest as String)?.trim()
@ -100,86 +101,311 @@ public class ChangeManagement implements Serializable {
}
}
void uploadFileToTransportRequest(BackendType type, String changeId, String transportRequestId, String applicationId, String filePath, String endpoint, String credentialsId, String cmclientOpts = '') {
String createTransportRequestRFC(
Map docker,
String endpoint,
String developmentInstance,
String developmentClient,
String credentialsId,
String description,
boolean verbose) {
def args = null
def command = 'cts createTransportRequest'
def args = [
TRANSPORT_DESCRIPTION: description,
ABAP_DEVELOPMENT_INSTANCE: developmentInstance,
ABAP_DEVELOPMENT_CLIENT: developmentClient,
VERBOSE: verbose,
]
if(type == BackendType.SOLMAN) {
args = ['-cID', changeId,
'-tID', transportRequestId,
applicationId, "\"$filePath\""]
} else if (type == BackendType.CTS) {
args = ['-tID', transportRequestId,
"\"$filePath\""]
} else {
throw new IllegalArgumentException("Invalid backend type: ${type}")
try {
def transportRequestId = executeWithCredentials(
BackendType.RFC,
docker,
endpoint,
credentialsId,
command,
args,
true)
return new JsonSlurper().parseText(transportRequestId).REQUESTID
} catch(AbortException ex) {
throw new ChangeManagementException(
"Cannot create transport request: ${ex.getMessage()}", ex)
}
int rc = executeWithCredentials(type,
endpoint,
credentialsId,
'upload-file-to-transport',
args,
false,
cmclientOpts) as int
if(rc == 0) {
return
} else {
throw new ChangeManagementException("Cannot upload file '$filePath' for change document '$changeId' with transport request '$transportRequestId'. Return code from cmclient: $rc.")
}
}
def executeWithCredentials(BackendType type, String endpoint, String credentialsId, String command, List<String> args, boolean returnStdout = false, String clientOpts = '') {
void uploadFileToTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String applicationId,
String filePath,
String endpoint,
String credentialsId,
String cmclientOpts = '') {
def args = [
'-cID', changeId,
'-tID', transportRequestId,
applicationId, "\"$filePath\""
]
int rc = executeWithCredentials(
BackendType.SOLMAN,
[:],
endpoint,
credentialsId,
'upload-file-to-transport',
args,
false,
cmclientOpts) as int
if(rc != 0) {
throw new ChangeManagementException(
"Cannot upload file into transport request. Return code from cm client: $rc.")
}
}
void uploadFileToTransportRequestCTS(
String transportRequestId,
String filePath,
String endpoint,
String credentialsId,
String cmclientOpts = '') {
def args = [
'-tID', transportRequestId,
"\"$filePath\""
]
int rc = executeWithCredentials(
BackendType.CTS,
[:],
endpoint,
credentialsId,
'upload-file-to-transport',
args,
false,
cmclientOpts) as int
if(rc != 0) {
throw new ChangeManagementException(
"Cannot upload file into transport request. Return code from cm client: $rc.")
}
}
void uploadFileToTransportRequestRFC(
Map docker,
String transportRequestId,
String applicationName,
String filePath,
String endpoint,
String credentialsId,
String developmentInstance,
String developmentClient,
String applicationDescription,
String abapPackage,
String codePage,
boolean acceptUnixStyleEndOfLine,
boolean failOnWarning,
boolean verbose) {
def args = [
ABAP_DEVELOPMENT_INSTANCE: developmentInstance,
ABAP_DEVELOPMENT_CLIENT: developmentClient,
ABAP_APPLICATION_NAME: applicationName,
ABAP_APPLICATION_DESC: applicationDescription,
ABAP_PACKAGE: abapPackage,
ZIP_FILE_URL: filePath,
CODE_PAGE: codePage,
ABAP_ACCEPT_UNIX_STYLE_EOL: acceptUnixStyleEndOfLine ? 'X' : '-',
FAIL_UPLOAD_ON_WARNING: Boolean.toString(failOnWarning),
VERBOSE: Boolean.toString(verbose),
]
int rc = executeWithCredentials(
BackendType.RFC,
docker,
endpoint,
credentialsId,
"cts uploadToABAP:${transportRequestId}",
args,
false) as int
if(rc != 0) {
throw new ChangeManagementException(
"Cannot upload file into transport request. Return code from rfc client: $rc.")
}
}
def executeWithCredentials(
BackendType type,
Map docker,
String endpoint,
String credentialsId,
String command,
def args,
boolean returnStdout = false,
String clientOpts = '') {
def script = this.script
script.withCredentials([script.usernamePassword(
credentialsId: credentialsId,
passwordVariable: 'password',
usernameVariable: 'username')]) {
def cmScript = getCMCommandLine(type, endpoint, script.username, script.password,
command, args,
clientOpts)
Map shArgs = [:]
if(returnStdout)
shArgs.put('returnStdout', true)
else
shArgs.put('returnStatus', true)
shArgs.put('script', cmScript)
def result = 1
// user and password are masked by withCredentials
script.echo """[INFO] Executing command line: "${cmScript}"."""
return script.sh(shArgs)
switch(type) {
case BackendType.RFC:
if(! (args in Map)) {
throw new IllegalArgumentException("args expected as Map for backend types ${[BackendType.RFC]}")
}
shArgs.script = command
args = args.plus([
ABAP_DEVELOPMENT_SERVER: endpoint,
ABAP_DEVELOPMENT_USER: script.username,
ABAP_DEVELOPMENT_PASSWORD: script.password,
])
// user and password are masked by withCredentials
script.echo """[INFO] Executing command line: "${shArgs.script}"."""
script.dockerExecute(
script: script,
dockerImage: docker.image,
dockerOptions: docker.options,
dockerEnvVars: (docker.envVars?:[:]).plus(args),
dockerPullImage: docker.pullImage) {
result = script.sh(shArgs)
}
break
case BackendType.SOLMAN:
case BackendType.CTS:
if(! (args in Collection))
throw new IllegalArgumentException("args expected as Collection for backend types ${[BackendType.SOLMAN, BackendType.CTS]}")
shArgs.script = getCMCommandLine(type, endpoint, script.username, script.password,
command, args,
clientOpts)
// user and password are masked by withCredentials
script.echo """[INFO] Executing command line: "${shArgs.script}"."""
result = script.sh(shArgs)
break
}
return result
}
}
void releaseTransportRequest(BackendType type,String changeId, String transportRequestId, String endpoint, String credentialsId, String clientOpts = '') {
void releaseTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String endpoint,
String credentialsId,
String clientOpts = '') {
def cmd
List args = []
def cmd = 'release-transport'
def args = [
'-cID',
changeId,
'-tID',
transportRequestId,
]
if(type == BackendType.SOLMAN) {
cmd = 'release-transport'
args << '-cID'
args << changeId
} else if(type == BackendType.CTS) {
cmd = 'export-transport'
} else {
throw new IllegalStateException("Invalid backend type: '${type}'")
}
int rc = executeWithCredentials(
BackendType.SOLMAN,
[:],
endpoint,
credentialsId,
cmd,
args,
false,
clientOpts) as int
args << '-tID'
args << transportRequestId
int rc = executeWithCredentials(type, endpoint, credentialsId, cmd, args, false, clientOpts) as int
if(rc == 0) {
return
} else {
if(rc != 0) {
throw new ChangeManagementException("Cannot release Transport Request '$transportRequestId'. Return code from cmclient: $rc.")
}
}
void releaseTransportRequestCTS(
String transportRequestId,
String endpoint,
String credentialsId,
String clientOpts = '') {
def cmd = 'export-transport'
def args = [
'-tID',
transportRequestId,
]
int rc = executeWithCredentials(
BackendType.CTS,
[:],
endpoint,
credentialsId,
cmd,
args,
false) as int
if(rc != 0) {
throw new ChangeManagementException("Cannot release Transport Request '$transportRequestId'. Return code from cmclient: $rc.")
}
}
void releaseTransportRequestRFC(
Map docker,
String transportRequestId,
String endpoint,
String developmentInstance,
String developmentClient,
String credentialsId,
boolean verbose) {
def cmd = "cts releaseTransport:${transportRequestId}"
def args = [
ABAP_DEVELOPMENT_INSTANCE: developmentInstance,
ABAP_DEVELOPMENT_CLIENT: developmentClient,
VERBOSE: verbose,
]
int rc = executeWithCredentials(
BackendType.RFC,
docker,
endpoint,
credentialsId,
cmd,
args,
false) as int
if(rc != 0) {
throw new ChangeManagementException("Cannot release Transport Request '$transportRequestId'. Return code from rfcclient: $rc.")
}
}
String getCMCommandLine(BackendType type,
String endpoint,
String username,

View File

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

View File

@ -9,25 +9,23 @@ class NeoCommandHelper {
private Script step
private DeployMode deployMode
private Map deploymentConfiguration
private String pathToNeoExecutable
private String user
private String password
private String source
//Warning: Commands generated with this class can contain passwords and should only be used within the step withCredentials
NeoCommandHelper(Script step, DeployMode deployMode, Map deploymentConfiguration, String pathToNeoExecutable,
NeoCommandHelper(Script step, DeployMode deployMode, Map deploymentConfiguration,
String user, String password, String source) {
this.step = step
this.deployMode = deployMode
this.deploymentConfiguration = deploymentConfiguration
this.pathToNeoExecutable = pathToNeoExecutable
this.user = user
this.password = password
this.source = source
}
private String prolog() {
return "\"${pathToNeoExecutable}\""
return 'neo.sh'
}
String statusCommand() {

View File

@ -115,9 +115,11 @@ class FioriOnCloudPlatformPipelineTest extends BasePiperTest {
.commonPipelineEnvironment
.configuration = [steps:
[neoDeploy:
[ host: 'hana.example.com',
account: 'myTestAccount',
]
[neo:
[ host: 'hana.example.com',
account: 'myTestAccount',
]
]
]
]
@ -139,7 +141,7 @@ class FioriOnCloudPlatformPipelineTest extends BasePiperTest {
// the neo deploy call:
Assert.assertThat(shellRule.shell,
new CommandLineMatcher()
.hasProlog("\"/opt/sap/neo/tools/neo.sh\" deploy-mta")
.hasProlog("neo.sh deploy-mta")
.hasSingleQuotedOption('host', 'hana\\.example\\.com')
.hasSingleQuotedOption('account', 'myTestAccount')
.hasSingleQuotedOption('password', 'terceSpot')

View File

@ -84,109 +84,10 @@ class NeoDeployTest extends BasePiperTest {
helper.registerAllowedMethod('dockerExecute', [Map, Closure], null)
helper.registerAllowedMethod('fileExists', [String], { s -> return new File(workspacePath, s).exists() })
helper.registerAllowedMethod('pwd', [], { return workspacePath })
mockShellCommands()
nullScript.commonPipelineEnvironment.configuration = [steps: [neoDeploy: [neo: [host: 'test.deploy.host.com', account: 'trialuser123']]]]
}
@Test
void straightForwardTestCompatibilityConfiguration(){
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.* status .*', 'Status: STARTED')
nullScript.commonPipelineEnvironment.configuration = [
steps: [
neoDeploy: [
host: 'test.deploy.host.com',
account: 'trialuser123',
neoCredentialsId: 'myCredentialsId'
]]]
stepRule.step.neoDeploy(script: nullScript,
archivePath: warArchiveName,
deployMode: 'warParams',
applicationName: 'testApp',
runtime: 'neo-javaee6-wp',
runtimeVersion: '2.125',
warAction: 'rolling-update',
vmSize: 'lite')
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" rolling-update")
.hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com')
.hasSingleQuotedOption('account', 'trialuser123')
.hasSingleQuotedOption('application', 'testApp')
.hasSingleQuotedOption('runtime', 'neo-javaee6-wp')
.hasSingleQuotedOption('runtime-version', '2\\.125')
.hasSingleQuotedOption('size', 'lite')
.hasSingleQuotedOption('user', 'anonymous')
.hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*')
.hasSingleQuotedOption('source', '.*\\.war'))
}
@Test
void straightForwardTestConfigViaConfigProperties() {
boolean buildStatusHasBeenSet = false
boolean notifyOldConfigFrameworkUsed = false
nullScript.commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'test.deploy.host.com')
nullScript.commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'trialuser123')
nullScript.commonPipelineEnvironment.configuration = [:]
nullScript.currentBuild = [setResult: { buildStatusHasBeenSet = true }]
def utils = new Utils() {
void pushToSWA(Map parameters, Map config) {
notifyOldConfigFrameworkUsed = parameters.stepParam4
}
}
stepRule.step.neoDeploy(script: nullScript,
source: archiveName,
neo: [credentialsId: 'myCredentialsId'],
utils: utils
)
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" deploy-mta")
.hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com')
.hasSingleQuotedOption('account', 'trialuser123')
.hasOption('synchronous', '')
.hasSingleQuotedOption('user', 'anonymous')
.hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*')
.hasSingleQuotedOption('source', '.*'))
assert !buildStatusHasBeenSet
assert notifyOldConfigFrameworkUsed
}
@Test
void testConfigViaConfigPropertiesSetsBuildToUnstable() {
def buildStatus = 'SUCCESS'
nullScript.commonPipelineEnvironment.setConfigProperty('DEPLOY_HOST', 'test.deploy.host.com')
nullScript.commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'trialuser123')
nullScript.commonPipelineEnvironment.configuration = [:]
nullScript.currentBuild = [setResult: { r -> buildStatus = r }]
System.setProperty('com.sap.piper.featureFlag.buildUnstableWhenOldConfigFrameworkIsUsedByNeoDeploy',
Boolean.TRUE.toString())
try {
stepRule.step.neoDeploy(script: nullScript,
source: archiveName,
neo:[credentialsId: 'myCredentialsId'],
utils: utils
)
} finally {
System.clearProperty('com.sap.piper.featureFlag.buildUnstableWhenOldConfigFrameworkIsUsedByNeoDeploy')
}
assert buildStatus == 'UNSTABLE'
}
@Test
void straightForwardTestConfigViaConfiguration() {
@ -205,7 +106,7 @@ class NeoDeployTest extends BasePiperTest {
)
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" deploy-mta")
new CommandLineMatcher().hasProlog("neo.sh deploy-mta")
.hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com')
.hasSingleQuotedOption('account', 'trialuser123')
.hasOption('synchronous', '')
@ -231,7 +132,7 @@ class NeoDeployTest extends BasePiperTest {
)
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" deploy-mta")
new CommandLineMatcher().hasProlog("neo.sh deploy-mta")
.hasSingleQuotedOption('host', 'configuration-frwk\\.deploy\\.host\\.com')
.hasSingleQuotedOption('account', 'configurationFrwkUser123')
.hasOption('synchronous', '')
@ -246,7 +147,7 @@ class NeoDeployTest extends BasePiperTest {
stepRule.step.neoDeploy(script: nullScript)
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" deploy-mta")
new CommandLineMatcher().hasProlog("neo.sh deploy-mta")
.hasSingleQuotedOption('source', 'archive.mtar'))
}
@ -257,7 +158,7 @@ class NeoDeployTest extends BasePiperTest {
source: "archive.mtar")
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" deploy-mta")
new CommandLineMatcher().hasProlog("neo.sh deploy-mta")
.hasSingleQuotedOption('source', 'archive.mtar'))
}
@ -282,7 +183,7 @@ class NeoDeployTest extends BasePiperTest {
)
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" deploy-mta")
new CommandLineMatcher().hasProlog("neo.sh deploy-mta")
.hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com')
.hasSingleQuotedOption('account', 'trialuser123')
.hasOption('synchronous', '')
@ -292,69 +193,6 @@ class NeoDeployTest extends BasePiperTest {
)
}
@Test
void neoHomeNotSetTest() {
mockHomeVariablesNotSet()
stepRule.step.neoDeploy(script: nullScript,
source: archiveName
)
assert shellRule.shell.find { c -> c.contains('"neo.sh" deploy-mta') }
assert loggingRule.log.contains('SAP Cloud Platform Console Client is on PATH.')
assert loggingRule.log.contains("Using SAP Cloud Platform Console Client 'neo.sh'.")
}
@Test
void neoHomeAsParameterTest() {
mockHomeVariablesNotSet()
stepRule.step.neoDeploy(script: nullScript,
source: archiveName,
neo:[credentialsId: 'myCredentialsId'],
neoHome: '/param/neo'
)
assert shellRule.shell.find { c -> c = "\"/param/neo/tools/neo.sh\" deploy-mta" }
assert loggingRule.log.contains("SAP Cloud Platform Console Client home '/param/neo' retrieved from configuration.")
assert loggingRule.log.contains("Using SAP Cloud Platform Console Client '/param/neo/tools/neo.sh'.")
}
@Test
void neoHomeFromEnvironmentTest() {
stepRule.step.neoDeploy(script: nullScript,
source: archiveName
)
assert shellRule.shell.find { c -> c.contains("\"/opt/neo/tools/neo.sh\" deploy-mta") }
assert loggingRule.log.contains("SAP Cloud Platform Console Client home '/opt/neo' retrieved from environment.")
assert loggingRule.log.contains("Using SAP Cloud Platform Console Client '/opt/neo/tools/neo.sh'.")
}
@Test
void neoHomeFromCustomStepConfigurationTest() {
mockHomeVariablesNotSet()
nullScript.commonPipelineEnvironment.configuration = [steps: [neoDeploy: [neo: [host: 'test.deploy.host.com', account: 'trialuser123'], neoHome: '/config/neo']]]
stepRule.step.neoDeploy(script: nullScript,
source: archiveName
)
assert shellRule.shell.find { c -> c = "\"/config/neo/tools/neo.sh\" deploy-mta" }
assert loggingRule.log.contains("SAP Cloud Platform Console Client home '/config/neo' retrieved from configuration.")
assert loggingRule.log.contains("Using SAP Cloud Platform Console Client '/config/neo/tools/neo.sh'.")
}
@Test
void archiveNotProvidedTest() {
@ -392,7 +230,7 @@ class NeoDeployTest extends BasePiperTest {
stepRule.step.neoDeploy(script: nullScript, source: archiveName, deployMode: 'mta')
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" deploy-mta")
new CommandLineMatcher().hasProlog("neo.sh deploy-mta")
.hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com')
.hasSingleQuotedOption('account', 'trialuser123')
.hasOption('synchronous', '')
@ -417,7 +255,7 @@ class NeoDeployTest extends BasePiperTest {
source: warArchiveName)
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" deploy")
new CommandLineMatcher().hasProlog("neo.sh deploy")
.hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com')
.hasSingleQuotedOption('account', 'trialuser123')
.hasSingleQuotedOption('application', 'testApp')
@ -448,7 +286,7 @@ class NeoDeployTest extends BasePiperTest {
)
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" rolling-update")
new CommandLineMatcher().hasProlog("neo.sh rolling-update")
.hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com')
.hasSingleQuotedOption('account', 'trialuser123')
.hasSingleQuotedOption('application', 'testApp')
@ -478,7 +316,7 @@ class NeoDeployTest extends BasePiperTest {
Assert.assertThat(shellRule.shell,
new CommandLineMatcher()
.hasProlog("\"/opt/neo/tools/neo.sh\" deploy")
.hasProlog("neo.sh deploy")
.hasSingleQuotedOption('application', 'testApp'))
}
@ -541,7 +379,7 @@ class NeoDeployTest extends BasePiperTest {
)
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" deploy")
new CommandLineMatcher().hasProlog("neo.sh deploy")
.hasArgument("config.properties")
.hasSingleQuotedOption('user', 'defaultUser')
.hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*')
@ -566,7 +404,7 @@ class NeoDeployTest extends BasePiperTest {
])
Assert.assertThat(shellRule.shell,
new CommandLineMatcher().hasProlog("\"/opt/neo/tools/neo.sh\" rolling-update")
new CommandLineMatcher().hasProlog("neo.sh rolling-update")
.hasArgument('config.properties')
.hasSingleQuotedOption('user', 'defaultUser')
.hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*')
@ -654,57 +492,4 @@ class NeoDeployTest extends BasePiperTest {
size: 'lite'
])
}
@Test
void deployHostProvidedAsDeprecatedParameterTest() {
nullScript.commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'configPropsUser123')
stepRule.step.neoDeploy(script: nullScript,
source: archiveName,
deployHost: "my.deploy.host.com"
)
assert loggingRule.log.contains("[WARNING][neoDeploy] Deprecated parameter 'deployHost' is used. This will not work anymore in future versions. Use parameter 'host' instead.")
}
@Test
void deployAccountProvidedAsDeprecatedParameterTest() {
nullScript.commonPipelineEnvironment.setConfigProperty('CI_DEPLOY_ACCOUNT', 'configPropsUser123')
stepRule.step.neoDeploy(script: nullScript,
source: archiveName,
neo: [
host: "my.deploy.host.com",
],
deployAccount: "myAccount"
)
assert loggingRule.log.contains("Deprecated parameter 'deployAccount' is used. This will not work anymore in future versions. Use parameter 'account' instead.")
}
private mockShellCommands() {
String javaVersion = '''openjdk version \"1.8.0_121\"
OpenJDK Runtime Environment (build 1.8.0_121-8u121-b13-1~bpo8+1-b13)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)'''
shellRule.setReturnValue(Type.REGEX, '.*java -version.*', javaVersion)
String neoVersion = '''SAP Cloud Platform Console Client
SDK version : 3.39.10
Runtime : neo-java-web'''
shellRule.setReturnValue(Type.REGEX, '.*neo.sh version.*', neoVersion)
shellRule.setReturnValue(Type.REGEX, '.*JAVA_HOME.*', '/opt/java')
shellRule.setReturnValue(Type.REGEX, '.*NEO_HOME.*', '/opt/neo')
shellRule.setReturnValue(Type.REGEX, '.*which java.*', 0)
shellRule.setReturnValue(Type.REGEX, '.*which neo.*', 0)
}
private mockHomeVariablesNotSet() {
shellRule.setReturnValue(Type.REGEX, '.*JAVA_HOME.*', '')
shellRule.setReturnValue(Type.REGEX, '.*NEO_HOME.*', '')
shellRule.setReturnValue(Type.REGEX, '.*which java.*', 0)
shellRule.setReturnValue(Type.REGEX, '.*which neo.*', 0)
}
}

View File

@ -0,0 +1,56 @@
import static org.junit.Assert.assertEquals
import hudson.AbortException
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.JenkinsDockerExecuteRule
import util.JenkinsReadYamlRule
import util.JenkinsShellCallRule
import util.JenkinsStepRule
import util.Rules
class NpmExecuteTest extends BasePiperTest {
private ExpectedException thrown = new ExpectedException().none()
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this)
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsReadYamlRule yamlRule = new JenkinsReadYamlRule(this)
@Rule
public RuleChain ruleChain = Rules
.getCommonRules(this)
.around(thrown)
.around(yamlRule)
.around(dockerExecuteRule)
.around(shellRule)
.around(stepRule)
@Before
void init() {
helper.registerAllowedMethod 'fileExists', [String], { s -> s == 'package.json' }
}
@Test
void testNpmExecute() {
stepRule.step.npmExecute(script: nullScript, dockerImage: 'node:8-stretch')
assertEquals 'node:8-stretch', dockerExecuteRule.dockerParams.dockerImage
}
@Test
void testNpmExecuteWithClosure() {
stepRule.step.npmExecute(script: nullScript, dockerImage: 'node:8-stretch', npmCommand: 'run build') { }
assert shellRule.shell.find { c -> c.contains('npm run build') }
}
@Test
void testNoPackageJson() {
helper.registerAllowedMethod 'fileExists', [String], { false }
thrown.expect AbortException
thrown.expectMessage '[npmExecute] package.json is not found.'
stepRule.step.npmExecute(script: nullScript, dockerImage: 'node:8-stretch', npmCommand: 'run build')
}
}

View File

@ -1,3 +1,10 @@
import static org.hamcrest.Matchers.allOf
import static org.hamcrest.Matchers.containsString
import java.util.Map
import org.hamcrest.Matchers
import org.hamcrest.core.StringContains
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -54,7 +61,7 @@ public class TransportRequestCreateTest extends BasePiperTest {
}
@Test
public void changeIdNotProvidedTest() {
public void changeIdNotProvidedSOLANTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("Change document id not provided (parameter: 'changeDocumentId' or via commit history).")
@ -73,7 +80,7 @@ public class TransportRequestCreateTest extends BasePiperTest {
}
@Test
public void developmentSystemIdNotProvidedTest() {
public void developmentSystemIdNotProvidedSOLMANTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage("ERROR - NO VALUE AVAILABLE FOR developmentSystemId")
@ -82,7 +89,7 @@ public class TransportRequestCreateTest extends BasePiperTest {
}
@Test
public void createTransportRequestFailureTest() {
public void createTransportRequestFailureSOLMANTest() {
ChangeManagement cm = new ChangeManagement(nullScript) {
@ -154,8 +161,7 @@ public class TransportRequestCreateTest extends BasePiperTest {
String description,
String endpoint,
String credentialsId,
String clientOpts
) {
String clientOpts) {
result.transportType = transportType
result.targetSystemId = targetSystemId
result.description = description
@ -186,6 +192,120 @@ public class TransportRequestCreateTest extends BasePiperTest {
assert loggingRule.log.contains("[INFO] Transport Request '001' has been successfully created.")
}
@Test
public void createTransportRequestSuccessRFCTest() {
def result = [:]
ChangeManagement cm = new ChangeManagement(nullScript) {
String createTransportRequestRFC(
Map docker,
String endpoint,
String developmentInstance,
String developmentClient,
String credentialsId,
String description,
boolean verbose) {
result.docker = docker
result.endpoint = endpoint
result.developmentClient = developmentClient
result.developmentInstance= developmentInstance
result.credentialsId = credentialsId
result.description = description
result.verbose = verbose
return '001'
}
}
stepRule.step.transportRequestCreate(
script: nullScript,
changeManagement: [
type: 'RFC',
rfc: [
developmentInstance: '01',
developmentClient: '001',
],
endpoint: 'https://example.org/rfc',
],
developmentSystemId: '001',
description: '',
cmUtils: cm,
verbose: true)
assert nullScript.commonPipelineEnvironment.getTransportRequestId() == '001'
assert result == [
docker: [
image: 'rfc',
options: [],
envVars: [:],
pullImage: true
],
endpoint: 'https://example.org/rfc',
developmentClient: '001',
developmentInstance: '01',
credentialsId: 'CM',
description: '',
verbose: true
]
assert loggingRule.log.contains("[INFO] Creating transport request.")
assert loggingRule.log.contains("[INFO] Transport Request '001' has been successfully created.")
}
@Test
public void createTransportRequestFailureRFCTest() {
thrown.expect(AbortException)
thrown.expectMessage('upload failed')
ChangeManagement cm = new ChangeManagement(nullScript) {
String createTransportRequestRFC(
Map docker,
String endpoint,
String developmentClient,
String developmentInstance,
String credentialsId,
String description,
boolean verbose) {
throw new ChangeManagementException('upload failed')
}
}
stepRule.step.transportRequestCreate(
script: nullScript,
changeManagement: [
type: 'RFC',
rfc: [
developmentInstance: '01',
developmentClient: '001',
],
endpoint: 'https://example.org/rfc',
],
developmentSystemId: '001',
description: '',
cmUtils: cm)
}
@Test
public void createTransportRequestSanityChecksRFCTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage(allOf(
containsString('changeManagement/rfc/developmentInstance'),
containsString('changeManagement/rfc/developmentClient'),
))
stepRule.step.transportRequestCreate(
script: nullScript,
changeManagement: [
type: 'RFC',
])
}
@Test
public void cmIntegrationSwichtedOffTest() {

View File

@ -1,3 +1,7 @@
import static org.hamcrest.Matchers.allOf
import static org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -10,6 +14,7 @@ import com.sap.piper.cm.ChangeManagementException
import util.BasePiperTest
import util.JenkinsCredentialsRule
import util.JenkinsDockerExecuteRule
import util.JenkinsStepRule
import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
@ -48,7 +53,7 @@ public class TransportRequestReleaseTest extends BasePiperTest {
}
@Test
public void changeIdNotProvidedTest() {
public void changeDocumentIdNotProvidedSOLMANTest() {
ChangeManagement cm = new ChangeManagement(nullScript) {
String getChangeDocumentId(String from,
@ -84,14 +89,14 @@ public class TransportRequestReleaseTest extends BasePiperTest {
}
@Test
public void releaseTransportRequestFailureTest() {
public void releaseTransportRequestFailsSOLMANTest() {
thrown.expect(AbortException)
thrown.expectMessage("Something went wrong")
ChangeManagement cm = new ChangeManagement(nullScript) {
void releaseTransportRequest(BackendType type,
void releaseTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String endpoint,
@ -106,7 +111,257 @@ public class TransportRequestReleaseTest extends BasePiperTest {
}
@Test
public void releaseTransportRequestSuccessTest() {
public void releaseTransportRequestFailsCTSTest() {
thrown.expect(AbortException)
thrown.expectMessage("Something went wrong")
nullScript
.commonPipelineEnvironment
.configuration
.general
.changeManagement
.type = 'CTS'
ChangeManagement cm = new ChangeManagement(nullScript) {
void releaseTransportRequestCTS(
String transportRequestId,
String endpoint,
String credentialsId,
String clientOpts) {
throw new ChangeManagementException('Something went wrong')
}
}
stepRule.step.transportRequestRelease(
script: nullScript,
transportRequestId: '001',
cmUtils: cm)
}
@Test
public void releaseTransportRequestSuccessRFCTest() {
def receivedParameters
nullScript
.commonPipelineEnvironment
.configuration
.general
.changeManagement =
[
credentialsId: 'CM',
type: 'RFC',
endpoint: 'https://example.org/rfc',
rfc: [
dockerImage: 'rfc',
dockerOptions: [],
],
]
ChangeManagement cm = new ChangeManagement(nullScript) {
void releaseTransportRequestRFC(
Map docker,
String transportRequestId,
String endpoint,
String developmentInstance,
String developmentClient,
String credentialsId,
boolean verbose) {
receivedParameters = [
docker: docker,
transportRequestId: transportRequestId,
endpoint: endpoint,
developmentInstance: developmentInstance,
developmentClient: developmentClient,
credentialsId: credentialsId,
verbose: verbose,
]
}
}
stepRule.step.transportRequestRelease(
script: nullScript,
transportRequestId: '002',
changeManagement: [
rfc: [
developmentClient: '003',
developmentInstance: '002',
]
],
verbose: true,
cmUtils: cm)
assert receivedParameters == [
docker: [
image: 'rfc',
options: [],
envVars: [:],
pullImage: true,
],
transportRequestId: '002',
endpoint: 'https://example.org/rfc',
developmentInstance: '002',
developmentClient: '003',
credentialsId: 'CM',
'verbose': true,
]
}
@Test
public void releaseTransportRequestSuccessCTSTest() {
def receivedParameters
nullScript
.commonPipelineEnvironment
.configuration
.general
.changeManagement =
[
credentialsId: 'CM',
type: 'CTS',
endpoint: 'https://example.org/cts'
]
ChangeManagement cm = new ChangeManagement(nullScript) {
void releaseTransportRequestCTS(
String transportRequestId,
String endpoint,
String credentialsId,
String clientOpts = '') {
receivedParameters = [
transportRequestId: transportRequestId,
endpoint: endpoint,
credentialsId: credentialsId,
clientOpts: clientOpts
]
}
}
stepRule.step.transportRequestRelease(
script: nullScript,
transportRequestId: '002',
cmUtils: cm)
assert receivedParameters == [
transportRequestId: '002',
endpoint: 'https://example.org/cts',
credentialsId: 'CM',
clientOpts: ''
]
}
@Test
public void releaseTransportRequestFailsRFCTest() {
thrown.expect(AbortException)
thrown.expectMessage('Failed releasing transport request.')
nullScript
.commonPipelineEnvironment
.configuration
.general
.changeManagement =
[
credentialsId: 'CM',
type: 'RFC',
endpoint: 'https://example.org/rfc',
rfc: [dockerImage: 'rfc']
]
ChangeManagement cm = new ChangeManagement(nullScript) {
void releaseTransportRequestRFC(
Map docker,
String transportRequestId,
String endpoint,
String developmentInstance,
String developmentClient,
String credentialsId,
boolean verbose) {
throw new ChangeManagementException('Failed releasing transport request.')
}
}
stepRule.step.transportRequestRelease(
script: nullScript,
transportRequestId: '002',
changeManagement: [
rfc: [
developmentClient: '003',
developmentInstance: '002'
]
],
cmUtils: cm)
}
@Test
public void releaseTransportRequestSanityChecksSOLMANTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage(allOf(
containsString('ERROR - NO VALUE AVAILABLE FOR'),
containsString('changeManagement/endpoint')))
// changeDocumentId and transportRequestId are not checked
// by the sanity checks here since they are looked up from
// commit history in case they are not provided.
nullScript
.commonPipelineEnvironment
.configuration = null
stepRule.step.transportRequestRelease(
script: nullScript,
changeManagement: [type: 'SOLMAN']
)
}
@Test
public void releaseTransportRequestSanityChecksCTSTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage(allOf(
containsString('ERROR - NO VALUE AVAILABLE FOR'),
containsString('changeManagement/endpoint')))
nullScript
.commonPipelineEnvironment
.configuration = null
stepRule.step.transportRequestRelease(
script: nullScript,
changeManagement: [type: 'CTS']
)
}
@Test
public void releaseTransportRequestSanityChecksRFCTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage(allOf(
containsString('ERROR - NO VALUE AVAILABLE FOR:'),
containsString('changeManagement/endpoint'),
containsString('developmentClient')))
nullScript
.commonPipelineEnvironment
.configuration = null
stepRule.step.transportRequestRelease(
script: nullScript,
changeManagement: [type: 'RFC'],
transportRequestId: '002')
}
@Test
public void releaseTransportRequestSuccessSOLMANTest() {
// Here we test only the case where the transportRequestId is
// provided via parameters. The other cases are tested by
@ -118,14 +373,13 @@ public class TransportRequestReleaseTest extends BasePiperTest {
Map receivedParams = [:]
ChangeManagement cm = new ChangeManagement(nullScript) {
void releaseTransportRequest(BackendType type,
void releaseTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String endpoint,
String credentialsId,
String clientOpts) {
receivedParams.type = type
receivedParams.changeId = changeId
receivedParams.transportRequestId = transportRequestId
receivedParams.endpoint = endpoint
@ -136,7 +390,7 @@ public class TransportRequestReleaseTest extends BasePiperTest {
stepRule.step.transportRequestRelease(script: nullScript, changeDocumentId: '001', transportRequestId: '002', cmUtils: cm)
assert receivedParams == [type: BackendType.SOLMAN,
assert receivedParams == [
changeId: '001',
transportRequestId: '002',
endpoint: 'https://example.org/cm',

View File

@ -1,11 +1,17 @@
import static org.hamcrest.Matchers.allOf
import static org.hamcrest.Matchers.containsString
import java.util.List
import java.util.Map
import org.hamcrest.Matchers
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import com.sap.piper.JenkinsUtils
import com.sap.piper.cm.BackendType
import com.sap.piper.cm.ChangeManagement
import com.sap.piper.cm.ChangeManagementException
@ -119,10 +125,10 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
}
@Test
public void uploadFileToTransportRequestFailureTest() {
public void uploadFileToTransportRequestSOLMANFailureTest() {
ChangeManagement cm = new ChangeManagement(nullScript) {
void uploadFileToTransportRequest(BackendType type,
void uploadFileToTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String applicationId,
@ -152,19 +158,14 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
loggingRule.expect("[INFO] File '/path' has been successfully uploaded to transport request '002'.")
ChangeManagement cm = new ChangeManagement(nullScript) {
void uploadFileToTransportRequest(BackendType type,
String changeId,
void uploadFileToTransportRequestCTS(
String transportRequestId,
String applicationId,
String filePath,
String endpoint,
String credentialsId,
String cmclientOpts) {
cmUtilReceivedParams.type = type
cmUtilReceivedParams.changeId = changeId
cmUtilReceivedParams.transportRequestId = transportRequestId
cmUtilReceivedParams.applicationId = applicationId
cmUtilReceivedParams.filePath = filePath
cmUtilReceivedParams.endpoint = endpoint
cmUtilReceivedParams.credentialsId = credentialsId
@ -180,10 +181,7 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
assert cmUtilReceivedParams ==
[
type: BackendType.CTS,
changeId: null,
transportRequestId: '002',
applicationId: null,
filePath: '/path',
endpoint: 'https://example.org/cm',
credentialsId: 'CM',
@ -191,6 +189,166 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
]
}
@Test
public void uploadFileToTransportRequestRFCSanityChecksTest() {
thrown.expect(IllegalArgumentException)
thrown.expectMessage(allOf(
containsString('NO VALUE AVAILABLE FOR'),
containsString('applicationUrl'),
containsString('developmentInstance'),
containsString('developmentClient'),
containsString('applicationDescription'),
containsString('abapPackage'),
containsString('applicationName')))
stepRule.step.transportRequestUploadFile(script: nullScript,
transportRequestId: '123456', //no sanity check, can be read from git history
changeManagement: [type: 'RFC'],
)
}
@Test
public void uploadFileToTransportRequestRFCSuccessTest() {
def cmUtilsReceivedParams
nullScript.commonPipelineEnvironment.configuration =
[general:
[changeManagement:
[
endpoint: 'https://example.org/rfc'
]
]
]
def cm = new ChangeManagement(nullScript) {
void uploadFileToTransportRequestRFC(
Map docker,
String transportRequestId,
String applicationId,
String applicationURL,
String endpoint,
String credentialsId,
String developmentInstance,
String developmentClient,
String applicationDescription,
String abapPackage,
String codePage,
boolean acceptUnixStyleLineEndings,
boolean failUploadOnWarning,
boolean verbose) {
cmUtilsReceivedParams = [
docker: docker,
transportRequestId: transportRequestId,
applicationName: applicationId,
applicationURL: applicationURL,
endpoint: endpoint,
credentialsId: credentialsId,
developmentInstance: developmentInstance,
developmentClient: developmentClient,
applicationDescription: applicationDescription,
abapPackage: abapPackage,
codePage: codePage,
acceptUnixStyleLineEndings: acceptUnixStyleLineEndings,
failUploadOnWarning: failUploadOnWarning,
]
}
}
stepRule.step.transportRequestUploadFile(script: nullScript,
applicationUrl: 'http://example.org/blobstore/xyz.zip',
codePage: 'UTF-9',
acceptUnixStyleLineEndings: true,
transportRequestId: '123456',
changeManagement: [
type: 'RFC',
rfc: [
developmentClient: '002',
developmentInstance: '001'
]
],
applicationName: '42',
applicationDescription: 'Lorem ipsum',
abapPackage: 'XYZ',
cmUtils: cm,)
assert cmUtilsReceivedParams ==
[
docker: [
image: 'rfc',
options: [],
envVars: [:],
pullImage: true
],
transportRequestId: '123456',
applicationName: '42',
applicationURL: 'http://example.org/blobstore/xyz.zip',
endpoint: 'https://example.org/rfc',
credentialsId: 'CM',
developmentInstance: '001',
developmentClient: '002',
applicationDescription: 'Lorem ipsum',
abapPackage:'XYZ',
codePage: 'UTF-9',
acceptUnixStyleLineEndings: true,
failUploadOnWarning: true,
]
}
@Test
public void uploadFileToTransportRequestRFCUploadFailsTest() {
thrown.expect(AbortException)
thrown.expectMessage('upload failed')
def cm = new ChangeManagement(nullScript) {
void uploadFileToTransportRequestRFC(
Map docker,
String transportRequestId,
String applicationId,
String applicationURL,
String endpoint,
String credentialsId,
String developmentInstance,
String developmentClient,
String applicationDescription,
String abapPackage,
String codePage,
boolean acceptUnixStyleLineEndings,
boolean failOnUploadWarning,
boolean verbose) {
throw new ChangeManagementException('upload failed')
}
}
stepRule.step.transportRequestUploadFile(script: nullScript,
applicationUrl: 'http://example.org/blobstore/xyz.zip',
codePage: 'UTF-9',
acceptUnixStyleLineEndings: true,
transportRequestId: '123456',
changeManagement: [
type: 'RFC',
rfc: [
docker: [
image: 'rfc',
options: [],
envVars: [:],
pullImage: false,
],
developmentClient: '002',
developmentInstance: '001',
]
],
applicationName: '42',
applicationDescription: 'Lorem ipsum',
abapPackage: 'XYZ',
cmUtils: cm,)
}
@Test
public void uploadFileToTransportRequestSOLMANSuccessTest() {
@ -202,7 +360,7 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
loggingRule.expect("[INFO] File '/path' has been successfully uploaded to transport request '002' of change document '001'.")
ChangeManagement cm = new ChangeManagement(nullScript) {
void uploadFileToTransportRequest(BackendType type,
void uploadFileToTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String applicationId,
@ -211,7 +369,6 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
String credentialsId,
String cmclientOpts) {
cmUtilReceivedParams.type = type
cmUtilReceivedParams.changeId = changeId
cmUtilReceivedParams.transportRequestId = transportRequestId
cmUtilReceivedParams.applicationId = applicationId
@ -231,7 +388,6 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
assert cmUtilReceivedParams ==
[
type: BackendType.SOLMAN,
changeId: '001',
transportRequestId: '002',
applicationId: 'app',
@ -243,14 +399,14 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
}
@Test
public void uploadFileToTransportRequestSuccessApplicationIdFromConfigurationTest() {
public void uploadFileToTransportRequestSOLMANSuccessApplicationIdFromConfigurationTest() {
nullScript.commonPipelineEnvironment.configuration.put(['steps',
[transportRequestUploadFile:
[applicationId: 'AppIdfromConfig']]])
ChangeManagement cm = new ChangeManagement(nullScript) {
void uploadFileToTransportRequest(BackendType type,
void uploadFileToTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String applicationId,
@ -274,13 +430,13 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
}
@Test
public void uploadFileToTransportRequestFilePathFromParameters() {
public void uploadFileToTransportRequestSOLMANFilePathFromParameters() {
// this one is not used when file path is provided via signature
nullScript.commonPipelineEnvironment.setMtarFilePath('/path2')
ChangeManagement cm = new ChangeManagement(nullScript) {
void uploadFileToTransportRequest(BackendType type,
void uploadFileToTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String applicationId,
@ -304,13 +460,13 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
}
@Test
public void uploadFileToTransportRequestFilePathFromCommonPipelineEnvironment() {
public void uploadFileToTransportRequestSOLMANFilePathFromCommonPipelineEnvironment() {
// this one is used since there is nothing in the signature
nullScript.commonPipelineEnvironment.setMtarFilePath('/path2')
ChangeManagement cm = new ChangeManagement(nullScript) {
void uploadFileToTransportRequest(BackendType type,
void uploadFileToTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String applicationId,
@ -333,13 +489,13 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
}
@Test
public void uploadFileToTransportRequestUploadFailureTest() {
public void uploadFileToTransportRequestSOLMANUploadFailureTest() {
thrown.expect(AbortException)
thrown.expectMessage('Upload failure.')
ChangeManagement cm = new ChangeManagement(nullScript) {
void uploadFileToTransportRequest(BackendType type,
void uploadFileToTransportRequestSOLMAN(
String changeId,
String transportRequestId,
String applicationId,
@ -362,7 +518,7 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
@Test
public void invalidBackendTypeTest() {
thrown.expect(AbortException)
thrown.expectMessage('Invalid backend type: \'DUMMY\'. Valid values: [SOLMAN, CTS, NONE]. ' +
thrown.expectMessage('Invalid backend type: \'DUMMY\'. Valid values: [SOLMAN, CTS, RFC, NONE]. ' +
'Configuration: \'changeManagement/type\'.')
stepRule.step.transportRequestUploadFile(script: nullScript,

View File

@ -1,12 +1,13 @@
package com.sap.piper.cm
import static org.hamcrest.Matchers.allOf
import static org.hamcrest.Matchers.contains
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.equalTo
import static org.hamcrest.Matchers.hasItem
import static org.hamcrest.Matchers.is
import static org.hamcrest.Matchers.not
import static org.junit.Assert.assertThat
import static org.junit.Assert.assertEquals
import org.hamcrest.Matchers
import org.junit.Assert
@ -22,6 +23,7 @@ import util.JenkinsLoggingRule
import util.JenkinsScriptLoaderRule
import util.JenkinsShellCallRule
import util.JenkinsCredentialsRule
import util.JenkinsDockerExecuteRule
import util.Rules
import hudson.AbortException
@ -32,6 +34,7 @@ public class ChangeManagementTest extends BasePiperTest {
private JenkinsShellCallRule script = new JenkinsShellCallRule(this)
private JenkinsLoggingRule logging = new JenkinsLoggingRule(this)
private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this)
@Rule
public RuleChain rules = Rules.getCommonRules(this)
@ -39,6 +42,7 @@ public class ChangeManagementTest extends BasePiperTest {
.around(script)
.around(logging)
.around(new JenkinsCredentialsRule(this).withCredentials('me','user','password'))
.around(dockerExecuteRule)
@Test
public void testRetrieveChangeDocumentIdOutsideGitWorkTreeTest() {
@ -167,6 +171,57 @@ public void testGetCommandLineWithCMClientOpts() {
}
@Test
public void testCreateTransportRequestRFCSucceeds() {
script.setReturnValue('cts createTransportRequest', '{"REQUESTID":"XYZK9000004"}')
def transportRequestId = new ChangeManagement(nullScript).createTransportRequestRFC(
[image: 'rfc', options: []],
'https://example.org/rfc', // endpoint
'01', // instance
'001', // client
'me', // credentialsId
'Lorem ipsum', // description
true // verbose
)
assert dockerExecuteRule.dockerParams.dockerImage == 'rfc'
assert dockerExecuteRule.dockerParams.dockerEnvVars == [
TRANSPORT_DESCRIPTION: 'Lorem ipsum',
ABAP_DEVELOPMENT_INSTANCE: '01',
ABAP_DEVELOPMENT_CLIENT: '001',
ABAP_DEVELOPMENT_SERVER: 'https://example.org/rfc',
ABAP_DEVELOPMENT_USER: 'user',
ABAP_DEVELOPMENT_PASSWORD: 'password',
VERBOSE: true
]
assert transportRequestId == 'XYZK9000004'
}
@Test
public void testCreateTransportRequestRFCFails() {
thrown.expect(ChangeManagementException)
thrown.expectMessage('Cannot create transport request: script returned exit code 3')
script.setReturnValue('cts createTransportRequest',
{ throw new AbortException('script returned exit code 3')})
def transportRequestId = new ChangeManagement(nullScript).createTransportRequestRFC(
[image: 'rfc', options: []],
'https://example.org/rfc', // endpoint
'001', // client
'01', // instance
'me', // credentialsId
'Lorem ipsum', // description
true, //verbose
)
}
@Test
public void testCreateTransportRequestCTSSucceeds() {
@ -185,32 +240,13 @@ public void testGetCommandLineWithCMClientOpts() {
}
@Test
public void testCreateTransportRequestFails() {
script.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*upload-file-to-transport.*', 1)
thrown.expect(ChangeManagementException)
thrown.expectMessage('Cannot upload file \'/path\' for change document \'001\''+
' with transport request \'002\'. Return code from cmclient: 1.')
new ChangeManagement(nullScript).uploadFileToTransportRequest(BackendType.SOLMAN,
'001',
'002',
'XXX',
'/path',
'https://example.org/cm',
'me')
}
@Test
public void testUploadFileToTransportSucceedsSOLMAN() {
// the regex provided below is an implicit check that the command line is fine.
script.setReturnValue(JenkinsShellCallRule.Type.REGEX, 'upload-file-to-transport.*-cID 001 -tID 002 XXX "/path"', 0)
new ChangeManagement(nullScript).uploadFileToTransportRequest(
BackendType.SOLMAN,
new ChangeManagement(nullScript).uploadFileToTransportRequestSOLMAN(
'001',
'002',
'XXX',
@ -228,11 +264,8 @@ public void testGetCommandLineWithCMClientOpts() {
// the regex provided below is an implicit check that the command line is fine.
script.setReturnValue(JenkinsShellCallRule.Type.REGEX, '-t CTS upload-file-to-transport -tID 002 "/path"', 0)
new ChangeManagement(nullScript).uploadFileToTransportRequest(
BackendType.CTS,
null,
new ChangeManagement(nullScript).uploadFileToTransportRequestCTS(
'002',
null,
'/path',
'https://example.org/cm',
'me')
@ -242,15 +275,85 @@ public void testGetCommandLineWithCMClientOpts() {
}
@Test
public void testUploadFileToTransportFails() {
public void testUploadFileToTransportSucceedsRFC() {
new ChangeManagement(nullScript).uploadFileToTransportRequestRFC(
[image:'rfc', options: [], pullImage: true],
'002', //transportRequestId
'001', // applicationId
'https://example.org/mypath/deployArtifact.zip',
'https://example.org/rfc',
'me',
'00', //developmentInstance
'001', // developmentClient
'Lorem ipsum', // applicationDescription
'XYZ', // abapPackage
'UTF-9', //codePage
true, // accept unix style EOL
true, // failUploadOnWarning
false, // verbose
)
assert dockerExecuteRule.dockerParams.dockerImage == 'rfc'
assert dockerExecuteRule.dockerParams.dockerPullImage == true
assert dockerExecuteRule.dockerParams.dockerEnvVars ==
[
ABAP_DEVELOPMENT_INSTANCE: '00',
ABAP_DEVELOPMENT_CLIENT: '001',
ABAP_APPLICATION_NAME: '001',
ABAP_APPLICATION_DESC: 'Lorem ipsum',
ABAP_PACKAGE: 'XYZ',
ZIP_FILE_URL: 'https://example.org/mypath/deployArtifact.zip',
ABAP_DEVELOPMENT_SERVER: 'https://example.org/rfc',
ABAP_DEVELOPMENT_USER: 'user',
ABAP_DEVELOPMENT_PASSWORD: 'password',
CODE_PAGE: 'UTF-9',
ABAP_ACCEPT_UNIX_STYLE_EOL: 'X',
FAIL_UPLOAD_ON_WARNING: 'true',
VERBOSE: 'false'
]
assertThat(script.shell, contains('cts uploadToABAP:002'))
}
@Test
public void testUploadFileToTransportFailsRFC() {
thrown.expect(ChangeManagementException)
thrown.expectMessage("Cannot upload file '/path' for change document '001' with transport request '002'. " +
"Return code from cmclient: 1.")
thrown.expectMessage('Cannot upload file into transport request. Return code from rfc client: 1.')
script.setReturnValue('cts uploadToABAP:002', 1)
new ChangeManagement(nullScript).uploadFileToTransportRequestRFC(
[:],
'002', //transportRequestId
'001', // applicationId
'https://example.org/mypath/deployArtifact.zip',
'https://example.org/rfc',
'me',
'00', //developmentInstance
'001', // developmentClient
'Lorem ipsum', // applicationDescription
'XYZ', // abapPackage
'UTF-9', // codePage
true, // accept unix style EOL
true, // failUploadOnWarning
false, // verbose
)
}
@Test
public void testUploadFileToTransportFailsSOLMAN() {
thrown.expect(ChangeManagementException)
thrown.expectMessage("Cannot upload file into transport request. " +
"Return code from cm client: 1.")
script.setReturnValue(JenkinsShellCallRule.Type.REGEX,, 'upload-file-to-transport', 1)
new ChangeManagement(nullScript).uploadFileToTransportRequest(BackendType.SOLMAN,
new ChangeManagement(nullScript).uploadFileToTransportRequestSOLMAN(
'001',
'002',
'XXX',
@ -265,8 +368,7 @@ public void testGetCommandLineWithCMClientOpts() {
// the regex provided below is an implicit check that the command line is fine.
script.setReturnValue(JenkinsShellCallRule.Type.REGEX, '-t SOLMAN release-transport.*-cID 001.*-tID 002', 0)
new ChangeManagement(nullScript).releaseTransportRequest(
BackendType.SOLMAN,
new ChangeManagement(nullScript).releaseTransportRequestSOLMAN(
'001',
'002',
'https://example.org',
@ -283,9 +385,7 @@ public void testGetCommandLineWithCMClientOpts() {
// the regex provided below is an implicit check that the command line is fine.
script.setReturnValue(JenkinsShellCallRule.Type.REGEX, '-t CTS export-transport.*-tID 002', 0)
new ChangeManagement(nullScript).releaseTransportRequest(
BackendType.CTS,
null,
new ChangeManagement(nullScript).releaseTransportRequestCTS(
'002',
'https://example.org',
'me',
@ -296,7 +396,31 @@ public void testGetCommandLineWithCMClientOpts() {
}
@Test
public void testReleaseTransportRequestFails() {
public void testReleaseTransportRequestSucceedsRFC() {
new ChangeManagement(nullScript).releaseTransportRequestRFC(
[:],
'002',
'https://example.org',
'002',
'001',
'me',
true)
assert dockerExecuteRule.dockerParams.dockerEnvVars == [
ABAP_DEVELOPMENT_SERVER: 'https://example.org',
ABAP_DEVELOPMENT_USER: 'user',
ABAP_DEVELOPMENT_PASSWORD: 'password',
ABAP_DEVELOPMENT_CLIENT: '001',
ABAP_DEVELOPMENT_INSTANCE: '002',
VERBOSE: true,
]
assertThat(script.shell, hasItem('cts releaseTransport:002'))
}
@Test
public void testReleaseTransportRequestFailsSOLMAN() {
thrown.expect(ChangeManagementException)
thrown.expectMessage("Cannot release Transport Request '002'. Return code from cmclient: 1.")
@ -304,16 +428,13 @@ public void testGetCommandLineWithCMClientOpts() {
// the regex provided below is an implicit check that the command line is fine.
script.setReturnValue(JenkinsShellCallRule.Type.REGEX, 'release-transport.*-cID 001.*-tID 002', 1)
new ChangeManagement(nullScript).releaseTransportRequest(
BackendType.SOLMAN,
new ChangeManagement(nullScript).releaseTransportRequestSOLMAN(
'001',
'002',
'https://example.org',
'me',
'openSesame')
'me')
}
private GitUtils gitUtilsMock(boolean insideWorkTree, String[] changeIds) {
return new GitUtils() {
public boolean insideWorkTree() {

View File

@ -34,7 +34,6 @@ class NeoCommandHelperTest extends BasePiperTest {
String source = (deployMode == DeployMode.MTA) ? 'file.mta' : 'file.war'
String username = 'username'
String password = 'password'
String neoExecutable = '/path/tools/neo.sh';
nullScript.STEP_NAME="neoDeploy"
@ -42,7 +41,6 @@ class NeoCommandHelperTest extends BasePiperTest {
nullScript,
deployMode,
deploymentConfiguration,
neoExecutable,
username,
password,
source
@ -52,7 +50,7 @@ class NeoCommandHelperTest extends BasePiperTest {
@Test
void testStatusCommand() {
String actual = getTestFixture(DeployMode.WAR_PARAMS).statusCommand()
String expected = "\"/path/tools/neo.sh\" status --host 'host_value' --account 'account_value' " +
String expected = "neo.sh status --host 'host_value' --account 'account_value' " +
"--application 'application_value' --user 'username' --password 'password'"
Assert.assertEquals(expected, actual)
}
@ -60,14 +58,14 @@ class NeoCommandHelperTest extends BasePiperTest {
@Test
void testStatusCommandForProperties() {
String actual = getTestFixture(DeployMode.WAR_PROPERTIES_FILE).statusCommand()
String expected = "\"/path/tools/neo.sh\" status file.properties --user 'username' --password 'password'"
String expected = "neo.sh status file.properties --user 'username' --password 'password'"
Assert.assertEquals(expected, actual)
}
@Test
void testRollingUpdateCommand() {
String actual = getTestFixture(DeployMode.WAR_PARAMS).rollingUpdateCommand()
String basicCommand = "\"/path/tools/neo.sh\" rolling-update --host 'host_value' --account 'account_value' " +
String basicCommand = "neo.sh rolling-update --host 'host_value' --account 'account_value' " +
"--application 'application_value' --user 'username' --password 'password' --source 'file.war'"
Assert.assertTrue(actual.contains(basicCommand))
@ -81,14 +79,14 @@ class NeoCommandHelperTest extends BasePiperTest {
@Test
void testRollingUpdateCommandForProperties() {
String actual = getTestFixture(DeployMode.WAR_PROPERTIES_FILE).rollingUpdateCommand()
String expected = "\"/path/tools/neo.sh\" rolling-update file.properties --user 'username' --password 'password' --source 'file.war' "
String expected = "neo.sh rolling-update file.properties --user 'username' --password 'password' --source 'file.war' "
Assert.assertEquals(expected, actual)
}
@Test
void testDeployCommand() {
String actual = getTestFixture(DeployMode.WAR_PARAMS).deployCommand()
String basicCommand = "\"/path/tools/neo.sh\" deploy --host 'host_value' --account 'account_value' " +
String basicCommand = "neo.sh deploy --host 'host_value' --account 'account_value' " +
"--application 'application_value' --user 'username' --password 'password' --source 'file.war'"
Assert.assertTrue(actual.contains(basicCommand))
@ -102,14 +100,14 @@ class NeoCommandHelperTest extends BasePiperTest {
@Test
void testDeployCommandForProperties() {
String actual = getTestFixture(DeployMode.WAR_PROPERTIES_FILE).deployCommand()
String expected = "\"/path/tools/neo.sh\" deploy file.properties --user 'username' --password 'password' --source 'file.war' "
String expected = "neo.sh deploy file.properties --user 'username' --password 'password' --source 'file.war' "
Assert.assertEquals(expected, actual)
}
@Test
void testRestartCommand() {
String actual = getTestFixture(DeployMode.WAR_PARAMS).restartCommand()
String expected = "\"/path/tools/neo.sh\" restart --synchronous --host 'host_value' --account 'account_value' " +
String expected = "neo.sh restart --synchronous --host 'host_value' --account 'account_value' " +
"--application 'application_value' --user 'username' --password 'password'"
Assert.assertEquals(expected, actual)
}
@ -117,14 +115,14 @@ class NeoCommandHelperTest extends BasePiperTest {
@Test
void testRestartCommandForProperties() {
String actual = getTestFixture(DeployMode.WAR_PROPERTIES_FILE).restartCommand()
String expected = "\"/path/tools/neo.sh\" restart --synchronous file.properties --user 'username' --password 'password'"
String expected = "neo.sh restart --synchronous file.properties --user 'username' --password 'password'"
Assert.assertEquals(expected, actual)
}
@Test
void deployMta() {
String actual = getTestFixture(DeployMode.MTA).deployMta()
String expected = "\"/path/tools/neo.sh\" deploy-mta --synchronous --host 'host_value' --account 'account_value' " +
String expected = "neo.sh deploy-mta --synchronous --host 'host_value' --account 'account_value' " +
"--user 'username' --password 'password' --source 'file.mta'"
Assert.assertEquals(expected, actual)
}

View File

@ -35,7 +35,10 @@ void call(Map parameters = [:]) {
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
.mixin([
artifactVersion: script.commonPipelineEnvironment.getArtifactVersion()
artifactVersion: script.commonPipelineEnvironment.getArtifactVersion(),
influxPrefix: script.commonPipelineEnvironment.getGithubOrg() && script.commonPipelineEnvironment.getGithubRepo()
? "${script.commonPipelineEnvironment.getGithubOrg()}_${script.commonPipelineEnvironment.getGithubRepo()}"
: null
])
.mixin(parameters, PARAMETER_KEYS)
.addIfNull('customData', script.commonPipelineEnvironment.getInfluxCustomData())

View File

@ -1,6 +1,7 @@
import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateDocumentation
import com.sap.piper.GitUtils
import com.sap.piper.Utils
@ -9,34 +10,67 @@ import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
/** port mappings required for containers. This will only take effect inside a Kubernetes pod, format [[containerPort: 1111, hostPort: 1111]] */
/**
* Map which defines per docker image the port mappings, e.g. `containerPortMappings: ['selenium/standalone-chrome': [[name: 'selPort', containerPort: 4444, hostPort: 4444]]]`.
*/
'containerPortMappings',
/** envVars to be set in the execution container if required */
/** A map of environment variables to set in the container, e.g. [http_proxy:'proxy:8080']. */
'dockerEnvVars',
/** Docker image for code execution */
/** The 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. */
'dockerImage',
/** name of the Docker container. If not on Kubernetes pod, this will define the network-alias to the NPM container and is thus required for accessing the server, example http://karma:9876 (default). */
/**
* Kubernetes only:
* Name of the container launching `dockerImage`.
* SideCar only:
* Name of the container in local network.
*/
'dockerName',
/** user home directory for Docker execution. This will only take effect inside a Kubernetes pod. */
/**
* Kubernetes only:
* Specifies a dedicated user home directory for the container which will be passed as value for environment variable `HOME`.
*/
'dockerWorkspace',
/**
* With `failOnError` the behavior in case tests fail can be defined.
* @possibleValues `true`, `false`
*/
'failOnError',
/** The command that is executed to install the test tool. */
'installCommand',
/** Define the paths of the modules to execute tests on. */
'modules',
/** The command that is executed to start the tests. */
'runCommand',
/** envVars to be set in Selenium container if required */
/** A map of environment variables to set in the sidecar container, similar to `dockerEnvVars`. */
'sidecarEnvVars',
/** image for Selenium execution which runs as sidecar to dockerImage */
/** The name of the docker image of the sidecar container. If empty, no sidecar container is started. */
'sidecarImage',
/** name of the Selenium container. If not on Kubernetes pod, this will define the network-alias to the Selenium container and is thus required for accessing the server, example http://selenium:4444 (default) */
/**
* as `dockerName` for the sidecar container
*/
'sidecarName',
/** volume bind. This will not take effect in Kubernetes pod. */
/** Volumes that should be mounted into the sidecar container. */
'sidecarVolumeBind',
/** list of stash names which are required to be unstashed before test run */
/** If specific stashes should be considered for the tests, their names need to be passed via the parameter `stashContent`. */
'stashContent'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* In this step the ([Karma test runner](http://karma-runner.github.io)) is executed.
*
* The step is using the `seleniumExecuteTest` step to spin up two containers in a Docker network:
*
* * a Selenium/Chrome container (`selenium/standalone-chrome`)
* * a NodeJS container (`node:8-stretch`)
*
* In the Docker network, the containers can be referenced by the values provided in `dockerName` and `sidecarName`, the default values are `karma` and `selenium`. These values must be used in the `hostname` properties of the test configuration ([Karma](https://karma-runner.github.io/1.0/config/configuration-file.html) and [WebDriver](https://github.com/karma-runner/karma-webdriver-launcher#usage)).
*
* !!! note
* In a Kubernetes environment, the containers both need to be referenced with `localhost`.
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
final script = checkScript(this, parameters) ?: this

View File

@ -1,6 +1,5 @@
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import com.sap.piper.tools.ToolDescriptor
import com.sap.piper.tools.neo.DeployMode
import com.sap.piper.tools.neo.NeoCommandHelper
import com.sap.piper.tools.neo.WarAction
@ -25,21 +24,6 @@ import static com.sap.piper.Prerequisites.checkScript
'warAction'
])
@Field Map CONFIG_KEY_COMPATIBILITY = [
neo : [
host : 'host',
account : 'account',
application : 'applicationName',
credentialsId : 'neoCredentialsId',
propertiesFile: 'propertiesFile',
runtime : 'runtime',
runtimeVersion: 'runtimeVersion',
size : 'vmSize'
],
source: 'archivePath'
]
void call(parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
@ -49,19 +33,17 @@ void call(parameters = [:]) {
prepareDefaultValues script: script
Map stepCompatibilityConfiguration = handleCompatibility(script, parameters)
// load default & individual configuration
Map configuration = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixin(stepCompatibilityConfiguration)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName ?: env.STAGE_NAME, STEP_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName ?: env.STAGE_NAME, STEP_CONFIG_KEYS)
.addIfEmpty('source', script.commonPipelineEnvironment.getMtarFilePath())
.mixin(parameters, PARAMETER_KEYS, CONFIG_KEY_COMPATIBILITY)
.mixin(parameters, PARAMETER_KEYS)
.withMandatoryProperty('neo')
.withMandatoryProperty('source')
.withMandatoryProperty('neo/credentialsId')
.withPropertyInValues('deployMode', DeployMode.stringValues())
.use()
@ -73,52 +55,37 @@ void call(parameters = [:]) {
stepParam2: configuration.warAction == 'rolling-update'?'blue-green':'standard', // ['deploy', 'deploy-mta', 'rolling-update']
stepParamKey3: 'scriptMissing',
stepParam3: parameters?.script == null,
stepParamKey4: 'legacyConfig',
stepParam4: !stepCompatibilityConfiguration.isEmpty(),
], configuration)
ToolDescriptor neo = new ToolDescriptor('SAP Cloud Platform Console Client', 'NEO_HOME', 'neoHome', '/tools/', 'neo.sh', null, 'version')
ToolDescriptor java = new ToolDescriptor('Java', 'JAVA_HOME', '', '/bin/', 'java', '1.8.0', '-version 2>&1')
if (configuration.neo.credentialsId) {
withCredentials([usernamePassword(
credentialsId: configuration.neo.credentialsId,
passwordVariable: 'NEO_PASSWORD',
usernameVariable: 'NEO_USERNAME')]) {
withCredentials([usernamePassword(
credentialsId: configuration.neo.credentialsId,
passwordVariable: 'NEO_PASSWORD',
usernameVariable: 'NEO_USERNAME')]) {
assertPasswordRules(NEO_PASSWORD)
assertPasswordRules(NEO_PASSWORD)
dockerExecute(
script: script,
dockerImage: configuration.dockerImage,
dockerEnvVars: configuration.dockerEnvVars,
dockerOptions: configuration.dockerOptions
) {
dockerExecute(
script: script,
dockerImage: configuration.dockerImage,
dockerEnvVars: configuration.dockerEnvVars,
dockerOptions: configuration.dockerOptions
) {
DeployMode deployMode = DeployMode.fromString(configuration.deployMode)
neo.verify(this, configuration)
java.verify(this, configuration)
NeoCommandHelper neoCommandHelper = new NeoCommandHelper(
this,
deployMode,
configuration.neo,
NEO_USERNAME,
NEO_PASSWORD,
configuration.source
)
String neoExecutable = neo.getToolExecutable(script, configuration)
DeployMode deployMode = DeployMode.fromString(configuration.deployMode)
NeoCommandHelper neoCommandHelper = new NeoCommandHelper(
this,
deployMode,
configuration.neo,
neoExecutable,
NEO_USERNAME,
NEO_PASSWORD,
configuration.source
)
lock("$STEP_NAME :${neoCommandHelper.resourceLock()}") {
deploy(script, utils, configuration, neoCommandHelper, configuration.dockerImage, deployMode)
}
lock("$STEP_NAME :${neoCommandHelper.resourceLock()}") {
deploy(script, utils, configuration, neoCommandHelper, configuration.dockerImage, deployMode)
}
}
} else {
error("[neoDeploy] No credentials defined for the deployment. Please specify the value for credentialsId for neo.")
}
}
}
@ -168,61 +135,6 @@ private boolean isAppRunning(NeoCommandHelper commandHelper) {
return status.contains('Status: STARTED')
}
private handleCompatibility(script, parameters) {
final Map neoCompatibilityConfiguration = [:]
// Backward compatibility: ensure old configuration is taken into account
// The old configuration in not stage / step specific
def defaultDeployHost = script.commonPipelineEnvironment.getConfigProperty('DEPLOY_HOST')
if (defaultDeployHost) {
echo "[WARNING][${STEP_NAME}] A deprecated configuration framework is used for configuring parameter 'DEPLOY_HOST'. This configuration framework will be removed in future versions."
neoCompatibilityConfiguration.put('host', defaultDeployHost)
}
def defaultDeployAccount = script.commonPipelineEnvironment.getConfigProperty('CI_DEPLOY_ACCOUNT')
if (defaultDeployAccount) {
echo "[WARNING][${STEP_NAME}] A deprecated configuration framework is used for configuring parameter 'DEPLOY_ACCOUNT'. This configuration framekwork will be removed in future versions."
neoCompatibilityConfiguration.put('account', defaultDeployAccount)
}
if (parameters.deployHost && !parameters.host) {
echo "[WARNING][${STEP_NAME}] Deprecated parameter 'deployHost' is used. This will not work anymore in future versions. Use parameter 'host' instead."
parameters.put('host', parameters.deployHost)
}
if (parameters.deployAccount && !parameters.account) {
echo "[WARNING][${STEP_NAME}] Deprecated parameter 'deployAccount' is used. This will not work anymore in future versions. Use parameter 'account' instead."
parameters.put('account', parameters.deployAccount)
}
def credId = script.commonPipelineEnvironment.getConfigProperty('neoCredentialsId')
if (credId && !parameters.neoCredentialsId) {
echo "[WARNING][${STEP_NAME}] Deprecated parameter 'neoCredentialsId' from old configuration framework is used. This will not work anymore in future versions."
parameters.put('neoCredentialsId', credId)
}
if (!neoCompatibilityConfiguration.isEmpty()) {
echo "[WARNING][$STEP_NAME] You are using a deprecated configuration framework. This will be removed in " +
'futureVersions.\nAdd snippet below to \'./pipeline/config.yml\' and remove ' +
'file \'.pipeline/configuration.properties\'.\n' +
"""|steps:
| neoDeploy:
| neo:
| host: ${neoCompatibilityConfiguration.get('host', '<Add host here>')}
| account: ${neoCompatibilityConfiguration.get('account', '<Add account here>')}
""".stripMargin()
if (Boolean.getBoolean('com.sap.piper.featureFlag.buildUnstableWhenOldConfigFrameworkIsUsedByNeoDeploy')) {
script.currentBuild.setResult('UNSTABLE')
echo "[WARNING][$STEP_NAME] Build has been set to unstable since old config framework is used."
}
return [neo: neoCompatibilityConfiguration]
}
return [:]
}
private assertPasswordRules(String password) {
if (password.startsWith("@")) {
error("Your password for the deployment to SAP Cloud Platform contains characters which are not " +

73
vars/npmExecute.groovy Normal file
View File

@ -0,0 +1,73 @@
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 = [
/**
* Name of the docker image that should be used, in which node should be installed and configured. Default value is 'node:8-stretch'.
*/
'dockerImage',
/**
* URL of default NPM registry
*/
'defaultNpmRegistry',
/**
* Which NPM command should be executed.
*/
'npmCommand']
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS + [
/**
* Docker options to be set when starting the container.
*/
'dockerOptions']
/**
* Executes NPM commands inside a docker container.
* Docker image, docker options and npm commands can be specified or configured.
*/
@GenerateDocumentation
void call(Map parameters = [:], body = null) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
final 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, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.use()
new Utils().pushToSWA([
step: STEP_NAME,
stepParamKey1: 'scriptMissing',
stepParam1: parameters?.script == null
], configuration)
try {
if (!fileExists('package.json')) {
error "[${STEP_NAME}] package.json is not found."
}
dockerExecute(script: script, dockerImage: configuration.dockerImage, dockerOptions: configuration.dockerOptions) {
if (configuration.defaultNpmRegistry) {
sh "npm config set registry ${configuration.defaultNpmRegistry}"
}
if (configuration.npmCommand) {
sh "npm ${configuration.npmCommand}"
}
if (body) {
body()
}
}
} catch (Exception e) {
println "Error while executing npm. Here are the logs:"
sh "cat ~/.npm/_logs/*"
throw e
}
}
}

View File

@ -6,7 +6,7 @@ import groovy.transform.Field
@Field STEP_NAME = getClass().getName()
void call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: 'prepareDefaultValues', stepParameters: parameters) {
handlePipelineStepErrors (stepName: 'prepareDefaultValues', stepParameters: parameters, echoDetails: false) {
if(!DefaultValueCache.getInstance() || parameters.customDefaults) {
def defaultValues = [:]
def configFileList = ['default_pipeline_environment.yml']

View File

@ -14,7 +14,7 @@ void call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: 'toolValidate', stepParameters: parameters) {
echo '[WARNING][toolValidate] This step is deprecated, and it will be removed in future versions. Validation is automatically done inside the steps.'
echo '[WARNING][toolValidate] This step is deprecated, and it will be removed in future versions.'
def tool = parameters.tool
def home = parameters.home

View File

@ -23,6 +23,7 @@ import hudson.AbortException
'developmentSystemId', // SOLMAN
'targetSystem', // CTS
'transportType', // CTS
'verbose', // RFC
]
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus(['changeDocumentId'])
@ -38,6 +39,7 @@ void call(parameters = [:]) {
ChangeManagement cm = parameters.cmUtils ?: new ChangeManagement(script)
ConfigurationHelper configHelper = ConfigurationHelper.newInstance(this)
.collectValidationFailures()
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
@ -60,6 +62,9 @@ void call(parameters = [:]) {
.withMandatoryProperty('transportType', null, { backendType == BackendType.CTS})
.withMandatoryProperty('targetSystem', null, { backendType == BackendType.CTS})
.withMandatoryProperty('description', null, { backendType == BackendType.CTS})
.withMandatoryProperty('changeManagement/rfc/developmentInstance', null, {backendType == BackendType.RFC})
.withMandatoryProperty('changeManagement/rfc/developmentClient', null, {backendType == BackendType.RFC})
.withMandatoryProperty('verbose', null, {backendType == BackendType.RFC})
def changeDocumentId = null
@ -88,7 +93,8 @@ void call(parameters = [:]) {
creatingMessage << '.'
echo creatingMessage.join()
try {
try {
if(backendType == BackendType.SOLMAN) {
transportRequestId = cm.createTransportRequestSOLMAN(
configuration.changeDocumentId,
@ -104,6 +110,15 @@ void call(parameters = [:]) {
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)
} else {
throw new IllegalArgumentException("Invalid backend type: '${backendType}'.")
}

View File

@ -25,6 +25,7 @@ import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrati
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([
'changeDocumentId',
'transportRequestId',
'verbose',
])
void call(parameters = [:]) {
@ -49,12 +50,16 @@ void call(parameters = [:]) {
if(backendType == BackendType.NONE) return
configHelper
.collectValidationFailures()
.withMandatoryProperty('changeManagement/clientOpts')
.withMandatoryProperty('changeManagement/credentialsId')
.withMandatoryProperty('changeManagement/endpoint')
.withMandatoryProperty('changeManagement/git/to')
.withMandatoryProperty('changeManagement/git/from')
.withMandatoryProperty('changeManagement/git/format')
.withMandatoryProperty('changeManagement/rfc/developmentInstance', null, { backendType == BackendType.RFC})
.withMandatoryProperty('changeManagement/rfc/developmentClient', null, { backendType == BackendType.RFC})
.withMandatoryProperty('verbose', null, { backendType == BackendType.RFC})
configuration = configHelper.use()
@ -89,13 +94,44 @@ void call(parameters = [:]) {
echo closingMessage.join()
try {
cm.releaseTransportRequest(backendType,
configuration.changeDocumentId,
configuration.transportRequestId,
configuration.changeManagement.endpoint,
configuration.changeManagement.credentialsId,
configuration.changeManagement.clientOpts)
switch(backendType) {
case BackendType.SOLMAN:
cm.releaseTransportRequestSOLMAN(
configuration.changeDocumentId,
configuration.transportRequestId,
configuration.changeManagement.endpoint,
configuration.changeManagement.credentialsId,
configuration.changeManagement.clientOpts)
break
case BackendType.CTS:
cm.releaseTransportRequestCTS(
configuration.transportRequestId,
configuration.changeManagement.endpoint,
configuration.changeManagement.credentialsId,
configuration.changeManagement.clientOpts)
break
case BackendType.RFC:
cm.releaseTransportRequestRFC(
configuration.changeManagement.rfc.docker,
configuration.transportRequestId,
configuration.changeManagement.endpoint,
configuration.changeManagement.rfc.developmentInstance,
configuration.changeManagement.rfc.developmentClient,
configuration.changeManagement.credentialsId,
configuration.verbose)
break
default:
throw new IllegalArgumentException("Invalid backend type: '${backendType}'.")
}
} catch(ChangeManagementException ex) {
throw new AbortException(ex.getMessage())
}

View File

@ -21,12 +21,19 @@ import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrati
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
'applicationId'
'applicationName', // RFC
'applicationId', // SOLMAN
'applicationDescription',
'filePath', // SOLMAN, CTS
'applicationUrl', // RFC
'abapPackage',
'codePage', //RFC
'acceptUnixStyleLineEndings', // RFC
'verbose', // RFC
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([
'changeDocumentId',
'filePath',
'transportRequestId'])
void call(parameters = [:]) {
@ -51,6 +58,7 @@ void call(parameters = [:]) {
if(backendType == BackendType.NONE) return
configHelper
.collectValidationFailures()
.withMandatoryProperty('changeManagement/changeDocumentLabel')
.withMandatoryProperty('changeManagement/clientOpts')
.withMandatoryProperty('changeManagement/credentialsId')
@ -59,7 +67,22 @@ void call(parameters = [:]) {
.withMandatoryProperty('changeManagement/git/from')
.withMandatoryProperty('changeManagement/git/to')
.withMandatoryProperty('changeManagement/git/format')
.withMandatoryProperty('filePath')
.withMandatoryProperty('filePath', null, { backendType in [BackendType.SOLMAN, BackendType.CTS] })
.withMandatoryProperty('applicationUrl', null, { backendType == BackendType.RFC })
.withMandatoryProperty('codePage', null, { backendType == BackendType.RFC })
.withMandatoryProperty('acceptUnixStyleLineEndings', null, { backendType == BackendType.RFC })
.withMandatoryProperty('changeManagement/rfc/developmentInstance', null, { backendType == BackendType.RFC })
.withMandatoryProperty('changeManagement/rfc/developmentClient', null, { backendType == BackendType.RFC })
.withMandatoryProperty('changeManagement/rfc/docker/image', null, {backendType == BackendType.RFC})
.withMandatoryProperty('changeManagement/rfc/docker/options', null, {backendType == BackendType.RFC})
.withMandatoryProperty('changeManagement/rfc/docker/envVars', null, {backendType == BackendType.RFC})
.withMandatoryProperty('changeManagement/rfc/docker/pullImage', null, {backendType == BackendType.RFC})
.withMandatoryProperty('applicationDescription', null, { backendType == BackendType.RFC })
.withMandatoryProperty('abapPackage', null, { backendType == BackendType.RFC })
.withMandatoryProperty('applicationId', null, {backendType == BackendType.SOLMAN})
.withMandatoryProperty('applicationName', null, {backendType == BackendType.RFC})
.withMandatoryProperty('failOnWarning', null, {backendType == BackendType.RFC})
.withMandatoryProperty('verbose', null, {backendType == BackendType.RFC})
new Utils().pushToSWA([
step: STEP_NAME,
@ -85,14 +108,15 @@ void call(parameters = [:]) {
configHelper
.withMandatoryProperty('changeDocumentId',
"Change document id not provided (parameter: \'changeDocumentId\' or via commit history).")
.withMandatoryProperty('applicationId')
}
configuration = configHelper
.withMandatoryProperty('transportRequestId',
"Transport request id not provided (parameter: \'transportRequestId\' or via commit history).")
.use()
def uploadingMessage = ["[INFO] Uploading file '${configuration.filePath}' to transport request '${configuration.transportRequestId}'"]
def uploadingMessage = ['[INFO] Uploading file ' +
"'${backendType == BackendType.RFC ? configuration.applicationUrl : configuration.filePath}' " +
"to transport request '${configuration.transportRequestId}'"]
if(backendType == BackendType.SOLMAN)
uploadingMessage << " of change document '${configuration.changeDocumentId}'"
uploadingMessage << '.'
@ -101,22 +125,55 @@ void call(parameters = [:]) {
try {
switch(backendType) {
cm.uploadFileToTransportRequest(backendType,
configuration.changeDocumentId,
configuration.transportRequestId,
configuration.applicationId,
configuration.filePath,
configuration.changeManagement.endpoint,
configuration.changeManagement.credentialsId,
configuration.changeManagement.clientOpts)
case BackendType.SOLMAN:
cm.uploadFileToTransportRequestSOLMAN(
configuration.changeDocumentId,
configuration.transportRequestId,
configuration.applicationId,
configuration.filePath,
configuration.changeManagement.endpoint,
configuration.changeManagement.credentialsId,
configuration.changeManagement.clientOpts)
break
case BackendType.CTS:
cm.uploadFileToTransportRequestCTS(
configuration.transportRequestId,
configuration.filePath,
configuration.changeManagement.endpoint,
configuration.changeManagement.credentialsId,
configuration.changeManagement.clientOpts)
break
case BackendType.RFC:
cm.uploadFileToTransportRequestRFC(
configuration.changeManagement.rfc.docker ?: [],
configuration.transportRequestId,
configuration.applicationName,
configuration.applicationUrl,
configuration.changeManagement.endpoint,
configuration.changeManagement.credentialsId,
configuration.changeManagement.rfc.developmentInstance,
configuration.changeManagement.rfc.developmentClient,
configuration.applicationDescription,
configuration.abapPackage,
configuration.codePage,
configuration.acceptUnixStyleLineEndings,
configuration.failOnWarning,
configuration.verbose
)
break
}
} catch(ChangeManagementException ex) {
throw new AbortException(ex.getMessage())
}
def uploadedMessage = ["[INFO] File '${configuration.filePath}' has been successfully uploaded to transport request '${configuration.transportRequestId}'"]
def uploadedMessage = ["[INFO] File '${backendType == BackendType.RFC ? configuration.applicationUrl : configuration.filePath}' has been successfully uploaded to transport request '${configuration.transportRequestId}'"]
if(backendType == BackendType.SOLMAN)
uploadedMessage << " of change document '${configuration.changeDocumentId}'"
uploadedMessage << '.'

View File

@ -74,7 +74,12 @@ import static com.sap.piper.Prerequisites.checkScript
/**
* With `testRepository` the tests can be loaded from another reposirory.
*/
'testRepository'
'testRepository',
/**
* The `testServerUrl` is passed as environment variable `TARGET_SERVER_URL` to the test execution.
* The tests should read the host information from this environment variable in order to be infrastructure agnostic.
*/
'testServerUrl'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
@ -123,6 +128,7 @@ void call(Map parameters = [:]) {
config.stashContent = config.testRepository ? [GitUtils.handleTestRepository(this, config)] : utils.unstashAll(config.stashContent)
config.installCommand = SimpleTemplateEngine.newInstance().createTemplate(config.installCommand).make([config: config]).toString()
config.runCommand = SimpleTemplateEngine.newInstance().createTemplate(config.runCommand).make([config: config]).toString()
config.dockerEnvVars.TARGET_SERVER_URL = config.dockerEnvVars.TARGET_SERVER_URL ?: config.testServerUrl
seleniumExecuteTests(
script: script,