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 Continuous Deployment scenarios to various Clouds. */ @GenerateDocumentation void call(Map parameters = [:]) { handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) { final script = checkScript(this, parameters) ?: this String stageName = parameters.stageName ?: env.STAGE_NAME // load default & individual configuration Map config = ConfigurationHelper.newInstance(this) .loadStepDefaults(CONFIG_KEY_COMPATIBILITY, stageName) .mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY) .mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS, CONFIG_KEY_COMPATIBILITY) .mixinStageConfig(script.commonPipelineEnvironment, stageName, 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(pipelineStatusResponseObj)}" } error "[${STEP_NAME}] Spinnaker pipeline failed with ${pipelineStatusResponseObj.status}" } } }