mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +02:00
1b202f879a
* Provide flag for skipping docker image pulls Usefull for testing while developing images. Without pulling images it is possible to use images available in the local registry only.
323 lines
12 KiB
Groovy
323 lines
12 KiB
Groovy
import static com.sap.piper.Prerequisites.checkScript
|
|
|
|
import com.cloudbees.groovy.cps.NonCPS
|
|
import com.sap.piper.ConfigurationHelper
|
|
import com.sap.piper.GenerateDocumentation
|
|
import com.sap.piper.JenkinsUtils
|
|
import com.sap.piper.Utils
|
|
import com.sap.piper.k8s.ContainerMap
|
|
import groovy.transform.Field
|
|
|
|
@Field def STEP_NAME = getClass().getName()
|
|
@Field def PLUGIN_ID_DOCKER_WORKFLOW = 'docker-workflow'
|
|
|
|
@Field Set GENERAL_CONFIG_KEYS = [
|
|
/**
|
|
*
|
|
*/
|
|
'jenkinsKubernetes'
|
|
]
|
|
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
|
|
/**
|
|
* Kubernetes only:
|
|
* Allows to specify start command for container created with dockerImage parameter to overwrite Piper default (`/usr/bin/tail -f /dev/null`).
|
|
*/
|
|
'containerCommand',
|
|
/**
|
|
* Map which defines per docker image the port mappings, e.g. `containerPortMappings: ['selenium/standalone-chrome': [[name: 'selPort', containerPort: 4444, hostPort: 4444]]]`.
|
|
*/
|
|
'containerPortMappings',
|
|
/**
|
|
* Kubernetes only:
|
|
* Allows to specify the shell to be used for execution of commands.
|
|
*/
|
|
'containerShell',
|
|
/**
|
|
* Environment variables to set in the container, e.g. [http_proxy: 'proxy:8080'].
|
|
*/
|
|
'dockerEnvVars',
|
|
/**
|
|
* Name of the docker image that should be used. If empty, Docker is not used and the command is executed directly on the Jenkins system.
|
|
*/
|
|
'dockerImage',
|
|
/**
|
|
* Kubernetes only:
|
|
* Name of the container launching `dockerImage`.
|
|
* SideCar only:
|
|
* Name of the container in local network.
|
|
*/
|
|
'dockerName',
|
|
/**
|
|
* Docker options to be set when starting the container (List or String).
|
|
*/
|
|
'dockerOptions',
|
|
/**
|
|
* Volumes that should be mounted into the container.
|
|
*/
|
|
'dockerVolumeBind',
|
|
/**
|
|
* Set this to 'false' to bypass a docker image pull. Usefull during development process. Allows testing of images which are available in the local registry only.
|
|
*/
|
|
'dockerPullImage',
|
|
/**
|
|
* Kubernetes only:
|
|
* Specifies a dedicated user home directory for the container which will be passed as value for environment variable `HOME`.
|
|
*/
|
|
'dockerWorkspace',
|
|
/**
|
|
* as `dockerEnvVars` for the sidecar container
|
|
*/
|
|
'sidecarEnvVars',
|
|
/**
|
|
* as `dockerImage` for the sidecar container
|
|
*/
|
|
'sidecarImage',
|
|
/**
|
|
* as `dockerName` for the sidecar container
|
|
*/
|
|
'sidecarName',
|
|
/**
|
|
* as `dockerOptions` for the sidecar container
|
|
*/
|
|
'sidecarOptions',
|
|
/**
|
|
* as `dockerVolumeBind` for the sidecar container
|
|
*/
|
|
'sidecarVolumeBind',
|
|
/**
|
|
* Set this to 'false' to bypass a docker image pull. Usefull during development process. Allows testing of images which are available in the local registry only.
|
|
*/
|
|
'sidecarPullImage',
|
|
/**
|
|
* as `dockerWorkspace` for the sidecar container
|
|
*/
|
|
'sidecarWorkspace',
|
|
/**
|
|
* Specific stashes that should be considered for the step execution.
|
|
*/
|
|
'stashContent'
|
|
])
|
|
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
|
|
|
|
/**
|
|
* Executes a closure inside a docker container with the specified docker image.
|
|
* The workspace is mounted into the docker image.
|
|
* Proxy environment variables defined on the Jenkins machine are also available in the Docker container.
|
|
*/
|
|
@GenerateDocumentation
|
|
void call(Map parameters = [:], body) {
|
|
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
|
|
|
|
final script = checkScript(this, parameters) ?: this
|
|
|
|
def utils = parameters?.juStabUtils ?: new Utils()
|
|
|
|
Map config = ConfigurationHelper.newInstance(this)
|
|
.loadStepDefaults()
|
|
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
|
|
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
|
|
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
|
|
.mixin(parameters, PARAMETER_KEYS)
|
|
.use()
|
|
if (isKubernetes() && config.dockerImage) {
|
|
if (env.POD_NAME && isContainerDefined(config)) {
|
|
container(getContainerDefined(config)) {
|
|
echo "[INFO][${STEP_NAME}] Executing inside a Kubernetes Container."
|
|
body()
|
|
sh "chown -R 1000:1000 ."
|
|
}
|
|
} else {
|
|
if (!config.sidecarImage) {
|
|
dockerExecuteOnKubernetes(
|
|
script: script,
|
|
containerCommand: config.containerCommand,
|
|
containerShell: config.containerShell,
|
|
dockerImage: config.dockerImage,
|
|
dockerPullImage: config.dockerPullImage,
|
|
dockerEnvVars: config.dockerEnvVars,
|
|
dockerWorkspace: config.dockerWorkspace,
|
|
stashContent: config.stashContent
|
|
){
|
|
echo "[INFO][${STEP_NAME}] Executing inside a Kubernetes Pod"
|
|
body()
|
|
}
|
|
} else {
|
|
Map paramMap = [
|
|
script: script,
|
|
containerCommands: [:],
|
|
containerEnvVars: [:],
|
|
containerPullImageFlags: [:],
|
|
containerMap: [:],
|
|
containerName: config.dockerName,
|
|
containerPortMappings: [:],
|
|
containerWorkspaces: [:],
|
|
stashContent: config.stashContent
|
|
]
|
|
paramMap.containerCommands[config.sidecarImage] = ''
|
|
|
|
paramMap.containerEnvVars[config.dockerImage] = config.dockerEnvVars
|
|
paramMap.containerEnvVars[config.sidecarImage] = config.sidecarEnvVars
|
|
|
|
paramMap.containerPullImageFlags[config.dockerImage] = config.dockerPullImage
|
|
paramMap.containerPullImageFlags[config.sidecarImage] = config.sidecarPullImage
|
|
|
|
paramMap.containerMap[config.dockerImage] = config.dockerName
|
|
paramMap.containerMap[config.sidecarImage] = config.sidecarName
|
|
|
|
paramMap.containerPortMappings = config.containerPortMappings
|
|
|
|
paramMap.containerWorkspaces[config.dockerImage] = config.dockerWorkspace
|
|
paramMap.containerWorkspaces[config.sidecarImage] = ''
|
|
|
|
dockerExecuteOnKubernetes(paramMap){
|
|
echo "[INFO][${STEP_NAME}] Executing inside a Kubernetes Pod with sidecar container"
|
|
body()
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
boolean executeInsideDocker = true
|
|
if (!JenkinsUtils.isPluginActive(PLUGIN_ID_DOCKER_WORKFLOW)) {
|
|
echo "[WARNING][${STEP_NAME}] Docker not supported. Plugin '${PLUGIN_ID_DOCKER_WORKFLOW}' is not installed or not active. Configured docker image '${config.dockerImage}' will not be used."
|
|
executeInsideDocker = false
|
|
}
|
|
|
|
returnCode = sh script: 'docker ps -q > /dev/null', returnStatus: true
|
|
if (returnCode != 0) {
|
|
echo "[WARNING][$STEP_NAME] Cannot connect to docker daemon (command 'docker ps' did not return with '0'). Configured docker image '${config.dockerImage}' will not be used."
|
|
executeInsideDocker = false
|
|
}
|
|
if (executeInsideDocker && config.dockerImage) {
|
|
utils.unstashAll(config.stashContent)
|
|
def image = docker.image(config.dockerImage)
|
|
if (config.dockerPullImage) image.pull()
|
|
else echo"[INFO][$STEP_NAME] Skipped pull of image '${config.dockerImage}'."
|
|
if (!config.sidecarImage) {
|
|
image.inside(getDockerOptions(config.dockerEnvVars, config.dockerVolumeBind, config.dockerOptions)) {
|
|
body()
|
|
}
|
|
} else {
|
|
def networkName = "sidecar-${UUID.randomUUID()}"
|
|
sh "docker network create ${networkName}"
|
|
try{
|
|
def sidecarImage = docker.image(config.sidecarImage)
|
|
if (config.sidecarPullImage) sidecarImage.pull()
|
|
else echo"[INFO][$STEP_NAME] Skipped pull of image '${config.sidecarImage}'."
|
|
config.sidecarOptions = config.sidecarOptions?:[]
|
|
if (config.sidecarName)
|
|
config.sidecarOptions.add("--network-alias ${config.sidecarName}")
|
|
config.sidecarOptions.add("--network ${networkName}")
|
|
sidecarImage.withRun(getDockerOptions(config.sidecarEnvVars, config.sidecarVolumeBind, config.sidecarOptions)) { c ->
|
|
config.dockerOptions = config.dockerOptions?:[]
|
|
if (config.dockerName)
|
|
config.dockerOptions.add("--network-alias ${config.dockerName}")
|
|
config.dockerOptions.add("--network ${networkName}")
|
|
image.inside(getDockerOptions(config.dockerEnvVars, config.dockerVolumeBind, config.dockerOptions)) {
|
|
echo "[INFO][${STEP_NAME}] Running with sidecar container."
|
|
body()
|
|
}
|
|
}
|
|
}finally{
|
|
sh "docker network remove ${networkName}"
|
|
}
|
|
}
|
|
} else {
|
|
echo "[INFO][${STEP_NAME}] Running on local environment."
|
|
body()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Returns a string with docker options containing
|
|
* environment variables (if set).
|
|
* Possible to extend with further options.
|
|
* @param dockerEnvVars Map with environment variables
|
|
*/
|
|
@NonCPS
|
|
private getDockerOptions(Map dockerEnvVars, Map dockerVolumeBind, def dockerOptions) {
|
|
def specialEnvironments = [
|
|
'http_proxy',
|
|
'https_proxy',
|
|
'no_proxy',
|
|
'HTTP_PROXY',
|
|
'HTTPS_PROXY',
|
|
'NO_PROXY'
|
|
]
|
|
def options = []
|
|
if (dockerEnvVars) {
|
|
for (String k : dockerEnvVars.keySet()) {
|
|
options.add("--env ${k}=${dockerEnvVars[k].toString()}")
|
|
}
|
|
}
|
|
|
|
for (String envVar : specialEnvironments) {
|
|
if (dockerEnvVars == null || !dockerEnvVars.containsKey(envVar)) {
|
|
options.add("--env ${envVar}")
|
|
}
|
|
}
|
|
|
|
if (dockerVolumeBind) {
|
|
for (String k : dockerVolumeBind.keySet()) {
|
|
options.add("--volume ${k}:${dockerVolumeBind[k].toString()}")
|
|
}
|
|
}
|
|
|
|
if (dockerOptions) {
|
|
if (dockerOptions instanceof CharSequence) {
|
|
dockerOptions = [dockerOptions]
|
|
}
|
|
if (dockerOptions instanceof List) {
|
|
for (String option : dockerOptions) {
|
|
options << escapeBlanks(option)
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException("Unexpected type for dockerOptions. Expected was either a list or a string. Actual type was: '${dockerOptions.getClass()}'")
|
|
}
|
|
}
|
|
return options.join(' ')
|
|
}
|
|
|
|
|
|
boolean isContainerDefined(config) {
|
|
Map containerMap = ContainerMap.instance.getMap()
|
|
|
|
if (!containerMap.containsKey(env.POD_NAME)) {
|
|
return false
|
|
}
|
|
|
|
return containerMap.get(env.POD_NAME).containsKey(config.dockerImage)
|
|
}
|
|
|
|
|
|
def getContainerDefined(config) {
|
|
return ContainerMap.instance.getMap().get(env.POD_NAME).get(config.dockerImage).toLowerCase()
|
|
}
|
|
|
|
|
|
boolean isKubernetes() {
|
|
return Boolean.valueOf(env.ON_K8S)
|
|
}
|
|
|
|
/*
|
|
* Escapes blanks for values in key/value pairs
|
|
* E.g. <code>description=Lorem ipsum</code> is
|
|
* changed to <code>description=Lorem\ ipsum</code>.
|
|
*/
|
|
@NonCPS
|
|
def escapeBlanks(def s) {
|
|
|
|
def EQ='='
|
|
def parts=s.split(EQ)
|
|
|
|
if(parts.length == 2) {
|
|
parts[1]=parts[1].replaceAll(' ', '\\\\ ')
|
|
s = parts.join(EQ)
|
|
}
|
|
|
|
return s
|
|
}
|