2018-08-21 15:45:59 +02:00
import com.sap.piper.ConfigurationHelper
import com.sap.piper.JenkinsUtils
import com.sap.piper.k8s.SystemEnv
import groovy.transform.Field
import hudson.AbortException
@Field def STEP_NAME = 'dockerExecuteOnKubernetes'
@Field def PLUGIN_ID_KUBERNETES = 'kubernetes'
@Field Set GENERAL_CONFIG_KEYS = [ 'jenkinsKubernetes' ]
2018-10-04 17:06:42 +02:00
@Field Set PARAMETER_KEYS = [
'containerCommands' , //specify start command for containers to overwrite Piper default (`/usr/bin/tail -f /dev/null`). If container's defaultstart command should be used provide empty string like: `['selenium/standalone-chrome': '']`
'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]]]
'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' ,
'dockerEnvVars'
]
2018-08-21 15:45:59 +02:00
@Field Set STEP_CONFIG_KEYS = PARAMETER_KEYS . plus ( [ 'stashIncludes' , 'stashExcludes' ] )
void call ( Map parameters = [ : ] , body ) {
handlePipelineStepErrors ( stepName: STEP_NAME , stepParameters: parameters ) {
if ( ! JenkinsUtils . isPluginActive ( PLUGIN_ID_KUBERNETES ) ) {
error ( "[ERROR][${STEP_NAME}] not supported. Plugin '${PLUGIN_ID_KUBERNETES}' is not installed or not active." )
}
final script = parameters . script
if ( script = = null )
script = [ commonPipelineEnvironment: commonPipelineEnvironment ]
ConfigurationHelper configHelper = ConfigurationHelper
. loadStepDefaults ( this )
. 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 ]
2018-08-21 15:45:59 +02:00
}
2018-10-08 11:54:13 +02:00
executeOnPod ( config , 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-10-08 11:54:13 +02:00
void executeOnPod ( Map config , 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-10-08 11:54:13 +02:00
if ( config . containerName )
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 ) {
container ( name: config . containerName ) {
try {
unstashWorkspace ( config , 'workspace' )
body ( )
} finally {
stashWorkspace ( config , 'container' )
}
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
}
}
private void stashWorkspace ( config , prefix ) {
try {
// Every dockerImage used in the dockerExecuteOnKubernetes should have user id 1000
sh "chown -R 1000:1000 ."
stash (
name: "${prefix}-${config.uniqueId}" ,
include: config . stashIncludes . workspace ,
exclude: config . stashExcludes . excludes
)
} catch ( AbortException | IOException e ) {
echo "${e.getMessage()}"
}
}
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
}