mirror of
https://github.com/SAP/jenkins-library.git
synced 2026-06-19 22:58:55 +02:00
Merge remote-tracking branch 'github/master' into HEAD
This commit is contained in:
@@ -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|
|
||||
@@ -2,47 +2,11 @@
|
||||
|
||||
## Description
|
||||
|
||||
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.
|
||||
Content here is generated from corresponnding step, see `vars`.
|
||||
|
||||
## 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.
|
||||
* `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
|
||||
Content here is generated from corresponnding step, see `vars`.
|
||||
|
||||
## Kubernetes support
|
||||
|
||||
@@ -50,27 +14,7 @@ If the Jenkins is setup on a Kubernetes cluster, then you can execute the closur
|
||||
|
||||
## 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|
|
||||
|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|
|
||||
Content here is generated from corresponnding step, see `vars`.
|
||||
|
||||
## 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
|
||||
@@ -9,6 +9,7 @@ nav:
|
||||
- checksPublishResults: steps/checksPublishResults.md
|
||||
- cloudFoundryDeploy: steps/cloudFoundryDeploy.md
|
||||
- commonPipelineEnvironment: steps/commonPipelineEnvironment.md
|
||||
- containerExecuteStructureTests: steps/containerExecuteStructureTests.md
|
||||
- dockerExecute: steps/dockerExecute.md
|
||||
- dockerExecuteOnKubernetes: steps/dockerExecuteOnKubernetes.md
|
||||
- durationMeasure: steps/durationMeasure.md
|
||||
|
||||
@@ -143,12 +143,21 @@ steps:
|
||||
mtaDeployPlugin:
|
||||
dockerImage: 's4sdk/docker-cf-cli'
|
||||
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:
|
||||
stashContent: []
|
||||
dockerExecuteOnKubernetes:
|
||||
stashContent: []
|
||||
stashIncludes:
|
||||
workspace: '**/*.*'
|
||||
workspace: '**/*'
|
||||
stashExcludes:
|
||||
workspace: 'nohup.out'
|
||||
githubPublishRelease:
|
||||
@@ -261,7 +270,7 @@ steps:
|
||||
opensourceConfiguration: '**/srcclr.yml, **/vulas-custom.properties, **/.nsprc, **/.retireignore, **/.retireignore.json, **/.snyk'
|
||||
pipelineConfigAndTests: '.pipeline/**'
|
||||
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:
|
||||
buildDescriptor: '**/node_modules/**/package.json'
|
||||
deployDescriptor: ''
|
||||
@@ -345,6 +354,16 @@ steps:
|
||||
developmentSystemId: null
|
||||
transportRequestUploadFile:
|
||||
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
|
||||
piperStageWrapper:
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.sap.piper
|
||||
|
||||
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
|
||||
def getMandatoryParameter(Map map, paramName, defaultValue = null) {
|
||||
@@ -72,67 +75,23 @@ def unstashAll(stashContent) {
|
||||
return unstashedContent
|
||||
}
|
||||
|
||||
def generateSha1Inline(input) {
|
||||
return "`echo -n '${input}' | sha1sum | sed 's/ -//'`"
|
||||
@NonCPS
|
||||
def generateSha1(input) {
|
||||
return MessageDigest
|
||||
.getInstance("SHA-1")
|
||||
.digest(input.getBytes(StandardCharsets.UTF_8))
|
||||
.encodeHex().toString()
|
||||
}
|
||||
|
||||
void pushToSWA(Map parameters, Map config) {
|
||||
try {
|
||||
//allow opt-out via configuration
|
||||
if (!config?.collectTelemetryData) {
|
||||
echo "[${parameters.get('step')}] Telemetry Report to SWA disabled!"
|
||||
return
|
||||
}
|
||||
parameters.actionName = parameters.get('actionName') ?: 'Piper Library OS'
|
||||
parameters.eventType = parameters.get('eventType') ?: 'library-os'
|
||||
parameters.jobUrlSha1 = generateSha1(env.JOB_URL)
|
||||
parameters.buildUrlSha1 = generateSha1(env.BUILD_URL)
|
||||
|
||||
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 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!"
|
||||
Telemetry.notify(this, config, parameters)
|
||||
} 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'))
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.sap.piper
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import static org.junit.Assert.assertThat
|
||||
import org.junit.rules.ExpectedException
|
||||
@@ -63,38 +64,10 @@ class UtilsTest extends BasePiperTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSWAReporting() {
|
||||
utils.env = [BUILD_URL: 'something', JOB_URL: 'nothing']
|
||||
utils.pushToSWA([step: 'anything'], [collectTelemetryData: true])
|
||||
void testGenerateSHA1() {
|
||||
def result = utils.generateSha1('ContinuousDelivery')
|
||||
// asserts
|
||||
assertThat(shellRule.shell, hasItem(containsString('curl -G -v "https://webanalytics.cfapps.eu10.hana.ondemand.com/tracker/log"')))
|
||||
assertThat(shellRule.shell, hasItem(containsString('action_name=Piper Library OS')))
|
||||
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!'))
|
||||
// generated with "echo -n 'ContinuousDelivery' | sha1sum | sed 's/ -//'"
|
||||
assertThat(result, is('0dad6c33b6246702132454f604dee80740f399ad'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
def nullScript = InvokerHelper.createScript(null, new Binding())
|
||||
nullScript.currentBuild = [:]
|
||||
nullScript.env = [:]
|
||||
LibraryLoadingTestExecutionListener.prepareObjectInterceptors(nullScript)
|
||||
return nullScript
|
||||
}
|
||||
|
||||
@@ -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
@@ -3,6 +3,7 @@ import static com.sap.piper.Prerequisites.checkScript
|
||||
import com.cloudbees.groovy.cps.NonCPS
|
||||
|
||||
import com.sap.piper.ConfigurationHelper
|
||||
import com.sap.piper.GenerateDocumentation
|
||||
import com.sap.piper.JenkinsUtils
|
||||
import com.sap.piper.Utils
|
||||
import com.sap.piper.k8s.ContainerMap
|
||||
@@ -12,28 +13,92 @@ import groovy.transform.Field
|
||||
@Field def STEP_NAME = getClass().getName()
|
||||
@Field def PLUGIN_ID_DOCKER_WORKFLOW = 'docker-workflow'
|
||||
|
||||
@Field Set GENERAL_CONFIG_KEYS = ['jenkinsKubernetes']
|
||||
|
||||
@Field Set PARAMETER_KEYS = [
|
||||
'containerPortMappings',
|
||||
'containerCommand',
|
||||
'containerShell',
|
||||
'dockerEnvVars',
|
||||
'dockerImage',
|
||||
'dockerName',
|
||||
'dockerOptions',
|
||||
'dockerWorkspace',
|
||||
'dockerVolumeBind',
|
||||
'sidecarEnvVars',
|
||||
'sidecarImage',
|
||||
'sidecarName',
|
||||
'sidecarOptions',
|
||||
'sidecarWorkspace',
|
||||
'sidecarVolumeBind',
|
||||
'stashContent'
|
||||
@Field Set GENERAL_CONFIG_KEYS = [
|
||||
/**
|
||||
*
|
||||
*/
|
||||
'jenkinsKubernetes'
|
||||
]
|
||||
@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) {
|
||||
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
|
||||
|
||||
@@ -153,7 +218,7 @@ void call(Map parameters = [:], body) {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
/*
|
||||
* Returns a string with docker options containing
|
||||
* environment variables (if set).
|
||||
* Possible to extend with further options.
|
||||
@@ -224,7 +289,7 @@ boolean isKubernetes() {
|
||||
return Boolean.valueOf(env.ON_K8S)
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Escapes blanks for values in key/value pairs
|
||||
* E.g. <code>description=Lorem ipsum</code> is
|
||||
* changed to <code>description=Lorem\ ipsum</code>.
|
||||
|
||||
@@ -9,8 +9,10 @@ import hudson.AbortException
|
||||
|
||||
@Field def STEP_NAME = getClass().getName()
|
||||
@Field def PLUGIN_ID_KUBERNETES = 'kubernetes'
|
||||
@Field Set GENERAL_CONFIG_KEYS = ['jenkinsKubernetes']
|
||||
@Field Set PARAMETER_KEYS = [
|
||||
@Field Set GENERAL_CONFIG_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`).
|
||||
'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
|
||||
@@ -22,9 +24,14 @@ import hudson.AbortException
|
||||
'dockerImage',
|
||||
'dockerWorkspace',
|
||||
'dockerEnvVars',
|
||||
'stashContent'
|
||||
]
|
||||
@Field Set STEP_CONFIG_KEYS = PARAMETER_KEYS.plus(['stashIncludes', 'stashExcludes'])
|
||||
'stashContent',
|
||||
'stashExcludes',
|
||||
'stashIncludes'
|
||||
])
|
||||
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.minus([
|
||||
'stashIncludes',
|
||||
'stashExcludes'
|
||||
])
|
||||
|
||||
void call(Map parameters = [:], body) {
|
||||
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
|
||||
@@ -84,12 +91,13 @@ void executeOnPod(Map config, utils, Closure body) {
|
||||
if (config.containerShell) {
|
||||
containerParams.shell = config.containerShell
|
||||
}
|
||||
echo "ContainerConfig: ${containerParams}"
|
||||
container(containerParams){
|
||||
try {
|
||||
utils.unstashAll(config.stashContent)
|
||||
body()
|
||||
} finally {
|
||||
stashWorkspace(config, 'container')
|
||||
stashWorkspace(config, 'container', true)
|
||||
}
|
||||
}
|
||||
} 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}"
|
||||
try {
|
||||
// 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(
|
||||
name: stashName,
|
||||
include: config.stashIncludes.workspace,
|
||||
exclude: config.stashExcludes.excludes
|
||||
includes: config.stashIncludes.workspace,
|
||||
excludes: config.stashExcludes.workspace
|
||||
)
|
||||
return stashName
|
||||
} catch (AbortException | IOException e) {
|
||||
|
||||
@@ -54,7 +54,6 @@ private void stageLocking(Map config, Closure body) {
|
||||
}
|
||||
|
||||
private void executeStage(script, originalStage, stageName, config, utils) {
|
||||
|
||||
boolean projectExtensions
|
||||
boolean globalExtensions
|
||||
def startTime = System.currentTimeMillis()
|
||||
@@ -108,14 +107,19 @@ private void executeStage(script, originalStage, stageName, config, utils) {
|
||||
stageName: stageName,
|
||||
stepParamKey1: 'buildResult',
|
||||
stepParam1: "${script.currentBuild.currentResult}",
|
||||
buildResult: "${script.currentBuild.currentResult}",
|
||||
stepParamKey2: 'stageStartTime',
|
||||
stepParam2: "${startTime}",
|
||||
stageStartTime: "${startTime}",
|
||||
stepParamKey3: 'stageDuration',
|
||||
stepParam3: "${duration}",
|
||||
stageDuration: "${duration}",
|
||||
stepParamKey4: 'projectExtension',
|
||||
stepParam4: "${projectExtensions}",
|
||||
projectExtension: "${projectExtensions}",
|
||||
stepParamKey5: 'globalExtension',
|
||||
stepParam5: "${globalExtensions}"
|
||||
stepParam5: "${globalExtensions}",
|
||||
globalExtension: "${globalExtensions}"
|
||||
], config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user