github.com/jaylevin/jenkins-library@v1.230.4/vars/cfManifestSubstituteVariables.groovy (about) 1 import com.sap.piper.ConfigurationHelper 2 import com.sap.piper.GenerateDocumentation 3 import com.sap.piper.variablesubstitution.ExecutionContext 4 import com.sap.piper.variablesubstitution.DebugHelper 5 import com.sap.piper.variablesubstitution.YamlUtils 6 import groovy.transform.Field 7 8 import static com.sap.piper.Prerequisites.checkScript 9 10 @Field String STEP_NAME = getClass().getName() 11 @Field Set GENERAL_CONFIG_KEYS = [] 12 @Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS + [ 13 /** 14 * The `String` path of the Yaml file to replace variables in. 15 * Defaults to "manifest.yml" if not specified otherwise. 16 */ 17 'manifestFile', 18 /** 19 * The `String` path of the Yaml file to produce as output. 20 * If not specified this will default to `manifestFile` and overwrite it. 21 */ 22 'outputManifestFile', 23 /** 24 * The `List` of `String` paths of the Yaml files containing the variable values to use as a replacement in the manifest file. 25 * Defaults to `["manifest-variables.yml"]` if not specified otherwise. The order of the files given in the list is relevant 26 * in case there are conflicting variable names and values within variable files. In such a case, the values of the last file win. 27 */ 28 'manifestVariablesFiles', 29 /** 30 * A `List` of `Map` entries for key-value pairs used for variable substitution within the file given by `manifestFile`. 31 * Defaults to an empty list, if not specified otherwise. This can be used to set variables like it is provided 32 * by `cf push --var key=value`. 33 * 34 * The order of the maps of variables given in the list is relevant in case there are conflicting variable names and values 35 * between maps contained within the list. In case of conflicts, the last specified map in the list will win. 36 * 37 * Though each map entry in the list can contain more than one key-value pair for variable substitution, it is recommended 38 * to stick to one entry per map, and rather declare more maps within the list. The reason is that 39 * if a map in the list contains more than one key-value entry, and the entries are conflicting, the 40 * conflict resolution behavior is undefined (since map entries have no sequence). 41 * 42 * Note: variables defined via `manifestVariables` always win over conflicting variables defined via any file given 43 * by `manifestVariablesFiles` - no matter what is declared before. This reproduces the same behavior as can be 44 * observed when using `cf push --var` in combination with `cf push --vars-file`. 45 */ 46 'manifestVariables' 47 ] 48 49 @Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS 50 51 /** 52 * Step to substitute variables in a given YAML file with those specified in one or more variables files given by the 53 * `manifestVariablesFiles` parameter. This follows the behavior of `cf push --vars-file`, and can be 54 * used as a pre-deployment step if commands other than `cf push` are used for deployment (e.g. `cf blue-green-deploy`). 55 * 56 * The format to reference a variable in the manifest YAML file is to use double parentheses `((` and `))`, e.g. `((variableName))`. 57 * 58 * You can declare variable assignments as key value-pairs inside a YAML variables file following the 59 * [Cloud Foundry standards](https://docs.cloudfoundry.org/devguide/deploy-apps/manifest-attributes.html#variable-substitution) format. 60 * 61 * Optionally, you can also specify a direct list of key-value mappings for variables using the `manifestVariables` parameter. 62 * Variables given in the `manifestVariables` list will take precedence over those found in variables files. This follows 63 * the behavior of `cf push --var`, and works in combination with `manifestVariablesFiles`. 64 * 65 * The step is activated by the presence of the file specified by the `manifestFile` parameter and all variables files 66 * specified by the `manifestVariablesFiles` parameter, or if variables are passed in directly via `manifestVariables`. 67 * 68 * In case no `manifestVariablesFiles` were explicitly specified, a default named `manifest-variables.yml` will be looked 69 * for and if present will activate this step also. This is to support convention over configuration. 70 */ 71 @GenerateDocumentation 72 void call(Map arguments = [:]) { 73 handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: arguments) { 74 def script = checkScript(this, arguments) ?: this 75 String stageName = arguments.stageName ?: env.STAGE_NAME 76 77 // load default & individual configuration 78 Map config = ConfigurationHelper.newInstance(this) 79 .loadStepDefaults([:], stageName) 80 .mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS) 81 .mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS) 82 .mixin(arguments, PARAMETER_KEYS) 83 .use() 84 85 String defaultManifestFileName = "manifest.yml" 86 String defaultManifestVariablesFileName = "manifest-variables.yml" 87 88 Boolean manifestVariablesFilesExplicitlySpecified = config.manifestVariablesFiles != null 89 90 String manifestFilePath = config.manifestFile ?: defaultManifestFileName 91 List<String> manifestVariablesFiles = (config.manifestVariablesFiles != null) ? config.manifestVariablesFiles : [ defaultManifestVariablesFileName ] 92 List<Map<String, Object>> manifestVariablesList = config.manifestVariables ?: [] 93 String outputFilePath = config.outputManifestFile ?: manifestFilePath 94 95 DebugHelper debugHelper = new DebugHelper(script, config) 96 YamlUtils yamlUtils = new YamlUtils(script, debugHelper) 97 98 Boolean manifestExists = fileExists manifestFilePath 99 Boolean manifestVariablesFilesExist = allManifestVariableFilesExist(manifestVariablesFiles) 100 Boolean manifestVariablesListSpecified = !manifestVariablesList.isEmpty() 101 102 if (!manifestExists) { 103 echo "[CFManifestSubstituteVariables] Could not find YAML file at ${manifestFilePath}. Skipping variable substitution." 104 return 105 } 106 107 if (!manifestVariablesFilesExist && manifestVariablesFilesExplicitlySpecified) { 108 // If the user explicitly specified a list of variables files, make sure they all exist. 109 // Otherwise throw an error so the user knows that he / she made a mistake. 110 error "[CFManifestSubstituteVariables] Could not find all given manifest variable substitution files. Make sure all files given as manifestVariablesFiles exist." 111 } 112 113 def result 114 ExecutionContext context = new ExecutionContext() 115 116 if (!manifestVariablesFilesExist && !manifestVariablesFilesExplicitlySpecified) { 117 // If no variables files exist (not even the default one) we check if at least we have a list of variables. 118 119 if (!manifestVariablesListSpecified) { 120 // If we have no variable values to replace references with, we skip substitution. 121 echo "[CFManifestSubstituteVariables] Could not find any default manifest variable substitution file at ${defaultManifestVariablesFileName}, and no manifest variables list was specified. Skipping variable substitution." 122 return 123 } 124 125 // If we have a list of variables specified, we can start replacing them... 126 result = substitute(manifestFilePath, [], manifestVariablesList, yamlUtils, context, debugHelper) 127 } 128 else { 129 // If we have at least one existing variable substitution file, we can start replacing variables... 130 result = substitute(manifestFilePath, manifestVariablesFiles, manifestVariablesList, yamlUtils, context, debugHelper) 131 } 132 133 if (!context.variablesReplaced) { 134 // If no variables have been replaced at all, we skip writing a file. 135 echo "[CFManifestSubstituteVariables] No variables were found or could be replaced in ${manifestFilePath}. Skipping variable substitution." 136 return 137 } 138 139 // writeYaml won't overwrite the file. You need to delete it first. 140 deleteFile(outputFilePath) 141 142 writeYaml file: outputFilePath, data: result 143 144 echo "[CFManifestSubstituteVariables] Replaced variables in ${manifestFilePath}." 145 echo "[CFManifestSubstituteVariables] Wrote output file (with variables replaced) at ${outputFilePath}." 146 } 147 } 148 149 /* 150 * Substitutes variables specified in files and as lists in a given manifest file. 151 * @param manifestFilePath - the path to the manifest file to replace variables in. 152 * @param manifestVariablesFiles - the paths to variables substitution files. 153 * @param manifestVariablesList - the list of variables data to replace variables with. 154 * @param yamlUtils - the `YamlUtils` used for variable substitution. 155 * @param context - an `ExecutionContext` to examine if any variables have been replaced and should be written. 156 * @param debugHelper - a debug output helper. 157 * @return an Object graph of Yaml data with variables substituted (if any were found and could be replaced). 158 */ 159 private Object substitute(String manifestFilePath, List<String> manifestVariablesFiles, List<Map<String, Object>> manifestVariablesList, YamlUtils yamlUtils, ExecutionContext context, DebugHelper debugHelper) { 160 Boolean noVariablesReplaced = true 161 162 def manifestData = loadManifestData(manifestFilePath, debugHelper) 163 164 // replace variables from list first. 165 List<Map<String,Object>> reversedManifestVariablesList = manifestVariablesList.reverse() // to make sure last one wins. 166 167 def result = manifestData 168 for (Map<String, Object> manifestVariableData : reversedManifestVariablesList) { 169 def executionContext = new ExecutionContext() 170 result = yamlUtils.substituteVariables(result, manifestVariableData, executionContext) 171 noVariablesReplaced = noVariablesReplaced && !executionContext.variablesReplaced // remember if variables were replaced. 172 } 173 174 // replace remaining variables from files 175 List<String> reversedManifestVariablesFilesList = manifestVariablesFiles.reverse() // to make sure last one wins. 176 for (String manifestVariablesFilePath : reversedManifestVariablesFilesList) { 177 def manifestVariablesFileData = loadManifestVariableFileData(manifestVariablesFilePath, debugHelper) 178 def executionContext = new ExecutionContext() 179 result = yamlUtils.substituteVariables(result, manifestVariablesFileData, executionContext) 180 noVariablesReplaced = noVariablesReplaced && !executionContext.variablesReplaced // remember if variables were replaced. 181 } 182 183 context.variablesReplaced = !noVariablesReplaced 184 return result 185 } 186 187 /* 188 * Loads the contents of a manifest.yml file by parsing Yaml and returning the 189 * object graph. May return a `List<Object>` (in case more YAML segments are in the file) 190 * or a `Map<String, Object>` in case there is just one segment. 191 * @param manifestFilePath - the file path of the manifest to parse. 192 * @param debugHelper - a debug output helper. 193 * @return the parsed object graph. 194 */ 195 private Object loadManifestData(String manifestFilePath, DebugHelper debugHelper) { 196 try { 197 // may return a List<Object> (in case more YAML segments are in the file) 198 // or a Map<String, Object> in case there is just one segment. 199 def result = readYaml file: manifestFilePath 200 echo "[CFManifestSubstituteVariables] Loaded manifest at ${manifestFilePath}!" 201 return result 202 } 203 catch(Exception ex) { 204 debugHelper.debug("Exception: ${ex}") 205 echo "[CFManifestSubstituteVariables] Could not load manifest file at ${manifestFilePath}. Exception was: ${ex}" 206 throw ex 207 } 208 } 209 210 /* 211 * Loads the contents of a manifest variables file by parsing Yaml and returning the 212 * object graph. May return a `List<Object>` (in case more YAML segments are in the file) 213 * or a `Map<String, Object>` in case there is just one segment. 214 * @param variablesFilePath - the path to the variables file to parse. 215 * @param debugHelper - a debug output helper. 216 * @return the parsed object graph. 217 */ 218 private Object loadManifestVariableFileData(String variablesFilePath, DebugHelper debugHelper) { 219 try { 220 // may return a List<Object> (in case more YAML segments are in the file) 221 // or a Map<String, Object> in case there is just one segment. 222 def result = readYaml file: variablesFilePath 223 echo "[CFManifestSubstituteVariables] Loaded variables file at ${variablesFilePath}!" 224 return result 225 } 226 catch(Exception ex) { 227 debugHelper.debug("Exception: ${ex}") 228 echo "[CFManifestSubstituteVariables] Could not load manifest variables file at ${variablesFilePath}. Exception was: ${ex}" 229 throw ex 230 } 231 } 232 233 /* 234 * Checks if all file paths given in the list exist as files. 235 * @param manifestVariablesFiles - the list of file paths pointing to manifest variables files. 236 * @return `true`, if all given files exist, `false` otherwise. 237 */ 238 private boolean allManifestVariableFilesExist(List<String> manifestVariablesFiles) { 239 for (String filePath : manifestVariablesFiles) { 240 Boolean fileExists = fileExists filePath 241 if (!fileExists) { 242 echo "[CFManifestSubstituteVariables] Did not find manifest variable substitution file at ${filePath}." 243 return false 244 } 245 } 246 return true 247 } 248 249 /* 250 * Removes the given file, if it exists. 251 * @param filePath - the path to the file to remove. 252 */ 253 private void deleteFile(String filePath) { 254 255 Boolean fileExists = fileExists file: filePath 256 if(fileExists) { 257 Boolean failure = sh script: "rm '${filePath}'", returnStatus: true 258 if(!failure) { 259 echo "[CFManifestSubstituteVariables] Successfully deleted file '${filePath}'." 260 } 261 else { 262 error "[CFManifestSubstituteVariables] Could not delete file '${filePath}'. Check file permissions." 263 } 264 } 265 }