2020-02-25 11:20:15 +02:00
import com.sap.piper.BashUtils
import com.sap.piper.CfManifestUtils
import com.sap.piper.ConfigurationHelper
2019-03-28 15:38:25 +02:00
import com.sap.piper.GenerateDocumentation
2020-02-25 11:20:15 +02:00
import com.sap.piper.JenkinsUtils
2018-07-30 09:28:24 +02:00
import com.sap.piper.Utils
import groovy.transform.Field
2020-02-25 11:20:15 +02:00
import static com . sap . piper . Prerequisites . checkScript
2018-11-29 10:54:05 +02:00
@Field String STEP_NAME = getClass ( ) . getName ( )
2018-10-25 14:24:17 +02:00
2020-04-22 11:50:30 +02:00
@Field Set GENERAL_CONFIG_KEYS = [
2020-10-15 19:03:16 +02:00
/ * *
* This is set in the common pipeline environment by the build tool , e . g . during the mtaBuild step .
* /
2020-07-06 16:16:48 +02:00
'buildTool' ,
2018-07-30 09:28:24 +02:00
'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' ,
2019-09-24 08:49:25 +02:00
/ * *
* Defines the manifest variables Yaml files to be used to replace variable references in manifest . This parameter
* is optional and will default to ` [ "manifest-variables.yml" ] ` . This can be used to set variable files like it
* is provided by ` cf push - - vars - file < file > ` .
*
* If the manifest is present and so are all variable files , a variable substitution will be triggered that uses
* the ` cfManifestSubstituteVariables ` step before deployment . The format of variable references follows the
* [ Cloud Foundry standard ] ( https: //docs.cloudfoundry.org/devguide/deploy-apps/manifest-attributes.html#variable-substitution).
* @parentConfigKey cloudFoundry
* /
'manifestVariablesFiles' ,
/ * *
* Defines a ` List ` of variables as key - value ` Map ` objects used for variable substitution within the file given by ` manifest ` .
* Defaults to an empty list , if not specified otherwise . This can be used to set variables like it is provided
* by ` cf push - - var key = value ` .
*
* The order of the maps of variables given in the list is relevant in case there are conflicting variable names and values
* between maps contained within the list . In case of conflicts , the last specified map in the list will win .
*
* Though each map entry in the list can contain more than one key - value pair for variable substitution , it is recommended
* to stick to one entry per map , and rather declare more maps within the list . The reason is that
* if a map in the list contains more than one key - value entry , and the entries are conflicting , the
* conflict resolution behavior is undefined ( since map entries have no sequence ) .
*
* Note: variables defined via ` manifestVariables ` always win over conflicting variables defined via any file given
* by ` manifestVariablesFiles ` - no matter what is declared before . This is the same behavior as can be
* observed when using ` cf push - - var ` in combination with ` cf push - - vars - file ` .
2020-10-26 17:59:51 +02:00
* @parentConfigKey cloudFoundry
2019-09-24 08:49:25 +02:00
* /
'manifestVariables' ,
2019-03-28 15:38:25 +02:00
/ * *
* Cloud Foundry target organization .
* @parentConfigKey cloudFoundry
* /
'org' ,
/ * *
* Cloud Foundry target space .
* @parentConfigKey cloudFoundry
* /
'space' ,
/ * *
* Defines the tool which should be used for deployment .
2020-07-06 16:16:48 +02:00
* If it is not set it will be inferred automatically based on the buildTool , i . e . , for MTA projects ` mtaDeployPlugin ` will be used and ` cf_native ` for other types of projects .
2019-03-28 15:38:25 +02:00
* @possibleValues 'cf_native' , 'mtaDeployPlugin'
* /
2018-07-30 09:28:24 +02:00
'deployTool' ,
2019-03-28 15:38:25 +02:00
/ * *
2020-12-11 14:11:36 +02:00
* Defines the type of deployment , either ` standard ` deployment , which results in a system downtime , or a zero - downtime ` blue - green ` deployment .
* If 'cf_native' as deployTool and 'blue-green' as deployType is used in combination , your manifest . yaml may only contain one application .
2020-02-07 19:46:03 +02:00
* If this application has the option 'no-route' active the deployType will be changed to 'standard' .
2019-03-28 15:38:25 +02:00
* @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' ,
/ * *
2019-10-02 13:28:54 +02:00
* Additional parameters passed to cf native deployment command .
* /
'cfNativeDeployParameters' ,
/ * *
* Addition command line options for cf api command .
* No escaping / quoting is performed . Not recommanded for productive environments .
* /
'apiParameters' ,
/ * *
* Addition command line options for cf login command .
* No escaping / quoting is performed . Not recommanded for productive environments .
* /
'loginParameters' ,
/ * *
* Additional parameters passed to mta deployment command .
2019-03-28 15:38:25 +02:00
* /
2018-07-30 09:28:24 +02:00
'mtaDeployParameters' ,
2020-07-06 16:16:48 +02:00
/ * *
* Defines a map of credentials that need to be replaced in the ` mtaExtensionDescriptor ` .
* This map needs to be created as ` value - to - be - replaced ` : ` id - of - a - credential - in - jenkins `
* /
'mtaExtensionCredentials' ,
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
/ * *
2020-07-15 08:32:05 +02:00
* Defines the path to * . mtar for deployment with the mtaDeployPlugin . If not specified , it will use the mta file created in mtaBuild or search for an mtar file in the workspace .
2019-03-28 15:38:25 +02:00
* /
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 .
* /
2019-09-27 16:10:43 +02:00
'smokeTestStatusCode' ,
2019-10-02 13:28:54 +02:00
/ * *
2020-02-25 11:20:15 +02:00
* Provides more output . May reveal sensitive information .
* @possibleValues true , false
* /
2019-09-27 16:10:43 +02:00
'verbose' ,
2020-02-25 11:20:15 +02:00
/ * *
* Docker image deployments are supported ( via manifest file in general ) [ https: //docs.cloudfoundry.org/devguide/deploy-apps/manifest-attributes.html#docker].
* If no manifest is used , this parameter defines the image to be deployed . The specified name of the image is
* passed to the ` - - docker - image ` parameter of the cf CLI and must adhere it ' s naming pattern ( e . g . REPO / IMAGE: TAG ) .
* See ( cf CLI documentation ) [ https: //docs.cloudfoundry.org/devguide/deploy-apps/push-docker.html] for details.
*
* Note: The used Docker registry must be visible for the targeted Cloud Foundry instance .
* /
'deployDockerImage' ,
/ * *
* If the specified image in ` deployDockerImage ` is contained in a Docker registry , which requires authorization
* this defines the credentials to be used .
* /
'dockerCredentialsId' ,
2020-08-24 18:10:45 +02:00
/ * *
* Toggle to activate the new go - implementation of the step . Off by default .
* @possibleValues true , false
* /
'useGoStep' ,
2019-03-28 15:38:25 +02:00
]
2020-04-22 11:50:30 +02:00
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
2019-03-28 15:38:25 +02:00
2020-07-06 12:16:06 +02:00
@Field Map CONFIG_KEY_COMPATIBILITY = [
cloudFoundry: [
apiEndpoint: 'cfApiEndpoint' ,
appName: 'cfAppName' ,
credentialsId: 'cfCredentialsId' ,
manifest: 'cfManifest' ,
manifestVariablesFiles: 'cfManifestVariablesFiles' ,
manifestVariables: 'cfManifestVariables' ,
org: 'cfOrg' ,
space: 'cfSpace' ,
] ,
mtaExtensionDescriptor: 'cloudFoundry/mtaExtensionDescriptor'
]
2018-10-25 14:24:17 +02:00
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
2020-10-15 18:53:45 +02:00
* 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 .
2019-03-28 15:38:25 +02:00
*
* ! ! ! note
2020-10-15 18:53:45 +02:00
* Cloud Foundry supports the deployment of multiple applications using a single manifest file .
* This option is supported with Piper .
2019-03-28 15:38:25 +02:00
*
* 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 ) {
2020-08-26 15:32:58 +02:00
final script = checkScript ( this , parameters ) ? : this
2019-01-18 09:25:22 +02:00
def utils = parameters . juStabUtils ? : new Utils ( )
def jenkinsUtils = parameters . jenkinsUtilsStub ? : new JenkinsUtils ( )
2020-08-26 15:32:58 +02:00
String stageName = parameters . stageName ? : env . STAGE_NAME
2018-07-30 09:28:24 +02:00
2018-10-17 11:05:20 +02:00
Map config = ConfigurationHelper . newInstance ( this )
2020-08-26 15:32:58 +02:00
. loadStepDefaults ( [ : ] , stageName )
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 )
2020-08-26 15:32:58 +02:00
. mixinStageConfig ( script . commonPipelineEnvironment , stageName , STEP_CONFIG_KEYS , CONFIG_KEY_COMPATIBILITY )
2018-09-06 16:45:30 +02:00
. mixin ( parameters , PARAMETER_KEYS , CONFIG_KEY_COMPATIBILITY )
2020-07-06 16:16:48 +02:00
. addIfEmpty ( 'buildTool' , script . commonPipelineEnvironment . getBuildTool ( ) )
. dependingOn ( 'buildTool' ) . mixin ( 'deployTool' )
2018-07-30 09:28:24 +02:00
. dependingOn ( 'deployTool' ) . mixin ( 'dockerImage' )
. dependingOn ( 'deployTool' ) . mixin ( 'dockerWorkspace' )
. withMandatoryProperty ( 'cloudFoundry/org' )
. withMandatoryProperty ( 'cloudFoundry/space' )
2020-10-13 14:14:47 +02:00
. withMandatoryProperty ( 'cloudFoundry/credentialsId' , null , { c - > return ! c . containsKey ( 'vaultAppRoleTokenCredentialsId' ) | | ! c . containsKey ( 'vaultAppRoleSecretTokenCredentialsId' ) } )
2018-07-30 09:28:24 +02:00
. use ( )
2020-08-24 18:10:45 +02:00
if ( config . useGoStep = = true ) {
2020-12-09 15:31:07 +02:00
utils . unstashAll ( [ "deployDescriptor" ] )
2020-08-24 18:10:45 +02:00
List credentials = [
[ type: 'usernamePassword' , id: 'cfCredentialsId' , env: [ 'PIPER_username' , 'PIPER_password' ] ] ,
[ type: 'usernamePassword' , id: 'dockerCredentialsId' , env: [ 'PIPER_dockerUsername' , 'PIPER_dockerPassword' ] ]
]
piperExecuteBin ( parameters , STEP_NAME , 'metadata/cloudFoundryDeploy.yaml' , credentials )
return
}
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
2020-07-06 11:45:36 +02:00
// validate cf app name to avoid a failing deployment due to invalid chars
if ( config . cloudFoundry . appName ) {
String appName = config . cloudFoundry . appName . toString ( )
boolean isValidCfAppName = appName . matches ( "^[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9]\$" )
if ( ! isValidCfAppName ) {
echo "WARNING: Your application name $appName contains non-alphanumeric characters which may lead to errors in the future, as they are not supported by CloudFoundry.\n" +
"For more details please visit https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings"
// Underscore in the app name will lead to errors because cf uses the appname as part of the url which may not contain underscores
if ( appName . contains ( "_" ) ) {
error ( "Your application name $appName contains a '_' (underscore) which is not allowed, only letters, dashes and numbers can be used. " +
"Please change the name to fit this requirement.\n" +
"For more details please visit https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings." )
}
if ( appName . startsWith ( "-" ) | | appName . endsWith ( "-" ) ) {
error ( "Your application name $appName contains a starts or ends with a '-' (dash) which is not allowed, only letters, dashes and numbers can be used. " +
"Please change the name to fit this requirement.\n" +
"For more details please visit https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings." )
}
}
}
2019-09-20 09:57:28 +02:00
boolean deployTriggered = false
2019-01-18 09:25:22 +02:00
boolean deploySuccess = true
try {
if ( config . deployTool = = 'mtaDeployPlugin' ) {
2019-09-20 09:57:28 +02:00
deployTriggered = true
handleMTADeployment ( config , script )
2018-07-30 09:28:24 +02:00
}
2019-09-20 09:57:28 +02:00
else if ( config . deployTool = = 'cf_native' ) {
deployTriggered = true
handleCFNativeDeployment ( config , script )
}
else {
deployTriggered = false
echo "[${STEP_NAME}] WARNING! Found unsupported deployTool. Skipping deployment."
2019-01-18 09:25:22 +02:00
}
} catch ( err ) {
deploySuccess = false
throw err
} finally {
2019-09-20 09:57:28 +02:00
if ( deployTriggered ) {
2019-01-18 09:25:22 +02:00
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
}
}
2019-09-20 09:57:28 +02:00
private void handleMTADeployment ( Map config , script ) {
// set default mtar path
2020-07-15 08:32:05 +02:00
if ( ! config . mtaPath ) {
config . mtaPath = script . commonPipelineEnvironment . mtarFilePath ? : findMtar ( )
}
2019-09-20 09:57:28 +02:00
dockerExecute ( script: script , dockerImage: config . dockerImage , dockerWorkspace: config . dockerWorkspace , stashContent: config . stashContent ) {
deployMta ( config )
}
}
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 ) {
2020-02-07 19:46:03 +02:00
error "[${STEP_NAME}] 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
}
2020-02-07 19:46:03 +02:00
error "[${STEP_NAME}] No *.mtar file found!"
2018-07-30 09:28:24 +02:00
}
2020-02-25 11:20:15 +02:00
def deployMta ( config ) {
2020-07-06 16:16:48 +02:00
String mtaExtensionDescriptorParam = ''
if ( config . mtaExtensionDescriptor ) {
if ( ! fileExists ( config . mtaExtensionDescriptor ) ) {
error "[${STEP_NAME}] The mta extension descriptor file ${config.mtaExtensionDescriptor} does not exist at the configured location."
}
mtaExtensionDescriptorParam = "-e ${config.mtaExtensionDescriptor}"
if ( config . mtaExtensionCredentials ) {
handleMtaExtensionCredentials ( config )
}
}
2019-09-20 09:57:28 +02:00
def deployCommand = 'deploy'
if ( config . deployType = = 'blue-green' ) {
deployCommand = 'bg-deploy'
if ( config . mtaDeployParameters . indexOf ( '--no-confirm' ) < 0 ) {
config . mtaDeployParameters + = ' --no-confirm'
}
}
2020-07-06 16:16:48 +02:00
def deployStatement = "cf ${deployCommand} ${config.mtaPath} ${config.mtaDeployParameters} ${mtaExtensionDescriptorParam}"
2019-10-22 13:53:08 +02:00
def apiStatement = "cf api ${config.cloudFoundry.apiEndpoint} ${config.apiParameters}"
2019-10-02 13:28:54 +02:00
2020-07-06 16:16:48 +02:00
echo "[${STEP_NAME}] Deploying MTA (${config.mtaPath}) with following parameters: ${mtaExtensionDescriptorParam} ${config.mtaDeployParameters}"
try {
deploy ( apiStatement , deployStatement , config , null )
} finally {
if ( config . mtaExtensionCredentials & & config . mtaExtensionDescriptor & & fileExists ( config . mtaExtensionDescriptor ) ) {
sh "mv --force ${config.mtaExtensionDescriptor}.original ${config.mtaExtensionDescriptor} || echo 'The file ${config.mtaExtensionDescriptor}.original could not be renamed.'"
}
}
}
private void handleMtaExtensionCredentials ( Map < ? , ? > config ) {
echo "[${STEP_NAME}] Modifying ${config.mtaExtensionDescriptor}. Adding credential values from Jenkins."
sh "cp ${config.mtaExtensionDescriptor} ${config.mtaExtensionDescriptor}.original"
Map mtaExtensionCredentials = config . mtaExtensionCredentials
String fileContent = ''
try {
fileContent = readFile config . mtaExtensionDescriptor
} catch ( Exception e ) {
error ( "[${STEP_NAME}] Unable to read mta extension file ${config.mtaExtensionDescriptor}. (${e.getMessage()})" )
}
mtaExtensionCredentials . each { key , credentialsId - >
withCredentials ( [ string ( credentialsId: credentialsId , variable: 'mtaExtensionCredential' ) ] ) {
fileContent = fileContent . replace ( '<%= ' + key . toString ( ) + ' %>' , mtaExtensionCredential . toString ( ) )
}
}
writeFile file: config . mtaExtensionDescriptor , text: fileContent
2018-07-30 09:28:24 +02:00
}
2020-02-07 19:46:03 +02:00
private checkAndUpdateDeployTypeForNotSupportedManifest ( Map config ) {
String manifestFile = config . cloudFoundry . manifest ? : 'manifest.yml'
if ( config . deployType = = 'blue-green' & & fileExists ( manifestFile ) ) {
Map manifest = readYaml file: manifestFile
List applications = manifest . applications
if ( applications ) {
if ( applications . size ( ) > 1 ) {
error "[${STEP_NAME}] Your manifest contains more than one application. For blue green deployments your manifest file may contain only one application."
}
if ( applications . size = = 1 & & applications [ 0 ] [ 'no-route' ] ) {
echo '[WARNING] Blue green deployment is not possible for application without route. Using deployment type "standard" instead.'
config . deployType = 'standard'
}
}
}
}
2019-09-20 09:57:28 +02:00
private void handleCFNativeDeployment ( Map config , script ) {
config . smokeTest = ''
2020-02-07 19:46:03 +02:00
checkAndUpdateDeployTypeForNotSupportedManifest ( config )
2018-11-27 12:47:44 +02:00
if ( config . deployType = = 'blue-green' ) {
2020-02-25 11:20:15 +02:00
prepareBlueGreenCfNativeDeploy ( config , script )
2018-11-27 12:47:44 +02:00
} else {
2019-09-20 09:57:28 +02:00
prepareCfPushCfNativeDeploy ( config )
2018-11-27 12:47:44 +02:00
}
2019-09-20 09:57:28 +02:00
echo "[${STEP_NAME}] CF native deployment (${config.deployType}) with:"
echo "[${STEP_NAME}] - cfAppName=${config.cloudFoundry.appName}"
echo "[${STEP_NAME}] - cfManifest=${config.cloudFoundry.manifest}"
2020-02-25 11:20:15 +02:00
echo "[${STEP_NAME}] - cfManifestVariables=${config.cloudFoundry.manifestVariables ?: 'none specified'}"
echo "[${STEP_NAME}] - cfManifestVariablesFiles=${config.cloudFoundry.manifestVariablesFiles ?: 'none specified'}"
echo "[${STEP_NAME}] - cfdeployDockerImage=${config.deployDockerImage ?: 'none specified'}"
echo "[${STEP_NAME}] - cfdockerCredentialsId=${config.dockerCredentialsId ?: 'none specified'}"
2019-09-20 09:57:28 +02:00
echo "[${STEP_NAME}] - smokeTestScript=${config.smokeTestScript}"
checkIfAppNameIsAvailable ( config )
2020-02-25 11:20:15 +02:00
def dockerCredentials = [ ]
if ( config . dockerCredentialsId ! = null & & config . dockerCredentialsId ! = '' ) {
dockerCredentials . add ( usernamePassword (
credentialsId: config . dockerCredentialsId ,
passwordVariable: 'dockerPassword' ,
usernameVariable: 'dockerUsername'
) )
}
withCredentials ( dockerCredentials ) {
dockerExecute (
script: script ,
dockerImage: config . dockerImage ,
dockerWorkspace: config . dockerWorkspace ,
stashContent: config . stashContent ,
dockerEnvVars: [
CF_HOME : "${config.dockerWorkspace}" ,
CF_PLUGIN_HOME : "${config.dockerWorkspace}" ,
// if the Docker registry requires authentication the DOCKER_PASSWORD env variable must be set
2020-04-07 09:10:38 +02:00
CF_DOCKER_PASSWORD: "${dockerCredentials.isEmpty() ? '' : dockerPassword}" ,
2020-02-25 11:20:15 +02:00
STATUS_CODE : "${config.smokeTestStatusCode}"
]
) {
2020-04-07 09:10:38 +02:00
if ( dockerCredentials . size ( ) > 0 ) {
config . dockerUsername = dockerUsername
}
2020-02-25 11:20:15 +02:00
deployCfNative ( config )
}
2018-11-27 12:47:44 +02:00
}
}
2019-09-24 08:49:25 +02:00
private prepareBlueGreenCfNativeDeploy ( config , script ) {
2019-09-20 09:57:28 +02:00
if ( config . smokeTestScript = = 'blueGreenCheckScript.sh' ) {
writeFile file: config . smokeTestScript , text: libraryResource ( config . smokeTestScript )
}
2019-01-30 11:07:00 +02:00
2020-04-22 21:59:14 +02:00
if ( config . smokeTestScript ) {
config . smokeTest = '--smoke-test $(pwd)/' + config . smokeTestScript
sh "chmod +x ${config.smokeTestScript}"
}
2019-01-30 11:07:00 +02:00
2019-09-20 09:57:28 +02:00
config . deployCommand = 'blue-green-deploy'
2019-09-24 08:49:25 +02:00
cfManifestSubstituteVariables (
script: script ,
manifestFile: config . cloudFoundry . manifest ,
manifestVariablesFiles: config . cloudFoundry . manifestVariablesFiles ,
manifestVariables: config . cloudFoundry . manifestVariables
)
2019-09-20 09:57:28 +02:00
handleLegacyCfManifest ( config )
if ( ! config . keepOldInstance ) {
config . deployOptions = '--delete-old-apps'
}
}
2019-01-30 11:07:00 +02:00
2019-09-20 09:57:28 +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
2018-11-27 12:47:44 +02:00
}
}
2019-09-20 09:57:28 +02:00
private prepareCfPushCfNativeDeploy ( config ) {
config . deployCommand = 'push'
2019-09-24 08:49:25 +02:00
config . deployOptions = "${varOptions(config)}${varFileOptions(config)}"
}
private varOptions ( Map config ) {
String varPart = ''
if ( config . cloudFoundry . manifestVariables ) {
if ( ! ( config . cloudFoundry . manifestVariables in List ) ) {
error "[${STEP_NAME}] ERROR: Parameter config.cloudFoundry.manifestVariables is not a List!"
}
config . cloudFoundry . manifestVariables . each {
if ( ! ( it in Map ) ) {
error "[${STEP_NAME}] ERROR: Parameter config.cloudFoundry.manifestVariables.$it is not a Map!"
}
it . keySet ( ) . each { varKey - >
String varValue = BashUtils . quoteAndEscape ( it . get ( varKey ) . toString ( ) )
varPart + = " --var $varKey=$varValue"
}
}
}
if ( varPart ) echo "We will add the following string to the cf push call:$varPart !"
return varPart
}
private String varFileOptions ( Map config ) {
String varFilePart = ''
if ( config . cloudFoundry . manifestVariablesFiles ) {
if ( ! ( config . cloudFoundry . manifestVariablesFiles in List ) ) {
error "[${STEP_NAME}] ERROR: Parameter config.cloudFoundry.manifestVariablesFiles is not a List!"
}
config . cloudFoundry . manifestVariablesFiles . each {
if ( fileExists ( it ) ) {
varFilePart + = " --vars-file ${BashUtils.quoteAndEscape(it)}"
} else {
echo "[${STEP_NAME}] [WARNING] We skip adding not-existing file '$it' as a vars-file to the cf create-service-push call"
}
}
}
if ( varFilePart ) echo "We will add the following string to the cf push call:$varFilePart !"
return varFilePart
2019-09-20 09:57:28 +02:00
}
2018-07-30 09:28:24 +02:00
2019-09-20 09:57:28 +02:00
private checkIfAppNameIsAvailable ( config ) {
if ( config . cloudFoundry . appName = = null | | config . cloudFoundry . appName = = '' ) {
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)"
}
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-01-22 17:13:59 +02:00
}
}
2019-09-20 09:57:28 +02:00
}
2018-07-30 09:28:24 +02:00
2020-02-25 11:20:15 +02:00
def deployCfNative ( config ) {
// the deployStatement is complex and has lot of options; using a list and findAll allows to put each option
// as a single list element; if a option is not set (= null or '') this removed before every element is joined
// via a single whitespace; results in a single line deploy statement
def deployStatement = [
'cf' ,
config . deployCommand ,
config . cloudFoundry . appName ,
config . deployOptions ,
config . cloudFoundry . manifest ? "-f '${config.cloudFoundry.manifest}'" : null ,
2020-04-07 09:10:38 +02:00
config . deployDockerImage & & config . deployType ! = 'blue-green' ? "--docker-image ${config.deployDockerImage}" : null ,
config . dockerUsername & & config . deployType ! = 'blue-green' ? "--docker-username ${dockerUsername}" : null ,
2020-02-25 11:20:15 +02:00
config . smokeTest ,
config . cfNativeDeployParameters
] . findAll { s - > s ! = null & & s ! = '' } . join ( " " )
deploy ( null , deployStatement , config , { c - > stopOldAppIfRunning ( c ) } )
2019-10-22 13:53:08 +02:00
}
2020-02-25 11:20:15 +02:00
private deploy ( String cfApiStatement , String cfDeployStatement , config , Closure postDeployAction ) {
2019-10-22 13:53:08 +02:00
2018-07-30 09:28:24 +02:00
withCredentials ( [ usernamePassword (
credentialsId: config . cloudFoundry . credentialsId ,
passwordVariable: 'password' ,
usernameVariable: 'username'
) ] ) {
2019-09-27 16:10:43 +02:00
def cfTraceFile = 'cf.log'
2019-10-02 13:28:54 +02:00
def deployScript = "" " # ! /bin/ bash
2018-07-30 09:28:24 +02:00
set + x
2019-01-07 13:54:00 +02:00
set - e
2019-09-20 09:57:28 +02:00
export HOME = $ { config . dockerWorkspace }
2019-09-27 16:10:43 +02:00
export CF_TRACE = $ { cfTraceFile }
2019-10-22 13:53:08 +02:00
$ { cfApiStatement ? : '' }
2019-10-02 13:28:54 +02:00
cf login - u \ "${username}\" -p '${password}' -a ${config.cloudFoundry.apiEndpoint} -o \"${config.cloudFoundry.org}\" -s \"${config.cloudFoundry.space}\" $ { config . loginParameters }
2018-07-30 09:28:24 +02:00
cf plugins
2019-10-22 13:53:08 +02:00
$ { cfDeployStatement }
2019-09-20 09:57:28 +02:00
"" "
2020-02-25 11:20:15 +02:00
if ( config . verbose ) {
2019-10-02 13:28:54 +02:00
// Password contained in output below is hidden by withCredentials
echo "[INFO][${STEP_NAME}] Executing command: '${deployScript}'."
}
2019-10-22 13:53:08 +02:00
2020-02-14 14:41:36 +02:00
try {
sh deployScript
} catch ( e ) {
handleCfCliLog ( cfTraceFile )
2019-09-27 16:10:43 +02:00
2019-09-20 09:57:28 +02:00
error "[${STEP_NAME}] ERROR: The execution of the deploy command failed, see the log for details."
2019-04-12 09:07:53 +02:00
}
2020-02-25 11:20:15 +02:00
if ( config . verbose ) {
2020-02-14 14:41:36 +02:00
handleCfCliLog ( cfTraceFile )
}
2019-10-22 13:53:08 +02:00
2020-02-25 11:20:15 +02:00
if ( postDeployAction ) postDeployAction ( config )
2019-10-22 13:53:08 +02:00
2018-07-30 09:28:24 +02:00
sh "cf logout"
}
}
2018-10-17 11:01:09 +02:00
2020-02-14 14:41:36 +02:00
private void handleCfCliLog ( String logFile ) {
if ( fileExists ( file: logFile ) ) {
echo '### START OF CF CLI TRACE OUTPUT ###'
// Would be nice to inline the two next lines, but that is not understood by the test framework
def cfTrace = readFile ( file: logFile )
echo cfTrace
echo '### END OF CF CLI TRACE OUTPUT ###'
} else {
echo "No trace file found at '${logFile}'"
}
}
2019-09-20 09:57:28 +02:00
private void stopOldAppIfRunning ( Map config ) {
String oldAppName = "${config.cloudFoundry.appName}-old"
String cfStopOutputFileName = "${UUID.randomUUID()}-cfStopOutput.txt"
if ( config . keepOldInstance & & config . deployType = = 'blue-green' ) {
2020-02-14 14:41:36 +02:00
try {
sh "cf stop $oldAppName &> $cfStopOutputFileName"
} catch ( e ) {
2019-09-20 09:57:28 +02:00
String cfStopOutput = readFile ( file: cfStopOutputFileName )
if ( ! cfStopOutput . contains ( "$oldAppName not found" ) ) {
error "[${STEP_NAME}] ERROR: Could not stop application $oldAppName. Error: $cfStopOutput"
}
}
2018-10-17 11:01:09 +02:00
}
}
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
}