1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00
sap-jenkins-library/vars/abapEnvironmentPullGitRepo.groovy

293 lines
12 KiB
Groovy
Raw Normal View History

import static com.sap.piper.Prerequisites.checkScript
import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateDocumentation
import com.sap.piper.JenkinsUtils
import com.sap.piper.Utils
import com.sap.piper.BashUtils
import groovy.json.JsonSlurper
import hudson.AbortException
import groovy.transform.Field
import java.util.UUID
import java.util.regex.*
@Field def STEP_NAME = getClass().getName()
@Field Set STEP_CONFIG_KEYS = [
/**
* Specifies the host address of the SAP Cloud Platform ABAP Environment system
*/
'host',
/**
* Jenkins CredentialsId containing the communication user and password of the communciation scenario SAP_COM_0510
*/
'credentialsId',
/**
* Specifies the name of the Repository (Software Component) on the SAP Cloud Platform ABAP Environment system
*/
'repositoryName',
'cloudFoundry',
/**
* Cloud Foundry API endpoint.
* @parentConfigKey cloudFoundry
*/
'apiEndpoint',
'credentialsId',
/**
* Cloud Foundry target organization.
* @parentConfigKey cloudFoundry
*/
'org',
/**
* Cloud Foundry target space.
* @parentConfigKey cloudFoundry
*/
'space',
/**
* Cloud Foundry service instance, for which the service key will be created.
* @parentConfigKey cloudFoundry
*/
'serviceInstance',
/**
* Cloud Foundry service key, which will be created.
* @parentConfigKey cloudFoundry
*/
'serviceKey',
/** @see dockerExecute */
'dockerImage',
/** @see dockerExecute */
'dockerWorkspace'
]
@Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
@Field Map CONFIG_KEY_COMPATIBILITY = [cloudFoundry: [apiEndpoint: 'cfApiEndpoint', credentialsId: 'cfCredentialsId', org: 'cfOrg', space: 'cfSpace', serviceInstance: 'cfServiceInstance', serviceKey: 'cfServiceKey']]
/**
* Pulls a Repository (Software Component) to a SAP Cloud Platform ABAP Environment system.
*
* This is either possible by providing the host and the credentialsId of the communication arrangement or by providing access to a service key for the communication arrangement SAP_COM_0510 on cloud foundry.
*
* !!! note "Git Repository and Software Component"
* In SAP Cloud Platform ABAP Environment Git repositories are wrapped in Software Components (which are managed in the App "Manage Software Components")
* Currently, those two names are used synonymous.
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters, failOnError: true) {
def script = checkScript(this, parameters) ?: this
Map configuration = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.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)
.collectValidationFailures()
.withMandatoryProperty('repositoryName')
.use()
String userColonPassword
String urlString
if (configuration.credentialsId != null && configuration.host != null) {
echo "[${STEP_NAME}] Info: Using configuration: credentialsId: $configuration.credentialsId and host: $configuration.host"
withCredentials([usernamePassword(credentialsId: configuration.credentialsId, usernameVariable: 'USER', passwordVariable: 'PASSWORD')]) {
userColonPassword = "${USER}:${PASSWORD}"
urlString = 'https://' + configuration.host + '/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull'
}
} else {
echo "[${STEP_NAME}] Info: Using Cloud Foundry service key $configuration.cloudFoundry.serviceKey for service instance $configuration.cloudFoundry.serviceInstance"
dockerExecute(script:script,dockerImage: configuration.dockerImage, dockerWorkspace: configuration.dockerWorkspace) {
String jsonString = getServiceKey(configuration)
Map responseJson = readJSON(text : jsonString)
userColonPassword = responseJson.abap.username + ":" + responseJson.abap.password
urlString = responseJson.url + '/sap/opu/odata/sap/MANAGE_GIT_REPOSITORY/Pull'
}
}
if (userColonPassword != null && urlString != null) {
String authToken = userColonPassword.bytes.encodeBase64().toString()
executeAbapEnvironmentPullGitRepo(configuration, urlString, authToken)
} else {
error "[${STEP_NAME}] Error: Necessary parameters not available"
}
}
}
private String getServiceKey(Map configuration) {
String responseFile = "response-${UUID.randomUUID().toString()}.txt"
withCredentials([
usernamePassword(credentialsId: configuration.cloudFoundry.credentialsId, passwordVariable: 'CF_PASSWORD', usernameVariable: 'CF_USERNAME')
]) {
bashScript =
"""#!/bin/bash
set +x
set -e
export HOME=${configuration.dockerWorkspace}
cf login -u ${BashUtils.quoteAndEscape(CF_USERNAME)} -p ${BashUtils.quoteAndEscape(CF_PASSWORD)} -a ${configuration.cloudFoundry.apiEndpoint} -o ${BashUtils.quoteAndEscape(configuration.cloudFoundry.org)} -s ${BashUtils.quoteAndEscape(configuration.cloudFoundry.space)};
cf service-key ${BashUtils.quoteAndEscape(configuration.cloudFoundry.serviceInstance)} ${BashUtils.quoteAndEscape(configuration.cloudFoundry.serviceKey)} > \"${responseFile}\"
"""
String responseString
try {
def status = sh returnStatus: true, script: bashScript
if (status != 0) {
echo "[${STEP_NAME}] Info: Could not get the service key $configuration.cloudFoundry.serviceKey for service instance $configuration.cloudFoundry.serviceInstance"
}
responseString = readFile(responseFile)
} finally {
sh "cf logout"
sh script : """#!/bin/bash
rm -f ${responseFile}
"""
}
def p = Pattern.compile(/\{.*\}$/, Pattern.MULTILINE | Pattern.DOTALL)
def m = responseString =~ p
String jsonString
if (m.find()) {
return m[0]
} else {
echo "[${STEP_NAME}] Info: Could not parse the service key $configuration.cloudFoundry.serviceKey"
return null
}
}
}
private executeAbapEnvironmentPullGitRepo(Map configuration, String urlString, String authToken) {
echo "[${STEP_NAME}] General Parameters: URL = \"${urlString}\", repositoryName = \"${configuration.repositoryName}\""
HeaderFiles headerFiles = new HeaderFiles()
try {
String urlPullEntity = triggerPull(configuration, urlString, authToken, headerFiles)
if (urlPullEntity != null) {
String finalStatus = pollPullStatus(urlPullEntity, authToken, headerFiles)
if (finalStatus != 'S') {
error "[${STEP_NAME}] Pull Failed"
}
} else {
error "[${STEP_NAME}] Pull Failed"
}
} finally {
workspaceCleanup(headerFiles)
}
}
private String triggerPull(Map configuration, String url, String authToken, HeaderFiles headerFiles) {
String entityUri = null
def xCsrfTokenScript = """#!/bin/bash
curl -I -X GET ${url} \
-H 'Authorization: Basic ${authToken}' \
-H 'Accept: application/json' \
-H 'x-csrf-token: fetch' \
-D ${headerFiles.authFile} \
"""
sh ( script : xCsrfTokenScript, returnStdout: true )
HttpHeaderProperties headerProperties = new HttpHeaderProperties(readFile(headerFiles.authFile))
checkRequestStatus(headerProperties)
def scriptPull = """#!/bin/bash
curl -X POST \"${url}\" \
-H 'Authorization: Basic ${authToken}' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'x-csrf-token: ${headerProperties.xCsrfToken}' \
--cookie ${headerFiles.authFile} \
-D ${headerFiles.postFile} \
-d '{ \"sc_name\": \"${configuration.repositoryName}\" }'
"""
def response = sh (
script : scriptPull,
returnStdout: true )
checkRequestStatus(new HttpHeaderProperties(readFile(headerFiles.postFile)))
JsonSlurper slurper = new JsonSlurper()
Map responseJson = slurper.parseText(response)
if (responseJson.d != null) {
entityUri = responseJson.d.__metadata.uri.toString()
echo "[${STEP_NAME}] Pull Status: ${responseJson.d.status_descr.toString()}"
} else {
error "[${STEP_NAME}] Error: ${responseJson?.error?.message?.value?.toString()?:'No message available'}"
}
echo "[${STEP_NAME}] Entity URI: ${entityUri}"
return entityUri
}
private String pollPullStatus(String url, String authToken, HeaderFiles headerFiles) {
String status = "R";
while(status == "R") {
Thread.sleep(5000)
def pollScript = """#!/bin/bash
curl -X GET "${url}" \
-H 'Authorization: Basic ${authToken}' \
-H 'Accept: application/json' \
-D ${headerFiles.pollFile}
"""
def pollResponse = sh (
script : pollScript,
returnStdout: true )
checkRequestStatus(new HttpHeaderProperties(readFile(headerFiles.pollFile)))
JsonSlurper slurper = new JsonSlurper()
Map pollResponseJson = slurper.parseText(pollResponse)
if (pollResponseJson.d != null) {
status = pollResponseJson.d.status.toString()
} else {
error "[${STEP_NAME}] Error: ${pollResponseJson?.error?.message?.value?.toString()?:'No message available'}"
}
echo "[${STEP_NAME}] Pull Status: ${pollResponseJson.d.status_descr.toString()}"
}
return status
}
private void checkRequestStatus(HttpHeaderProperties httpHeader) {
if (httpHeader.statusCode == 400) {
echo "[${STEP_NAME}] Info: ${httpHeader.statusCode} ${httpHeader.statusMessage}"
} else if (httpHeader.statusCode > 201) {
error "[${STEP_NAME}] Error: ${httpHeader.statusCode} ${httpHeader.statusMessage}"
}
}
private void workspaceCleanup(HeaderFiles headerFiles) {
String cleanupScript = """#!/bin/bash
rm -f ${headerFiles.authFile} ${headerFiles.postFile} ${headerFiles.pollFile}
"""
sh ( script : cleanupScript )
}
public class HttpHeaderProperties{
Integer statusCode
String statusMessage
String xCsrfToken
HttpHeaderProperties(String header) {
def statusCodeRegex = header =~ /(?<=HTTP\/1.[0-9]\s)[0-9]{3}(?=\s)/
if (statusCodeRegex.find()) {
statusCode = statusCodeRegex[0].toInteger()
}
def statusMessageRegex = header =~ /(?<=HTTP\/1.[0-9]\s[0-9]{3}\s).*/
if (statusMessageRegex.find()) {
statusMessage = statusMessageRegex[0]
}
def xCsrfTokenRegex = header =~ /(?<=x-csrf-token:\s).*/
if (xCsrfTokenRegex.find()) {
xCsrfToken = xCsrfTokenRegex[0]
}
}
}
public class HeaderFiles{
String authFile
String postFile
String pollFile
HeaderFiles() {
String uuid = UUID.randomUUID().toString()
this.authFile = "headerFileAuth-${uuid}.txt"
this.postFile = "headerFilePost-${uuid}.txt"
this.pollFile = "headerFilePoll-${uuid}.txt"
}
}