1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00
sap-jenkins-library/vars/neoDeploy.groovy
Srinikitha Kondreddy 0234e4908c
Invalidate cache for neo deployments (#2209)
* Add additional parameters for invalidating cache

* Fetch bearer token

* Fetch x-csrf token

* Add echo for testing

* Add echo

* Add another echo

* Check status

* Debug

* Clean up

* Throw exception

* Code review changes

* Review changes

* Add test

* Fix tests

* Fetch bearer token

* Fetch x-csrf token

* Add echo for testing

* Add echo

* Add another echo

* Check status

* Debug

* Resolve conflicts

* Resolve conflicts

* Code review changes

* Review changes

* fix conflicts

* Fix indent

* Add new parameter to define portal landscape region

* Add default value for new param

* Fix test

* Remove example

Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>

* Check cache invalidation for html5 apps

* Add nesting

Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>

* Add docu for invalidating cache for html5 apps (#2306)

* Add docu for invalidating cache for html5 apps

* Improve content

* Docu review changes

* Remove heading

Co-authored-by: Sarah Lendle <44202907+SarahLendle@users.noreply.github.com>

* Update documentation/docs/steps/neoDeploy.md

Co-authored-by: Sarah Lendle <44202907+SarahLendle@users.noreply.github.com>

Co-authored-by: Sarah Lendle <44202907+SarahLendle@users.noreply.github.com>

Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>
Co-authored-by: Sarah Lendle <44202907+SarahLendle@users.noreply.github.com>
2020-11-09 12:15:00 +01:00

408 lines
16 KiB
Groovy

import com.sap.piper.GenerateDocumentation
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import com.sap.piper.StepAssertions
import com.sap.piper.tools.neo.DeployMode
import com.sap.piper.tools.neo.NeoCommandHelper
import com.sap.piper.tools.neo.WarAction
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
'neo',
/**
* The SAP Cloud Platform account to deploy to.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'account',
/**
* Name of the application you want to manage, configure, or deploy.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'application',
/**
* The Jenkins credentials containing user and password used for SAP CP deployment.
* @parentConfigKey neo
*/
'credentialsId',
/**
* Map of environment variables in the form of KEY: VALUE.
* @parentConfigKey neo
*/
'environment',
/**
* The SAP Cloud Platform host to deploy to.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'host',
/**
* The path to the .properties file in which all necessary deployment properties for the application are defined.
* @parentConfigKey neo
* @mandatory for deployMode=warPropertiesFile
*/
'propertiesFile',
/**
* Name of SAP Cloud Platform application runtime.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'runtime',
/**
* Version of SAP Cloud Platform application runtime.
* @parentConfigKey neo
* @mandatory for deployMode=warParams
*/
'runtimeVersion',
/**
* Compute unit (VM) size. Acceptable values: lite, pro, prem, prem-plus.
* @parentConfigKey neo
*/
'size',
/**
* String of VM arguments passed to the JVM.
* @parentConfigKey neo
*/
'vmArguments',
/**
* Boolean to enable/disable invalidating the cache after deployment.
* @possibleValues `true`, `false`
* @parentConfigKey neo
*/
'invalidateCache',
/**
* Portal landscape region subscribed to in SAP Cloud Platform.
* @parentConfigKey neo
*/
'portalLandscape',
/**
* UsernamePassword type credential containing SAP Cloud Platform OAuth client ID and client secret.
* @parentConfigKey neo
*/
'oauthCredentialId',
/**
* Site ID of the SAP Fiori Launchpad containing the SAP Fiori app. If not set, the cache of the default site, as defined in the Portal service, is invalidated.
* @parentConfigKey neo
*/
'siteId'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* The deployment mode which should be used. Available options are:
* *`'mta'` - default,
* *`'warParams'` - deploying WAR file and passing all the deployment parameters via the function call,
* *`'warPropertiesFile'` - deploying WAR file and putting all the deployment parameters in a .properties file.
* @possibleValues 'mta', 'warParams', 'warPropertiesFile'
*/
'deployMode',
/**
* @see dockerExecute
*/
'dockerEnvVars',
/**
* @see dockerExecute
*/
'dockerImage',
/**
* @see dockerExecute
*/
'dockerOptions',
/**
* Extension files. Provided to the neo command via parameter `--extensions` (`-e`). Only valid for deploy mode `mta`.
*/
'extensions',
/**
* The path to the archive for deployment to SAP CP. If not provided the following defaults are used based on the deployMode:
* *`'mta'` - The `mtarFilePath` from common pipeline environment is used instead.
* *`'warParams'` and `'warPropertiesFile'` - The following template will be used "<mavenDeploymentModule>/target/<artifactId>.<packaging>"
*/
'source',
/**
* Path to the maven module which contains the deployment artifact.
*/
'mavenDeploymentModule'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([
/**
* Action mode when using WAR file mode. Available options are `deploy` (default) and `rolling-update` which performs update of an application without downtime in one go.
* @possibleValues 'deploy', 'rolling-update'
*/
'warAction'
])
/**
* Deploys an Application to SAP Cloud Platform (SAP CP) using the SAP Cloud Platform Console Client (Neo Java Web SDK).
*/
@GenerateDocumentation
void call(parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
def script = checkScript(this, parameters) ?: this
def utils = parameters.utils ?: new Utils()
String stageName = parameters.stageName ?: env.STAGE_NAME
// load default & individual configuration
ConfigurationHelper configHelper = ConfigurationHelper.newInstance(this)
.loadStepDefaults([:], stageName)
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.collectValidationFailures()
.withPropertyInValues('deployMode', DeployMode.stringValues())
Map configuration = configHelper.use()
DeployMode deployMode = DeployMode.fromString(configuration.deployMode)
def isWarParamsDeployMode = { deployMode == DeployMode.WAR_PARAMS },
isNotWarPropertiesDeployMode = {deployMode != DeployMode.WAR_PROPERTIES_FILE}
if(!configuration.source){
configHelper.mixin([source: getDefaultSource(script, configuration, deployMode)])
}
configuration = configHelper
.withMandatoryProperty('source')
.withMandatoryProperty('neo/credentialsId')
.withMandatoryProperty('neo/application', null, isWarParamsDeployMode)
.withMandatoryProperty('neo/runtime', null, isWarParamsDeployMode)
.withMandatoryProperty('neo/runtimeVersion', null, isWarParamsDeployMode)
.withMandatoryProperty('neo/host', null, isNotWarPropertiesDeployMode)
.withMandatoryProperty('neo/account', null, isNotWarPropertiesDeployMode)
.use()
Set extensionFileNames
if(configuration.extensions == null) {
extensionFileNames = []
} else {
extensionFileNames = configuration.extensions in Collection ? configuration.extensions : [configuration.extensions]
}
if( ! extensionFileNames.findAll { it == null || it.isEmpty() }.isEmpty() )
error "At least one extension file name was null or empty: ${extensionFileNames}."
if(deployMode != DeployMode.MTA && ! extensionFileNames.isEmpty())
error "Extensions (${extensionFileNames} found for deploy mode ${deployMode}. Extensions are only supported for deploy mode '${DeployMode.MTA}')"
utils.pushToSWA([
step: STEP_NAME,
stepParamKey1: 'deployMode',
stepParam1: configuration.deployMode == 'mta'?'mta':'war', // ['mta', 'warParams', 'warPropertiesFile']
stepParamKey2: 'warAction',
stepParam2: configuration.warAction == 'rolling-update'?'blue-green':'standard', // ['deploy', 'deploy-mta', 'rolling-update']
stepParamKey3: 'scriptMissing',
stepParam3: parameters?.script == null,
], configuration)
withCredentials([usernamePassword(
credentialsId: configuration.neo.credentialsId,
passwordVariable: 'NEO_PASSWORD',
usernameVariable: 'NEO_USERNAME')]) {
assertPasswordRules(NEO_PASSWORD)
dockerExecute(
script: script,
dockerImage: configuration.dockerImage,
dockerEnvVars: configuration.dockerEnvVars,
dockerOptions: configuration.dockerOptions
) {
StepAssertions.assertFileExists(this, configuration.source)
for(CharSequence extensionFile in extensionFileNames) {
StepAssertions.assertFileExists(this, extensionFile)
}
NeoCommandHelper neoCommandHelper = new NeoCommandHelper(
this,
deployMode,
configuration.neo,
extensionFileNames,
NEO_USERNAME,
NEO_PASSWORD,
configuration.source
)
lock("$STEP_NAME:${neoCommandHelper.resourceLock()}") {
deploy(script, configuration, neoCommandHelper, configuration.dockerImage, deployMode)
}
if(configuration.neo.invalidateCache == true) {
if (configuration.deployMode == 'mta') {
echo "Triggering invalidation of cache for html5 applications"
invalidateCache(configuration)
} else {
echo "Invalidation of cache is ignored. It is performed only for html5 applications."
}
}
}
}
}
}
private invalidateCache(configuration){
def account = configuration.neo.account
def host = configuration.neo.host
def portalLandscape = configuration.neo.portalLandscape
withCredentials([usernamePassword(
credentialsId: configuration.neo.oauthCredentialId,
passwordVariable: 'OAUTH_NEO_CLIENT_SECRET',
usernameVariable: 'OAUTH_NEO_CLIENT_ID')]) {
def bearerTokenResponse = sh(
script: """#!/bin/bash
curl -X POST -u "${OAUTH_NEO_CLIENT_ID}:${OAUTH_NEO_CLIENT_SECRET}" \
--fail \
"https://oauthasservices-${account}.${host}/oauth2/api/v1/token?grant_type=client_credentials&scope=write,read"
""",
returnStdout: true)
def bearerToken = readJSON(text: bearerTokenResponse).access_token
echo "Retrieved bearer token."
def fetchXcsrfTokenResponse = sh(
script: """#!/bin/bash
curl -i -L \
-c 'cookies.jar' \
-H 'X-CSRF-Token: Fetch' \
-H "Authorization: Bearer ${bearerToken}" \
--fail \
"https://${portalLandscape}-${account}.${host}/fiori/api/v1/csrf"
""",
returnStdout: true)
def xcsrfToken = readProperties(text: fetchXcsrfTokenResponse)["X-CSRF-Token"]
def siteId = configuration.neo.siteId ?: ""
if(! siteId){
echo "Using the default site defined in Portal service and invalidating the cache."
}
else{
echo "Invalidating the cache for site with Id: ${siteId}."
}
def statusCode = sh(
script: """#!/bin/bash
curl -X POST -L \
-b 'cookies.jar' \
-H "X-CSRF-Token: ${xcsrfToken}" \
-H "Authorization: Bearer ${bearerToken}" \
-d "{\"siteId\":${siteId}}" \
-so /dev/null \
-w '%{response_code}' \
"https://${portalLandscape}-${account}.${host}/fiori/v1/operations/invalidateCache"
""",
returnStdout: true).trim()
if(! siteId && statusCode == "500") {
error "Invalidating the cache failed. " +
"As no siteId is set, the default site defined in the portal UI is used. " +
"Please verify a default site is defined in Portal service. " +
"Alternatively, configure the siteId parameter for this step to invalidate the cache of that specific site."
} else if(! statusCode == "200" || ! statusCode == "201" ){
error "Invalidating the cache failed with response code: ${statusCode}."
}
echo "Successfully invalidated the cache."
}
}
private deploy(script, Map configuration, NeoCommandHelper neoCommandHelper, dockerImage, DeployMode deployMode) {
String logFolder = "logs/neo/${UUID.randomUUID()}"
try {
sh "mkdir -p ${logFolder}"
withEnv(["neo_logging_location=${pwd()}/${logFolder}"]) {
if (deployMode.isWarDeployment()) {
ConfigurationHelper.newInstance(this, configuration).withPropertyInValues('warAction', WarAction.stringValues())
WarAction warAction = WarAction.fromString(configuration.warAction)
if (warAction == WarAction.ROLLING_UPDATE) {
if (!isAppRunning(neoCommandHelper)) {
warAction = WarAction.DEPLOY
echo "Rolling update not possible because application is not running. Falling back to standard deployment."
}
}
echo "Link to the application dashboard: ${neoCommandHelper.cloudCockpitLink()}"
if (warAction == WarAction.ROLLING_UPDATE) {
try {
sh neoCommandHelper.rollingUpdateCommand()
} catch (e) {
error "[ERROR][${STEP_NAME}] The execution of the deploy command failed, see the log for details."
}
} else {
try {
sh neoCommandHelper.deployCommand()
} catch (e) {
error "[ERROR][${STEP_NAME}] The execution of the deploy command failed, see the log for details."
}
sh neoCommandHelper.restartCommand()
}
} else if (deployMode == DeployMode.MTA) {
try {
sh neoCommandHelper.deployMta()
} catch (e) {
error "[ERROR][${STEP_NAME}] The execution of the deploy command failed, see the log for details."
}
}
}
}
catch (Exception ex) {
echo "Error while deploying to SAP Cloud Platform. Here are the neo.sh logs:"
try {
sh "cat ${logFolder}/*"
} catch(Exception e) {
echo "Unable to provide the logs."
ex.addSuppressed(e)
}
throw ex
}
}
private boolean isAppRunning(NeoCommandHelper commandHelper) {
def status = sh script: "${commandHelper.statusCommand()} || true", returnStdout: true
return status.contains('Status: STARTED')
}
private assertPasswordRules(String password) {
if (password.startsWith("@")) {
error("Your password for the deployment to SAP Cloud Platform contains characters which are not " +
"supported by the neo tools. " +
"For example it is not allowed that the password starts with @. " +
"Please consult the documentation for the neo command line tool for more information: " +
"https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/8900b22376f84c609ee9baf5bf67130a.html")
}
}
private getDefaultSource(Script script, Map configuration, DeployMode deployMode){
if(deployMode == DeployMode.MTA) {
return script.commonPipelineEnvironment.getMtarFilePath()
}
String pomFile = "${configuration.mavenDeploymentModule}/pom.xml"
if(!fileExists(pomFile)){
error("The configured mavenDeploymentModule (${configuration.mavenDeploymentModule}) does not contain a pom file.")
}
def pom = readMavenPom file: pomFile
String source = "${configuration.mavenDeploymentModule}/target/${pom.artifactId}.${pom.packaging}"
return source
}