2018-09-21 16:55:31 +02:00
import static com . sap . piper . Prerequisites . checkScript
2018-08-21 15:45:59 +02:00
import com.sap.piper.ConfigurationHelper
import com.sap.piper.JenkinsUtils
2018-11-05 12:24:25 +02:00
import com.sap.piper.Utils
2018-08-21 15:45:59 +02:00
import com.sap.piper.k8s.SystemEnv
import groovy.transform.Field
import hudson.AbortException
2018-11-29 10:54:05 +02:00
@Field def STEP_NAME = getClass ( ) . getName ( )
2018-08-21 15:45:59 +02:00
@Field def PLUGIN_ID_KUBERNETES = 'kubernetes'
2019-02-04 10:03:58 +02:00
@Field Set GENERAL_CONFIG_KEYS = [
'jenkinsKubernetes'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS . plus ( [
2019-01-14 15:43:07 +02:00
'containerCommand' , // specify start command for container created with dockerImage parameter to overwrite Piper default (`/usr/bin/tail -f /dev/null`).
'containerCommands' , //specify start command for containers to overwrite Piper default (`/usr/bin/tail -f /dev/null`). If container's default start command should be used provide empty string like: `['selenium/standalone-chrome': '']`
2018-10-04 17:06:42 +02:00
'containerEnvVars' , //specify environment variables per container. If not provided dockerEnvVars will be used
'containerMap' , //specify multiple images which then form a kubernetes pod, example: containerMap: ['maven:3.5-jdk-8-alpine': 'mavenexecute','selenium/standalone-chrome': 'selenium']
'containerName' , //optional configuration in combination with containerMap to define the container where the commands should be executed in
'containerPortMappings' , //map which defines per docker image the port mappings, like containerPortMappings: ['selenium/standalone-chrome': [[name: 'selPort', containerPort: 4444, hostPort: 4444]]]
2019-01-08 20:44:28 +02:00
'containerShell' , // allows to specify the shell to be executed for container with containerName
2018-10-04 17:06:42 +02:00
'containerWorkspaces' , //specify workspace (=home directory of user) per container. If not provided dockerWorkspace will be used. If empty, home directory will not be set.
'dockerImage' ,
'dockerWorkspace' ,
2018-11-05 12:24:25 +02:00
'dockerEnvVars' ,
2019-02-04 10:03:58 +02:00
'stashContent' ,
'stashExcludes' ,
'stashIncludes'
] )
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS . minus ( [
'stashIncludes' ,
'stashExcludes'
] )
2018-08-21 15:45:59 +02:00
void call ( Map parameters = [ : ] , body ) {
handlePipelineStepErrors ( stepName: STEP_NAME , stepParameters: parameters ) {
2018-09-21 16:55:31 +02:00
2018-10-31 09:40:12 +02:00
final script = checkScript ( this , parameters ) ? : this
2018-09-21 16:55:31 +02:00
2018-08-21 15:45:59 +02:00
if ( ! JenkinsUtils . isPluginActive ( PLUGIN_ID_KUBERNETES ) ) {
error ( "[ERROR][${STEP_NAME}] not supported. Plugin '${PLUGIN_ID_KUBERNETES}' is not installed or not active." )
}
2018-11-05 12:24:25 +02:00
def utils = parameters ? . juStabUtils ? : new Utils ( )
2018-08-21 15:45:59 +02:00
2018-10-17 11:05:20 +02:00
ConfigurationHelper configHelper = ConfigurationHelper . newInstance ( this )
2018-09-07 10:08:16 +02:00
. loadStepDefaults ( )
2018-08-21 15:45:59 +02:00
. mixinGeneralConfig ( script . commonPipelineEnvironment , GENERAL_CONFIG_KEYS )
. mixinStepConfig ( script . commonPipelineEnvironment , STEP_CONFIG_KEYS )
2018-08-29 10:31:01 +02:00
. mixinStageConfig ( script . commonPipelineEnvironment , parameters . stageName ? : env . STAGE_NAME , STEP_CONFIG_KEYS )
2018-08-21 15:45:59 +02:00
. mixin ( parameters , PARAMETER_KEYS )
. addIfEmpty ( 'uniqueId' , UUID . randomUUID ( ) . toString ( ) )
2018-10-08 11:54:13 +02:00
Map config = configHelper . use ( )
2018-08-21 15:45:59 +02:00
2018-10-08 11:54:13 +02:00
if ( ! parameters . containerMap ) {
configHelper . withMandatoryProperty ( 'dockerImage' )
config . containerName = 'container-exec'
config . containerMap = [ "${config.get('dockerImage')}" : config . containerName ]
2019-01-14 15:43:07 +02:00
config . containerCommands = config . containerCommand ? [ "${config.get('dockerImage')}" : config . containerCommand ] : null
2018-08-21 15:45:59 +02:00
}
2018-11-05 12:24:25 +02:00
executeOnPod ( config , utils , body )
2018-08-21 15:45:59 +02:00
}
}
def getOptions ( config ) {
return [ name : 'dynamic-agent-' + config . uniqueId ,
label : config . uniqueId ,
containers: getContainerList ( config ) ]
}
2018-11-05 12:24:25 +02:00
void executeOnPod ( Map config , utils , Closure body ) {
2018-08-21 15:45:59 +02:00
/ *
* There could be exceptions thrown by
- The podTemplate
- The container method
- The body
* We use nested exception handling in this case .
2018-10-08 11:54:13 +02:00
* In the first 2 cases , the 'container' stash is not created because the inner try / finally is not reached .
2018-10-05 10:51:01 +02:00
* However , the workspace has not been modified and don ' t need to be restored .
* In case third case , we need to create the 'container' stash to bring the modified content back to the host .
2018-08-21 15:45:59 +02:00
* /
try {
2018-11-05 12:24:25 +02:00
if ( config . containerName & & config . stashContent . isEmpty ( ) ) {
config . stashContent . add ( stashWorkspace ( config , 'workspace' ) )
}
2018-08-21 15:45:59 +02:00
podTemplate ( getOptions ( config ) ) {
node ( config . uniqueId ) {
2018-10-08 11:54:13 +02:00
if ( config . containerName ) {
2019-01-08 20:44:28 +02:00
Map containerParams = [ name: config . containerName ]
if ( config . containerShell ) {
containerParams . shell = config . containerShell
}
2019-01-31 10:39:13 +02:00
echo "ContainerConfig: ${containerParams}"
2019-01-08 20:44:28 +02:00
container ( containerParams ) {
2018-10-08 11:54:13 +02:00
try {
2018-11-05 12:24:25 +02:00
utils . unstashAll ( config . stashContent )
2018-10-08 11:54:13 +02:00
body ( )
} finally {
2019-01-31 10:39:13 +02:00
stashWorkspace ( config , 'container' , true )
2018-10-08 11:54:13 +02:00
}
2018-10-05 10:51:01 +02:00
}
2018-10-08 11:54:13 +02:00
} else {
body ( )
2018-08-21 15:45:59 +02:00
}
}
}
} finally {
2018-10-08 11:54:13 +02:00
if ( config . containerName )
unstashWorkspace ( config , 'container' )
2018-08-21 15:45:59 +02:00
}
}
2019-01-31 10:39:13 +02:00
private String stashWorkspace ( config , prefix , boolean chown = false ) {
2018-11-05 12:24:25 +02:00
def stashName = "${prefix}-${config.uniqueId}"
2018-08-21 15:45:59 +02:00
try {
// Every dockerImage used in the dockerExecuteOnKubernetes should have user id 1000
2019-01-31 10:39:13 +02:00
if ( chown ) {
sh "" " # ! $ { config . containerShell ? : '/bin/sh' }
chown - R 1000 : 1000 . "" "
}
2018-08-21 15:45:59 +02:00
stash (
2018-11-05 12:24:25 +02:00
name: stashName ,
2019-01-31 09:49:31 +02:00
includes: config . stashIncludes . workspace ,
excludes: config . stashExcludes . excludes
2018-08-21 15:45:59 +02:00
)
2018-11-05 12:24:25 +02:00
return stashName
2018-08-21 15:45:59 +02:00
} catch ( AbortException | IOException e ) {
echo "${e.getMessage()}"
}
2018-11-05 12:24:25 +02:00
return null
2018-08-21 15:45:59 +02:00
}
private void unstashWorkspace ( config , prefix ) {
try {
unstash "${prefix}-${config.uniqueId}"
} catch ( AbortException | IOException e ) {
echo "${e.getMessage()}"
}
}
private List getContainerList ( config ) {
2018-10-04 17:06:42 +02:00
2018-08-21 15:45:59 +02:00
result = [ ]
2018-10-04 17:06:42 +02:00
result . push ( containerTemplate (
name: 'jnlp' ,
image: config . jenkinsKubernetes . jnlpAgent
) )
2018-08-21 15:45:59 +02:00
config . containerMap . each { imageName , containerName - >
2018-10-04 17:06:42 +02:00
def templateParameters = [
name: containerName . toLowerCase ( ) ,
2018-08-21 15:45:59 +02:00
image: imageName ,
alwaysPullImage: true ,
2018-10-04 17:06:42 +02:00
envVars: getContainerEnvs ( config , imageName )
]
if ( ! config . containerCommands ? . get ( imageName ) ? . isEmpty ( ) ) {
templateParameters . command = config . containerCommands ? . get ( imageName ) ? : '/usr/bin/tail -f /dev/null'
}
if ( config . containerPortMappings ? . get ( imageName ) ) {
def ports = [ ]
def portCounter = 0
config . containerPortMappings . get ( imageName ) . each { mapping - >
mapping . name = "${containerName}${portCounter}" . toString ( )
ports . add ( portMapping ( mapping ) )
portCounter + +
}
templateParameters . ports = ports
}
result . push ( containerTemplate ( templateParameters ) )
2018-08-21 15:45:59 +02:00
}
return result
}
/ * *
* Returns a list of envVar object consisting of set
* environment variables , params ( Parametrized Build ) and working directory .
* ( Kubernetes - Plugin only ! )
* @param config Map with configurations
* /
2018-10-04 17:06:42 +02:00
private List getContainerEnvs ( config , imageName ) {
2018-08-21 15:45:59 +02:00
def containerEnv = [ ]
2018-10-04 17:06:42 +02:00
def dockerEnvVars = config . containerEnvVars ? . get ( imageName ) ? : config . dockerEnvVars ? : [ : ]
def dockerWorkspace = config . containerWorkspaces ? . get ( imageName ) ! = null ? config . containerWorkspaces ? . get ( imageName ) : config . dockerWorkspace ? : ''
2018-08-21 15:45:59 +02:00
if ( dockerEnvVars ) {
for ( String k : dockerEnvVars . keySet ( ) ) {
containerEnv < < envVar ( key: k , value: dockerEnvVars [ k ] . toString ( ) )
}
}
if ( dockerWorkspace ) {
containerEnv < < envVar ( key: "HOME" , value: dockerWorkspace )
}
// Inherit the proxy information from the master to the container
SystemEnv systemEnv = new SystemEnv ( )
for ( String env : systemEnv . getEnv ( ) . keySet ( ) ) {
containerEnv < < envVar ( key: env , value: systemEnv . get ( env ) )
}
// ContainerEnv array can't be empty. Using a stub to avoid failure.
if ( ! containerEnv ) {
containerEnv < < envVar ( key: "EMPTY_VAR" , value: " " )
}
return containerEnv
}