1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-11-24 08:32:32 +02:00
sap-jenkins-library/vars/piperExecuteBin.groovy
Holger Partsch d4e298464e
Final round of adding quoting to prevent command injection (#5167)
* refactor: use import alias

* fix: add quoting to further shell steps
2024-11-04 14:05:47 +01:00

316 lines
13 KiB
Groovy

import com.sap.piper.BashUtils
import com.sap.piper.DebugReport
import com.sap.piper.DefaultValueCache
import com.sap.piper.JenkinsUtils
import com.sap.piper.MapUtils
import com.sap.piper.PiperGoUtils
import com.sap.piper.Utils
import com.sap.piper.analytics.InfluxData
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
import static com.sap.piper.BashUtils.quoteAndEscape as q
@Field String STEP_NAME = getClass().getName()
void call(Map parameters = [:], String stepName, String metadataFile, List credentialInfo, boolean failOnMissingReports = false, boolean failOnMissingLinks = false, boolean failOnError = false) {
Map handlePipelineStepErrorsParameters = [stepName: stepName, stepParameters: parameters]
if (failOnError) {
handlePipelineStepErrorsParameters.failOnError = true
}
handlePipelineStepErrors(handlePipelineStepErrorsParameters) {
Script script = checkScript(this, parameters) ?: this
def jenkinsUtils = parameters.jenkinsUtilsStub ?: new JenkinsUtils()
def utils = parameters.juStabUtils ?: new Utils()
String piperGoPath = parameters.piperGoPath ?: './piper'
prepareExecution(script, utils, parameters)
prepareMetadataResource(script, metadataFile)
Map stepParameters = prepareStepParameters(parameters)
echo "Step params $stepParameters"
withEnv([
"PIPER_parametersJSON=${groovy.json.JsonOutput.toJson(stepParameters)}",
"PIPER_correlationID=${env.BUILD_URL}",
//ToDo: check if parameters make it into docker image on JaaS
]) {
String defaultConfigArgs = getCustomDefaultConfigsArg()
String customConfigArg = getCustomConfigArg(script)
echo "PIPER_parametersJSON: ${groovy.json.JsonOutput.toJson(stepParameters)}"
// get context configuration
Map config
handleErrorDetails(stepName) {
config = getStepContextConfig(script, piperGoPath, metadataFile, defaultConfigArgs, customConfigArg)
echo "Context Config: ${config}"
}
//Add ANS credential information to the config
ansHookServiceKeyCredentialsId =
DefaultValueCache.getInstance().getDefaultValues().hooks?.ans?.serviceKeyCredentialsId
config += ["ansHookServiceKeyCredentialsId": ansHookServiceKeyCredentialsId]
// prepare stashes
// first eliminate non existing stashes
config.stashContent = utils.unstashAll(config.stashContent)
// then make sure that commonPipelineEnvironment, config, ... is also available when step stashing is active
if (config.stashContent?.size() > 0) {
config.stashContent.add('pipelineConfigAndTests')
config.stashContent.add('piper-bin')
config.stashContent.add('pipelineStepReports')
}
if (parameters.stashNoDefaultExcludes) {
// Merge this parameter which is only relevant in Jenkins context
// (for dockerExecuteOnKubernetes step) and go binary doesn't know about
config.stashNoDefaultExcludes = parameters.stashNoDefaultExcludes
}
dockerWrapper(script, stepName, config) {
handleErrorDetails(stepName) {
writePipelineEnv(script: script, piperGoPath: piperGoPath)
utils.unstash('pipelineStepReports')
try {
try {
try {
credentialWrapper(config, credentialInfo) {
sh "${piperGoPath} ${stepName}${defaultConfigArgs}${customConfigArg}"
}
} finally {
jenkinsUtils.handleStepResults(stepName, failOnMissingReports, failOnMissingLinks)
}
} finally {
readPipelineEnv(script: script, piperGoPath: piperGoPath)
}
} finally {
InfluxData.readFromDisk(script)
utils.stash name: 'pipelineStepReports', includes: '.pipeline/stepReports/**', allowEmpty: true
}
}
}
}
}
}
// reused in sonarExecuteScan
static void prepareExecution(Script script, Utils utils, Map parameters = [:]) {
def piperGoUtils = parameters.piperGoUtils ?: new PiperGoUtils(script, utils)
piperGoUtils.unstashPiperBin()
utils.unstash('pipelineConfigAndTests')
}
// reused in sonarExecuteScan
static Map prepareStepParameters(Map parameters) {
Map stepParameters = [:].plus(parameters)
stepParameters.remove('script')
stepParameters.remove('jenkinsUtilsStub')
stepParameters.remove('piperGoPath')
stepParameters.remove('juStabUtils')
stepParameters.remove('piperGoUtils')
// When converting to JSON and back again, entries which had a 'null' value will now have a value
// of type 'net.sf.json.JSONNull', for which the Groovy Truth resolves to 'true' in for example if-conditions
return MapUtils.pruneNulls(stepParameters)
}
// reused in sonarExecuteScan
static void prepareMetadataResource(Script script, String metadataFile) {
script.writeFile(file: ".pipeline/tmp/${metadataFile}", text: script.libraryResource(metadataFile))
}
// reused in sonarExecuteScan
static Map getStepContextConfig(Script script, String piperGoPath, String metadataFile, String defaultConfigArgs, String customConfigArg) {
return script.readJSON(text: script.sh(returnStdout: true, script: "${piperGoPath} getConfig --contextConfig --stepMetadata '.pipeline/tmp/${metadataFile}'${defaultConfigArgs}${customConfigArg}"))
}
static String getCustomDefaultConfigs() {
// The default config files were extracted from merged library
// resources by setupCommonPipelineEnvironment.groovy into .pipeline/.
List customDefaults = DefaultValueCache.getInstance().getCustomDefaults()
for (int i = 0; i < customDefaults.size(); i++) {
customDefaults[i] = q(".pipeline/${customDefaults[i]}")
}
return customDefaults.join(',')
}
// reused in sonarExecuteScan
static String getCustomDefaultConfigsArg() {
String customDefaults = getCustomDefaultConfigs()
if (customDefaults) {
return " --defaultConfig ${customDefaults} --ignoreCustomDefaults"
}
return ''
}
// reused in sonarExecuteScan
static String getCustomConfigArg(def script) {
if (script?.commonPipelineEnvironment?.configurationFile
&& script.commonPipelineEnvironment.configurationFile != '.pipeline/config.yml'
&& script.commonPipelineEnvironment.configurationFile != '.pipeline/config.yaml') {
return " --customConfig ${q(script.commonPipelineEnvironment.configurationFile)}"
}
return ''
}
// reused in sonarExecuteScan
void dockerWrapper(script, stepName, config, body) {
if (config.dockerImage) {
echo "[INFO] executing pipeline step '${stepName}' with docker image '${config.dockerImage}'"
Map dockerExecuteParameters = [:].plus(config)
dockerExecuteParameters.script = script
dockerExecute(dockerExecuteParameters) {
body()
}
} else {
body()
}
}
// reused in sonarExecuteScan
void credentialWrapper(config, List credentialInfo, body) {
credentialInfo = handleVaultCredentials(config, credentialInfo)
credentialInfo = handleANSCredentials(config, credentialInfo)
if (credentialInfo.size() > 0) {
def creds = []
def sshCreds = []
credentialInfo.each { cred ->
def credentialsId
if (cred.resolveCredentialsId == false) {
credentialsId = cred.id
} else {
credentialsId = config[cred.id]
}
if (credentialsId) {
switch (cred.type) {
case "file":
creds.add(file(credentialsId: credentialsId, variable: cred.env[0]))
break
case "token":
creds.add(string(credentialsId: credentialsId, variable: cred.env[0]))
break
case "usernamePassword":
creds.add(usernamePassword(credentialsId: credentialsId, usernameVariable: cred.env[0], passwordVariable: cred.env[1]))
break
case "ssh":
sshCreds.add(credentialsId)
break
default:
error("invalid credential type: ${cred.type}")
}
}
}
// remove credentialIds that were probably defaulted and which are not present in jenkins
if (containsVaultConfig(config)) {
creds = removeMissingCredentials(creds, config)
sshCreds = removeMissingCredentials(sshCreds, config)
}
if (sshCreds.size() > 0) {
sshagent (sshCreds) {
withCredentials(creds) {
body()
}
}
} else {
withCredentials(creds) {
body()
}
}
} else {
body()
}
}
List removeMissingCredentials(List creds, Map config) {
return creds.findAll { credentialExists(it, config) }
}
boolean credentialExists(cred, Map config) {
try {
withCredentials([cred]) {
return true
}
} catch (e) {
return false
}
}
boolean containsVaultConfig(Map config) {
def approleIsUsed = config.containsKey('vaultAppRoleTokenCredentialsId') && config.containsKey('vaultAppRoleSecretTokenCredentialsId')
def tokenIsUsed = config.containsKey('vaultTokenCredentialsId')
return approleIsUsed || tokenIsUsed
}
// Injects vaultCredentials if steps supports resolving parameters from vault
List handleVaultCredentials(config, List credentialInfo) {
if (config.containsKey('vaultAppRoleTokenCredentialsId') && config.containsKey('vaultAppRoleSecretTokenCredentialsId')) {
credentialInfo += [[type: 'token', id: 'vaultAppRoleTokenCredentialsId', env: ['PIPER_vaultAppRoleID']],
[type: 'token', id: 'vaultAppRoleSecretTokenCredentialsId', env: ['PIPER_vaultAppRoleSecretID']]]
}
if (config.containsKey('vaultTokenCredentialsId')) {
credentialInfo += [[type: 'token', id: 'vaultTokenCredentialsId', env: ['PIPER_vaultToken']]]
}
return credentialInfo
}
List handleANSCredentials(config, List credentialInfo){
if (config.containsKey('ansHookServiceKeyCredentialsId')) {
credentialInfo += [[type: 'token', id: 'ansHookServiceKeyCredentialsId', env: ['PIPER_ansHookServiceKey']]]
}
return credentialInfo
}
// reused in sonarExecuteScan
void handleErrorDetails(String stepName, Closure body) {
try {
body()
} catch (ex) {
def errorDetailsFileName = "${stepName}_errorDetails.json"
if (fileExists(file: errorDetailsFileName)) {
def errorDetails = readJSON(file: errorDetailsFileName)
def errorCategory = ""
if (errorDetails.category) {
errorCategory = " (category: ${errorDetails.category})"
DebugReport.instance.failedBuild.category = errorDetails.category
}
error "[${stepName}] Step execution failed${errorCategory}. Error: ${errorDetails.error ?: errorDetails.message}"
}
error "[${stepName}] Step execution failed. Error: ${ex}, please see log file for more details."
}
}
// it can be used in 2 ways:
// 1. use stage, step arguments and leave stepOutputFile "" and stageOutputFile "" empty to check if this step is active in this stage (true means step is active, false - not active or something went wrong)
// 2. use stageOutputFile and/or stepOutputFile and leave step "" and stage "" to write the whole map of steps in all stages into a file[s] (true means no errors, false - something went wrong)
// if both presented - priority is option 2
static boolean checkIfStepActive(Map parameters = [:], Script script, String piperGoPath, String stageConfig = "", String stepOutputFile = "", String stageOutputFile = "", String stage = "", String step = "") {
def utils = parameters.juStabUtils ?: new Utils()
def piperGoUtils = parameters.piperGoUtils ?: new PiperGoUtils(script, utils)
def flags = "--stageConfig ${stageConfig} --useV1"
if (stageOutputFile) {
flags += " --stageOutputFile ${stageOutputFile}"
}
if (stepOutputFile) {
flags += " --stepOutputFile ${stepOutputFile}"
}
if (!stage || !step) {
stage = "_"
step = "_"
}
flags += " --stage ${stage} --step ${step}"
flags += getCustomDefaultConfigsArg()
flags += getCustomConfigArg(script)
piperGoUtils.unstashPiperBin()
def returnCode = script.sh(returnStatus: true, script: "${piperGoPath} checkIfStepActive ${flags}")
return (returnCode == 0)
}