mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +02:00
e54f18e6bc
Introduce xs deploy
339 lines
11 KiB
Groovy
339 lines
11 KiB
Groovy
import com.sap.piper.JenkinsUtils
|
|
|
|
import static com.sap.piper.Prerequisites.checkScript
|
|
|
|
import com.sap.piper.BashUtils
|
|
import com.sap.piper.ConfigurationHelper
|
|
import com.sap.piper.GenerateDocumentation
|
|
import com.sap.piper.Utils
|
|
|
|
import groovy.transform.Field
|
|
|
|
import hudson.AbortException
|
|
|
|
@Field String STEP_NAME = getClass().getName()
|
|
|
|
@Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS
|
|
|
|
@Field Set STEP_CONFIG_KEYS = [
|
|
'action',
|
|
'apiUrl',
|
|
'credentialsId',
|
|
'deploymentId',
|
|
'deployIdLogPattern',
|
|
'deployOpts',
|
|
/** A map containing properties forwarded to dockerExecute. For more details see [here][dockerExecute] */
|
|
'docker',
|
|
'loginOpts',
|
|
'mode',
|
|
'mtaPath',
|
|
'org',
|
|
'space',
|
|
'xsSessionFile',
|
|
]
|
|
|
|
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs an XS deployment
|
|
*
|
|
* In case of blue-green deployments the step is called for the deployment in the narrower sense
|
|
* and later again for resuming or aborting. In this case both calls needs to be performed from the
|
|
* same directory.
|
|
*/
|
|
@GenerateDocumentation
|
|
void call(Map parameters = [:]) {
|
|
|
|
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
|
|
|
|
def utils = parameters.juStabUtils ?: new Utils()
|
|
|
|
final script = checkScript(this, parameters) ?: this
|
|
|
|
ConfigurationHelper configHelper = 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)
|
|
.addIfEmpty('mtaPath', script.commonPipelineEnvironment.getMtarFilePath())
|
|
.addIfEmpty('deploymentId', script.commonPipelineEnvironment.xsDeploymentId)
|
|
.mixin(parameters, PARAMETER_KEYS)
|
|
|
|
Map config = configHelper.use()
|
|
|
|
DeployMode mode = config.mode
|
|
|
|
if(mode == DeployMode.NONE) {
|
|
echo "Deployment skipped intentionally. Deploy mode '${mode.toString()}'."
|
|
return
|
|
}
|
|
|
|
Action action = config.action
|
|
|
|
if(mode == DeployMode.DEPLOY && action != Action.NONE) {
|
|
error "Cannot perform action '${action.toString()}' in mode '${mode.toString()}'. Only action '${Action.NONE.toString()}' is allowed."
|
|
}
|
|
|
|
boolean performLogin = ((mode == DeployMode.DEPLOY) || (mode == DeployMode.BG_DEPLOY && !(action in [Action.RESUME, Action.ABORT])))
|
|
boolean performLogout = ((mode == DeployMode.DEPLOY) || (mode == DeployMode.BG_DEPLOY && action != Action.NONE))
|
|
|
|
boolean sessionExists = fileExists file: config.xsSessionFile
|
|
|
|
if( (! performLogin) && (! sessionExists) ) {
|
|
error 'For the current configuration an already existing session is required. But there is no already existing session.'
|
|
}
|
|
|
|
configHelper
|
|
.collectValidationFailures()
|
|
/**
|
|
* Used for finalizing the blue-green deployment.
|
|
* @possibleValues RESUME, ABORT, RETRY
|
|
*/
|
|
.withMandatoryProperty('action')
|
|
/** The file name of the file representing the sesssion after `xs login`. Should not be changed normally. */
|
|
.withMandatoryProperty('xsSessionFile')
|
|
/** Regex pattern for retrieving the ID of the deployment. */
|
|
.withMandatoryProperty('deployIdLogPattern')
|
|
/**
|
|
* Controls if there is a standard deployment or a blue green deployment
|
|
* @possibleValues DEPLOY, BG_DEPLOY
|
|
*/
|
|
.withMandatoryProperty('mode')
|
|
/** The endpoint */
|
|
.withMandatoryProperty('apiUrl')
|
|
/** The organization */
|
|
.withMandatoryProperty('org')
|
|
/** The space */
|
|
.withMandatoryProperty('space')
|
|
/** Additional options appended to the login command. Only needed for sophisticated cases.
|
|
* When provided it is the duty of the provider to ensure proper quoting / escaping.
|
|
*/
|
|
.withMandatoryProperty('loginOpts')
|
|
/** Additional options appended to the deploy command. Only needed for sophisticated cases.
|
|
* When provided it is the duty of the provider to ensure proper quoting / escaping.
|
|
*/
|
|
.withMandatoryProperty('deployOpts')
|
|
/** The credentialsId */
|
|
.withMandatoryProperty('credentialsId')
|
|
/** The path to the deployable. If not provided explicitly it is retrieved from the common pipeline environment
|
|
* (Parameter `mtarFilePath`).
|
|
*/
|
|
.withMandatoryProperty('mtaPath', null, {action == Action.NONE})
|
|
.withMandatoryProperty('deploymentId',
|
|
'No deployment id provided, neither via parameters nor via common pipeline environment. Was there a deployment before?',
|
|
{action in [Action.RESUME, Action.ABORT, Action.RETRY]})
|
|
.use()
|
|
|
|
utils.pushToSWA([
|
|
step: STEP_NAME,
|
|
], config)
|
|
|
|
if(action == Action.NONE) {
|
|
boolean deployableExists = fileExists file: config.mtaPath
|
|
if(! deployableExists)
|
|
error "Deployable '${config.mtaPath}' does not exist."
|
|
}
|
|
|
|
if(performLogin) {
|
|
login(script, config)
|
|
}
|
|
|
|
def failures = []
|
|
|
|
if(action in [Action.RESUME, Action.ABORT, Action.RETRY]) {
|
|
|
|
complete(script, mode, action, config, failures)
|
|
|
|
} else {
|
|
|
|
deploy(script, mode, config, failures)
|
|
}
|
|
|
|
if (performLogout || failures) {
|
|
logout(script, config, failures)
|
|
|
|
} else {
|
|
echo "Skipping logout in order to be able to resume or abort later."
|
|
}
|
|
|
|
if(failures) {
|
|
error "Failed command(s): ${failures}. Check earlier log for details."
|
|
}
|
|
}
|
|
}
|
|
|
|
void login(Script script, Map config) {
|
|
|
|
withCredentials([usernamePassword(
|
|
credentialsId: config.credentialsId,
|
|
passwordVariable: 'password',
|
|
usernameVariable: 'username'
|
|
)]) {
|
|
|
|
def returnCode = executeXSCommand([script: script].plus(config.docker),
|
|
[
|
|
"xs login -a ${config.apiUrl} -u ${username} -p ${BashUtils.quoteAndEscape(password)} -o ${config.org} -s ${config.space} ${config.loginOpts}",
|
|
'RC=$?',
|
|
"[ \$RC == 0 ] && cp \"\${HOME}/${config.xsSessionFile}\" .",
|
|
'exit $RC'
|
|
])
|
|
|
|
if(returnCode != 0)
|
|
error "xs login failed."
|
|
}
|
|
|
|
boolean existsXsSessionFileAfterLogin = fileExists file: config.xsSessionFile
|
|
if(! existsXsSessionFileAfterLogin)
|
|
error "Session file ${config.xsSessionFile} not found in current working directory after login."
|
|
}
|
|
|
|
void deploy(Script script, DeployMode mode, Map config, def failures) {
|
|
|
|
def deploymentLog
|
|
|
|
try {
|
|
lock(getLockIdentifier(config)) {
|
|
deploymentLog = executeXSCommand([script: script].plus(config.docker),
|
|
[
|
|
"cp ${config.xsSessionFile} \${HOME}",
|
|
"xs ${mode.toString()} '${config.mtaPath}' -f ${config.deployOpts}"
|
|
], true)
|
|
}
|
|
|
|
echo "Deploy log: ${deploymentLog}"
|
|
|
|
} catch(AbortException e) {
|
|
echo "deployment failed. Message: ${e.getMessage()}, Log: ${deploymentLog}}"
|
|
failures << "xs ${mode.toString()}"
|
|
}
|
|
|
|
if(mode == DeployMode.BG_DEPLOY) {
|
|
|
|
if(! failures.isEmpty()) {
|
|
|
|
echo "Retrieval of deploymentId skipped since prior deployment was not successfull."
|
|
|
|
} else {
|
|
|
|
for (def logLine : deploymentLog.readLines()) {
|
|
def matcher = logLine =~ config.deployIdLogPattern
|
|
if(matcher.find()) {
|
|
script.commonPipelineEnvironment.xsDeploymentId = matcher[0][1]
|
|
echo "DeploymentId: ${script.commonPipelineEnvironment.xsDeploymentId}."
|
|
break
|
|
}
|
|
}
|
|
if(script.commonPipelineEnvironment.xsDeploymentId == null) {
|
|
failures << "Cannot lookup deploymentId. Search pattern was: '${config.deployIdLogPattern}'."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void complete(Script script, DeployMode mode, Action action, Map config, def failures) {
|
|
|
|
if(mode != DeployMode.BG_DEPLOY)
|
|
error "Action '${action.toString()}' can only be performed for mode '${DeployMode.BG_DEPLOY.toString()}'. Current mode is: '${mode.toString()}'."
|
|
|
|
def returnCode = 1
|
|
|
|
lock(getLockIdentifier(config)) {
|
|
returnCode = executeXSCommand([script: script].plus(config.docker),
|
|
[
|
|
"cp ${config.xsSessionFile} \${HOME}",
|
|
"xs ${mode.toString()} -i ${config.deploymentId} -a ${action.toString()}"
|
|
])
|
|
}
|
|
|
|
if(returnCode != 0) {
|
|
echo "${mode.toString()} with action '${action.toString()}' failed with return code ${returnCode}."
|
|
failures << "xs ${mode.toString()} -a ${action.toString()}"
|
|
}
|
|
}
|
|
|
|
void logout(Script script, Map config, def failures) {
|
|
|
|
def returnCode = executeXSCommand([script: script].plus(config.docker),
|
|
[
|
|
"cp ${config.xsSessionFile} \${HOME}",
|
|
'xs logout'
|
|
])
|
|
|
|
if(returnCode != 0) {
|
|
failures << 'xs logout'
|
|
}
|
|
|
|
sh "XSCONFIG=${config.xsSessionFile}; [ -f \${XSCONFIG} ] && rm \${XSCONFIG}"
|
|
}
|
|
|
|
String getLockIdentifier(Map config) {
|
|
"$STEP_NAME:${config.apiUrl}:${config.org}:${config.space}"
|
|
}
|
|
|
|
def executeXSCommand(Map dockerOptions, List commands, boolean returnStdout = false) {
|
|
|
|
def r
|
|
|
|
dockerExecute(dockerOptions) {
|
|
|
|
// in case there are credentials contained in the commands we assume
|
|
// the call is properly wrapped by withCredentials(./.)
|
|
echo "Executing: '${commands}'."
|
|
|
|
List prelude = [
|
|
'#!/bin/bash'
|
|
]
|
|
|
|
List script = (prelude + commands)
|
|
|
|
params = [
|
|
script: script.join('\n')
|
|
]
|
|
|
|
if(returnStdout) {
|
|
params << [ returnStdout: true ]
|
|
} else {
|
|
params << [ returnStatus: true ]
|
|
}
|
|
|
|
r = sh params
|
|
|
|
if( (! returnStdout ) && r != 0) {
|
|
|
|
try {
|
|
echo "xs logs:"
|
|
|
|
sh 'LOG_FOLDER=${HOME}/.xs_logs; [ -d ${LOG_FOLDER} ] && cat ${LOG_FOLDER}/*'
|
|
|
|
} catch(Exception e) {
|
|
|
|
echo "Cannot provide xs logs: ${e.getMessage()}."
|
|
}
|
|
|
|
echo "Executing of commands '${commands}' failed. Check earlier logs for details."
|
|
}
|
|
}
|
|
r
|
|
}
|