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
|
## 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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'))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.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>.
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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