2019-01-18 09:25:22 +02:00
import com.sap.piper.JenkinsUtils
2018-09-21 16:55:31 +02:00
import static com . sap . piper . Prerequisites . checkScript
2019-03-28 15:38:25 +02:00
import com.sap.piper.GenerateDocumentation
2018-07-30 09:28:24 +02:00
import com.sap.piper.Utils
import com.sap.piper.ConfigurationHelper
2018-10-17 11:01:09 +02:00
import com.sap.piper.CfManifestUtils
2018-07-30 09:28:24 +02:00
import groovy.transform.Field
2018-11-29 10:54:05 +02:00
@Field String STEP_NAME = getClass ( ) . getName ( )
2018-10-25 14:24:17 +02:00
@Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS
2018-07-30 09:28:24 +02:00
@Field Set STEP_CONFIG_KEYS = [
'cloudFoundry' ,
2019-03-28 15:38:25 +02:00
/ * *
* Cloud Foundry API endpoint .
* @parentConfigKey cloudFoundry
* /
'apiEndpoint' ,
/ * *
* Defines the name of the application to be deployed to the Cloud Foundry space .
* @parentConfigKey cloudFoundry
* /
'appName' ,
/ * *
* Credentials to be used for deployment .
* @parentConfigKey cloudFoundry
* /
'credentialsId' ,
/ * *
* Defines the manifest to be used for deployment to Cloud Foundry .
* @parentConfigKey cloudFoundry
* /
'manifest' ,
/ * *
* Cloud Foundry target organization .
* @parentConfigKey cloudFoundry
* /
'org' ,
/ * *
* Cloud Foundry target space .
* @parentConfigKey cloudFoundry
* /
'space' ,
/ * *
* Defines the tool which should be used for deployment .
* @possibleValues 'cf_native' , 'mtaDeployPlugin'
* /
2018-07-30 09:28:24 +02:00
'deployTool' ,
2019-03-28 15:38:25 +02:00
/ * *
* Defines the type of deployment , either ` standard ` deployment which results in a system downtime or a zero - downtime ` blue - green ` deployment .
* @possibleValues 'standard' , 'blue-green'
* /
2018-07-30 09:28:24 +02:00
'deployType' ,
2019-03-28 15:38:25 +02:00
/ * *
* In case of a ` blue - green ` deployment the old instance will be deleted by default . If this option is set to true the old instance will remain stopped in the Cloud Foundry space .
* @possibleValues true , false
* /
2018-11-27 12:47:44 +02:00
'keepOldInstance' ,
2019-03-28 15:38:25 +02:00
/** @see dockerExecute */
2018-07-30 09:28:24 +02:00
'dockerImage' ,
2019-03-28 15:38:25 +02:00
/** @see dockerExecute */
2018-07-30 09:28:24 +02:00
'dockerWorkspace' ,
2019-03-28 15:38:25 +02:00
/** @see dockerExecute */
'stashContent' ,
/ * *
* Defines additional parameters passed to mta for deployment with the mtaDeployPlugin .
* /
2018-07-30 09:28:24 +02:00
'mtaDeployParameters' ,
2019-03-28 15:38:25 +02:00
/ * *
* Defines additional extension descriptor file for deployment with the mtaDeployPlugin .
* /
2018-07-30 09:28:24 +02:00
'mtaExtensionDescriptor' ,
2019-03-28 15:38:25 +02:00
/ * *
* Defines the path to * . mtar for deployment with the mtaDeployPlugin .
* /
2018-07-30 09:28:24 +02:00
'mtaPath' ,
2019-03-28 15:38:25 +02:00
/ * *
* Allows to specify a script which performs a check during blue - green deployment . The script gets the FQDN as parameter and returns ` exit code 0 ` in case check returned ` smokeTestStatusCode ` .
* More details can be found [ here ] ( https: //github.com/bluemixgaragelondon/cf-blue-green-deploy#how-to-use) <br /> Currently this option is only considered for deployTool `cf_native`.
* /
2018-07-30 09:28:24 +02:00
'smokeTestScript' ,
2019-03-28 15:38:25 +02:00
/ * *
* Expected status code returned by the check .
* /
'smokeTestStatusCode'
]
2018-07-30 09:28:24 +02:00
@Field Map CONFIG_KEY_COMPATIBILITY = [ cloudFoundry: [ apiEndpoint: 'cfApiEndpoint' , appName: 'cfAppName' , credentialsId: 'cfCredentialsId' , manifest: 'cfManifest' , org: 'cfOrg' , space: 'cfSpace' ] ]
2018-10-25 14:24:17 +02:00
2018-07-30 09:28:24 +02:00
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
2019-03-28 15:38:25 +02:00
/ * *
* Deploys an application to a test or production space within Cloud Foundry .
* Deployment can be done
*
* * in a standard way
* * in a zero downtime manner ( using a [ blue - green deployment approach ] ( https: //martinfowler.com/bliki/BlueGreenDeployment.html))
*
* ! ! ! note "Deployment supports multiple deployment tools"
* Currently the following are supported:
*
* * Standard ` cf push ` and [ Bluemix blue - green plugin ] ( https: //github.com/bluemixgaragelondon/cf-blue-green-deploy#how-to-use)
* * [ MTA CF CLI Plugin ] ( https: //github.com/cloudfoundry-incubator/multiapps-cli-plugin)
*
* ! ! ! note
* Due to [ an incompatible change ] ( https: //github.com/cloudfoundry/cli/issues/1445) in the Cloud Foundry CLI, multiple buildpacks are not supported by this step.
* If your ` application ` contains a list of ` buildpacks ` instead a single ` buildpack ` , this will be automatically re - written by the step when blue - green deployment is used .
*
* ! ! ! note
* Cloud Foundry supports the deployment of multiple applications using a single manifest file .
* This option is supported with Piper .
*
* In this case define ` appName: '' ` since the app name for the individual applications have to be defined via the manifest .
* You can find details in the [ Cloud Foundry Documentation ] ( https: //docs.cloudfoundry.org/devguide/deploy-apps/manifest.html#multi-apps)
* /
@GenerateDocumentation
2018-08-30 16:33:07 +02:00
void call ( Map parameters = [ : ] ) {
2018-07-30 09:28:24 +02:00
handlePipelineStepErrors ( stepName: STEP_NAME , stepParameters: parameters ) {
2019-01-18 09:25:22 +02:00
def utils = parameters . juStabUtils ? : new Utils ( )
def jenkinsUtils = parameters . jenkinsUtilsStub ? : new JenkinsUtils ( )
2018-07-30 09:28:24 +02:00
2019-02-07 09:58:00 +02:00
final script = checkScript ( this , parameters ) ? : this
2018-07-30 09:28:24 +02:00
2018-10-17 11:05:20 +02:00
Map config = ConfigurationHelper . newInstance ( this )
2018-09-07 10:08:16 +02:00
. loadStepDefaults ( )
2018-10-25 14:24:17 +02:00
. mixinGeneralConfig ( script . commonPipelineEnvironment , GENERAL_CONFIG_KEYS , CONFIG_KEY_COMPATIBILITY )
2018-09-06 16:45:30 +02:00
. 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 )
2018-07-30 09:28:24 +02:00
. dependingOn ( 'deployTool' ) . mixin ( 'dockerImage' )
. dependingOn ( 'deployTool' ) . mixin ( 'dockerWorkspace' )
. withMandatoryProperty ( 'cloudFoundry/org' )
. withMandatoryProperty ( 'cloudFoundry/space' )
. withMandatoryProperty ( 'cloudFoundry/credentialsId' )
. use ( )
2019-01-21 09:47:34 +02:00
utils . pushToSWA ( [
step: STEP_NAME ,
stepParamKey1: 'deployTool' ,
stepParam1: config . deployTool ,
stepParamKey2: 'deployType' ,
stepParam2: config . deployType ,
stepParamKey3: 'scriptMissing' ,
stepParam3: parameters ? . script = = null
] , config )
2018-08-09 11:35:33 +02:00
2019-03-28 15:38:25 +02:00
echo "[${STEP_NAME}] General parameters: deployTool=${config.deployTool}, deployType=${config.deployType}, cfApiEndpoint=${config.cloudFoundry.apiEndpoint}, cfOrg=${config.cloudFoundry.org}, cfSpace=${config.cloudFoundry.space}, cfCredentialsId=${config.cloudFoundry.credentialsId}"
2018-07-30 09:28:24 +02:00
2018-11-27 17:02:06 +02:00
//make sure that all relevant descriptors, are available in workspace
utils . unstashAll ( config . stashContent )
//make sure that for further execution whole workspace, e.g. also downloaded artifacts are considered
2018-11-27 17:29:38 +02:00
config . stashContent = [ ]
2018-07-30 09:28:24 +02:00
2019-01-18 09:25:22 +02:00
boolean deploy = false
boolean deploySuccess = true
try {
if ( config . deployTool = = 'mtaDeployPlugin' ) {
deploy = true
// set default mtar path
config = ConfigurationHelper . newInstance ( this , config )
. addIfEmpty ( 'mtaPath' , config . mtaPath ? : findMtar ( ) )
. use ( )
dockerExecute ( script: script , dockerImage: config . dockerImage , dockerWorkspace: config . dockerWorkspace , stashContent: config . stashContent ) {
deployMta ( config )
}
2018-07-30 09:28:24 +02:00
}
2019-01-18 09:25:22 +02:00
if ( config . deployTool = = 'cf_native' ) {
deploy = true
config . smokeTest = ''
2018-07-30 09:28:24 +02:00
2019-01-18 09:25:22 +02:00
if ( config . smokeTestScript = = 'blueGreenCheckScript.sh' ) {
writeFile file: config . smokeTestScript , text: libraryResource ( config . smokeTestScript )
}
2018-08-15 09:46:08 +02:00
2019-01-18 09:25:22 +02:00
config . smokeTest = '--smoke-test $(pwd)/' + config . smokeTestScript
sh "chmod +x ${config.smokeTestScript}"
2018-07-30 09:28:24 +02:00
2019-01-18 09:25:22 +02:00
echo "[${STEP_NAME}] CF native deployment (${config.deployType}) with cfAppName=${config.cloudFoundry.appName}, cfManifest=${config.cloudFoundry.manifest}, smokeTestScript=${config.smokeTestScript}"
2018-07-30 09:28:24 +02:00
2019-01-18 09:25:22 +02:00
dockerExecute (
script: script ,
dockerImage: config . dockerImage ,
dockerWorkspace: config . dockerWorkspace ,
stashContent: config . stashContent ,
dockerEnvVars: [ CF_HOME: "${config.dockerWorkspace}" , CF_PLUGIN_HOME: "${config.dockerWorkspace}" , STATUS_CODE: "${config.smokeTestStatusCode}" ]
) {
deployCfNative ( config )
}
}
} catch ( err ) {
deploySuccess = false
throw err
} finally {
if ( deploy ) {
reportToInflux ( script , config , deploySuccess , jenkinsUtils )
2018-07-30 09:28:24 +02:00
}
}
2019-01-18 09:25:22 +02:00
2018-07-30 09:28:24 +02:00
}
}
def findMtar ( ) {
2019-02-07 09:58:00 +02:00
def mtarFiles = findFiles ( glob: '**/*.mtar' )
2018-07-30 09:28:24 +02:00
if ( mtarFiles . length > 1 ) {
2019-02-07 09:58:00 +02:00
error "Found multiple *.mtar files, please specify file via mtaPath parameter! ${mtarFiles}"
2018-07-30 09:28:24 +02:00
}
if ( mtarFiles . length = = 1 ) {
return mtarFiles [ 0 ] . path
}
error 'No *.mtar file found!'
}
def deployCfNative ( config ) {
withCredentials ( [ usernamePassword (
credentialsId: config . cloudFoundry . credentialsId ,
passwordVariable: 'password' ,
usernameVariable: 'username'
) ] ) {
2018-11-27 12:47:44 +02:00
def deployCommand = selectCfDeployCommandForDeployType ( config )
2018-07-30 09:28:24 +02:00
if ( config . deployType = = 'blue-green' ) {
2018-10-17 11:01:09 +02:00
handleLegacyCfManifest ( config )
2018-07-30 09:28:24 +02:00
} else {
config . smokeTest = ''
}
2018-11-27 12:47:44 +02:00
def blueGreenDeployOptions = deleteOptionIfRequired ( config )
2018-07-30 09:28:24 +02:00
// check if appName is available
if ( config . cloudFoundry . appName = = null | | config . cloudFoundry . appName = = '' ) {
2018-11-07 11:39:30 +02:00
if ( config . deployType = = 'blue-green' ) {
error "[${STEP_NAME}] ERROR: Blue-green plugin requires app name to be passed (see https://github.com/bluemixgaragelondon/cf-blue-green-deploy/issues/27)"
}
2018-07-30 09:28:24 +02:00
if ( fileExists ( config . cloudFoundry . manifest ) ) {
def manifest = readYaml file: config . cloudFoundry . manifest
if ( ! manifest | | ! manifest . applications | | ! manifest . applications [ 0 ] . name )
error "[${STEP_NAME}] ERROR: No appName available in manifest ${config.cloudFoundry.manifest}."
} else {
error "[${STEP_NAME}] ERROR: No manifest file ${config.cloudFoundry.manifest} found."
}
}
2019-04-12 09:07:53 +02:00
def returnCode = sh returnStatus: true , script: "" " # ! /bin/ bash
2019-01-21 09:47:34 +02:00
set + x
2019-01-07 13:54:00 +02:00
set - e
2018-07-30 09:28:24 +02:00
export HOME = $ { config . dockerWorkspace }
cf login - u \ "${username}\" -p '${password}' -a ${config.cloudFoundry.apiEndpoint} -o \"${config.cloudFoundry.org}\" -s \"${config.cloudFoundry.space}\"
cf plugins
2018-11-27 12:47:44 +02:00
cf $ { deployCommand } $ { config . cloudFoundry . appName ? : '' } $ { blueGreenDeployOptions } - f '${config.cloudFoundry.manifest}' $ { config . smokeTest }
"" "
2019-04-12 09:07:53 +02:00
if ( returnCode ! = 0 ) {
error "[ERROR][${STEP_NAME}] The execution of the deploy command failed, see the log for details."
}
2019-01-30 11:07:00 +02:00
stopOldAppIfRunning ( config )
2018-07-30 09:28:24 +02:00
sh "cf logout"
}
}
2018-11-27 12:47:44 +02:00
private String selectCfDeployCommandForDeployType ( Map config ) {
if ( config . deployType = = 'blue-green' ) {
return 'blue-green-deploy'
} else {
return 'push'
}
}
private String deleteOptionIfRequired ( Map config ) {
boolean deleteOldInstance = ! config . keepOldInstance
if ( deleteOldInstance & & config . deployType = = 'blue-green' ) {
return '--delete-old-apps'
} else {
return ''
}
}
2019-01-30 11:07:00 +02:00
private void stopOldAppIfRunning ( Map config ) {
String oldAppName = "${config.cloudFoundry.appName}-old"
String cfStopOutputFileName = "${UUID.randomUUID()}-cfStopOutput.txt"
2018-11-27 12:47:44 +02:00
if ( config . keepOldInstance & & config . deployType = = 'blue-green' ) {
2019-01-30 11:07:00 +02:00
int cfStopReturncode = sh ( returnStatus: true , script: "cf stop $oldAppName &> $cfStopOutputFileName" )
if ( cfStopReturncode > 0 ) {
String cfStopOutput = readFile ( file: cfStopOutputFileName )
if ( ! cfStopOutput . contains ( "$oldAppName not found" ) ) {
error "Could not stop application $oldAppName. Error: $cfStopOutput"
}
}
2018-11-27 12:47:44 +02:00
}
}
2018-07-30 09:28:24 +02:00
def deployMta ( config ) {
if ( config . mtaExtensionDescriptor = = null ) config . mtaExtensionDescriptor = ''
if ( ! config . mtaExtensionDescriptor . isEmpty ( ) & & ! config . mtaExtensionDescriptor . startsWith ( '-e ' ) ) config . mtaExtensionDescriptor = "-e ${config.mtaExtensionDescriptor}"
def deployCommand = 'deploy'
2019-01-22 17:13:59 +02:00
if ( config . deployType = = 'blue-green' ) {
2018-07-30 09:28:24 +02:00
deployCommand = 'bg-deploy'
2019-01-22 17:13:59 +02:00
if ( config . mtaDeployParameters . indexOf ( '--no-confirm' ) < 0 ) {
config . mtaDeployParameters + = ' --no-confirm'
}
}
2018-07-30 09:28:24 +02:00
withCredentials ( [ usernamePassword (
credentialsId: config . cloudFoundry . credentialsId ,
passwordVariable: 'password' ,
usernameVariable: 'username'
) ] ) {
echo "[${STEP_NAME}] Deploying MTA (${config.mtaPath}) with following parameters: ${config.mtaExtensionDescriptor} ${config.mtaDeployParameters}"
2019-04-12 09:07:53 +02:00
def returnCode = sh returnStatus: true , script: "" " # ! /bin/ bash
2018-07-30 09:28:24 +02:00
export HOME = $ { config . dockerWorkspace }
set + x
2019-01-07 13:54:00 +02:00
set - e
2018-07-30 09:28:24 +02:00
cf api $ { config . cloudFoundry . apiEndpoint }
cf login - u $ { username } - p '${password}' - a $ { config . cloudFoundry . apiEndpoint } - o \ "${config.cloudFoundry.org}\" -s \"${config.cloudFoundry.space}\"
cf plugins
cf $ { deployCommand } $ { config . mtaPath } $ { config . mtaDeployParameters } $ { config . mtaExtensionDescriptor } "" "
2019-04-12 09:07:53 +02:00
if ( returnCode ! = 0 ) {
error "[ERROR][${STEP_NAME}] The execution of the deploy command failed, see the log for details."
}
2018-07-30 09:28:24 +02:00
sh "cf logout"
}
}
2018-10-17 11:01:09 +02:00
def handleLegacyCfManifest ( config ) {
def manifest = readYaml file: config . cloudFoundry . manifest
String originalManifest = manifest . toString ( )
manifest = CfManifestUtils . transform ( manifest )
String transformedManifest = manifest . toString ( )
if ( originalManifest ! = transformedManifest ) {
echo "" " The file $ { config . cloudFoundry . manifest } is not compatible with the Cloud Foundry blue - green deployment plugin . Re - writing inline .
See this issue if you are interested in the background: https: //github.com/cloudfoundry/cli/issues/1445.\n
Original manifest file content: $originalManifest \ n
Transformed manifest file content: $transformedManifest "" "
sh "rm ${config.cloudFoundry.manifest}"
writeYaml file: config . cloudFoundry . manifest , data: manifest
}
}
2019-01-18 09:25:22 +02:00
private void reportToInflux ( script , config , deploySuccess , JenkinsUtils jenkinsUtils ) {
def deployUser = ''
withCredentials ( [ usernamePassword (
credentialsId: config . cloudFoundry . credentialsId ,
passwordVariable: 'password' ,
usernameVariable: 'username'
) ] ) {
deployUser = username
}
def timeFinished = new Date ( ) . format ( 'MMM dd, yyyy - HH:mm:ss' )
def triggerCause = jenkinsUtils . isJobStartedByUser ( ) ? 'USER' : ( jenkinsUtils . isJobStartedByTimer ( ) ? 'TIMER' : 'OTHER' )
def deploymentData = [ deployment_data: [
artifactUrl: 'n/a' , //might be added later on during pipeline run (written to commonPipelineEnvironment)
deployTime: timeFinished ,
jobTrigger: triggerCause
] ]
def deploymentDataTags = [ deployment_data: [
artifactVersion: script . commonPipelineEnvironment . getArtifactVersion ( ) ,
deployUser: deployUser ,
deployResult: deploySuccess ? 'SUCCESS' : 'FAILURE' ,
cfApiEndpoint: config . cloudFoundry . apiEndpoint ,
cfOrg: config . cloudFoundry . org ,
cfSpace: config . cloudFoundry . space ,
] ]
2019-01-18 16:33:36 +02:00
influxWriteData script: script , customData: [ : ] , customDataTags: [ : ] , customDataMap: deploymentData , customDataMapTags: deploymentDataTags
2019-01-18 09:25:22 +02:00
}