1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-11-06 09:09:19 +02:00

Add step spinnakerTriggerPipeline (#793)

This commit is contained in:
Oliver Nocon
2019-11-06 15:37:14 +01:00
committed by GitHub
parent bb230d3b9b
commit a04489cd35
3 changed files with 380 additions and 0 deletions

View File

@@ -512,6 +512,10 @@ steps:
options: []
pullRequestProvider: 'GitHub'
sonarScannerDownloadUrl: 'https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.3.0.1492-linux.zip'
spinnakerTriggerPipeline:
certFileCredentialsId: 'spinnaker-client-certificate'
keyFileCredentialsId: 'spinnaker-client-key'
timeout: 60
testsPublishResults:
failOnError: false
junit:

View File

@@ -0,0 +1,202 @@
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 SpinnakerTriggerPipelineTest extends BasePiperTest {
private ExpectedException exception = new ExpectedException().none()
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsLoggingRule logginRule = new JenkinsLoggingRule(this)
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
private JenkinsReadJsonRule readJsonRule = new JenkinsReadJsonRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(exception)
.around(shellRule)
.around(logginRule)
.around(readJsonRule)
.around(stepRule)
class EnvMock {
def STAGE_NAME = 'testStage'
Map getEnvironment() {
return [key1: 'value1', key2: 'value2']
}
}
def credentialFileList = []
def timeout = 0
@Before
void init() {
binding.setVariable('env', new EnvMock())
credentialFileList = []
helper.registerAllowedMethod('file', [Map], { m ->
credentialFileList.add(m)
return m
})
Map credentialFileNames = [
'spinnaker-client-certificate': 'clientCert.file',
'spinnaker-client-key': 'clientKey.file'
]
helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c ->
l.each { fileCredentials ->
binding.setProperty(fileCredentials.variable, credentialFileNames[fileCredentials.credentialsId])
}
try {
c()
} finally {
l.each { fileCredentials ->
binding.setProperty(fileCredentials.variable, null)
}
}
})
helper.registerAllowedMethod('timeout', [Integer.class, Closure.class] , {i, body ->
timeout = i
return body()
})
//not sure where this comes from!
helper.registerAllowedMethod('waitUntil', [Closure.class], {body ->
List responseStatus = ['RUNNING', 'PAUSED', 'NOT_STARTED']
while (!body()) {
//take another round with a different response status
responseStatus.each {status ->
shellRule.setReturnValue('curl -X GET https://spinnakerTest.url/testRef --silent --cert $clientCertificate --key $clientKey', "{\"status\": \"${status}\"}")
shellRule.setReturnValue('curl -X GET https://spinnakerTest.url/testRef --verbose --cert $clientCertificate --key $clientKey', "{\"status\": \"${status}\"}")
}
}
})
shellRule.setReturnValue('curl -H \'Content-Type: application/json\' -X POST -d \'{"parameters":{"param1":"val1"}}\' --silent --cert $clientCertificate --key $clientKey https://spinnakerTest.url/pipelines/spinnakerTestApp/spinnakerTestPipeline', '{"ref": "/testRef"}')
shellRule.setReturnValue('curl -H \'Content-Type: application/json\' -X POST -d \'{"parameters":{"param1":"val1"}}\' --verbose --cert $clientCertificate --key $clientKey https://spinnakerTest.url/pipelines/spinnakerTestApp/spinnakerTestPipeline', '{"ref": "/testRef"}')
shellRule.setReturnValue('curl -X GET https://spinnakerTest.url/testRef --silent --cert $clientCertificate --key $clientKey', '{"status": "SUCCEEDED"}')
shellRule.setReturnValue('curl -X GET https://spinnakerTest.url/testRef --verbose --cert $clientCertificate --key $clientKey', '{"status": "SUCCEEDED"}')
}
@Test
void testDefaults() {
nullScript.commonPipelineEnvironment.configuration = [
general: [
spinnakerGateUrl: 'https://spinnakerTest.url',
spinnakerApplication: 'spinnakerTestApp',
verbose: true
],
stages: [
testStage: [
spinnakerPipeline: 'spinnakerTestPipeline',
pipelineParameters: [param1: 'val1']
]
]
]
stepRule.step.spinnakerTriggerPipeline(
script: nullScript
)
assertThat(timeout, is(60))
assertThat(logginRule.log, containsString('Triggering Spinnaker pipeline with parameters:'))
assertThat(logginRule.log, containsString('Spinnaker pipeline /testRef triggered, waiting for the pipeline to finish'))
assertThat(credentialFileList,
hasItem(
allOf(
hasEntry('credentialsId', 'spinnaker-client-key'),
hasEntry('variable', 'clientKey')
)
)
)
assertThat(credentialFileList,
hasItem(
allOf(
hasEntry('credentialsId', 'spinnaker-client-certificate'),
hasEntry('variable', 'clientCertificate')
)
)
)
}
@Test
void testDisabledPipelineCheck() {
nullScript.commonPipelineEnvironment.configuration = [
general: [
spinnaker: [
gateUrl: 'https://spinnakerTest.url',
application: 'spinnakerTestApp'
]
],
stages: [
testStage: [
pipelineNameOrId: 'spinnakerTestPipeline',
pipelineParameters: [param1: 'val1']
]
]
]
stepRule.step.spinnakerTriggerPipeline(
script: nullScript,
timeout: 0
)
assertThat(logginRule.log, containsString('Exiting without waiting for Spinnaker pipeline result.'))
assertThat(timeout, is(0))
}
@Test
void testTriggerFailure() {
nullScript.commonPipelineEnvironment.configuration = [
general: [
spinnakerGateUrl: 'https://spinnakerTest.url',
spinnakerApplication: 'spinnakerTestApp'
],
stages: [
testStage: [
spinnakerPipeline: 'spinnakerTestPipeline'
]
]
]
shellRule.setReturnValue('curl -H \'Content-Type: application/json\' -X POST --silent --cert $clientCertificate --key $clientKey https://spinnakerTest.url/pipelines/spinnakerTestApp/spinnakerTestPipeline', '{}')
exception.expectMessage('Failed to trigger Spinnaker pipeline')
stepRule.step.spinnakerTriggerPipeline(
script: nullScript,
)
}
@Test
void testPipelineFailure() {
nullScript.commonPipelineEnvironment.configuration = [
general: [
spinnakerGateUrl: 'https://spinnakerTest.url',
spinnakerApplication: 'spinnakerTestApp'
],
stages: [
testStage: [
spinnakerPipeline: 'spinnakerTestPipeline',
pipelineParameters: [param1: 'val1']
]
]
]
shellRule.setReturnValue('curl -X GET https://spinnakerTest.url/testRef --silent --cert $clientCertificate --key $clientKey', '{"status": "FAILED"}')
exception.expectMessage('Spinnaker pipeline failed with FAILED')
stepRule.step.spinnakerTriggerPipeline(
script: nullScript,
)
}
}

