github.com/xgoffin/jenkins-library@v1.154.0/vars/xsDeploy.groovy (about) 1 import static com.sap.piper.Prerequisites.checkScript 2 3 import com.sap.piper.ConfigurationHelper 4 5 import com.sap.piper.DefaultValueCache 6 import com.sap.piper.JenkinsUtils 7 import com.sap.piper.PiperGoUtils 8 9 10 import com.sap.piper.GenerateDocumentation 11 import com.sap.piper.Utils 12 13 import groovy.transform.Field 14 15 @Field String METADATA_FILE = 'metadata/xsDeploy.yaml' 16 @Field String PIPER_DEFAULTS = 'default_pipeline_environment.yml' 17 @Field String STEP_NAME = getClass().getName() 18 @Field String METADATA_FOLDER = '.pipeline' // metadata file contains already the "metadata" folder level, hence we end up in a folder ".pipeline/metadata" 19 @Field String ADDITIONAL_CONFIGS_FOLDER='.pipeline/additionalConfigs' 20 21 22 enum DeployMode { 23 DEPLOY, 24 BG_DEPLOY, 25 NONE 26 27 String toString() { 28 name().toLowerCase(Locale.ENGLISH).replaceAll('_', '-') 29 } 30 } 31 32 enum Action { 33 RESUME, 34 ABORT, 35 RETRY, 36 NONE 37 38 String toString() { 39 name().toLowerCase(Locale.ENGLISH) 40 } 41 } 42 43 void call(Map parameters = [:]) { 44 45 handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) { 46 47 final script = checkScript(this, parameters) ?: null 48 49 if(! script) { 50 error "Reference to surrounding pipeline script not provided (script: this)." 51 } 52 53 def utils = parameters.juStabUtils ?: new Utils() 54 def piperGoUtils = parameters.piperGoUtils ?: new PiperGoUtils(utils) 55 56 // 57 // The parameters map in provided from outside. That map might be used elsewhere in the pipeline 58 // hence we should not modify it here. So we create a new map based on the parameters map. 59 parameters = [:] << parameters 60 61 // hard to predict how these parameters looks like in its serialized form. Anyhow it is better 62 // not to have these parameters forwarded somehow to the go layer. 63 parameters.remove('juStabUtils') 64 parameters.remove('piperGoUtils') 65 parameters.remove('script') 66 67 piperGoUtils.unstashPiperBin() 68 69 // 70 // Printing the piper-go version. Should not be done here, but somewhere during materializing 71 // the piper binary. As long as we don't have it elsewhere we should keep it here. 72 def piperGoVersion = sh(returnStdout: true, script: "./piper version") 73 echo "PiperGoVersion: ${piperGoVersion}" 74 75 // 76 // since there is no valid config provided (... null) telemetry is disabled (same for other go releated steps at the moment). 77 utils.pushToSWA([ 78 step: STEP_NAME, 79 ], null) 80 81 String configFiles = prepareConfigurations([PIPER_DEFAULTS].plus(script.commonPipelineEnvironment.getCustomDefaults()), ADDITIONAL_CONFIGS_FOLDER) 82 83 writeFile(file: "${METADATA_FOLDER}/${METADATA_FILE}", text: libraryResource(METADATA_FILE)) 84 85 withEnv([ 86 "PIPER_parametersJSON=${groovy.json.JsonOutput.toJson(parameters)}", 87 ]) { 88 89 // 90 // context config gives us e.g. the docker image name. --> How does this work for customer maintained images? 91 // There is a name provided in the metadata file. But we do not provide a docker image for that. 92 // The user has to build that for her/his own. How do we expect to configure this? 93 94 String projectConfigScript = "./piper getConfig --stepMetadata '${METADATA_FOLDER}/${METADATA_FILE}' --defaultConfig ${configFiles}" 95 String contextConfigScript = projectConfigScript + " --contextConfig" 96 Map projectConfig = readJSON (text: sh(returnStdout: true, script: projectConfigScript)) 97 Map contextConfig = readJSON (text: sh(returnStdout: true, script: contextConfigScript)) 98 99 Map options = getOptions(parameters, projectConfig, contextConfig, script.commonPipelineEnvironment) 100 101 Action action = options.action 102 DeployMode mode = options.mode 103 104 if(parameters.verbose) { 105 echo "[INFO] ContextConfig: ${contextConfig}" 106 echo "[INFO] ProjectConfig: ${projectConfig}" 107 } 108 109 def mtarFilePath = script.commonPipelineEnvironment.mtarFilePath 110 111 def operationId = parameters.operationId 112 if(! operationId && mode == DeployMode.BG_DEPLOY && action != Action.NONE) { 113 operationId = script.commonPipelineEnvironment.xsDeploymentId 114 if (! operationId) { 115 throw new IllegalArgumentException('No operationId provided. Was there a deployment before?') 116 } 117 } 118 119 def xsDeployStdout 120 121 lock(getLockIdentifier(projectConfig)) { 122 123 withCredentials([usernamePassword( 124 credentialsId: contextConfig.credentialsId, 125 passwordVariable: 'PASSWORD', 126 usernameVariable: 'USERNAME')]) { 127 128 dockerExecute([script: this].plus([dockerImage: options.dockerImage, dockerPullImage: options.dockerPullImage])) { 129 xsDeployStdout = sh returnStdout: true, script: """#!/bin/bash 130 ./piper xsDeploy --defaultConfig ${configFiles} --username \${USERNAME} --password \${PASSWORD} ${mtarFilePath ? '--mtaPath ' + mtarFilePath : ''} ${operationId ? '--operationId ' + operationId : ''} 131 """ 132 } 133 134 } 135 } 136 137 if(mode == DeployMode.BG_DEPLOY && action == Action.NONE) { 138 script.commonPipelineEnvironment.xsDeploymentId = readJSON(text: xsDeployStdout).operationId 139 if (!script.commonPipelineEnvironment.xsDeploymentId) { 140 error "No Operation id returned from xs deploy step. This is required for mode '${mode}' and action '${action}'." 141 } 142 echo "[INFO] OperationId for subsequent resume or abort: '${script.commonPipelineEnvironment.xsDeploymentId}'." 143 } 144 } 145 } 146 } 147 148 String getLockIdentifier(Map config) { 149 "$STEP_NAME:${config.apiUrl}:${config.org}:${config.space}" 150 } 151 152 /* 153 * The returned string can be used directly in the command line for retrieving the configuration via go 154 */ 155 String prepareConfigurations(List configs, String configCacheFolder) { 156 157 for(def customDefault : configs) { 158 writeFile(file: "${ADDITIONAL_CONFIGS_FOLDER}/${customDefault}", text: libraryResource(customDefault)) 159 } 160 joinAndQuote(configs.reverse(), configCacheFolder) 161 } 162 163 /* 164 * prefix is supposed to be provided without trailing slash 165 */ 166 String joinAndQuote(List l, String prefix = '') { 167 _l = [] 168 169 if(prefix == null) { 170 prefix = '' 171 } 172 if(prefix.endsWith('/') || prefix.endsWith('\\')) 173 throw new IllegalArgumentException("Provide prefix (${prefix}) without trailing slash") 174 175 for(def e : l) { 176 def _e = '' 177 if(prefix.length() > 0) { 178 _e += prefix 179 _e += '/' 180 } 181 _e += e 182 _l << '"' + _e + '"' 183 } 184 _l.join(' ') 185 } 186 187 /* 188 ugly backward compatibility handling 189 retrieves docker options from project config or from landscape config layer(s) 190 191 precedence is 192 1.) parameters via signature 193 2.) project config (not nested) 194 3.) project config (nested inside docker node) 195 4.) context config (if applicable (docker)) 196 */ 197 Map getOptions(Map parameters, Map projectConfig, Map contextConfig, def cpe) { 198 199 Set configKeys = ['docker', 'mode', 'action', 'dockerImage', 'dockerPullImage'] 200 Map config = ConfigurationHelper.newInstance(this) 201 .loadStepDefaults() 202 .mixinGeneralConfig(cpe, configKeys) 203 .mixinStepConfig(cpe, configKeys) 204 .mixinStageConfig(cpe, env.STAGE_NAME, configKeys) 205 .mixin(parameters, configKeys) 206 .use() 207 208 def dockerImage = config.dockerImage ?: (projectConfig.dockerImage ?: (config.docker?.dockerImage ?: contextConfig.dockerImage)) 209 def dockerPullImage = config.dockerPullImage ?: (projectConfig.dockerPullImage ?: (config.docker?.dockerPullImage ?: contextConfig.dockerPullImage)) 210 def mode = config.mode ?: projectConfig.mode 211 def action = config.action ?: projectConfig.action 212 213 [dockerImage: dockerImage, dockerPullImage: dockerPullImage, mode: mode, action: action] 214 }