2020-01-20 16:58:57 +02:00
import com.cloudbees.groovy.cps.NonCPS
2018-12-12 12:45:11 +02:00
import com.sap.piper.Utils
import com.sap.piper.ConfigurationHelper
import com.sap.piper.ConfigurationLoader
2020-01-29 14:40:40 +02:00
import com.sap.piper.DebugReport
2018-12-12 12:45:11 +02:00
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 ( )
2020-01-20 16:58:57 +02:00
def stageName = parameters . stageName ? : env . STAGE_NAME
2018-12-12 12:45:11 +02:00
// load default & individual configuration
Map config = ConfigurationHelper . newInstance ( this )
. loadStepDefaults ( )
2020-01-20 16:58:57 +02:00
. mixin ( ConfigurationLoader . defaultStageConfiguration ( script , stageName ) )
2018-12-12 12:45:11 +02:00
. mixinGeneralConfig ( script . commonPipelineEnvironment )
. mixinStageConfig ( script . commonPipelineEnvironment , stageName )
. mixin ( parameters )
. addIfEmpty ( 'stageName' , stageName )
2020-07-17 14:27:21 +02:00
. addIfEmpty ( 'lockingResourceGroup' , script . commonPipelineEnvironment . projectName )
2018-12-12 12:45:11 +02:00
. dependingOn ( 'stageName' ) . mixin ( 'ordinal' )
. use ( )
stageLocking ( config ) {
2020-04-15 14:02:41 +02:00
def containerMap = ContainerMap . instance . getMap ( ) . get ( stageName ) ? : [ : ]
2020-07-29 19:51:27 +02:00
List environment = [ ]
2020-08-13 17:01:22 +02:00
if ( stageName & & stageName ! = env . STAGE_NAME ) {
// Avoid two sources of truth with regards to stageName.
// env.STAGE_NAME is filled from stage('Display name') {, it only serves the purpose of
// easily getting to the stage name from steps.
environment . add ( "STAGE_NAME=${stageName}" )
}
2020-07-29 19:51:27 +02:00
if ( config . sidecarImage ) {
echo "sidecarImage configured for stage '${stageName}': '${config.sidecarImage}'"
environment . add ( "SIDECAR_IMAGE=${config.sidecarImage}" )
}
2020-04-17 10:31:04 +02:00
if ( Boolean . valueOf ( env . ON_K8S ) & & ( containerMap . size ( ) > 0 | | config . runStageInPod ) ) {
2020-07-29 19:51:27 +02:00
environment . add ( "POD_NAME=${stageName}" )
withEnv ( environment ) {
2020-04-15 14:02:41 +02:00
dockerExecuteOnKubernetes ( script: script , containerMap: containerMap , stageName: stageName ) {
2020-03-19 11:49:28 +02:00
executeStage ( script , body , stageName , config , utils , parameters . telemetryDisabled )
2018-12-12 12:45:11 +02:00
}
}
2020-04-15 14:02:41 +02:00
} else {
2020-07-29 19:51:27 +02:00
withEnvWrapper ( environment ) {
node ( config . nodeLabel ) {
executeStage ( script , body , stageName , config , utils , parameters . telemetryDisabled )
}
2020-04-15 14:02:41 +02:00
}
2018-12-12 12:45:11 +02:00
}
}
}
2020-07-29 19:51:27 +02:00
private void withEnvWrapper ( List environment , Closure body ) {
if ( environment ) {
withEnv ( environment ) {
body ( )
}
} else {
body ( )
}
}
2018-12-12 12:45:11 +02:00
private void stageLocking ( Map config , Closure body ) {
if ( config . stageLocking ) {
2020-07-17 14:27:21 +02:00
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
}
2018-12-12 12:45:11 +02:00
body ( )
}
} else {
body ( )
}
}
2020-03-19 11:49:28 +02:00
private void executeStage ( script , originalStage , stageName , config , utils , telemetryDisabled = false ) {
2018-12-12 12:45:11 +02:00
boolean projectExtensions
boolean globalExtensions
def startTime = System . currentTimeMillis ( )
try {
2020-01-27 14:28:19 +02:00
// Add general stage stashes to config.stashContent
config . stashContent = utils . unstashStageFiles ( script , stageName , config . stashContent )
2018-12-12 12:45:11 +02:00
/ * 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
2020-08-27 15:49:09 +02:00
if ( globalExtensions & & allowExtensions ( script ) ) {
2018-12-12 12:45:11 +02:00
echo "[${STEP_NAME}] Found global interceptor '${globalInterceptorFile}' for ${stageName}."
// If we call the global interceptor, we will pass on originalStage as parameter
2020-01-29 14:40:40 +02:00
DebugReport . instance . globalExtensions . put ( stageName , "Overwrites" )
Closure modifiedOriginalStage = {
DebugReport . instance . globalExtensions . put ( stageName , "Extends" )
originalStage ( )
}
2018-12-12 12:45:11 +02:00
body = {
2020-01-29 14:40:40 +02:00
callInterceptor ( script , globalInterceptorFile , modifiedOriginalStage , stageName , config )
2018-12-12 12:45:11 +02:00
}
}
// Second, check if a project extension (within the same repository) exists
2020-08-27 15:49:09 +02:00
if ( projectExtensions & & allowExtensions ( script ) ) {
2018-12-12 12:45:11 +02:00
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
2020-01-29 14:40:40 +02:00
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 )
2020-01-20 16:58:57 +02:00
2018-12-12 12:45:11 +02:00
} else {
2020-01-31 10:57:49 +02:00
// 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!
2018-12-12 12:45:11 +02:00
body ( )
}
} finally {
//Perform stashing of selected files in workspace
2020-01-27 14:28:19 +02:00
utils . stashStageFiles ( script , stageName )
2018-12-12 12:45:11 +02:00
2020-03-19 11:49:28 +02:00
// 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).
2020-06-17 18:17:05 +02:00
if ( ! telemetryDisabled ) {
2020-03-19 11:49:28 +02:00
def duration = System . currentTimeMillis ( ) - startTime
utils . pushToSWA ( [
2020-06-17 18:17:05 +02:00
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}" ,
2020-03-19 11:49:28 +02:00
projectExtension: "${projectExtensions}" ,
2020-06-17 18:17:05 +02:00
stepParamKey5 : 'globalExtension' ,
stepParam5 : "${globalExtensions}" ,
globalExtension : "${globalExtensions}"
2020-03-19 11:49:28 +02:00
] , config )
}
2018-12-12 12:45:11 +02:00
}
}
2020-01-20 16:58:57 +02:00
private void callInterceptor ( Script script , String extensionFileName , Closure originalStage , String stageName , Map configuration ) {
2020-04-15 14:02:41 +02:00
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
2020-01-20 16:58:57 +02:00
}
}
@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
}
2020-03-31 12:51:34 +02:00
2020-08-27 15:49:09 +02:00
private static boolean allowExtensions ( Script script ) {
return script . env . PIPER_DISABLE_EXTENSIONS = = null | | Boolean . valueOf ( script . env . PIPER_DISABLE_EXTENSIONS ) = = false
2020-03-31 12:51:34 +02:00
}