1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00

Merge branch 'master' into pr/swaPushOldConfigUsed

This commit is contained in:
Oliver Nocon 2018-10-08 09:42:33 +02:00 committed by GitHub
commit 130fe6a941
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 744 additions and 88 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.idea/
bin/
.settings
logs
reports

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -2,32 +2,73 @@
## Description
Executes a closure inside a docker container with the specified docker image.
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.
## Parameters
| parameter | mandatory | default | possible values |
| -------------------|-----------|-----------------------------------|----------------------------|
| `script` | no | empty `globalPipelineEnvironment` | |
| `dockerImage` | no | '' | |
| `dockerEnvVars` | no | [:] | |
| `dockerOptions` | no | '' | |
| `dockerVolumeBind` | no | [:] | |
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
|script|yes|||
|containerPortMappings|no|||
|dockerEnvVars|no|`[:]`||
|dockerImage|no|`''`||
|dockerName|no|||
|dockerOptions|no|`''`||
|dockerVolumeBind|no|`[:]`||
|dockerWorkspace|no|||
|jenkinsKubernetes|no|`[jnlpAgent:s4sdk/jenkins-agent-k8s:latest]`||
|sidecarEnvVars|no|||
|sidecarImage|no|||
|sidecarName|no|||
|sidecarOptions|no|||
|sidecarVolumeBind|no|||
|sidecarWorkspace|no|||
* `script` defines the global script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for storing the measured duration.
* `dockerImage` Name of the docker image that should be used. If empty, Docker is not used.
* `dockerEnvVars` Environment variables to set in the container, e.g. [http_proxy:'proxy:8080']
* `containerPortMappings`: Map which defines per docker image the port mappings, like `containerPortMappings: ['selenium/standalone-chrome': [[name: 'selPort', containerPort: 4444, hostPort: 4444]]]`
* `dockerEnvVars`: Environment variables to set in the container, e.g. [http_proxy:'proxy:8080']
* `dockerImage`: 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.
* `dockerName`: only relevant for Kubernetes case: Name of the container launching `dockerImage`
* `dockerOptions` Docker options to be set when starting the container. It can be a list or a string.
* `dockerVolumeBind` Volumes that should be mounted into the container.
* `dockerWorkspace`: only relevant for Kubernetes case: specifies a dedicated user home directory for the container which will be passed as value for environment variable `HOME`
* `sidecarEnvVars` defines environment variables for the sidecar container, similar to `dockerEnvVars`
* `sidecarImage`: Name of the docker image of the sidecar container. Do not provide this value if no sidecar container is required.
* `sidecarName`: as `dockerName` for the sidecar container
* `sidecarOptions`: as `dockerOptions` for the sidecar container
* `sidecarVolumeBind`: as `dockerVolumeBind` for the sidecar container
* `sidecarWorkspace`: as `dockerWorkspace` for the sidecar container
## Kubernetes support
If the Jenkins is setup on a Kubernetes cluster, then you can execute the closure inside a container of a pod by setting an environment variable `ON_K8S` to `true`. However, it will ignore both `dockeOptions` and `dockerVolumeBind` values.
If the Jenkins is setup on a Kubernetes cluster, then you can execute the closure inside a container of a pod by setting an environment variable `ON_K8S` to `true`. However, it will ignore `containerPortMappings`, `dockerOptions` and `dockerVolumeBind` values.
## Step configuration
none
We recommend to define values of step parameters via [config.yml file](../configuration.md).
In following sections the configuration is possible:
| parameter | general | step | stage |
| ----------|-----------|---------|-----------------|
|script||||
|containerPortMappings||X|X|
|dockerEnvVars||X|X|
|dockerImage||X|X|
|dockerName||X|X|
|dockerOptions||X|X|
|dockerVolumeBind||X|X|
|dockerWorkspace||X|X|
|jenkinsKubernetes|X|||
|sidecarEnvVars||X|X|
|sidecarImage||X|X|
|sidecarName||X|X|
|sidecarOptions||X|X|
|sidecarVolumeBind||X|X|
|sidecarWorkspace||X|X|
## Return value
none
@ -38,7 +79,7 @@ none
## Exceptions
none
## Example 1: Run closure inside a docker container
## Example 1: Run closure inside a docker container
```groovy
dockerExecute(dockerImage: 'maven:3.5-jdk-7'){
@ -48,7 +89,7 @@ dockerExecute(dockerImage: 'maven:3.5-jdk-7'){
## Example 2: Run closure inside a container in a kubernetes pod
```sh
# set environment variable
# set environment variable
export ON_K8S=true"
```
@ -58,7 +99,26 @@ dockerExecute(script: this, dockerImage: 'maven:3.5-jdk-7'){
}
```
In the above example, the `dockerEcecute` step will internally invoke [dockerExecuteOnKubernetes](dockerExecuteOnKubernetes.md) step and execute the closure inside a pod.
In the above example, the `dockerEcecute` step will internally invoke [dockerExecuteOnKubernetes](dockerExecuteOnKubernetes.md) step and execute the closure inside a pod.
## Example 3: Run closure inside a container which is attached to a sidecar container (as for example used in [seleniumExecuteTests](seleniumExecuteTests.md):
```groovy
dockerExecute(
script: script,
containerPortMappings: [containerPortMappings:'selenium/standalone-chrome':[containerPort: 4444, hostPort: 4444]],
dockerImage: 'node:8-stretch',
dockerName: 'node',
dockerWorkspace: '/home/node',
sidecarImage: 'selenium/standalone-chrome',
sidecarName: 'selenium',
) {
git url: 'https://github.wdf.sap.corp/XXXXX/WebDriverIOTest.git'
sh '''npm install
node index.js
'''
}
```

