Merge remote-tracking branch 'github/master' into HEAD

This commit is contained in:
Marcus Holl
2019-02-05 09:25:50 +01:00
17 changed files with 1116 additions and 184 deletions
@@ -0,0 +1,82 @@
# containerExecuteStructureTests
## Description
In this step [Container Structure Tests](https://github.com/GoogleContainerTools/container-structure-test) are executed.
This testing framework allows you to execute different test types against a Docker container, for example:
* Command tests (only if a Docker Deamon is available)
* File existence tests
* File content tests
* Metadata test
## Prerequisites
Test configuration is available.
## Example
```
containerExecuteStructureTests(
script: this,
testConfiguration: 'config.yml',
testImage: 'node:latest'
)
```
## Parameters
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
|script|yes|||
|containerCommand|no|``||
|containerShell|no|``||
|dockerImage|yes|`ppiper/container-structure-test`||
|dockerOptions|no|`-u 0 --entrypoint=''`||
|failOnError|no|`true`|`true`, `false`|
|pullImage|no||`true`, `false`|
|stashContent|no|<ul><li>`tests`</li></ul>||
|testConfiguration|no|||
|testDriver|no|||
|testImage|no|||
|testReportFilePath|no|`cst-report.json`||
|verbose|no||`true`, `false`|
Details:
* `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.
* `containerCommand`: Only for Kubernetes environments: Command which is executed to keep container alive, defaults to '/usr/bin/tail -f /dev/null'
* containerShell: Only for Kubernetes environments: Shell to be used inside container, defaults to '/bin/sh'
* dockerImage: Docker image for code execution.
* dockerOptions: Options to be passed to Docker image when starting it (only relevant for non-Kubernetes case).
* failOnError: Defines the behavior, in case tests fail.
* pullImage: Only relevant for testDriver 'docker'.
* stashContent: If specific stashes should be considered for the tests, you can pass this via this parameter.
* testConfiguration: Container structure test configuration in yml or json format. You can pass a pattern in order to execute multiple tests.
* testDriver: Container structure test driver to be used for testing, please see [https://github.com/GoogleContainerTools/container-structure-test](https://github.com/GoogleContainerTools/container-structure-test) for details.
* testImage: Image to be tested
* testReportFilePath: Path and name of the test report which will be generated
* verbose: Print more detailed information into the log.
## 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||||
|containerCommand||X|X|
|containerShell||X|X|
|dockerImage||X|X|
|dockerOptions||X|X|
|failOnError||X|X|
|pullImage||X|X|
|stashContent||X|X|
|testConfiguration||X|X|
|testDriver||X|X|
|testImage||X|X|
|testReportFilePath||X|X|
|verbose|X|X|X|
+3 -59
View File
@@ -2,47 +2,11 @@
## Description ## Description
Executes a closure inside a docker container with the specified docker image. Content here is generated from corresponnding step, see `vars`.
The workspace is mounted into the docker image.
Proxy environment variables defined on the Jenkins machine are also available in the Docker container.
## Parameters ## Parameters
| parameter | mandatory | default | possible values |
| ----------|-----------|---------|-----------------|
|script|yes|||
|containerCommand|no|||
|containerPortMappings|no|||
|containerShell|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. Content here is generated from corresponnding step, see `vars`.
* `containerCommand`: only used in case exeuction environment is Kubernetes, allows to specify start command for container created with dockerImage parameter to overwrite Piper default (`/usr/bin/tail -f /dev/null`).
* `containerPortMappings`: Map which defines per docker image the port mappings, like `containerPortMappings: ['selenium/standalone-chrome': [[name: 'selPort', containerPort: 4444, hostPort: 4444]]]`
* `containerShell`: only used in case exeuction environment is Kubernetes, allows to specify the shell to be used for execution of commands
* `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`: Kubernetes case: Name of the container launching `dockerImage`, SideCar: Name of the container in local network
* `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 ## Kubernetes support
@@ -50,27 +14,7 @@ If the Jenkins is setup on a Kubernetes cluster, then you can execute the closur
## Step configuration ## Step configuration
We recommend to define values of step parameters via [config.yml file](../configuration.md). Content here is generated from corresponnding step, see `vars`.
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|
## Side effects ## Side effects
@@ -0,0 +1,19 @@
# uiVeri5ExecuteTests
## Description
Content here is generated from corresponnding step, see `vars`.
## Prerequisites
## Parameters
Content here is generated from corresponnding step, see `vars`.
## Step configuration
Content here is generated from corresponnding step, see `vars`.
## Exceptions
## Examples
+1
View File
@@ -9,6 +9,7 @@ nav:
- checksPublishResults: steps/checksPublishResults.md - checksPublishResults: steps/checksPublishResults.md
- cloudFoundryDeploy: steps/cloudFoundryDeploy.md - cloudFoundryDeploy: steps/cloudFoundryDeploy.md
- commonPipelineEnvironment: steps/commonPipelineEnvironment.md - commonPipelineEnvironment: steps/commonPipelineEnvironment.md
- containerExecuteStructureTests: steps/containerExecuteStructureTests.md
- dockerExecute: steps/dockerExecute.md - dockerExecute: steps/dockerExecute.md
- dockerExecuteOnKubernetes: steps/dockerExecuteOnKubernetes.md - dockerExecuteOnKubernetes: steps/dockerExecuteOnKubernetes.md
- durationMeasure: steps/durationMeasure.md - durationMeasure: steps/durationMeasure.md
+21 -2
View File
@@ -143,12 +143,21 @@ steps:
mtaDeployPlugin: mtaDeployPlugin:
dockerImage: 's4sdk/docker-cf-cli' dockerImage: 's4sdk/docker-cf-cli'
dockerWorkspace: '/home/piper' dockerWorkspace: '/home/piper'
containerExecuteStructureTests:
containerCommand: '/busybox/tail -f /dev/null'
containerShell: '/busybox/sh'
dockerImage: 'ppiper/container-structure-test'
dockerOptions: "-u 0 --entrypoint=''"
failOnError: true
stashContent:
- 'tests'
testReportFilePath: 'cst-report.json'
dockerExecute: dockerExecute:
stashContent: [] stashContent: []
dockerExecuteOnKubernetes: dockerExecuteOnKubernetes:
stashContent: [] stashContent: []
stashIncludes: stashIncludes:
workspace: '**/*.*' workspace: '**/*'
stashExcludes: stashExcludes:
workspace: 'nohup.out' workspace: 'nohup.out'
githubPublishRelease: githubPublishRelease:
@@ -261,7 +270,7 @@ steps:
opensourceConfiguration: '**/srcclr.yml, **/vulas-custom.properties, **/.nsprc, **/.retireignore, **/.retireignore.json, **/.snyk' opensourceConfiguration: '**/srcclr.yml, **/vulas-custom.properties, **/.nsprc, **/.retireignore, **/.retireignore.json, **/.snyk'
pipelineConfigAndTests: '.pipeline/**' pipelineConfigAndTests: '.pipeline/**'
securityDescriptor: '**/xs-security.json' securityDescriptor: '**/xs-security.json'
tests: '**/pom.xml, **/*.json, **/*.xml, **/src/**, **/node_modules/**, **/specs/**, **/env/**, **/*.js' tests: '**/pom.xml, **/*.json, **/*.xml, **/src/**, **/node_modules/**, **/specs/**, **/env/**, **/*.js, **/tests/**'
stashExcludes: stashExcludes:
buildDescriptor: '**/node_modules/**/package.json' buildDescriptor: '**/node_modules/**/package.json'
deployDescriptor: '' deployDescriptor: ''
@@ -345,6 +354,16 @@ steps:
developmentSystemId: null developmentSystemId: null
transportRequestUploadFile: transportRequestUploadFile:
transportRequestRelease: transportRequestRelease:
uiVeri5ExecuteTests:
failOnError: false
dockerEnvVars: {}
installCommand: 'npm install @ui5/uiveri5 --global --quiet'
seleniumPort: 4444
stashContent:
- 'buildDescriptor'
- 'tests'
testOptions: ''
runCommand: "uiveri5 --seleniumAddress='http://${config.seleniumHost}:${config.seleniumPort}/wd/hub'"
#defaults for stage wrapper #defaults for stage wrapper
piperStageWrapper: piperStageWrapper:
+16 -57
View File
@@ -1,7 +1,10 @@
package com.sap.piper package com.sap.piper
import com.cloudbees.groovy.cps.NonCPS import com.cloudbees.groovy.cps.NonCPS
import org.jenkinsci.plugins.workflow.steps.MissingContextVariableException import com.sap.piper.analytics.Telemetry
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
@NonCPS @NonCPS
def getMandatoryParameter(Map map, paramName, defaultValue = null) { def getMandatoryParameter(Map map, paramName, defaultValue = null) {
@@ -72,67 +75,23 @@ def unstashAll(stashContent) {
return unstashedContent return unstashedContent
} }
def generateSha1Inline(input) { @NonCPS
return "`echo -n '${input}' | sha1sum | sed 's/ -//'`" def generateSha1(input) {
return MessageDigest
.getInstance("SHA-1")
.digest(input.getBytes(StandardCharsets.UTF_8))
.encodeHex().toString()
} }
void pushToSWA(Map parameters, Map config) { void pushToSWA(Map parameters, Map config) {
try { try {
//allow opt-out via configuration parameters.actionName = parameters.get('actionName') ?: 'Piper Library OS'
if (!config?.collectTelemetryData) { parameters.eventType = parameters.get('eventType') ?: 'library-os'
echo "[${parameters.get('step')}] Telemetry Report to SWA disabled!" parameters.jobUrlSha1 = generateSha1(env.JOB_URL)
return parameters.buildUrlSha1 = generateSha1(env.BUILD_URL)
}
def swaCustom = [:] Telemetry.notify(this, config, parameters)
/* SWA custom parameters:
custom3 = step name (passed as parameter step)
custom4 = job url hashed (calculated)
custom5 = build url hashed (calculated)
custom10 = stage name
custom11 = step related parameter 1 (passed as parameter stepParam1)
custom12 = step related parameter 2 (passed as parameter stepParam2)
custom13 = step related parameter 3 (passed as parameter stepParam3)
custom14 = step related parameter 4 (passed as parameter stepParam4)
custom15 = step related parameter 5 (passed as parameter stepParam5)
*/
def swaUrl = 'https://webanalytics.cfapps.eu10.hana.ondemand.com/tracker/log'
def action_name = 'Piper Library OS'
def idsite = '827e8025-1e21-ae84-c3a3-3f62b70b0130'
def url = 'https://github.com/SAP/jenkins-library'
def event_type = parameters.get('eventType') ?: 'library-os'
swaCustom.custom3 = parameters.get('step')
swaCustom.custom4 = generateSha1Inline(env.JOB_URL)
swaCustom.custom5 = generateSha1Inline(env.BUILD_URL)
swaCustom.custom10 = parameters.get('stageName')
swaCustom.custom11 = parameters.get('stepParam1')
swaCustom.custom12 = parameters.get('stepParam2')
swaCustom.custom13 = parameters.get('stepParam3')
swaCustom.custom14 = parameters.get('stepParam4')
swaCustom.custom15 = parameters.get('stepParam5')
def options = []
options.push("-G")
options.push("-v \"${swaUrl}\"")
options.push("--data-urlencode \"action_name=${action_name}\"")
options.push("--data-urlencode \"idsite=${idsite}\"")
options.push("--data-urlencode \"url=${url}\"")
options.push("--data-urlencode \"event_type=${event_type}\"")
for(def key : ['custom3', 'custom4', 'custom5', 'custom10', 'custom11', 'custom12', 'custom13', 'custom14', 'custom15']){
if (swaCustom[key] != null) options.push("--data-urlencode \"${key}=${swaCustom[key]}\"")
}
options.push("--connect-timeout 5")
options.push("--max-time 20")
sh(returnStatus: true, script: "#!/bin/sh +x\ncurl ${options.join(' ')} > /dev/null 2>&1 || echo '[${parameters.get('step')}] Telemetry Report to SWA failed!'")
} catch (MissingContextVariableException noNode) {
echo "[${parameters.get('step')}] Telemetry Report to SWA skipped, no node available!"
} catch (ignore) { } catch (ignore) {
// some error occured in SWA reporting. This should not break anything though. // some error occured in telemetry reporting. This should not break anything though.
} }
} }
@@ -0,0 +1,97 @@
package com.sap.piper.analytics
import com.cloudbees.groovy.cps.NonCPS
import org.jenkinsci.plugins.workflow.steps.MissingContextVariableException
class Telemetry implements Serializable{
protected static Telemetry instance
protected List listenerList = []
protected Telemetry(){}
@NonCPS
protected static Telemetry getInstance(){
if(!instance) {
instance = new Telemetry()
registerListener({ steps, payload ->
piperOsDefaultReporting(steps, payload)
})
}
return instance
}
static void registerListener(Closure listener){
getInstance().listenerList.add(listener)
}
static notify(Script steps, Map config, Map payload){
//allow opt-out via configuration
if (!config?.collectTelemetryData) {
steps.echo "[${payload.step}] Telemetry reporting disabled!"
return
}
getInstance().listenerList.each { listener ->
try {
listener(steps, payload)
} catch (ignore) {
// some error occured in telemetry reporting. This should not break anything though.
steps.echo "[${payload.step}] Telemetry Report with listener failed: ${ignore.getMessage()}"
}
}
}
protected static void piperOsDefaultReporting(Script steps, Map payload) {
try {
def swaCustom = [:]
/* SWA custom parameters:
custom3 = step name (passed as parameter step)
custom4 = job url hashed (calculated)
custom5 = build url hashed (calculated)
custom10 = stage name
custom11 = step related parameter 1 (passed as parameter stepParam1)
custom12 = step related parameter 2 (passed as parameter stepParam2)
custom13 = step related parameter 3 (passed as parameter stepParam3)
custom14 = step related parameter 4 (passed as parameter stepParam4)
custom15 = step related parameter 5 (passed as parameter stepParam5)
*/
def swaUrl = 'https://webanalytics.cfapps.eu10.hana.ondemand.com/tracker/log'
def idsite = '827e8025-1e21-ae84-c3a3-3f62b70b0130'
def url = 'https://github.com/SAP/jenkins-library'
swaCustom.custom3 = payload.step
swaCustom.custom4 = payload.jobUrlSha1
swaCustom.custom5 = payload.buildUrlSha1
swaCustom.custom10 = payload.stageName
swaCustom.custom11 = payload.stepParam1
swaCustom.custom12 = payload.stepParam2
swaCustom.custom13 = payload.stepParam3
swaCustom.custom14 = payload.stepParam4
swaCustom.custom15 = payload.stepParam5
def options = []
options.push("-G")
options.push("-v \"${swaUrl}\"")
options.push("--data-urlencode \"action_name=${payload.actionName}\"")
options.push("--data-urlencode \"idsite=${idsite}\"")
options.push("--data-urlencode \"url=${url}\"")
options.push("--data-urlencode \"event_type=${payload.eventType}\"")
for(def key : ['custom3', 'custom4', 'custom5', 'custom10', 'custom11', 'custom12', 'custom13', 'custom14', 'custom15']){
if (swaCustom[key] != null) options.push("--data-urlencode \"${key}=${swaCustom[key]}\"")
}
options.push("--connect-timeout 5")
options.push("--max-time 20")
steps.sh(returnStatus: true, script: "#!/bin/sh +x\ncurl ${options.join(' ')} > /dev/null 2>&1 || echo '[${payload.step}] Telemetry Report to SWA failed!'")
} catch (MissingContextVariableException noNode) {
steps.echo "[${payload.step}] Telemetry Report to SWA skipped, no node available!"
}
}
}
@@ -0,0 +1,150 @@
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 ContainerExecuteStructureTestsTest 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(jscr)
.around(jlr)
.around(jsr) // needs to be activated after jedr, otherwise executeDocker is not mocked
@Before
void init() throws Exception {
helper.registerAllowedMethod('stash', [String.class], null)
helper.registerAllowedMethod("findFiles", [Map.class], { map ->
def files
if(map.glob == 'notFound.json')
files = []
else if(map.glob == 'cst/*.yml')
files = [
new File("cst/test1.yml"),
new File("cst/test2.yml")
]
else
files = [new File(map.glob)]
return files.toArray()
})
}
@Test
void testExecuteContainterStructureTestsDefault() throws Exception {
helper.registerAllowedMethod('readFile', [String.class], {s ->
return '{testResult: true}'
})
jsr.step.containerExecuteStructureTests(
script: nullScript,
juStabUtils: utils,
testConfiguration: 'cst/*.yml',
testImage: 'myRegistry/myImage:myTag'
)
// asserts
assertThat(jscr.shell, hasItem(allOf(
stringContainsInOrder(['#!/busybox/sh', 'container-structure-test', '--config']),
containsString("--config cst${File.separator}test1.yml"),
containsString("--config cst${File.separator}test2.yml"),
containsString('--driver docker'),
containsString('--image myRegistry/myImage:myTag'),
containsString('--test-report cst-report.json'),
)))
//currently no default Docker image
assertThat(jedr.dockerParams.dockerImage, is('ppiper/container-structure-test'))
assertThat(jedr.dockerParams.dockerOptions, is("-u 0 --entrypoint=''"))
assertThat(jedr.dockerParams.containerCommand, is('/busybox/tail -f /dev/null'))
assertThat(jedr.dockerParams.containerShell, is('/busybox/sh'))
assertThat(jlr.log, containsString('{testResult: true}'))
assertThat(jscr.shell, hasItem('docker pull myRegistry/myImage:myTag'))
}
@Test
void testExecuteContainterStructureTestsK8S() throws Exception {
def envDefault = nullScript.env
nullScript.env = [ON_K8S: 'true']
jsr.step.containerExecuteStructureTests(
script: nullScript,
juStabUtils: utils,
containerCommand: '/busybox/tail -f /dev/null',
containerShell: '/bin/sh',
dockerImage: 'myRegistry:55555/pathTo/myImage:myTag',
testConfiguration: 'cst/*.yml',
testImage: 'myRegistry/myImage:myTag'
)
nullScript.env = envDefault
// asserts
assertThat(jscr.shell, hasItem(allOf(
stringContainsInOrder(['#!/bin/sh', 'container-structure-test', '--config']),
containsString("--config cst${File.separator}test1.yml"),
containsString("--config cst${File.separator}test2.yml"),
containsString('--driver tar'),
containsString('--image myRegistry/myImage:myTag'),
containsString('--test-report cst-report.json'),
)))
assertThat(jedr.dockerParams.dockerImage, is('myRegistry:55555/pathTo/myImage:myTag'))
assertThat(jedr.dockerParams.containerCommand, is('/busybox/tail -f /dev/null'))
assertThat(jscr.shell, not(hasItem('docker pull myRegistry/myImage:myTag')))
}
@Test
void testExecuteContainterStructureTestsError() throws Exception {
helper.registerAllowedMethod('readFile', [String.class], {s ->
return '{testResult: true}'
})
helper.registerAllowedMethod('sh', [String.class], {s ->
if (s.startsWith('#!/busybox/sh\ncontainer-structure-test test')) {
throw new GroovyRuntimeException('shell call failed')
}
return null
})
thrown.expectMessage('shell call failed')
jsr.step.containerExecuteStructureTests(
script: nullScript,
juStabUtils: utils,
containerCommand: '/busybox/tail -f /dev/null',
containerShell: '/busybox/sh',
testConfiguration: 'cst/*.yml',
testImage: 'myRegistry/myImage:myTag'
)
}
@Test
void testExecuteContainterStructureTestsErrorNoFailure() throws Exception {
helper.registerAllowedMethod('readFile', [String.class], {s ->
return '{testResult: true}'
})
helper.registerAllowedMethod('sh', [String.class], {s ->
if (s.startsWith('#!/busybox/sh\ncontainer-structure-test test')) {
throw new GroovyRuntimeException('shell call failed')
}
return null
})
jsr.step.containerExecuteStructureTests(
script: nullScript,
juStabUtils: utils,
containerCommand: '/busybox/tail -f /dev/null',
containerShell: '/busybox/sh',
failOnError: false,
testConfiguration: 'cst/*.yml',
testImage: 'myRegistry/myImage:myTag'
)
assertThat(jlr.log, containsString('Test execution failed'))
}
}
+178
View File
@@ -0,0 +1,178 @@
#!groovy
package steps
import static org.hamcrest.Matchers.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.ExpectedException
import static org.junit.Assert.assertThat
import util.BasePiperTest
import util.JenkinsDockerExecuteRule
import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
import util.JenkinsStepRule
import util.Rules
class UiVeri5ExecuteTestsTest extends BasePiperTest {
private ExpectedException thrown = ExpectedException.none()
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
private JenkinsDockerExecuteRule dockerRule = new JenkinsDockerExecuteRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(thrown)
.around(new JenkinsReadYamlRule(this))
.around(dockerRule)
.around(loggingRule)
.around(stepRule)
def gitParams = [:]
def shellCommands = []
def seleniumMap = [:]
class MockBuild {
MockBuild(){}
def getRootDir(){
return new MockPath()
}
class MockPath {
MockPath(){}
// return default
String getAbsolutePath(){
return 'myPath'
}
}
}
@Before
void init() {
binding.setVariable('currentBuild', [
result: 'SUCCESS',
rawBuild: new MockBuild()
])
helper.registerAllowedMethod("git", [Map.class], { map -> gitParams = map})
helper.registerAllowedMethod("stash", [String.class], null)
helper.registerAllowedMethod("unstash", [String.class], { s -> return [s]})
helper.registerAllowedMethod("sh", [String.class], { s ->
if (s.contains('failure')) throw new RuntimeException('Test Error')
shellCommands.add(s.toString())
})
helper.registerAllowedMethod("sh", [Map.class], { map ->
return 'available'
})
helper.registerAllowedMethod('seleniumExecuteTests', [Map.class, Closure.class], { m, body ->
seleniumMap = m
return body()
})
}
@Test
void testDefault() throws Exception {
// execute test
stepRule.step.uiVeri5ExecuteTests([
script: nullScript,
juStabUtils: utils,
])
// asserts
assertThat(shellCommands, hasItem(containsString('npm install @ui5/uiveri5 --global --quiet')))
assertThat(shellCommands, hasItem(containsString('uiveri5 --seleniumAddress=\'http://selenium:4444/wd/hub\'')))
assertThat(seleniumMap.dockerImage, isEmptyOrNullString())
assertThat(seleniumMap.dockerWorkspace, isEmptyOrNullString())
}
@Test
void testDefaultOnK8s() throws Exception {
// prepare
binding.variables.env.ON_K8S = 'true'
// execute test
stepRule.step.uiVeri5ExecuteTests([
script: nullScript,
juStabUtils: utils,
])
// asserts
assertThat(shellCommands, hasItem(containsString('npm install @ui5/uiveri5 --global --quiet')))
assertThat(shellCommands, hasItem(containsString('uiveri5 --seleniumAddress=\'http://localhost:4444/wd/hub\'')))
}
@Test
void testWithCustomSidecar() throws Exception {
// execute test
stepRule.step.uiVeri5ExecuteTests([
script: nullScript,
juStabUtils: utils,
sidecarEnvVars: [myEnv: 'testValue'],
sidecarImage: 'myImage'
])
// asserts
assertThat(seleniumMap.sidecarImage, is('myImage'))
assertThat(seleniumMap.sidecarEnvVars.myEnv, is('testValue'))
}
@Test
void testWithTestRepository() throws Exception {
// execute test
stepRule.step.uiVeri5ExecuteTests([
script: nullScript,
juStabUtils: utils,
testRepository: 'git@myGitUrl'
])
// asserts
assertThat(seleniumMap, hasKey('stashContent'))
assertThat(seleniumMap.stashContent, hasItem(startsWith('testContent-')))
assertThat(gitParams, hasEntry('url', 'git@myGitUrl'))
assertThat(gitParams, not(hasKey('credentialsId')))
assertThat(gitParams, not(hasKey('branch')))
assertJobStatusSuccess()
}
@Test
void testWithTestRepositoryWithGitBranchAndCredentials() throws Exception {
// execute test
stepRule.step.uiVeri5ExecuteTests([
script: nullScript,
juStabUtils: utils,
testRepository: 'git@myGitUrl',
gitSshKeyCredentialsId: 'myCredentials',
gitBranch: 'myBranch'
])
// asserts
assertThat(gitParams, hasEntry('url', 'git@myGitUrl'))
assertThat(gitParams, hasEntry('credentialsId', 'myCredentials'))
assertThat(gitParams, hasEntry('branch', 'myBranch'))
assertJobStatusSuccess()
}
@Test
void testWithFailOnError() throws Exception {
thrown.expect(RuntimeException)
thrown.expectMessage('Test Error')
// execute test
stepRule.step.uiVeri5ExecuteTests([
juStabUtils: utils,
failOnError: true,
testOptions: 'failure',
script: nullScript
])
}
@Test
void testWithoutFailOnError() throws Exception {
// execute test
stepRule.step.uiVeri5ExecuteTests([
juStabUtils: utils,
testOptions: 'failure',
script: nullScript
])
// asserts
assertJobStatusSuccess()
}
}
+5 -32
View File
@@ -2,6 +2,7 @@ package com.sap.piper
import org.junit.Rule import org.junit.Rule
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import static org.junit.Assert.assertThat import static org.junit.Assert.assertThat
import org.junit.rules.ExpectedException import org.junit.rules.ExpectedException
@@ -63,38 +64,10 @@ class UtilsTest extends BasePiperTest {
} }
@Test @Test
void testSWAReporting() { void testGenerateSHA1() {
utils.env = [BUILD_URL: 'something', JOB_URL: 'nothing'] def result = utils.generateSha1('ContinuousDelivery')
utils.pushToSWA([step: 'anything'], [collectTelemetryData: true])
// asserts // asserts
assertThat(shellRule.shell, hasItem(containsString('curl -G -v "https://webanalytics.cfapps.eu10.hana.ondemand.com/tracker/log"'))) // generated with "echo -n 'ContinuousDelivery' | sha1sum | sed 's/ -//'"
assertThat(shellRule.shell, hasItem(containsString('action_name=Piper Library OS'))) assertThat(result, is('0dad6c33b6246702132454f604dee80740f399ad'))
assertThat(shellRule.shell, hasItem(containsString('custom3=anything')))
assertThat(shellRule.shell, hasItem(containsString('custom5=`echo -n \'something\' | sha1sum | sed \'s/ -//\'`')))
}
@Test
void testDisabledSWAReporting() {
utils.env = [BUILD_URL: 'something', JOB_URL: 'nothing']
utils.pushToSWA([step: 'anything'], [collectTelemetryData: false])
// asserts
assertThat(loggingRule.log, containsString('[anything] Telemetry Report to SWA disabled!'))
assertThat(shellRule.shell, not(hasItem(containsString('https://webanalytics.cfapps.eu10.hana.ondemand.com'))))
}
@Test
void testImplicitlyDisabledSWAReporting() {
utils.env = [BUILD_URL: 'something', JOB_URL: 'nothing']
utils.pushToSWA([step: 'anything'], null)
// asserts
assertThat(loggingRule.log, containsString('[anything] Telemetry Report to SWA disabled!'))
}
@Test
void testImplicitlyDisabledSWAReporting2() {
utils.env = [BUILD_URL: 'something', JOB_URL: 'nothing']
utils.pushToSWA([step: 'anything'], [:])
// asserts
assertThat(loggingRule.log, containsString('[anything] Telemetry Report to SWA disabled!'))
} }
} }
@@ -0,0 +1,145 @@
package com.sap.piper.analytics
import org.junit.Rule
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertThat
import static org.junit.Assume.assumeThat
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.hasItem
import static org.hamcrest.Matchers.is
import static org.hamcrest.Matchers.not
import static org.hamcrest.Matchers.empty
import util.JenkinsLoggingRule
import util.JenkinsShellCallRule
import util.BasePiperTest
import util.Rules
class TelemetryTest extends BasePiperTest {
private ExpectedException thrown = ExpectedException.none()
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private JenkinsShellCallRule jscr = new JenkinsShellCallRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(thrown)
.around(jscr)
.around(jlr)
private parameters
@Before
void setup() {
Telemetry.instance = null
parameters = [:]
}
@Test
void testCreateInstance() {
Telemetry.instance = new Telemetry()
// asserts
assertThat(Telemetry.getInstance().listenerList, is(empty()))
}
@Test
void testGetInstance() {
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
}
@Test
void testRegisterListenerAndNotify() {
// prepare
Map notificationPayload = [:]
Telemetry.instance = new Telemetry()
assumeThat(Telemetry.getInstance().listenerList, is(empty()))
Telemetry.registerListener({ steps, payload ->
notificationPayload = payload
})
// test
Telemetry.notify(nullScript, [collectTelemetryData: true], [step: 'anyStep', anything: 'something'])
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
assertThat(notificationPayload, is([step: 'anyStep', anything: 'something']))
}
@Test
void testNotifyWithOptOut() {
// prepare
Map notificationPayload = [:]
Telemetry.instance = new Telemetry()
assumeThat(Telemetry.getInstance().listenerList, is(empty()))
Telemetry.registerListener({ steps, payload ->
notificationPayload = payload
})
// test
Telemetry.notify(nullScript, [collectTelemetryData: false], [step: 'anyStep', anything: 'something'])
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
assertThat(jlr.log, containsString("[anyStep] Telemetry reporting disabled!"))
assertThat(notificationPayload.keySet(), is(empty()))
}
@Test
void testNotifyWithOptOutWithEmptyConfig() {
// prepare
Map notificationPayload = [:]
Telemetry.instance = new Telemetry()
assumeThat(Telemetry.getInstance().listenerList, is(empty()))
Telemetry.registerListener({ steps, payload ->
notificationPayload = payload
})
// test
Telemetry.notify(nullScript, [:], [step: 'anyStep', anything: 'something'])
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
assertThat(jlr.log, containsString("[anyStep] Telemetry reporting disabled!"))
assertThat(notificationPayload.keySet(), is(empty()))
}
@Test
void testNotifyWithOptOutWithoutConfig() {
// prepare
Map notificationPayload = [:]
Telemetry.instance = new Telemetry()
assumeThat(Telemetry.getInstance().listenerList, is(empty()))
Telemetry.registerListener({ steps, payload ->
notificationPayload = payload
})
// test
Telemetry.notify(nullScript, null, [step: 'anyStep', anything: 'something'])
// asserts
assertThat(Telemetry.getInstance().listenerList, is(not(empty())))
assertThat(jlr.log, containsString("[anyStep] Telemetry reporting disabled!"))
assertThat(notificationPayload.keySet(), is(empty()))
}
@Test
void testReportingToSWA() {
// prepare
assumeThat(Telemetry.getInstance().listenerList, is(not(empty())))
// test
Telemetry.notify(nullScript, [collectTelemetryData: true], [
actionName: 'Piper Library OS',
eventType: 'library-os',
jobUrlSha1: '1234',
buildUrlSha1: 'abcd',
step: 'anyStep',
stepParam1: 'something'
])
// asserts
assertThat(jscr.shell, hasItem(containsString('curl -G -v "https://webanalytics.cfapps.eu10.hana.ondemand.com/tracker/log"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "action_name=Piper Library OS"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "event_type=library-os"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "custom3=anyStep"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "custom4=1234"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "custom5=abcd"')))
assertThat(jscr.shell, hasItem(containsString('--data-urlencode "custom11=something"')))
}
}
@@ -16,6 +16,7 @@ class BasePiperTestContext {
Script nullScript() { Script nullScript() {
def nullScript = InvokerHelper.createScript(null, new Binding()) def nullScript = InvokerHelper.createScript(null, new Binding())
nullScript.currentBuild = [:] nullScript.currentBuild = [:]
nullScript.env = [:]
LibraryLoadingTestExecutionListener.prepareObjectInterceptors(nullScript) LibraryLoadingTestExecutionListener.prepareObjectInterceptors(nullScript)
return nullScript return nullScript
} }
+132
View File
@@ -0,0 +1,132 @@
import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
/**
* Print more detailed information into the log.
* @possibleValues `true`, `false`
*/
'verbose'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* Only for Kubernetes environments: Command which is executed to keep container alive, defaults to '/usr/bin/tail -f /dev/null'
*/
'containerCommand',
/**
* Only for Kubernetes environments: Shell to be used inside container, defaults to '/bin/sh'
*/
'containerShell',
/**
* Docker image for code execution.
*/
'dockerImage',
/**
* Options to be passed to Docker image when starting it (only relevant for non-Kubernetes case).
*/
'dockerOptions',
/**
* Defines the behavior, in case tests fail.
* @possibleValues `true`, `false`
*/
'failOnError',
/**
* Only relevant for testDriver 'docker'.
* @possibleValues `true`, `false`
*/
'pullImage',
/**
* If specific stashes should be considered for the tests, you can pass this via this parameter.
*/
'stashContent',
/**
* Container structure test configuration in yml or json format. You can pass a pattern in order to execute multiple tests.
*/
'testConfiguration',
/**
* Container structure test driver to be used for testing, please see https://github.com/GoogleContainerTools/container-structure-test for details.
*/
'testDriver',
/**
* Image to be tested
*/
'testImage',
/**
* Path and name of the test report which will be generated
*/
'testReportFilePath',
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
void call(Map parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
def script = checkScript(this, parameters) ?: this
def utils = parameters?.juStabUtils ?: new Utils()
// load default & individual configuration
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)
.addIfEmpty('testDriver', Boolean.valueOf(script.env.ON_K8S) ? 'tar' : 'docker')
.addIfNull('pullImage', !Boolean.valueOf(script.env.ON_K8S))
.withMandatoryProperty('dockerImage')
.use()
utils.pushToSWA([step: STEP_NAME], config)
config.stashContent = utils.unstashAll(config.stashContent)
List testConfig = findFiles(glob: config.testConfiguration)?.toList()
if (testConfig.isEmpty()) {
error "[${STEP_NAME}] No test description found with pattern '${config.testConfiguration}'"
} else {
echo "[${STEP_NAME}] Found files ${testConfig}"
}
def testConfigArgs = ''
testConfig.each {conf ->
testConfigArgs += "--config ${conf} "
}
//workaround for non-working '--pull' option in version 1.7.0 of container-structure-tests, see https://github.com/GoogleContainerTools/container-structure-test/issues/193
if (config.pullImage) {
if (config.verbose) echo "[${STEP_NAME}] Pulling image since configuration option pullImage is set to '${config.pullImage}'"
sh "docker pull ${config.testImage}"
}
try {
dockerExecute(
script: script,
containerCommand: config.containerCommand,
containerShell: config.containerShell,
dockerImage: config.dockerImage,
dockerOptions: config.dockerOptions,
stashContent: config.stashContent
) {
sh """#!${config.containerShell?:'/bin/sh'}
container-structure-test test ${testConfigArgs} --driver ${config.testDriver} --image ${config.testImage} --test-report ${config.testReportFilePath}${config.verbose ? ' --verbosity debug' : ''}"""
}
} catch (err) {
echo "[${STEP_NAME}] Test execution failed"
script.currentBuild.result = 'UNSTABLE'
if (config.failOnError) throw err
} finally {
echo "${readFile(config.testReportFilePath)}"
archiveArtifacts artifacts: config.testReportFilePath, allowEmptyArchive: true
}
}
}
+87 -22
View File
@@ -3,6 +3,7 @@ import static com.sap.piper.Prerequisites.checkScript
import com.cloudbees.groovy.cps.NonCPS import com.cloudbees.groovy.cps.NonCPS
import com.sap.piper.ConfigurationHelper import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateDocumentation
import com.sap.piper.JenkinsUtils import com.sap.piper.JenkinsUtils
import com.sap.piper.Utils import com.sap.piper.Utils
import com.sap.piper.k8s.ContainerMap import com.sap.piper.k8s.ContainerMap
@@ -12,28 +13,92 @@ import groovy.transform.Field
@Field def STEP_NAME = getClass().getName() @Field def STEP_NAME = getClass().getName()
@Field def PLUGIN_ID_DOCKER_WORKFLOW = 'docker-workflow' @Field def PLUGIN_ID_DOCKER_WORKFLOW = 'docker-workflow'
@Field Set GENERAL_CONFIG_KEYS = ['jenkinsKubernetes'] @Field Set GENERAL_CONFIG_KEYS = [
/**
@Field Set PARAMETER_KEYS = [ *
'containerPortMappings', */
'containerCommand', 'jenkinsKubernetes'
'containerShell',
'dockerEnvVars',
'dockerImage',
'dockerName',
'dockerOptions',
'dockerWorkspace',
'dockerVolumeBind',
'sidecarEnvVars',
'sidecarImage',
'sidecarName',
'sidecarOptions',
'sidecarWorkspace',
'sidecarVolumeBind',
'stashContent'
] ]
@Field Set STEP_CONFIG_KEYS = PARAMETER_KEYS @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',
/**
* 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',
/**
* 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) { void call(Map parameters = [:], body) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) { handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
@@ -153,7 +218,7 @@ void call(Map parameters = [:], body) {
/** /*
* Returns a string with docker options containing * Returns a string with docker options containing
* environment variables (if set). * environment variables (if set).
* Possible to extend with further options. * Possible to extend with further options.
@@ -224,7 +289,7 @@ boolean isKubernetes() {
return Boolean.valueOf(env.ON_K8S) return Boolean.valueOf(env.ON_K8S)
} }
/** /*
* Escapes blanks for values in key/value pairs * Escapes blanks for values in key/value pairs
* E.g. <code>description=Lorem ipsum</code> is * E.g. <code>description=Lorem ipsum</code> is
* changed to <code>description=Lorem\ ipsum</code>. * changed to <code>description=Lorem\ ipsum</code>.
+21 -10
View File
@@ -9,8 +9,10 @@ import hudson.AbortException
@Field def STEP_NAME = getClass().getName() @Field def STEP_NAME = getClass().getName()
@Field def PLUGIN_ID_KUBERNETES = 'kubernetes' @Field def PLUGIN_ID_KUBERNETES = 'kubernetes'
@Field Set GENERAL_CONFIG_KEYS = ['jenkinsKubernetes'] @Field Set GENERAL_CONFIG_KEYS = [
@Field Set PARAMETER_KEYS = [ 'jenkinsKubernetes'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
'containerCommand', // specify start command for container created with dockerImage parameter to overwrite Piper default (`/usr/bin/tail -f /dev/null`). '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': '']` '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': '']`
'containerEnvVars', //specify environment variables per container. If not provided dockerEnvVars will be used 'containerEnvVars', //specify environment variables per container. If not provided dockerEnvVars will be used
@@ -22,9 +24,14 @@ import hudson.AbortException
'dockerImage', 'dockerImage',
'dockerWorkspace', 'dockerWorkspace',
'dockerEnvVars', 'dockerEnvVars',
'stashContent' 'stashContent',
] 'stashExcludes',
@Field Set STEP_CONFIG_KEYS = PARAMETER_KEYS.plus(['stashIncludes', 'stashExcludes']) 'stashIncludes'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.minus([
'stashIncludes',
'stashExcludes'
])
void call(Map parameters = [:], body) { void call(Map parameters = [:], body) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) { handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
@@ -84,12 +91,13 @@ void executeOnPod(Map config, utils, Closure body) {
if (config.containerShell) { if (config.containerShell) {
containerParams.shell = config.containerShell containerParams.shell = config.containerShell
} }
echo "ContainerConfig: ${containerParams}"
container(containerParams){ container(containerParams){
try { try {
utils.unstashAll(config.stashContent) utils.unstashAll(config.stashContent)
body() body()
} finally { } finally {
stashWorkspace(config, 'container') stashWorkspace(config, 'container', true)
} }
} }
} else { } else {
@@ -103,15 +111,18 @@ void executeOnPod(Map config, utils, Closure body) {
} }
} }
private String stashWorkspace(config, prefix) { private String stashWorkspace(config, prefix, boolean chown = false) {
def stashName = "${prefix}-${config.uniqueId}" def stashName = "${prefix}-${config.uniqueId}"
try { try {
// Every dockerImage used in the dockerExecuteOnKubernetes should have user id 1000 // Every dockerImage used in the dockerExecuteOnKubernetes should have user id 1000
sh "chown -R 1000:1000 ." if (chown) {
sh """#!${config.containerShell?:'/bin/sh'}
chown -R 1000:1000 ."""
}
stash( stash(
name: stashName, name: stashName,
include: config.stashIncludes.workspace, includes: config.stashIncludes.workspace,
exclude: config.stashExcludes.excludes excludes: config.stashExcludes.workspace
) )
return stashName return stashName
} catch (AbortException | IOException e) { } catch (AbortException | IOException e) {
+6 -2
View File
@@ -54,7 +54,6 @@ private void stageLocking(Map config, Closure body) {
} }
private void executeStage(script, originalStage, stageName, config, utils) { private void executeStage(script, originalStage, stageName, config, utils) {
boolean projectExtensions boolean projectExtensions
boolean globalExtensions boolean globalExtensions
def startTime = System.currentTimeMillis() def startTime = System.currentTimeMillis()
@@ -108,14 +107,19 @@ private void executeStage(script, originalStage, stageName, config, utils) {
stageName: stageName, stageName: stageName,
stepParamKey1: 'buildResult', stepParamKey1: 'buildResult',
stepParam1: "${script.currentBuild.currentResult}", stepParam1: "${script.currentBuild.currentResult}",
buildResult: "${script.currentBuild.currentResult}",
stepParamKey2: 'stageStartTime', stepParamKey2: 'stageStartTime',
stepParam2: "${startTime}", stepParam2: "${startTime}",
stageStartTime: "${startTime}",
stepParamKey3: 'stageDuration', stepParamKey3: 'stageDuration',
stepParam3: "${duration}", stepParam3: "${duration}",
stageDuration: "${duration}",
stepParamKey4: 'projectExtension', stepParamKey4: 'projectExtension',
stepParam4: "${projectExtensions}", stepParam4: "${projectExtensions}",
projectExtension: "${projectExtensions}",
stepParamKey5: 'globalExtension', stepParamKey5: 'globalExtension',
stepParam5: "${globalExtensions}" stepParam5: "${globalExtensions}",
globalExtension: "${globalExtensions}"
], config) ], config)
} }
} }
+152
View File
@@ -0,0 +1,152 @@
import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateDocumentation
import com.sap.piper.GitUtils
import com.sap.piper.Utils
import groovy.text.SimpleTemplateEngine
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
/**
* In case a `testRepository` is provided and it is protected, access credentials (as Jenkins credentials) can be provided with `gitSshKeyCredentialsId`. **Note: In case of using a protected repository, `testRepository` should include the ssh link to the repository.**
* @possibleValues Jenkins credentialId
*/
'gitSshKeyCredentialsId'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* A map of environment variables to set in the container, e.g. [http_proxy:'proxy:8080'].
*/
'dockerEnvVars',
/**
* The 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',
/**
* Only relevant for Kubernetes case: Specifies a dedicated user home directory for the container which will be passed as value for environment variable `HOME`.
*/
'dockerWorkspace',
/**
* With `failOnError` the behavior in case tests fail can be defined.
* @possibleValues `true`, `false`
*/
'failOnError',
/**
* In case a `testRepository` is provided the branch in this repository can be specified with `gitBranch`.
*/
'gitBranch',
/**
* The command that is executed to install the test tool.
*/
'installCommand',
/**
* The command that is executed to start the tests.
*/
'runCommand',
/**
* The host of the selenium hub, this is set automatically to `localhost` in a Kubernetes environment (determined by the `ON_K8S` environment variable) of to `selenium` in any other case. The value is only needed for the `runCommand`.
*/
'seleniumHost',
/**
* The port of the selenium hub. The value is only needed for the `runCommand`.
*/
'seleniumPort',
/**
* A map of environment variables to set in the sidecar container, similar to `dockerEnvVars`.
*/
'sidecarEnvVars',
/**
* The name of the docker image of the sidecar container. If empty, no sidecar container is started.
*/
'sidecarImage',
/**
* If specific stashes should be considered for the tests, their names need to be passed via the parameter `stashContent`.
*/
'stashContent',
/**
* This allows to set specific options for the UIVeri5 execution. Details can be found [in the UIVeri5 documentation](https://github.com/SAP/ui5-uiveri5/blob/master/docs/config/config.md#configuration).
*/
'testOptions',
/**
* With `testRepository` the tests can be loaded from another reposirory.
*/
'testRepository'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* With this step [UIVeri5](https://github.com/SAP/ui5-uiveri5) tests can be executed.
*
* UIVeri5 describes following benefits on its GitHub page:
*
* * Automatic synchronization with UI5 app rendering so there is no need to add waits and sleeps to your test. Tests are reliable by design.
* * Tests are written in synchronous manner, no callbacks, no promise chaining so are really simple to write and maintain.
* * Full power of webdriverjs, protractor and jasmine - deferred selectors, custom matchers, custom locators.
* * Control locators (OPA5 declarative matchers) allow locating and interacting with UI5 controls.
* * Does not depend on testability support in applications - works with autorefreshing views, resizing elements, animated transitions.
* * Declarative authentications - authentication flow over OAuth2 providers, etc.
* * Console operation, CI ready, fully configurable, no need for java (comming soon) or IDE.
* * Covers full ui5 browser matrix - Chrome,Firefox,IE,Edge,Safari,iOS,Android.
* * Open-source, modify to suite your specific neeeds.
*
* !!! note "Browser Matrix"
* With this step and the underlying Docker image ([selenium/standalone-chrome](https://github.com/SeleniumHQ/docker-selenium/tree/master/StandaloneChrome)) only Chrome tests are possible.
*
* Testing of further browsers can be done with using a custom Docker image.
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
def script = checkScript(this, parameters) ?: this
def utils = parameters.juStabUtils ?: new Utils()
// load default & individual configuration
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)
.addIfEmpty('seleniumHost', isKubernetes()?'localhost':'selenium')
.use()
new Utils().pushToSWA([
step: STEP_NAME,
stepParamKey1: 'scriptMissing',
stepParam1: parameters?.script == null
], config)
config.stashContent = config.testRepository ? [GitUtils.handleTestRepository(this, config)] : utils.unstashAll(config.stashContent)
config.installCommand = SimpleTemplateEngine.newInstance().createTemplate(config.installCommand).make([config: config]).toString()
config.runCommand = SimpleTemplateEngine.newInstance().createTemplate(config.runCommand).make([config: config]).toString()
seleniumExecuteTests(
script: script,
buildTool: 'npm',
dockerEnvVars: config.dockerEnvVars,
dockerImage: config.dockerImage,
dockerName: config.dockerName,
dockerWorkspace: config.dockerWorkspace,
sidecarEnvVars: config.sidecarEnvVars,
sidecarImage: config.sidecarImage,
stashContent: config.stashContent
) {
try {
sh "NPM_CONFIG_PREFIX=~/.npm-global ${config.installCommand}"
sh "PATH=\$PATH:~/.npm-global/bin ${config.runCommand} ${config.testOptions}"
} catch (err) {
echo "[${STEP_NAME}] Test execution failed"
script.currentBuild.result = 'UNSTABLE'
if (config.failOnError) throw err
}
}
}
}
boolean isKubernetes() {
return Boolean.valueOf(env.ON_K8S)
}