mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-14 11:03:09 +02:00
293 lines
12 KiB
Groovy
293 lines
12 KiB
Groovy
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"
|
|
}
|
|
}
|