View File

@ -4,29 +4,64 @@
Executes a closure inside a container in a kubernetes pod. Proxy environment variables defined on the Jenkins machine are also available in the container.
## Prerequisites
## Prerequisites
* The Jenkins should be running on kubernetes.
* An environment variable `ON_K8S` should be created on Jenkins and initialized to `true`.
* An environment variable `ON_K8S` should be created on Jenkins and initialized to `true`. This could for example be done via _Jenkins_ - _Manage Jenkins_ - _Configure System_ - _Global properties_ - _Environment variables_
![Jenkins environment variable configuration](../images/k8s_env.png)
## Parameters
| parameter | mandatory | default | possible values |
| -------------------|-----------|-----------------------------------|----------------------------|
| `script` | no | empty `globalPipelineEnvironment` | |
| `dockerImage` | yes | | |
| `dockerEnvVars` | no | [:] | |
| `dockerWorkspace` | no | '' | |
| `containerMap` | no | [:] | |
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
|script|yes|||
|containerCommands|no|||
|containerEnvVars|no|||
|containerMap|no|`[:]`||
|containerName|no|||
|containerPortMappings|no|||
|containerWorkspaces|no|||
|dockerEnvVars|no|`[:]`||
|dockerImage|yes|||
|dockerWorkspace|no|`''`||
|jenkinsKubernetes|no|`[jnlpAgent:s4sdk/jenkins-agent-k8s:latest]`||
|stashExcludes|no|`[workspace:nohup.out]`||
|stashIncludes|no|`[workspace:**/*.*]`||
* `script` defines the global script environment of the Jenkins file run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for storing the measured duration.
* `containerCommands` specifies 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` specifies environment variables per container. If not provided `dockerEnvVars` will be used.
* `containerMap` A map of docker image to the name of the container. The pod will be created with all the images from this map and they are labled based on the value field of each map entry.
Example: `['maven:3.5-jdk-8-alpine': 'mavenExecute', 'selenium/standalone-chrome': 'selenium', 'famiko/jmeter-base': 'checkJMeter', 's4sdk/docker-cf-cli': 'cloudfoundry']`
* `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` specifies workspace (=home directory of user) per container. If not provided `dockerWorkspace` will be used. If empty, home directory will not be set.
* `dockerImage` Name of the docker image that should be used. If empty, Docker is not used.
* `dockerEnvVars` Environment variables to set in the container, e.g. [http_proxy:'proxy:8080']
* `dockerWorkspace` Docker options to be set when starting the container. It can be a list or a string.
* `containerMap` A map of docker image to the name of the container. The pod will be created with all the images from this map and they are labled based on the value field of each map entry.
Ex `['maven:3.5-jdk-8-alpine': 'mavenExecute', 'famiko/jmeter-base': 'checkJMeter', 's4sdk/docker-cf-cli': 'cloudfoundry']`
## Step configuration
none
We recommend to define values of step parameters via [config.yml file](../configuration.md).
In following sections the configuration is possible:
| parameter | general | step | stage |
| ----------|-----------|---------|-----------------|
|script||||
|containerCommands||X|X|
|containerEnvVars||X|X|
|containerMap||X|X|
|containerName||X|X|
|containerPortMappings||X|X|
|containerWorkspaces||X|X|
|dockerEnvVars||X|X|
|dockerImage||X|X|
|dockerWorkspace||X|X|
|jenkinsKubernetes|X|||
|stashExcludes||X|X|
|stashIncludes||X|X|
## Return value
none
@ -39,13 +74,13 @@ none
## Example 1: Run a closure in a single container pod
```sh
# set environment variable
# set environment variable
export ON_K8S=true"
```
```groovy
dockerExecuteOnKubernetes(script: script, dockerImage: 'maven:3.5-jdk-7'){
sh "mvn clean install"
sh "mvn clean install"
}
```
@ -53,14 +88,14 @@ In the above example, a pod will be created with a docker container of image `ma
## Example 2: Run a closure in a multi-container pod
```sh
# set environment variable
# set environment variable
export ON_K8S=true"
```
```groovy
dockerExecuteOnKubernetes(script: script, containerMap: ['maven:3.5-jdk-8-alpine': 'maven', 's4sdk/docker-cf-cli': 'cfcli']){
container('maven'){
sh "mvn clean install"
sh "mvn clean install"
}
container('cfcli'){
sh "cf plugins"
@ -68,7 +103,25 @@ dockerExecuteOnKubernetes(script: script, containerMap: ['maven:3.5-jdk-8-alpine
}
```
In the above example, a pod will be created with multiple Docker containers that are passed as a `containerMap`. The containers can be chosen for executing by referring their labels as shown in the example.
In the above example, a pod will be created with multiple Docker containers that are passed as a `containerMap`. The containers can be chosen for executing by referring their labels as shown in the example.
## Example 3: Running a closure in a dedicated container of a multi-container pod
```sh
# set environment variable
export ON_K8S=true"
```
```groovy
dockerExecuteOnKubernetes(
script: script,
containerCommands: ['selenium/standalone-chrome': ''],
containerMap: ['maven:3.5-jdk-8-alpine': 'maven', 'selenium/standalone-chrome': 'selenium'],
containerName: 'maven',
containerPortMappings: ['selenium/standalone-chrome': [containerPort: 4444, hostPort: 4444]]
containerWorkspaces: ['selenium/standalone-chrome': '']
){
echo "Executing inside a Kubernetes Pod inside 'maven' container to run Selenium tests"
sh "mvn clean install"
}
```

