mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +02:00
a61798ccbf
Co-authored-by: Florian Wilhelm <florian.wilhelm02@sap.com>
225 lines
9.6 KiB
Groovy
225 lines
9.6 KiB
Groovy
import com.cloudbees.groovy.cps.NonCPS
|
|
import com.sap.piper.Utils
|
|
import com.sap.piper.ConfigurationHelper
|
|
import com.sap.piper.ConfigurationLoader
|
|
import com.sap.piper.DebugReport
|
|
import com.sap.piper.k8s.ContainerMap
|
|
import groovy.transform.Field
|
|
|
|
import static com.sap.piper.Prerequisites.checkScript
|
|
|
|
@Field String STEP_NAME = 'piperStageWrapper'
|
|
|
|
void call(Map parameters = [:], body) {
|
|
|
|
final script = checkScript(this, parameters) ?: this
|
|
def utils = parameters.juStabUtils ?: new Utils()
|
|
|
|
def stageName = parameters.stageName ?: env.STAGE_NAME
|
|
|
|
// load default & individual configuration
|
|
Map config = ConfigurationHelper.newInstance(this)
|
|
.loadStepDefaults()
|
|
.mixin(ConfigurationLoader.defaultStageConfiguration(script, stageName))
|
|
.mixinGeneralConfig(script.commonPipelineEnvironment)
|
|
.mixinStageConfig(script.commonPipelineEnvironment, stageName)
|
|
.mixin(parameters)
|
|
.addIfEmpty('stageName', stageName)
|
|
.addIfEmpty('lockingResourceGroup', script.commonPipelineEnvironment.projectName)
|
|
.dependingOn('stageName').mixin('ordinal')
|
|
.use()
|
|
|
|
stageLocking(config) {
|
|
def containerMap = ContainerMap.instance.getMap().get(stageName) ?: [:]
|
|
List environment = []
|
|
if (config.sidecarImage) {
|
|
echo "sidecarImage configured for stage '${stageName}': '${config.sidecarImage}'"
|
|
environment.add("SIDECAR_IMAGE=${config.sidecarImage}")
|
|
}
|
|
if (Boolean.valueOf(env.ON_K8S) && (containerMap.size() > 0 || config.runStageInPod)) {
|
|
environment.add("POD_NAME=${stageName}")
|
|
withEnv(environment) {
|
|
dockerExecuteOnKubernetes(script: script, containerMap: containerMap, stageName: stageName) {
|
|
executeStage(script, body, stageName, config, utils, parameters.telemetryDisabled)
|
|
}
|
|
}
|
|
} else {
|
|
withEnvWrapper(environment) {
|
|
node(config.nodeLabel) {
|
|
executeStage(script, body, stageName, config, utils, parameters.telemetryDisabled)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void withEnvWrapper(List environment, Closure body) {
|
|
if (environment) {
|
|
withEnv(environment) {
|
|
body()
|
|
}
|
|
} else {
|
|
body()
|
|
}
|
|
}
|
|
|
|
private void stageLocking(Map config, Closure body) {
|
|
if (config.stageLocking) {
|
|
String resource = config.lockingResourceGroup?:env.JOB_NAME
|
|
if(config.lockingResource){
|
|
resource += "/${config.lockingResource}"
|
|
}
|
|
else if(config.ordinal){
|
|
resource += "/${config.ordinal}"
|
|
}
|
|
lock(resource: resource, inversePrecedence: true) {
|
|
if(config.ordinal) {
|
|
milestone config.ordinal
|
|
}
|
|
body()
|
|
}
|
|
} else {
|
|
body()
|
|
}
|
|
}
|
|
|
|
private void executeStage(script, originalStage, stageName, config, utils, telemetryDisabled = false) {
|
|
boolean projectExtensions
|
|
boolean globalExtensions
|
|
def startTime = System.currentTimeMillis()
|
|
|
|
try {
|
|
// Add general stage stashes to config.stashContent
|
|
config.stashContent = utils.unstashStageFiles(script, stageName, config.stashContent)
|
|
|
|
/* Defining the sources where to look for a project extension and a repository extension.
|
|
* Files need to be named like the executed stage to be recognized.
|
|
*/
|
|
def projectInterceptorFile = "${config.projectExtensionsDirectory}${stageName}.groovy"
|
|
def globalInterceptorFile = "${config.globalExtensionsDirectory}${stageName}.groovy"
|
|
projectExtensions = fileExists(projectInterceptorFile)
|
|
globalExtensions = fileExists(globalInterceptorFile)
|
|
// Pre-defining the real originalStage in body variable, might be overwritten later if extensions exist
|
|
def body = originalStage
|
|
|
|
// First, check if a global extension exists via a dedicated repository
|
|
if (globalExtensions && allowExtensions()) {
|
|
echo "[${STEP_NAME}] Found global interceptor '${globalInterceptorFile}' for ${stageName}."
|
|
// If we call the global interceptor, we will pass on originalStage as parameter
|
|
DebugReport.instance.globalExtensions.put(stageName, "Overwrites")
|
|
Closure modifiedOriginalStage = {
|
|
DebugReport.instance.globalExtensions.put(stageName, "Extends")
|
|
originalStage()
|
|
}
|
|
|
|
body = {
|
|
callInterceptor(script, globalInterceptorFile, modifiedOriginalStage, stageName, config)
|
|
}
|
|
}
|
|
|
|
// Second, check if a project extension (within the same repository) exists
|
|
if (projectExtensions && allowExtensions()) {
|
|
echo "[${STEP_NAME}] Running project interceptor '${projectInterceptorFile}' for ${stageName}."
|
|
// If we call the project interceptor, we will pass on body as parameter which contains either originalStage or the repository interceptor
|
|
if (projectExtensions && globalExtensions) {
|
|
DebugReport.instance.globalExtensions.put(stageName, "Unknown (Overwritten by local extension)")
|
|
}
|
|
DebugReport.instance.localExtensions.put(stageName, "Overwrites")
|
|
Closure modifiedOriginalBody = {
|
|
DebugReport.instance.localExtensions.put(stageName, "Extends")
|
|
if (projectExtensions && globalExtensions) {
|
|
DebugReport.instance.globalExtensions.put(stageName, "Overwrites")
|
|
}
|
|
body.call()
|
|
}
|
|
|
|
callInterceptor(script, projectInterceptorFile, modifiedOriginalBody, stageName, config)
|
|
|
|
} else {
|
|
// NOTE: It may appear more elegant to re-assign 'body' more than once and then call 'body()' after the
|
|
// if-block. This could lead to infinite loops however, as any change to the local variable 'body' will
|
|
// become visible in all of the closures at the time they run. I.e. 'body' inside any of the closures will
|
|
// reflect the last assignment and not its value at the time of constructing the closure!
|
|
body()
|
|
}
|
|
|
|
} finally {
|
|
//Perform stashing of selected files in workspace
|
|
utils.stashStageFiles(script, stageName)
|
|
|
|
// In general telemetry reporting is disabled by the config settings. This flag is used to disable the reporting when the config is not yet read (e.g. init stage).
|
|
if (!telemetryDisabled) {
|
|
def duration = System.currentTimeMillis() - startTime
|
|
utils.pushToSWA([
|
|
eventType : 'library-os-stage',
|
|
stageName : stageName,
|
|
stepParamKey1 : 'buildResult',
|
|
stepParam1 : "${script.currentBuild.currentResult}",
|
|
buildResult : "${script.currentBuild.currentResult}",
|
|
stepParamKey2 : 'stageStartTime',
|
|
stepParam2 : "${startTime}",
|
|
stageStartTime : "${startTime}",
|
|
stepParamKey3 : 'stageDuration',
|
|
stepParam3 : "${duration}",
|
|
stageDuration : "${duration}",
|
|
stepParamKey4 : 'projectExtension',
|
|
stepParam4 : "${projectExtensions}",
|
|
projectExtension: "${projectExtensions}",
|
|
stepParamKey5 : 'globalExtension',
|
|
stepParam5 : "${globalExtensions}",
|
|
globalExtension : "${globalExtensions}"
|
|
], config)
|
|
}
|
|
}
|
|
}
|
|
|
|
private void callInterceptor(Script script, String extensionFileName, Closure originalStage, String stageName, Map configuration) {
|
|
try {
|
|
Script interceptor = load(extensionFileName)
|
|
if (isOldInterceptorInterfaceUsed(interceptor)) {
|
|
echo("[Warning] The interface to implement extensions has changed. " +
|
|
"The extension $extensionFileName has to implement a method named 'call' with exactly one parameter of type Map. " +
|
|
"This map will have the properties script, originalStage, stageName, config. " +
|
|
"For example: def call(Map parameters) { ... }")
|
|
interceptor.call(originalStage, stageName, configuration, configuration)
|
|
} else {
|
|
validateInterceptor(interceptor, extensionFileName)
|
|
interceptor.call([
|
|
script : script,
|
|
originalStage: originalStage,
|
|
stageName : stageName,
|
|
config : configuration
|
|
])
|
|
}
|
|
} catch (Throwable error) {
|
|
if (!DebugReport.instance.failedBuild.step) {
|
|
DebugReport.instance.storeStepFailure("${stageName}(extended)", error, true)
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
@NonCPS
|
|
private boolean isInterceptorValid(Script interceptor) {
|
|
MetaMethod method = interceptor.metaClass.pickMethod("call", [Map.class] as Class[])
|
|
return method != null
|
|
}
|
|
|
|
private void validateInterceptor(Script interceptor, String extensionFileName) {
|
|
if (!isInterceptorValid(interceptor)) {
|
|
error("The extension $extensionFileName has to implement a method named 'call' with exactly one parameter of type Map. " +
|
|
"This map will have the properties script, originalStage, stageName, config. " +
|
|
"For example: def call(Map parameters) { ... }")
|
|
}
|
|
}
|
|
|
|
@NonCPS
|
|
private boolean isOldInterceptorInterfaceUsed(Script interceptor) {
|
|
MetaMethod method = interceptor.metaClass.pickMethod("call", [Closure.class, String.class, Map.class, Map.class] as Class[])
|
|
return method != null
|
|
}
|
|
|
|
private boolean allowExtensions() {
|
|
return env.PIPER_DISABLE_EXTENSIONS == null || Boolean.valueOf(env.PIPER_DISABLE_EXTENSIONS) == false
|
|
}
|