1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-20 05:19:40 +02:00

add wrapper for stages contained in library (#341)

* add wrapper for stages contained in library

`piperStageWrapper` provides a wrapper for stages which we may include into the library.
It will take care about extension capabilities, locking, node handling, ... which should be a capability of every stage contained in the library.
This commit is contained in:
Oliver Nocon 2018-12-12 11:45:11 +01:00 committed by GitHub
parent d303e49ebe
commit e556fba950
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 268 additions and 4 deletions

View File

@ -252,7 +252,7 @@ steps:
git: '**/gitmetadata/**'
opa5: '**/*.*'
opensourceConfiguration: '**/srcclr.yml, **/vulas-custom.properties, **/.nsprc, **/.retireignore, **/.retireignore.json, **/.snyk'
pipelineConfigAndTests: '.pipeline/*.*'
pipelineConfigAndTests: '.pipeline/**'
securityDescriptor: '**/xs-security.json'
tests: '**/pom.xml, **/*.json, **/*.xml, **/src/**, **/node_modules/**, **/specs/**, **/env/**, **/*.js'
stashExcludes:
@ -338,3 +338,12 @@ steps:
developmentSystemId: null
transportRequestUploadFile:
transportRequestRelease:
#defaults for stage wrapper
piperStageWrapper:
projectExtensionsDirectory: '.pipeline/extensions/'
globalExtensionsDirectory: ''
stageLocking: true
nodeLabel: ''
stashContent:
- 'pipelineConfigAndTests'

View File

@ -22,6 +22,26 @@ def stash(name, include = '**/*.*', exclude = '') {
steps.stash name: name, includes: include, excludes: exclude
}
def stashList(script, List stashes) {
for (def stash : stashes) {
def name = stash.name
def include = stash.includes
def exclude = stash.excludes
if (stash?.merge == true) {
String lockName = "${script.commonPipelineEnvironment.configuration.stashFiles}/${stash.name}"
lock(lockName) {
unstash stash.name
echo "Stash content: ${name} (include: ${include}, exclude: ${exclude})"
steps.stash name: name, includes: include, exclude: exclude, allowEmpty: true
}
} else {
echo "Stash content: ${name} (include: ${include}, exclude: ${exclude})"
steps.stash name: name, includes: include, exclude: exclude, allowEmpty: true
}
}
}
def stashWithMessage(name, msg, include = '**/*.*', exclude = '') {
try {
stash(name, include, exclude)
@ -70,6 +90,7 @@ void pushToSWA(Map parameters, Map config) {
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)
@ -81,11 +102,12 @@ void pushToSWA(Map parameters, Map config) {
def action_name = 'Piper Library OS'
def idsite = '827e8025-1e21-ae84-c3a3-3f62b70b0130'
def url = 'https://github.com/SAP/jenkins-library'
def event_type = 'library-os'
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')
@ -99,7 +121,7 @@ void pushToSWA(Map parameters, Map config) {
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', 'custom11', 'custom12', 'custom13', 'custom14', 'custom15']){
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")

View File

@ -109,6 +109,7 @@ public class CommonStepsTest extends BasePiperTest{
'pipelineExecute', // special step (infrastructure)
'commonPipelineEnvironment', // special step (infrastructure)
'handlePipelineStepErrors', // special step (infrastructure)
'piperStageWrapper' //intended to be called from within stages
]
@Test

View File

@ -0,0 +1,118 @@
#!groovy
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
import util.JenkinsStepRule
import util.Rules
import static org.hamcrest.CoreMatchers.containsString
import static org.hamcrest.CoreMatchers.is
import static org.junit.Assert.assertThat
class PiperStageWrapperTest extends BasePiperTest {
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private JenkinsStepRule jsr = new JenkinsStepRule(this)
private Map lockMap = [:]
private int countNodeUsage = 0
private String nodeLabel = ''
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(jlr)
.around(jsr)
@Before
void init() throws Exception {
helper.registerAllowedMethod('deleteDir', [], {return null})
helper.registerAllowedMethod('lock', [Map.class, Closure.class], {m, body ->
assertThat(m.resource.toString(), containsString('/10'))
lockMap = m
body()
})
helper.registerAllowedMethod('milestone', [Integer.class], {ordinal ->
assertThat(ordinal, is(10))
})
helper.registerAllowedMethod('node', [String.class, Closure.class], {s, body ->
nodeLabel = s
countNodeUsage++
body()
})
helper.registerAllowedMethod('fileExists', [String.class], {s ->
return false
})
}
@Test
void testDefault() {
def testInt = 1
jsr.step.piperStageWrapper(
script: nullScript,
juStabUtils: utils,
ordinal: 10,
stageName: 'test'
) {
testInt ++
}
assertThat(testInt, is(2))
assertThat(lockMap.size(), is(2))
assertThat(countNodeUsage, is(1))
}
@Test
void testNoLocking() {
def testInt = 1
jsr.step.piperStageWrapper(
script: nullScript,
juStabUtils: utils,
nodeLabel: 'testLabel',
ordinal: 10,
stageLocking: false,
stageName: 'test'
) {
testInt ++
}
assertThat(testInt, is(2))
assertThat(lockMap.size(), is(0))
assertThat(countNodeUsage, is(1))
assertThat(nodeLabel, is('testLabel'))
}
@Test
void testStageExit() {
helper.registerAllowedMethod('fileExists', [String.class], {s ->
return (s == '.pipeline/extensions/test.groovy')
})
helper.registerAllowedMethod('load', [String.class], {
return helper.loadScript('test/resources/stages/test.groovy')
})
def testInt = 1
jsr.step.piperStageWrapper(
script: nullScript,
juStabUtils: utils,
ordinal: 10,
stageName: 'test'
) {
testInt ++
}
assertThat(testInt, is(2))
assertThat(jlr.log, containsString('[piperStageWrapper] Running project interceptor \'.pipeline/extensions/test.groovy\' for test.'))
assertThat(jlr.log, containsString('Stage Name: test'))
assertThat(jlr.log, containsString('Config:'))
}
}

View File

@ -123,7 +123,7 @@ class LibraryLoadingTestExecutionListener extends AbstractTestExecutionListener
void beforeTestMethod(TestContext testContext) throws Exception {
super.beforeTestMethod(testContext)
def testInstance = testContext.getTestInstance()
testInstance.binding.setVariable('currentBuild', [result: 'SUCCESS'])
testInstance.binding.setVariable('currentBuild', [result: 'SUCCESS', currentResult: 'SUCCESS'])
PipelineTestHelper helper = LibraryLoadingTestExecutionListener.getSingletonInstance()
LibraryLoadingTestExecutionListener.START_METHOD_TRACKING = true
}

View File

@ -0,0 +1,6 @@
void call(body, stageName, config) {
echo "Stage Name: ${stageName}"
echo "Config: ${config}"
body()
}
return this

View File

@ -0,0 +1,108 @@
import com.sap.piper.Utils
import com.sap.piper.ConfigurationHelper
import com.sap.piper.ConfigurationLoader
import com.sap.piper.k8s.ContainerMap
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = 'piperStageWrapper'
void call(Map parameters = [:], body) {
final script = checkScript(this, parameters) ?: this
def utils = parameters.juStabUtils ?: new Utils()
def stageName = parameters.stageName?:env.STAGE_NAME
// load default & individual configuration
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixin(ConfigurationLoader.defaultStageConfiguration(this, stageName))
.mixinGeneralConfig(script.commonPipelineEnvironment)
.mixinStageConfig(script.commonPipelineEnvironment, stageName)
.mixin(parameters)
.addIfEmpty('stageName', stageName)
.dependingOn('stageName').mixin('ordinal')
.use()
stageLocking(config) {
def containerMap = ContainerMap.instance.getMap().get(stageName) ?: [:]
if (Boolean.valueOf(env.ON_K8S) && containerMap.size() > 0) {
withEnv(["POD_NAME=${stageName}"]) {
dockerExecuteOnKubernetes(script: script, containerMap: containerMap) {
executeStage(script, body, stageName, config, utils)
}
}
} else {
node(config.nodeLabel) {
executeStage(script, body, stageName, config, utils)
}
}
}
}
private void stageLocking(Map config, Closure body) {
if (config.stageLocking) {
lock(resource: "${env.JOB_NAME}/${config.ordinal}", inversePrecedence: true) {
milestone config.ordinal
body()
}
} else {
body()
}
}
private void executeStage(script, originalStage, stageName, config, utils) {
boolean projectExtensions
boolean globalExtensions
def startTime = System.currentTimeMillis()
try {
//Add general stage stashes to config.stashContent
config.stashContent += script.commonPipelineEnvironment.configuration.stageStashes?.get(stageName)?.unstash ?: []
utils.unstashAll(config.stashContent)
/* Defining the sources where to look for a project extension and a repository extension.
* Files need to be named like the executed stage to be recognized.
*/
def projectInterceptorFile = "${config.projectExtensionsDirectory}${stageName}.groovy"
def globalInterceptorFile = "${config.globalExtensionsDirectory}${stageName}.groovy"
projectExtensions = fileExists(projectInterceptorFile)
globalExtensions = fileExists(globalInterceptorFile)
// Pre-defining the real originalStage in body variable, might be overwritten later if extensions exist
def body = originalStage
// First, check if a global extension exists via a dedicated repository
if (globalExtensions) {
Script globalInterceptorScript = load(globalInterceptorFile)
echo "[${STEP_NAME}] Found global interceptor '${globalInterceptorFile}' for ${stageName}."
// If we call the global interceptor, we will pass on originalStage as parameter
body = {
globalInterceptorScript(body, stageName, config)
}
}
// Second, check if a project extension (within the same repository) exists
if (projectExtensions) {
Script projectInterceptorScript = load(projectInterceptorFile)
echo "[${STEP_NAME}] Running project interceptor '${projectInterceptorFile}' for ${stageName}."
// If we call the project interceptor, we will pass on body as parameter which contains either originalStage or the repository interceptor
projectInterceptorScript(body, stageName, config)
} else {
//TODO: assign projectInterceptorScript to body as done for globalInterceptorScript, currently test framework does not seem to support this case. Further investigations needed.
body()
}
} finally {
echo "Current build result in stage $stageName is ${script.currentBuild.currentResult}."
//Perform stashing of selected files in workspace
utils.stashList(script, script.commonPipelineEnvironment.configuration.stageStashes?.get(stageName)?.stashes ?: [])
deleteDir()
def duration = System.currentTimeMillis() - startTime
utils.pushToSWA([eventType: 'library-os-stage', stageName: stageName, stepParam1: "${script.currentBuild.currentResult}", stepParam2: "${startTime}", stepParam3: "${duration}", stepParam4: "${projectExtensions}", stepParam5: "${globalExtensions}"], config)
}
}