1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/vars/piperStageWrapper.groovy
Stephan Aßmus ddd10683c4 Added support for old "interceptor API" for extensions
This is part of the effort to get rid of the "runAsStage" step in the SDK pipeline, and use piperStageWrapper directly. The SDK pipeline currently needs to support for loading "old" extensions where the call() method had different parameters. The support for the exact API can and should be removed, however, having a mechanism for supporting old extension APIs seems beneficial in general.

Another crucial change is the deleteDir() call before unstashing at the beginning of the stage. Without this, the SDK pipeline fails to unstash, since apparently the workspace may not always be clean at that point.
2020-01-24 11:29:35 +01:00

165 lines
6.9 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.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)
.dependingOn('stageName').mixin('ordinal')
.use()
stageLocking(config) {
def containerMap = ContainerMap.instance.getMap().get(stageName) ?: [:]
if (Boolean.valueOf(env.ON_K8S) && containerMap.size() > 0) {
withEnv(["POD_NAME=${stageName}"]) {
dockerExecuteOnKubernetes(script: script, containerMap: containerMap) {
executeStage(script, body, stageName, config, utils)
}
}
} else {
node(config.nodeLabel) {
executeStage(script, body, stageName, config, utils)
}
}
}
}
private void stageLocking(Map config, Closure body) {
if (config.stageLocking) {
lock(resource: "${env.JOB_NAME}/${config.ordinal}", inversePrecedence: true) {
milestone config.ordinal
body()
}
} else {
body()
}
}
private void executeStage(script, originalStage, stageName, config, utils) {
boolean projectExtensions
boolean globalExtensions
def startTime = System.currentTimeMillis()
try {
//Add general stage stashes to config.stashContent
config.stashContent += script.commonPipelineEnvironment.configuration.stageStashes?.get(stageName)?.unstash ?: []
deleteDir()
utils.unstashAll(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) {
echo "[${STEP_NAME}] Found global interceptor '${globalInterceptorFile}' for ${stageName}."
// If we call the global interceptor, we will pass on originalStage as parameter
body = {
callInterceptor(script, globalInterceptorFile, originalStage, stageName, config)
}
}
// Second, check if a project extension (within the same repository) exists
if (projectExtensions) {
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
callInterceptor(script, projectInterceptorFile, body, stageName, config)
} else {
//TODO: assign projectInterceptorScript to body as done for globalInterceptorScript, currently test framework does not seem to support this case. Further investigations needed.
body()
}
} finally {
//Perform stashing of selected files in workspace
utils.stashList(script, script.commonPipelineEnvironment.configuration.stageStashes?.get(stageName)?.stashes ?: [])
deleteDir()
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) {
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
])
}
}
@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
}