View File

@ -0,0 +1,137 @@
# seleniumExecuteTests
## Description
Enables UI test execution with Selenium in a sidecar container.
The step executes a closure (see example below) connecting to a sidecar container with a Selenium Server.
When executing in a
* local Docker environment, please make sure to set Selenium host to **`selenium`** in your tests.
* Kubernetes environment, plese make sure to set Seleniums host to **`localhost`** in your tests.
!!! note "Proxy Environments"
If work in an environment containing a proxy, please make sure that `localhost`/`selenium` is added to your proxy exclusion list, e.g. via environment variable `NO_PROXY` & `no_proxy`. You can pass those via parameters `dockerEnvVars` and `sidecarEnvVars` directly to the containers if required.
## Prerequisites
none
## Example
```groovy
seleniumExecuteTests (script: this) {
git url: 'https://github.wdf.sap.corp/xxxxx/WebDriverIOTest.git'
sh '''npm install
node index.js'''
}
```
### Example test using WebdriverIO
Example based on http://webdriver.io/guide/getstarted/modes.html and http://webdriver.io/guide.html
#### Configuration for Local Docker Environment
```
var webdriverio = require('webdriverio');
var options = {
host: 'selenium',
port: 4444,
desiredCapabilities: {
browserName: 'chrome'
}
};
```
#### Configuration for Kubernetes Environment
```
var webdriverio = require('webdriverio');
var options = {
host: 'localhost',
port: 4444,
desiredCapabilities: {
browserName: 'chrome'
}
};
```
#### Test Code (index.js)
```
// ToDo: add configuration from above
webdriverio
.remote(options)
.init()
.url('http://www.google.com')
.getTitle().then(function(title) {
console.log('Title was: ' + title);
})
.end()
.catch(function(err) {
console.log(err);
});
```
## Parameters
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
|script|yes|||
|containerPortMappings|no|`[selenium/standalone-chrome:[[containerPort:4444, hostPort:4444]]]`||
|dockerImage|no|buildTool=`maven`: `maven:3.5-jdk-7`<br />buildTool=`npm`: `node:8-stretch`<br />||
|dockerName|no|buildTool=`maven`: `maven`<br />buildTool=`npm`: `npm`<br />||
|dockerWorkspace|no|buildTool=`maven`: ``<br />buildTool=`npm`: `/home/node`<br />||
|failOnError|no|`true`||
|gitBranch|no|||
|gitSshKeyCredentialsId|no|``||
|sidecarEnvVars|no|||
|sidecarImage|no|`selenium/standalone-chrome`||
|sidecarName|no|`selenium`||
|sidecarVolumeBind|no|`[/dev/shm:/dev/shm]`||
|stashContent|no|<ul><li>`tests`</li></ul>||
|testRepository|no|||
* `script` defines the global script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for storing the measured duration.
* `containerPortMappings`, see step [dockerExecute](dockerExecute.md)
* `dockerImage`, see step [dockerExecute](dockerExecute.md)
* `dockerName`, see step [dockerExecute](dockerExecute.md)
* `dockerWorkspace`, see step [dockerExecute](dockerExecute.md)
* `failOnError` specifies if the step should fail in case the execution of the body of this step fails.
* `sidecarEnvVars`, see step [dockerExecute](dockerExecute.md)
* `sidecarImage`, see step [dockerExecute](dockerExecute.md)
* `sidecarName`, see step [dockerExecute](dockerExecute.md)
* `sidecarVolumeBind`, see step [dockerExecute](dockerExecute.md)
* If specific stashes should be considered for the tests, you can pass this via parameter `stashContent`
* In case the test implementation is stored in a different repository than the code itself, you can define the repository containing the tests using parameter `testRepository` and if required `gitBranch` (for a different branch than master) and `gitSshKeyCredentialsId` (for protected repositories). For protected repositories the testRepository needs to contain the ssh git url.
## Step configuration
We recommend to define values of step parameters via [config.yml file](../configuration.md).
In following sections the configuration is possible:
| parameter | general | step | stage |
| ----------|-----------|---------|-----------------|
|script||||
|containerPortMappings|X|X|X|
|dockerImage|X|X|X|
|dockerName|X|X|X|
|dockerWorkspace|X|X|X|
|failOnError|X|X|X|
|gitBranch|X|X|X|
|gitSshKeyCredentialsId|X|X|X|
|sidecarEnvVars|X|X|X|
|sidecarImage|X|X|X|
|sidecarName|X|X|X|
|sidecarVolumeBind|X|X|X|
|stashContent|X|X|X|
|testRepository|X|X|X|
## Return value
none
## Side effects
none
## Exceptions
none

View File

@ -19,6 +19,7 @@ nav:
- pipelineExecute: steps/pipelineExecute.md
- pipelineStashFiles: steps/pipelineStashFiles.md
- prepareDefaultValues: steps/prepareDefaultValues.md
- seleniumExecuteTests: steps/seleniumExecuteTests.md
- setupCommonPipelineEnvironment: steps/setupCommonPipelineEnvironment.md
- toolValidate: steps/toolValidate.md
- transportRequestCreate: steps/transportRequestCreate.md