View File

@@ -0,0 +1,174 @@
import com.cloudbees.groovy.cps.NonCPS
import com.sap.piper.JsonUtils
import groovy.text.GStringTemplateEngine
import static com.sap.piper.Prerequisites.checkScript
import groovy.json.JsonOutput
import org.apache.commons.lang3.text.StrSubstitutor
import com.sap.piper.GenerateDocumentation
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import groovy.transform.Field
@Field def STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
'spinnaker',
/**
* Whether verbose output should be produced.
* @possibleValues `true`, `false`
*/
'verbose'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* Defines the id of the file credentials in your Jenkins credentials store which contain the client certificate file for Spinnaker authentication.
* @parentConfigKey spinnaker
*/
'certFileCredentialsId',
/**
* Defines the url of the Spinnaker Gateway Service as API endpoint for communication with Spinnaker.
* @parentConfigKey spinnaker
*/
'gateUrl',
/**
* Defines the id of the file credentials in your Jenkins credentials store which contain the private key file for Spinnaker authentication.
* @parentConfigKey spinnaker
*/
'keyFileCredentialsId',
/**
* Defines the name/id of the Spinnaker pipeline.
* @parentConfigKey spinnaker
*/
'pipelineNameOrId',
/**
* Parameter map containing Spinnaker pipeline parameters.
* @parentConfigKey spinnaker
*/
'pipelineParameters',
/**
* Defines the timeout in minutes for checking the Spinnaker pipeline result.
* By setting to `0` the check can be de-activated.
*/
'timeout'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
@Field Map CONFIG_KEY_COMPATIBILITY = [
application: 'spinnakerApplication',
certFileCredentialsId: 'certCredentialId',
gateUrl: 'spinnakerGateUrl',
keyFileCredentialsId: 'keyCredentialId',
pipelineNameOrId: 'spinnakerPipeline',
pipelineParameters: 'pipelineParameters',
spinnaker: [
application: 'application',
certFileCredentialsId: 'certFileCredentialsId',
keyFileCredentialsId: 'keyFileCredentialsId',
gateUrl: 'gateUrl',
pipelineParameters: 'pipelineParameters',
pipelineNameOrId: 'pipelineNameOrId'
]
]
/**
* Triggers a [Spinnaker](https://spinnaker.io) pipeline from a Jenkins pipeline.
* Spinnaker is for example used for Continuos Deployment scenarios to various Clouds.
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
final script = checkScript(this, parameters) ?: this
// load default & individual configuration
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults(CONFIG_KEY_COMPATIBILITY)
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY)
.mixin(parameters, PARAMETER_KEYS, CONFIG_KEY_COMPATIBILITY)
.withMandatoryProperty('spinnaker/gateUrl')
.withMandatoryProperty('spinnaker/application')
.withMandatoryProperty('spinnaker/pipelineNameOrId')
.use()
// telemetry reporting
new Utils().pushToSWA([
step: STEP_NAME
], config)
String paramsString = ""
if (config.spinnaker.pipelineParameters) {
def pipelineParameters = [parameters: config.spinnaker.pipelineParameters]
paramsString = "-d '${new GStringTemplateEngine().createTemplate(JsonOutput.toJson(pipelineParameters)).make([config: config, env: env]).toString()}'"
if (config.verbose) {
echo "[${STEP_NAME}] Triggering Spinnaker pipeline with parameters: ${paramsString}"
}
}
def pipelineTriggerResponse
//ToDO: support userId/pwd authentication or token authentication!
def curlVerbosity = (config.verbose) ? '--verbose ' : '--silent '
withCredentials([
file(credentialsId: config.spinnaker.keyFileCredentialsId, variable: 'clientKey'),
file(credentialsId: config.spinnaker.certFileCredentialsId, variable: 'clientCertificate')
]) {
// Trigger a pipeline execution by calling invokePipelineConfigUsingPOST1 (see https://www.spinnaker.io/reference/api/docs.html)
pipelineTriggerResponse = sh(returnStdout: true, script: "curl -H 'Content-Type: application/json' -X POST ${paramsString} ${curlVerbosity} --cert \$clientCertificate --key \$clientKey ${config.spinnaker.gateUrl}/pipelines/${config.spinnaker.application}/${config.spinnaker.pipelineNameOrId}").trim()
}
if (config.verbose) {
echo "[${STEP_NAME}] Spinnaker pipeline trigger response = ${pipelineTriggerResponse}"
}
def pipelineTriggerResponseObj = readJSON text: pipelineTriggerResponse
if (!pipelineTriggerResponseObj.ref) {
error "[${STEP_NAME}] Failed to trigger Spinnaker pipeline"
}
if (config.timeout == 0) {
echo "[${STEP_NAME}] Exiting without waiting for Spinnaker pipeline result."
return
}
echo "[${STEP_NAME}] Spinnaker pipeline ${pipelineTriggerResponseObj.ref} triggered, waiting for the pipeline to finish"
def pipelineStatusResponseObj
timeout(config.timeout) {
waitUntil {
def pipelineStatusResponse
sleep 10
withCredentials([
file(credentialsId: config.spinnaker.keyFileCredentialsId, variable: 'clientKey'),
file(credentialsId: config.spinnaker.certFileCredentialsId, variable: 'clientCertificate')
]) {
pipelineStatusResponse = sh returnStdout: true, script: "curl -X GET ${config.spinnaker.gateUrl}${pipelineTriggerResponseObj.ref} ${curlVerbosity} --cert \$clientCertificate --key \$clientKey"
}
pipelineStatusResponseObj = readJSON text: pipelineStatusResponse
echo "[${STEP_NAME}] Spinnaker pipeline ${pipelineTriggerResponseObj.ref} status: ${pipelineStatusResponseObj.status}"
if (pipelineStatusResponseObj.status in ['RUNNING', 'PAUSED', 'NOT_STARTED']) {
return false
} else {
return true
}
}
}
if (pipelineStatusResponseObj.status != 'SUCCEEDED') {
if (config.verbose) {
echo "[${STEP_NAME}] Full Spinnaker response = ${new JsonUtils().groovyObjectToPrettyJsonString(pipelineStatusResponse)}"
}
error "[${STEP_NAME}] Spinnaker pipeline failed with ${pipelineStatusResponseObj.status}"
}
}
}