2019-12-13 17:05:55 +02:00
|
|
|
import static com.sap.piper.Prerequisites.checkScript
|
|
|
|
|
2020-01-15 11:57:36 +02:00
|
|
|
import com.sap.piper.ConfigurationHelper
|
|
|
|
|
2019-12-16 11:40:44 +02:00
|
|
|
import com.sap.piper.DefaultValueCache
|
2019-09-11 13:42:38 +02:00
|
|
|
import com.sap.piper.JenkinsUtils
|
2019-12-13 17:05:55 +02:00
|
|
|
import com.sap.piper.PiperGoUtils
|
2019-09-11 13:42:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
import com.sap.piper.GenerateDocumentation
|
|
|
|
import com.sap.piper.Utils
|
|
|
|
|
|
|
|
import groovy.transform.Field
|
|
|
|
|
2019-12-13 17:05:55 +02:00
|
|
|
@Field String METADATA_FILE = 'metadata/xsDeploy.yaml'
|
2019-12-16 11:40:44 +02:00
|
|
|
@Field String PIPER_DEFAULTS = 'default_pipeline_environment.yml'
|
2019-09-11 13:42:38 +02:00
|
|
|
@Field String STEP_NAME = getClass().getName()
|
2019-12-19 09:56:10 +02:00
|
|
|
@Field String METADATA_FOLDER = '.pipeline' // metadata file contains already the "metadata" folder level, hence we end up in a folder ".pipeline/metadata"
|
|
|
|
@Field String ADDITIONAL_CONFIGS_FOLDER='.pipeline/additionalConfigs'
|
2019-09-11 13:42:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
enum DeployMode {
|
|
|
|
DEPLOY,
|
|
|
|
BG_DEPLOY,
|
|
|
|
NONE
|
|
|
|
|
|
|
|
String toString() {
|
|
|
|
name().toLowerCase(Locale.ENGLISH).replaceAll('_', '-')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Action {
|
|
|
|
RESUME,
|
|
|
|
ABORT,
|
|
|
|
RETRY,
|
|
|
|
NONE
|
|
|
|
|
|
|
|
String toString() {
|
|
|
|
name().toLowerCase(Locale.ENGLISH)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void call(Map parameters = [:]) {
|
|
|
|
|
|
|
|
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
|
|
|
|
|
2019-12-13 17:05:55 +02:00
|
|
|
final script = checkScript(this, parameters) ?: null
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2019-12-13 17:05:55 +02:00
|
|
|
if(! script) {
|
|
|
|
error "Reference to surrounding pipeline script not provided (script: this)."
|
2019-09-11 13:42:38 +02:00
|
|
|
}
|
|
|
|
|
2019-12-13 17:05:55 +02:00
|
|
|
def utils = parameters.juStabUtils ?: new Utils()
|
|
|
|
def piperGoUtils = parameters.piperGoUtils ?: new PiperGoUtils(utils)
|
|
|
|
|
|
|
|
//
|
|
|
|
// The parameters map in provided from outside. That map might be used elsewhere in the pipeline
|
|
|
|
// hence we should not modify it here. So we create a new map based on the parameters map.
|
|
|
|
parameters = [:] << parameters
|
|
|
|
|
2019-12-19 09:56:10 +02:00
|
|
|
// hard to predict how these parameters looks like in its serialized form. Anyhow it is better
|
2019-12-13 17:05:55 +02:00
|
|
|
// not to have these parameters forwarded somehow to the go layer.
|
|
|
|
parameters.remove('juStabUtils')
|
|
|
|
parameters.remove('piperGoUtils')
|
|
|
|
parameters.remove('script')
|
|
|
|
|
|
|
|
piperGoUtils.unstashPiperBin()
|
|
|
|
|
|
|
|
//
|
|
|
|
// Printing the piper-go version. Should not be done here, but somewhere during materializing
|
2019-12-19 09:56:10 +02:00
|
|
|
// the piper binary. As long as we don't have it elsewhere we should keep it here.
|
2019-12-13 17:05:55 +02:00
|
|
|
def piperGoVersion = sh(returnStdout: true, script: "./piper version")
|
|
|
|
echo "PiperGoVersion: ${piperGoVersion}"
|
|
|
|
|
|
|
|
//
|
2019-12-19 09:56:10 +02:00
|
|
|
// since there is no valid config provided (... null) telemetry is disabled (same for other go releated steps at the moment).
|
2019-09-11 13:42:38 +02:00
|
|
|
utils.pushToSWA([
|
|
|
|
step: STEP_NAME,
|
2019-12-13 17:05:55 +02:00
|
|
|
], null)
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2019-12-19 09:56:10 +02:00
|
|
|
String configFiles = prepareConfigurations([PIPER_DEFAULTS].plus(script.commonPipelineEnvironment.getCustomDefaults()), ADDITIONAL_CONFIGS_FOLDER)
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2019-12-19 09:56:10 +02:00
|
|
|
writeFile(file: "${METADATA_FOLDER}/${METADATA_FILE}", text: libraryResource(METADATA_FILE))
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2019-12-13 17:05:55 +02:00
|
|
|
withEnv([
|
|
|
|
"PIPER_parametersJSON=${groovy.json.JsonOutput.toJson(parameters)}",
|
|
|
|
]) {
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2019-12-13 17:05:55 +02:00
|
|
|
//
|
|
|
|
// context config gives us e.g. the docker image name. --> How does this work for customer maintained images?
|
|
|
|
// There is a name provided in the metadata file. But we do not provide a docker image for that.
|
|
|
|
// The user has to build that for her/his own. How do we expect to configure this?
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2019-12-20 12:51:43 +02:00
|
|
|
String projectConfigScript = "./piper getConfig --stepMetadata '${METADATA_FOLDER}/${METADATA_FILE}' --defaultConfig ${configFiles}"
|
2019-12-19 09:56:10 +02:00
|
|
|
String contextConfigScript = projectConfigScript + " --contextConfig"
|
|
|
|
Map projectConfig = readJSON (text: sh(returnStdout: true, script: projectConfigScript))
|
|
|
|
Map contextConfig = readJSON (text: sh(returnStdout: true, script: contextConfigScript))
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2020-01-15 11:57:36 +02:00
|
|
|
Map dockerOptions = getDockerOptions(script.commonPipelineEnvironment)
|
|
|
|
def dockerImage = dockerOptions.dockerImage
|
|
|
|
def dockerPullImage = dockerOptions.dockerPullImage
|
|
|
|
if(dockerImage == null || dockerImage.toString().trim().isEmpty()) dockerImage = contextConfig.dockerImage
|
|
|
|
if(dockerPullImage == null || dockerPullImage.toString().trim().isEmpty()) dockerPullImage = contextConfig.dockerPullImage
|
|
|
|
|
2019-12-13 17:05:55 +02:00
|
|
|
Action action = projectConfig.action
|
|
|
|
DeployMode mode = projectConfig.mode
|
|
|
|
|
|
|
|
if(parameters.verbose) {
|
2019-12-20 15:22:39 +02:00
|
|
|
echo "[INFO] ContextConfig: ${contextConfig}"
|
|
|
|
echo "[INFO] ProjectConfig: ${projectConfig}"
|
2019-12-13 17:05:55 +02:00
|
|
|
}
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2020-01-15 16:08:43 +02:00
|
|
|
def mtaFilePath = script.commonPipelineEnvironment.mtarFilePath
|
|
|
|
|
2019-12-20 15:49:57 +02:00
|
|
|
def operationId = parameters.operationId
|
|
|
|
if(! operationId && mode == DeployMode.BG_DEPLOY && action != Action.NONE) {
|
2019-12-13 17:05:55 +02:00
|
|
|
operationId = script.commonPipelineEnvironment.xsDeploymentId
|
|
|
|
if (! operationId) {
|
|
|
|
throw new IllegalArgumentException('No operationId provided. Was there a deployment before?')
|
|
|
|
}
|
|
|
|
}
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2019-12-13 17:05:55 +02:00
|
|
|
def xsDeployStdout
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2019-12-20 15:22:39 +02:00
|
|
|
lock(getLockIdentifier(projectConfig)) {
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2019-12-13 17:05:55 +02:00
|
|
|
withCredentials([usernamePassword(
|
2019-12-20 15:22:39 +02:00
|
|
|
credentialsId: contextConfig.credentialsId,
|
2019-12-13 17:05:55 +02:00
|
|
|
passwordVariable: 'PASSWORD',
|
|
|
|
usernameVariable: 'USERNAME')]) {
|
2019-09-11 13:42:38 +02:00
|
|
|
|
2020-01-15 11:57:36 +02:00
|
|
|
dockerExecute([script: this].plus([dockerImage: dockerImage, dockerPullImage: dockerPullImage])) {
|
2019-12-13 17:05:55 +02:00
|
|
|
xsDeployStdout = sh returnStdout: true, script: """#!/bin/bash
|
2020-01-15 16:08:43 +02:00
|
|
|
./piper xsDeploy --defaultConfig ${configFiles} --user \${USERNAME} --password \${PASSWORD} ${mtaFilePath ? '--mtaPath ' + mtaFilePath : ''} ${operationId ? '--operationId ' + operationId : ''}
|
2019-12-13 17:05:55 +02:00
|
|
|
"""
|
|
|
|
}
|
2019-09-11 13:42:38 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2019-12-13 17:05:55 +02:00
|
|
|
|
|
|
|
if(mode == DeployMode.BG_DEPLOY && action == Action.NONE) {
|
|
|
|
script.commonPipelineEnvironment.xsDeploymentId = readJSON(text: xsDeployStdout).operationId
|
|
|
|
if (!script.commonPipelineEnvironment.xsDeploymentId) {
|
|
|
|
error "No Operation id returned from xs deploy step. This is required for mode '${mode}' and action '${action}'."
|
|
|
|
}
|
|
|
|
echo "[INFO] OperationId for subsequent resume or abort: '${script.commonPipelineEnvironment.xsDeploymentId}'."
|
2019-09-11 13:42:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String getLockIdentifier(Map config) {
|
|
|
|
"$STEP_NAME:${config.apiUrl}:${config.org}:${config.space}"
|
|
|
|
}
|
2019-12-16 11:40:44 +02:00
|
|
|
|
2019-12-19 09:56:10 +02:00
|
|
|
/*
|
|
|
|
* The returned string can be used directly in the command line for retrieving the configuration via go
|
|
|
|
*/
|
|
|
|
String prepareConfigurations(List configs, String configCacheFolder) {
|
|
|
|
|
|
|
|
for(def customDefault : configs) {
|
|
|
|
writeFile(file: "${ADDITIONAL_CONFIGS_FOLDER}/${customDefault}", text: libraryResource(customDefault))
|
|
|
|
}
|
|
|
|
joinAndQuote(configs.reverse(), configCacheFolder)
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* prefix is supposed to be provided without trailing slash
|
|
|
|
*/
|
|
|
|
String joinAndQuote(List l, String prefix = '') {
|
2019-12-16 11:40:44 +02:00
|
|
|
_l = []
|
2019-12-19 09:56:10 +02:00
|
|
|
|
|
|
|
if(prefix == null) {
|
|
|
|
prefix = ''
|
|
|
|
}
|
|
|
|
if(prefix.endsWith('/') || prefix.endsWith('\\'))
|
|
|
|
throw new IllegalArgumentException("Provide prefix (${prefix}) without trailing slash")
|
|
|
|
|
2019-12-16 11:40:44 +02:00
|
|
|
for(def e : l) {
|
2020-01-07 13:40:58 +02:00
|
|
|
def _e = ''
|
|
|
|
if(prefix.length() > 0) {
|
|
|
|
_e += prefix
|
|
|
|
_e += '/'
|
|
|
|
}
|
|
|
|
_e += e
|
2019-12-19 09:56:10 +02:00
|
|
|
_l << '"' + _e + '"'
|
2019-12-16 11:40:44 +02:00
|
|
|
}
|
|
|
|
_l.join(' ')
|
|
|
|
}
|
2020-01-15 11:57:36 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// ugly backward compatibility handling
|
|
|
|
// retrieves docker options from project config or from landscape config layer(s)
|
|
|
|
//
|
|
|
|
Map getDockerOptions(def cpe) {
|
|
|
|
Set configKeys = ['docker']
|
|
|
|
Map config = ConfigurationHelper.newInstance(this)
|
|
|
|
.loadStepDefaults()
|
|
|
|
.mixinGeneralConfig(cpe, configKeys)
|
|
|
|
.mixinStepConfig(cpe, configKeys)
|
|
|
|
.mixinStageConfig(cpe, env.STAGE_NAME, configKeys)
|
|
|
|
.use()
|
|
|
|
|
|
|
|
[dockerImage: config?.docker.dockerImage, dockerPullImage: config?.docker.dockerPullImage]
|
|
|
|
}
|