View File

@ -10,7 +10,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.sap.cp.jenkins</groupId>
<artifactId>jenkins-library</artifactId>
<version>0.6</version>
<version>0.7</version>
<name>SAP CP Piper Library</name>
<description>Shared library containing steps and utilities to set up continuous deployment processes for SAP technologies.</description>

View File

@ -192,6 +192,28 @@ steps:
pipelineConfigAndTests: ''
securityDescriptor: ''
tests: ''
seleniumExecuteTests:
buildTool: 'npm'
containerPortMappings:
'selenium/standalone-chrome':
- containerPort: 4444
hostPort: 4444
dockerLinkAlias: 'selenium'
failOnError: true
sidecarImage: 'selenium/standalone-chrome'
sidecarName: 'selenium'
sidecarVolumeBind:
'/dev/shm': '/dev/shm'
stashContent:
- 'tests'
maven:
dockerImage: 'maven:3.5-jdk-7'
dockerName: 'maven'
dockerWorkspace: ''
npm:
dockerImage: 'node:8-stretch'
dockerName: 'npm'
dockerWorkspace: '/home/node'
snykExecute:
buildDescriptorFile: './package.json'
dockerImage: 'node:8-stretch'

View File

@ -16,6 +16,8 @@ import util.JenkinsStepRule
import util.PluginMock
import util.Rules
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
import static org.junit.Assert.assertTrue
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse
@ -48,6 +50,8 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
def imageList = []
def containerName = ''
def envList = []
def portList = []
def containerCommands = []
@Before
@ -55,6 +59,8 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
containersList = []
imageList = []
envList = []
portList = []
containerCommands = []
bodyExecuted = false
JenkinsUtils.metaClass.static.isPluginActive = { def s -> new PluginMock(s).isActive() }
helper.registerAllowedMethod('sh', [Map.class], {return whichDockerReturnValue})
@ -67,6 +73,10 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
containersList.add(option.name)
imageList.add(option.image)
envList.add(option.envVars)
portList.add(option.ports)
if (option.command) {
containerCommands.add(option.command)
}
}
body()
})
@ -77,7 +87,7 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
@Test
void testRunOnPodNoContainerMapOnlyDockerImage() throws Exception {
jsr.step.call(script: nullScript,
jsr.step.dockerExecuteOnKubernetes(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerOptions: '-it',
dockerVolumeBind: ['my_vol': '/my_vol'],
@ -90,12 +100,13 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
assertTrue(envList.toString().contains('http://proxy:8000'))
assertTrue(envList.toString().contains('/home/piper'))
assertTrue(bodyExecuted)
assertThat(containerCommands.size(), is(1))
}
@Test
void testDockerExecuteOnKubernetesWithCustomContainerMap() throws Exception {
jsr.step.call(script: nullScript,
jsr.step.dockerExecuteOnKubernetes(script: nullScript,
containerMap: ['maven:3.5-jdk-8-alpine': 'mavenexecute']) {
container(name: 'mavenexecute') {
bodyExecuted = true
@ -105,12 +116,13 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
assertTrue(containersList.contains('mavenexecute'))
assertTrue(imageList.contains('maven:3.5-jdk-8-alpine'))
assertTrue(bodyExecuted)
assertThat(containerCommands.size(), is(1))
}
@Test
void testDockerExecuteOnKubernetesWithCustomJnlpWithContainerMap() throws Exception {
nullScript.commonPipelineEnvironment.configuration = ['general': ['jenkinsKubernetes': ['jnlpAgent': 'myJnalpAgent']]]
jsr.step.call(script: nullScript,
jsr.step.dockerExecuteOnKubernetes(script: nullScript,
containerMap: ['maven:3.5-jdk-8-alpine': 'mavenexecute']) {
container(name: 'mavenexecute') {
bodyExecuted = true
@ -127,7 +139,7 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
@Test
void testDockerExecuteOnKubernetesWithCustomJnlpWithDockerImage() throws Exception {
nullScript.commonPipelineEnvironment.configuration = ['general': ['jenkinsKubernetes': ['jnlpAgent': 'myJnalpAgent']]]
jsr.step.call(script: nullScript,
jsr.step.dockerExecuteOnKubernetes(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine') {
bodyExecuted = true
}
@ -141,7 +153,7 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
@Test
void testDockerExecuteOnKubernetesWithCustomWorkspace() throws Exception {
jsr.step.call(script: nullScript,
jsr.step.dockerExecuteOnKubernetes(script: nullScript,
containerMap: ['maven:3.5-jdk-8-alpine': 'mavenexecute'],
dockerWorkspace: '/home/piper') {
container(name: 'mavenexecute') {
@ -154,7 +166,7 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
@Test
void testDockerExecuteOnKubernetesWithCustomEnv() throws Exception {
jsr.step.call(script: nullScript,
jsr.step.dockerExecuteOnKubernetes(script: nullScript,
containerMap: ['maven:3.5-jdk-8-alpine': 'mavenexecute'],
dockerEnvVars: ['customEnvKey': 'customEnvValue']) {
container(name: 'mavenexecute') {
@ -167,7 +179,7 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
@Test
void testDockerExecuteOnKubernetesUpperCaseContainerName() throws Exception {
jsr.step.call(script: nullScript,
jsr.step.dockerExecuteOnKubernetes(script: nullScript,
containerMap: ['maven:3.5-jdk-8-alpine': 'MAVENEXECUTE'],
dockerEnvVars: ['customEnvKey': 'customEnvValue']) {
container(name: 'mavenexecute') {
@ -183,7 +195,7 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
@Test
void testDockerExecuteOnKubernetesEmptyContainerMapNoDockerImage() throws Exception {
exception.expect(IllegalArgumentException.class);
jsr.step.call(script: nullScript,
jsr.step.dockerExecuteOnKubernetes(script: nullScript,
containerMap: [:],
dockerEnvVars: ['customEnvKey': 'customEnvValue']) {
container(name: 'jnlp') {
@ -193,6 +205,55 @@ class DockerExecuteOnKubernetesTest extends BasePiperTest {
assertFalse(bodyExecuted)
}
@Test
void testSidecarDefault() {
List portMapping = []
helper.registerAllowedMethod('portMapping', [Map.class], {m ->
portMapping.add(m)
return m
})
jsr.step.dockerExecuteOnKubernetes(
script: nullScript,
containerCommands: ['selenium/standalone-chrome': ''],
containerEnvVars: [
'selenium/standalone-chrome': ['customEnvKey': 'customEnvValue']
],
containerMap: [
'maven:3.5-jdk-8-alpine': 'mavenexecute',
'selenium/standalone-chrome': 'selenium'
],
containerName: 'mavenexecute',
containerPortMappings: [
'selenium/standalone-chrome': [[containerPort: 4444, hostPort: 4444]]
],
containerWorkspaces: [
'selenium/standalone-chrome': ''
],
dockerWorkspace: '/home/piper'
) {
bodyExecuted = true
}
assertThat(bodyExecuted, is(true))
assertThat(containerName, is('mavenexecute'))
assertThat(containersList, allOf(
hasItem('jnlp'),
hasItem('mavenexecute'),
hasItem('selenium'),
))
assertThat(imageList, allOf(
hasItem('s4sdk/jenkins-agent-k8s:latest'),
hasItem('maven:3.5-jdk-8-alpine'),
hasItem('selenium/standalone-chrome'),
))
assertThat(portList, hasItem(hasItem([name: 'selenium0', containerPort: 4444, hostPort: 4444])))
assertThat(portMapping, hasItem([name: 'selenium0', containerPort: 4444, hostPort: 4444]))
assertThat(containerCommands.size(), is(1))
assertThat(envList, hasItem(hasItem(allOf(hasEntry('key', 'customEnvKey'), hasEntry ('value','customEnvValue')))))
}
private container(options, body) {
containerName = options.name
body()

View File

@ -13,6 +13,8 @@ import util.JenkinsStepRule
import util.PluginMock
import util.Rules
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
import static org.junit.Assert.assertTrue
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse
@ -50,7 +52,7 @@ class DockerExecuteTest extends BasePiperTest {
})
binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true'])
ContainerMap.instance.setMap(['testpod': ['maven:3.5-jdk-8-alpine': 'mavenexec']])
jsr.step.call(script: nullScript,
jsr.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
@ -65,7 +67,7 @@ class DockerExecuteTest extends BasePiperTest {
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() })
binding.setVariable('env', [ON_K8S: 'true'])
ContainerMap.instance.setMap(['testpod': ['maven:3.5-jdk-8-alpine': 'mavenexec']])
jsr.step.call(script: nullScript,
jsr.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
@ -79,7 +81,7 @@ class DockerExecuteTest extends BasePiperTest {
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() })
binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true'])
ContainerMap.instance.setMap([:])
jsr.step.call(script: nullScript,
jsr.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
@ -93,7 +95,7 @@ class DockerExecuteTest extends BasePiperTest {
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() })
binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true'])
ContainerMap.instance.setMap(['testpod':[:]])
jsr.step.call(script: nullScript,
jsr.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
bodyExecuted = true
@ -104,7 +106,7 @@ class DockerExecuteTest extends BasePiperTest {
@Test
void testExecuteInsideDockerContainer() throws Exception {
jsr.step.call(script: nullScript, dockerImage: 'maven:3.5-jdk-8-alpine') {
jsr.step.dockerExecute(script: nullScript, dockerImage: 'maven:3.5-jdk-8-alpine') {
bodyExecuted = true
}
assertEquals('maven:3.5-jdk-8-alpine', docker.getImageName())
@ -115,7 +117,7 @@ class DockerExecuteTest extends BasePiperTest {
@Test
void testExecuteInsideDockerNoScript() throws Exception {
jsr.step.call(dockerImage: 'maven:3.5-jdk-8-alpine') {
jsr.step.dockerExecute(dockerImage: 'maven:3.5-jdk-8-alpine') {
bodyExecuted = true
}
assertEquals('maven:3.5-jdk-8-alpine', docker.getImageName())
@ -126,7 +128,7 @@ class DockerExecuteTest extends BasePiperTest {
@Test
void testExecuteInsideDockerContainerWithParameters() throws Exception {
jsr.step.call(script: nullScript,
jsr.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerOptions: '-it',
dockerVolumeBind: ['my_vol': '/my_vol'],
@ -142,7 +144,7 @@ class DockerExecuteTest extends BasePiperTest {
@Test
void testExecuteInsideDockerContainerWithDockerOptionsList() throws Exception {
jsr.step.call(script: nullScript,
jsr.step.dockerExecute(script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerOptions: ['-it', '--network=my-network'],
dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
@ -156,7 +158,7 @@ class DockerExecuteTest extends BasePiperTest {
@Test
void testDockerNotInstalledResultsInLocalExecution() throws Exception {
whichDockerReturnValue = 1
jsr.step.call(script: nullScript,
jsr.step.dockerExecute(script: nullScript,
dockerOptions: '-it') {
bodyExecuted = true
}
@ -166,10 +168,70 @@ class DockerExecuteTest extends BasePiperTest {
assertFalse(docker.isImagePulled())
}
@Test
void testSidecarDefault(){
jsr.step.dockerExecute(
script: nullScript,
dockerImage: 'maven:3.5-jdk-8-alpine',
sidecarEnvVars: ['testEnv':'testVal'],
sidecarImage: 'selenium/standalone-chrome',
sidecarVolumeBind: ['/dev/shm':'/dev/shm'],
sidecarName: 'testAlias',
sidecarPorts: ['4444':'4444', '1111':'1111']
) {
bodyExecuted = true
}
assertThat(bodyExecuted, is(true))
assertThat(docker.imagePullCount, is(2))
assertThat(docker.sidecarParameters, allOf(
containsString('--env testEnv=testVal'),
containsString('--volume /dev/shm:/dev/shm')
))
assertThat(docker.parameters, containsString('--link uniqueId:testAlias'))
}
@Test
void testSidecarKubernetes(){
boolean dockerExecuteOnKubernetesCalled = false
binding.setVariable('env', [ON_K8S: 'true'])
helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { params, body ->
dockerExecuteOnKubernetesCalled = true
assertThat(params.containerCommands['selenium/standalone-chrome'], is(''))
assertThat(params.containerEnvVars, allOf(hasEntry('selenium/standalone-chrome', ['testEnv': 'testVal']),hasEntry('maven:3.5-jdk-8-alpine', null)))
assertThat(params.containerMap, allOf(hasEntry('maven:3.5-jdk-8-alpine', 'maven'), hasEntry('selenium/standalone-chrome', 'selenium')))
assertThat(params.containerName, is('maven'))
assertThat(params.containerPortMappings['selenium/standalone-chrome'], hasItem(allOf(hasEntry('containerPort', 4444), hasEntry('hostPort', 4444))))
assertThat(params.containerWorkspaces['maven:3.5-jdk-8-alpine'], is('/home/piper'))
assertThat(params.containerWorkspaces['selenium/standalone-chrome'], is(''))
body()
})
jsr.step.dockerExecute(
script: nullScript,
containerPortMappings: [
'selenium/standalone-chrome': [[name: 'selPort', containerPort: 4444, hostPort: 4444]]
],
dockerImage: 'maven:3.5-jdk-8-alpine',
dockerName: 'maven',
dockerWorkspace: '/home/piper',
sidecarEnvVars: ['testEnv':'testVal'],
sidecarImage: 'selenium/standalone-chrome',
sidecarName: 'selenium',
sidecarVolumeBind: ['/dev/shm':'/dev/shm'],
dockerLinkAlias: 'testAlias',
) {
bodyExecuted = true
}
assertThat(bodyExecuted, is(true))
assertThat(dockerExecuteOnKubernetesCalled, is(true))
}
private class DockerMock {
private String imageName
private boolean imagePulled = false
private int imagePullCount = 0
private String parameters
private String sidecarParameters
DockerMock image(String imageName) {
this.imageName = imageName
@ -177,6 +239,7 @@ class DockerExecuteTest extends BasePiperTest {
}
void pull() {
imagePullCount++
imagePulled = true
}
@ -185,6 +248,11 @@ class DockerExecuteTest extends BasePiperTest {
body()
}
void withRun(String parameters, body) {
this.sidecarParameters = parameters
body([id: 'uniqueId'])
}
String getImageName() {
return imageName
}

View File

@ -0,0 +1,98 @@
import hudson.AbortException
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.*
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
class SeleniumExecuteTestsTest extends BasePiperTest {
private ExpectedException thrown = ExpectedException.none()
private JenkinsStepRule jsr = new JenkinsStepRule(this)
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private JenkinsShellCallRule jscr = new JenkinsShellCallRule(this)
private JenkinsDockerExecuteRule jedr = new JenkinsDockerExecuteRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(thrown)
.around(jedr)
.around(jsr) // needs to be activated after jedr, otherwise executeDocker is not mocked
boolean bodyExecuted = false
def gitMap
@Before
void init() throws Exception {
bodyExecuted = false
helper.registerAllowedMethod('git', [Map.class], {m ->
gitMap = m
})
}
@Test
void testExecuteSeleniumDefault() {
jsr.step.seleniumExecuteTests(
script: nullScript,
juStabUtils: utils
) {
bodyExecuted = true
}
assertThat(bodyExecuted, is(true))
assertThat(jedr.dockerParams.containerPortMappings, is(['selenium/standalone-chrome': [[containerPort: 4444, hostPort: 4444]]]))
assertThat(jedr.dockerParams.dockerImage, is('node:8-stretch'))
assertThat(jedr.dockerParams.dockerName, is('npm'))
assertThat(jedr.dockerParams.dockerWorkspace, is('/home/node'))
assertThat(jedr.dockerParams.sidecarEnvVars, is(null))
assertThat(jedr.dockerParams.sidecarImage, is('selenium/standalone-chrome'))
assertThat(jedr.dockerParams.sidecarName, is('selenium'))
assertThat(jedr.dockerParams.sidecarVolumeBind, is(['/dev/shm': '/dev/shm']))
}
@Test
void testExecuteSeleniumError() {
thrown.expectMessage('Error occured')
jsr.step.seleniumExecuteTests(
script: nullScript,
juStabUtils: utils
) {
throw new AbortException('Error occured')
}
}
@Test
void testExecuteSeleniumIgnoreError() {
jsr.step.seleniumExecuteTests(
script: nullScript,
failOnError: false,
juStabUtils: utils
) {
bodyExecuted = true
throw new AbortException('Error occured')
}
assertThat(bodyExecuted, is(true))
}
@Test
void testExecuteSeleniumCustomRepo() {
jsr.step.seleniumExecuteTests(
script: nullScript,
gitBranch: 'test',
gitSshKeyCredentialsId: 'testCredentials',
juStabUtils: utils,
testRepository: 'git@test/test.git'
) {
bodyExecuted = true
}
assertThat(bodyExecuted, is(true))
assertThat(gitMap, hasEntry('branch', 'test'))
assertThat(gitMap, hasEntry('credentialsId', 'testCredentials'))
assertThat(gitMap, hasEntry('url', 'git@test/test.git'))
}
}

View File

@ -9,11 +9,21 @@ import groovy.transform.Field
@Field Set GENERAL_CONFIG_KEYS = ['jenkinsKubernetes']
@Field Set PARAMETER_KEYS = ['dockerImage',
'dockerOptions',
'dockerWorkspace',
'dockerEnvVars',
'dockerVolumeBind']
@Field Set PARAMETER_KEYS = [
'containerPortMappings',
'dockerEnvVars',
'dockerImage',
'dockerName',
'dockerOptions',
'dockerWorkspace',
'dockerVolumeBind',
'sidecarName',
'sidecarEnvVars',
'sidecarImage',
'sidecarOptions',
'sidecarWorkspace',
'sidecarVolumeBind'
]
@Field Set STEP_CONFIG_KEYS = PARAMETER_KEYS
void call(Map parameters = [:], body) {
@ -31,19 +41,48 @@ void call(Map parameters = [:], body) {
if (isKubernetes() && config.dockerImage) {
if (env.POD_NAME && isContainerDefined(config)) {
container(getContainerDefined(config)) {
echo "Executing inside a Kubernetes Container"
echo "[INFO][${STEP_NAME}] Executing inside a Kubernetes Container."
body()
sh "chown -R 1000:1000 ."
}
} else {
dockerExecuteOnKubernetes(
script: script,
dockerImage: config.dockerImage,
dockerEnvVars: config.dockerEnvVars,
dockerWorkspace: config.dockerWorkspace
){
echo "Executing inside a Kubernetes Pod"
body()
if (!config.sidecarImage) {
dockerExecuteOnKubernetes(
script: script,
dockerImage: config.dockerImage,
dockerEnvVars: config.dockerEnvVars,
dockerWorkspace: config.dockerWorkspace
){
echo "[INFO][${STEP_NAME}] Executing inside a Kubernetes Pod"
body()
}
} else {
Map paramMap = [
script: script,
containerCommands: [:],
containerEnvVars: [:],
containerMap: [:],
containerName: config.dockerName,
containerPortMappings: [:],
containerWorkspaces: [:]
]
paramMap.containerCommands[config.sidecarImage] = ''
paramMap.containerEnvVars[config.dockerImage] = config.dockerEnvVars
paramMap.containerEnvVars[config.sidecarImage] = config.sidecarEnvVars
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 {
@ -67,8 +106,21 @@ void call(Map parameters = [:], body) {
if (executeInsideDocker && config.dockerImage) {
def image = docker.image(config.dockerImage)
image.pull()
image.inside(getDockerOptions(config.dockerEnvVars, config.dockerVolumeBind, config.dockerOptions)) {
body()
if (!config.sidecarImage) {
image.inside(getDockerOptions(config.dockerEnvVars, config.dockerVolumeBind, config.dockerOptions)) {
body()
}
} else {
def sidecarImage = docker.image(config.sidecarImage)
sidecarImage.pull()
sidecarImage.withRun(getDockerOptions(config.sidecarEnvVars, config.sidecarVolumeBind, config.sidecarOptions)) { c ->
config.dockerOptions = config.dockerOptions?:[]
config.dockerOptions.add("--link ${c.id}:${config.sidecarName}")
image.inside(getDockerOptions(config.dockerEnvVars, config.dockerVolumeBind, config.dockerOptions)) {
echo "[INFO][${STEP_NAME}] Running with sidecar container."
body()
}
}
}
} else {
echo "[INFO][${STEP_NAME}] Running on local environment."

View File

@ -7,10 +7,17 @@ import hudson.AbortException
@Field def STEP_NAME = 'dockerExecuteOnKubernetes'
@Field def PLUGIN_ID_KUBERNETES = 'kubernetes'
@Field Set GENERAL_CONFIG_KEYS = ['jenkinsKubernetes']
@Field Set PARAMETER_KEYS = ['dockerImage',
'dockerWorkspace',
'dockerEnvVars',
'containerMap']
@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'
]
@Field Set STEP_CONFIG_KEYS = PARAMETER_KEYS.plus(['stashIncludes', 'stashExcludes'])
void call(Map parameters = [:], body) {
@ -48,7 +55,14 @@ void executeOnPodWithCustomContainerList(Map parameters, body) {
def config = parameters.config
podTemplate(getOptions(config)) {
node(config.uniqueId) {
body()
//allow execution in dedicated container
if (config.containerName) {
container(name: config.containerName){
body()
}
} else {
body()
}
}
}
}
@ -70,9 +84,9 @@ void executeOnPodWithSingleContainer(Map parameters, body) {
- The container method
- The body
* We use nested exception handling in this case.
* In the first 2 cases, the workspace has not been modified. Hence, we can stash existing workspace as container and
* unstash in the finally block. In case of exception thrown by the body, we need to stash the workspace from the container
* in finally block
* In the first 2 cases, the 'container' stash is not created because the inner try/finally is not reached.
* 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.
*/
try {
stashWorkspace(config, 'workspace')
@ -84,13 +98,10 @@ void executeOnPodWithSingleContainer(Map parameters, body) {
body()
} finally {
stashWorkspace(config, 'container')
}
}
}
}
}
} catch (e) {
stashWorkspace(config, 'container')
throw e
} finally {
unstashWorkspace(config, 'container')
}
@ -119,16 +130,35 @@ private void unstashWorkspace(config, prefix) {
}
private List getContainerList(config) {
def envVars = getContainerEnvs(config)
result = []
result.push(containerTemplate(name: 'jnlp',
image: config.jenkinsKubernetes.jnlpAgent))
result.push(containerTemplate(
name: 'jnlp',
image: config.jenkinsKubernetes.jnlpAgent
))
config.containerMap.each { imageName, containerName ->
result.push(containerTemplate(name: containerName.toLowerCase(),
def templateParameters = [
name: containerName.toLowerCase(),
image: imageName,
alwaysPullImage: true,
command: '/usr/bin/tail -f /dev/null',
envVars: envVars))
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))
}
return result
}
@ -139,10 +169,10 @@ private List getContainerList(config) {
* (Kubernetes-Plugin only!)
* @param config Map with configurations
*/
private List getContainerEnvs(config) {
private List getContainerEnvs(config, imageName) {
def containerEnv = []
def dockerEnvVars = config.dockerEnvVars ?: [:]
def dockerWorkspace = config.dockerWorkspace ?: ''
def dockerEnvVars = config.containerEnvVars?.get(imageName) ?: config.dockerEnvVars ?: [:]
def dockerWorkspace = config.containerWorkspaces?.get(imageName) != null ? config.containerWorkspaces?.get(imageName) : config.dockerWorkspace ?: ''
if (dockerEnvVars) {
for (String k : dockerEnvVars.keySet()) {

View File

@ -0,0 +1,73 @@
import com.sap.piper.Utils
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import com.sap.piper.k8s.ContainerMap
import groovy.transform.Field
import groovy.text.SimpleTemplateEngine
@Field String STEP_NAME = 'seleniumExecuteTests'
@Field Set STEP_CONFIG_KEYS = [
'containerPortMappings', //port mappings required for containers. This will only take effect inside a Kubernetes pod, format [[containerPort: 1111, hostPort: 1111]]
'dockerImage', //Docker image for code execution
'dockerName', //name of the Docker container. This will only take effect inside a Kubernetes pod.
'dockerWorkspace', //user home directory for Docker execution. This will only take effect inside a Kubernetes pod.
'failOnError',
'gitBranch', //only if testRepository is used: branch of testRepository. Default is master
'gitSshKeyCredentialsId', //only if testRepository is used: ssh credentials id in case a protected testRepository is used
'sidecarEnvVars', //envVars to be set in Selenium container if required
'sidecarImage', //image for Selenium execution which runs as sidecar to dockerImage
'sidecarName', //name of the Selenium container. If not on Kubernetes pod, this will define the name of the link to the Selenium container and is thus required for accessing the server, example http://selenium:4444 (default)
'sidecarVolumeBind', //volume bind. This will not take effect in Kubernetes pod.
'stashContent', //list of stash names which are required to be unstashed before test run
'testRepository' //if tests are in a separate repository, git url can be defined. For protected repositories the git ssh url is required
]
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
def call(Map parameters = [:], Closure body) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
def script = parameters?.script ?: [commonPipelineEnvironment: commonPipelineEnvironment]
def utils = parameters?.juStabUtils ?: new Utils()
// load default & individual configuration
Map config = ConfigurationHelper
.loadStepDefaults(this)
.mixinGeneralConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.dependingOn('buildTool').mixin('dockerImage')
.dependingOn('buildTool').mixin('dockerName')
.dependingOn('buildTool').mixin('dockerWorkspace')
.use()
utils.pushToSWA([step: STEP_NAME], config)
dockerExecute(
script: script,
containerPortMappings: config.containerPortMappings,
dockerImage: config.dockerImage,
dockerName: config.dockerName,
dockerWorkspace: config.dockerWorkspace,
sidecarEnvVars: config.sidecarEnvVars,
sidecarImage: config.sidecarImage,
sidecarName: config.sidecarName,
sidecarVolumeBind: config.sidecarVolumeBind
) {
try {
if (config.testRepository) {
def gitParameters = [url: config.testRepository]
if (config.gitSshKeyCredentialsId) gitParameters.credentialsId = config.gitSshKeyCredentialsId
if (config.gitBranch) gitParameters.branch = config.gitBranch
git gitParameters
} else {
config.stashContent = utils.unstashAll(config.stashContent)
}
body()
} catch (err) {
if (config.failOnError) {
throw err
}
}
}
}
}