From d78dc454fbb83f58c88fd74409ac05a1f46604e8 Mon Sep 17 00:00:00 2001 From: Ramachandra Kamath Arbettu Date: Thu, 7 Jun 2018 13:58:32 +0200 Subject: [PATCH] Support K8S execution --- resources/default_pipeline_environment.yml | 4 + src/com/sap/piper/sysEnv.groovy | 42 ++++++ test/groovy/com/sap/piper/SysEnvTest.groovy | 43 ++++++ vars/dockerExecute.groovy | 9 +- vars/executeDockerOnKubernetes.groovy | 148 ++++++++++++++++++++ 5 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 src/com/sap/piper/sysEnv.groovy create mode 100644 test/groovy/com/sap/piper/SysEnvTest.groovy create mode 100644 vars/executeDockerOnKubernetes.groovy diff --git a/resources/default_pipeline_environment.yml b/resources/default_pipeline_environment.yml index afa66938e..7ac9841eb 100644 --- a/resources/default_pipeline_environment.yml +++ b/resources/default_pipeline_environment.yml @@ -78,6 +78,10 @@ steps: mavenExecute: dockerImage: 'maven:3.5-jdk-7' logSuccessfulMavenTransfers: false + executeDockerOnKubernetes: + stashContent: [] + stashBackConfig: + excludes: 'nohup.out' mtaBuild: buildTarget: 'NEO' mtaJarLocation: 'mta.jar' diff --git a/src/com/sap/piper/sysEnv.groovy b/src/com/sap/piper/sysEnv.groovy new file mode 100644 index 000000000..196d70fdb --- /dev/null +++ b/src/com/sap/piper/sysEnv.groovy @@ -0,0 +1,42 @@ +package com.sap.piper + +class SysEnv implements Serializable { + static final long serialVersionUID = 1L + + private Map env + + List envNames=[ + 'HTTP_PROXY', + 'HTTPS_PROXY', + 'NO_PROXY', + 'http_proxy', + 'https_proxy', + 'no_proxy' + ] + + public SysEnv() { + env= new HashMap() + fillMap() + } + + public String get(String key) { + return env.get(key) + } + + public Map getEnv() { + return env + } + + public String remove(String key) { + return env.remove(key) + } + + @NonCPS + private void fillMap() { + for (String name in envNames) { + if(System.getenv(name)){ + env.put(name,System.getenv(name)) + } + } + } +} diff --git a/test/groovy/com/sap/piper/SysEnvTest.groovy b/test/groovy/com/sap/piper/SysEnvTest.groovy new file mode 100644 index 000000000..28175ed8b --- /dev/null +++ b/test/groovy/com/sap/piper/SysEnvTest.groovy @@ -0,0 +1,43 @@ +package com.sap.piper + +import org.junit.Before +import org.junit.Test + +import static org.junit.Assert.assertEquals +import static org.junit.Assert.assertNotNull + +class SysEnvTest { + SysEnv env = null; + + @Before + void setUp() throws Exception { + env = new SysEnv() + assertNotNull(env) + } + + @Test + void testget() { + String name = 'HTTP_PROXY' + assertEquals(env.get(),System.getenv(name)) + + name = 'HTTPS_PROXY' + assertEquals(env.get(),System.getenv(name)) + + } + + @Test + void testgetEnv() { + Map envVars = env.getEnv() + String name = 'HTTP_PROXY' + assertNotNull(envVars) + assertEquals(envVars.get(name),env.get(name)) + } + + @Test + void testremove() { + String name = 'HTTP_PROXY' + env.remove(name) + assertEquals(env.getEnv().containsKey(name),false) + } + +} diff --git a/vars/dockerExecute.groovy b/vars/dockerExecute.groovy index e988f5d39..9c2779f37 100644 --- a/vars/dockerExecute.groovy +++ b/vars/dockerExecute.groovy @@ -1,5 +1,5 @@ import com.cloudbees.groovy.cps.NonCPS -import hudson.EnvVars; + def call(Map parameters = [:], body) { def STEP_NAME = 'dockerExecute' @@ -13,8 +13,11 @@ def call(Map parameters = [:], body) { def k8s = parameters.k8s ?: true if (k8s) { - echo "redirecting to executeDocker" - executeDocker(dockerImage:parameters.dockerImage,dockerEnvVars:parameters.dockerEnvVars,dockerOptions:parameters.dockerOptions,dockerVolumeBind:parameters.dockerVolumeBind) { + executeDockerOnKubernetes( + dockerImage: parameters.dockerImage, + dockerEnvVars: parameters.dockerEnvVars, + dockerOptions: parameters.dockerOptions, + dockerVolumeBind: parameters.dockerVolumeBind) { body() } } else if (dockerImage) { diff --git a/vars/executeDockerOnKubernetes.groovy b/vars/executeDockerOnKubernetes.groovy new file mode 100644 index 000000000..4dcbc86ff --- /dev/null +++ b/vars/executeDockerOnKubernetes.groovy @@ -0,0 +1,148 @@ +import com.cloudbees.groovy.cps.NonCPS +import com.sap.piper.SysEnv +import org.codehaus.groovy.GroovyException + +import java.util.UUID + +def call(Map parameters = [:], body) { + def STEP_NAME = 'executeDockerOnKubernetes' + def PLUGIN_ID_KUBERNETES = 'kubernetes' + + handlePipelineStepErrors(stepName: 'executeDockerOnKubernetes', stepParameters: parameters) { + if (!isPluginActive(PLUGIN_ID_KUBERNETES)) { + error("[ERROR][${STEP_NAME}] not supported. Plugin '${PLUGIN_ID_KUBERNETES}' is not installed or not active.") + } + + final script = parameters.script + prepareDefaultValues script: script + + Set parameterKeys = ['dindImage', + 'dockerImage', + 'dockerWorkspace', + 'dockerEnvVars'] + Set stepConfigurationKeys = parameterKeys + + Map config = ConfigurationMerger.merge(script, 'executeDockerOnKubernetes', + parameters, parameterKeys, + stepConfigurationKeys) + + config.uniqueId = UUID.randomUUID().toString() + + if (!config.dockerImage) throw new GroovyException('Docker image not specified.') + + def options = [name : env.jaas_owner + '-jaas', + label : uniqueId, + containers: getContainerList(config)] + + stashWorkspace(config) + podTemplate(options) { + node(config.uniqueId) { + echo "Execute container content in Kubernetes pod" + utils.unstashAll(config.stashContent) + container(name: 'container-exec') { + body() + } + stashContainer(config) + } + } + unstashContainer(config) + } +} + +private stashWorkspace(config) { + if (config.stashContent.size() == 0) { + try { + sh "chmod -R u+w ." + stash "workspace-${config.uniqueId}" + config.stashContent += 'workspace-' + config.uniqueId + } catch (hudson.AbortException e) { + echo "${e.getMessage()}" + } catch (java.io.IOException ioe) { + echo "${ioe.getMessage()}" + } + } +} + +private stashContainer(config) { + def stashBackConfig = config.stashBackConfig + try { + stashBackConfig.name = "container-${config.uniqueId}" + stash stashBackConfig + } catch (hudson.AbortException e) { + echo "${e.getMessage()}" + } catch (java.io.IOException ioe) { + echo "${ioe.getMessage()}" + } +} + +private unstashContainer(config) { + try { + unstash "container-${config.uniqueId}" + } catch (hudson.AbortException e) { + echo "${e.getMessage()}" + } catch (java.io.IOException ioe) { + echo "${ioe.getMessage()}" + } +} + +private getContainerList(config) { + def envVars + envVars = getContainerEnvs(config.dockerEnvVars, config.dockerWorkspace) + if (config.dindImage) { + envVars << envVar(key: 'DOCKER_HOST', value: '2375') + } + + result = [] + result.push(containerTemplate(name: 'jnlp', + image: 's4sdk/jnlp-k8s:latest', + args: '${computer.jnlpmac} ${computer.name}')) + result.push(containerTemplate(name: 'container-exec', + image: config.dockerImage, + alwaysPullImage: true, + command: '/usr/bin/tail -f /dev/null', + envVars: envVars)) + if (config.dindImage) result.push(containerTemplate(name: 'container-dind', + image: config.dindImage, + privileged: true)) + return result +} + +/** + * Returns a list of envVar object consisting of set + * environment variables, params (Parametrized Build) and working directory. + * (Kubernetes-Plugin only!) + * @param dockerEnvVars Map with environment variables + * @param dockerWorkspace Path to working dir + */ +private getContainerEnvs(dockerEnvVars, dockerWorkspace) { + def containerEnv = [] + + if (dockerEnvVars) { + for (String k : dockerEnvVars.keySet()) { + containerEnv << envVar(key: k, value: dockerEnvVars[k].toString()) + } + } + if (params) { + for (String k : params.keySet()) { + containerEnv << envVar(key: k, value: params[k].toString()) + } + } + if (dockerWorkspace) containerEnv << envVar(key: "HOME", value: dockerWorkspace) + + // Inherit the proxy information from the master to the container + def systemEnv = new SysEnv() + def envList = systemEnv.getEnv().keySet() + for (String env : envList) { + 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 +} + +@NonCPS +private isPluginActive(String pluginId) { + return Jenkins.instance.pluginManager.plugins.find { p -> p.isActive() && p.getShortName() == pluginId } +}