1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-18 05:18:24 +02:00

Merge branch 'master' into whitesourceExecuteScan

This commit is contained in:
Sven Merk 2019-07-19 14:28:26 +02:00 committed by GitHub
commit b1b480a7e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 933 additions and 10 deletions

View File

@ -1,2 +1,2 @@
# Test case configuration
referenceAppRepoUrl: "https://github.com/sap/cloud-s4-sdk-book"
referenceAppRepoUrl: "https://github.com/piper-validation/cloud-s4-sdk-book"

View File

@ -1,2 +1,2 @@
# Test case configuration
referenceAppRepoUrl: "https://github.com/sap/cloud-s4-sdk-book"
referenceAppRepoUrl: "https://github.com/piper-validation/cloud-s4-sdk-book"

View File

@ -0,0 +1,9 @@
# ${docGenStepName}
## ${docGenDescription}
## ${docGenParameters}
## ${docGenConfiguration}
## ${docJenkinsPluginDependencies}

View File

@ -0,0 +1,9 @@
# ${docGenStepName}
## ${docGenDescription}
## ${docGenParameters}
## ${docGenConfiguration}
## ${docJenkinsPluginDependencies}

View File

@ -37,6 +37,7 @@ nav:
- pipelineStashFiles: steps/pipelineStashFiles.md
- pipelineStashFilesAfterBuild: steps/pipelineStashFilesAfterBuild.md
- pipelineStashFilesBeforeBuild: steps/pipelineStashFilesBeforeBuild.md
- piperPublishWarnings: steps/piperPublishWarnings.md
- prepareDefaultValues: steps/prepareDefaultValues.md
- seleniumExecuteTests: steps/seleniumExecuteTests.md
- setupCommonPipelineEnvironment: steps/setupCommonPipelineEnvironment.md

View File

@ -450,6 +450,14 @@ steps:
tests: ''
noDefaultExludes:
- 'git'
piperPublishWarnings:
parserId: piper
parserName: Piper
parserPattern: '\[(INFO|WARNING|ERROR)\] (.*) \(([^) ]*)\/([^) ]*)\)'
parserScript: 'return builder.guessSeverity(matcher.group(1)).setMessage(matcher.group(2)).setModuleName(matcher.group(3)).setType(matcher.group(4)).buildOptional()'
recordIssuesSettings:
blameDisabled: true
enabledForFailure: true
seleniumExecuteTests:
buildTool: 'npm'
containerPortMappings:
@ -534,6 +542,10 @@ steps:
active: false
checkChangeInDevelopment:
failIfStatusIsNotInDevelopment: true
tmsUpload:
namedUser: 'Piper-Pipeline'
stashContent:
- 'buildResult'
transportRequestCreate:
developmentSystemId: null
verbose: false

View File

@ -1,10 +1,14 @@
package com.sap.piper
import com.cloudbees.groovy.cps.NonCPS
import jenkins.model.Jenkins
import org.jenkinsci.plugins.workflow.steps.MissingContextVariableException
import hudson.tasks.junit.TestResultAction
import jenkins.model.Jenkins
import org.apache.commons.io.IOUtils
import org.jenkinsci.plugins.workflow.steps.MissingContextVariableException
@API
@NonCPS
static def isPluginActive(pluginId) {
@ -19,6 +23,32 @@ static boolean hasTestFailures(build){
return action && action.getFailCount() != 0
}
boolean addWarningsNGParser(String id, String name, String regex, String script, String example = ''){
def classLoader = this.getClass().getClassLoader()
// usage of class loader to avoid plugin dependency for other use cases of JenkinsUtils class
def parserConfig = classLoader.loadClass('io.jenkins.plugins.analysis.warnings.groovy.ParserConfiguration', true)?.getInstance()
if(parserConfig.contains(id)){
return false
}else{
parserConfig.setParsers(
parserConfig.getParsers().plus(
classLoader.loadClass('io.jenkins.plugins.analysis.warnings.groovy.GroovyParser', true)?.newInstance(id, name, regex, script, example)
)
)
return true
}
}
@NonCPS
static String getFullBuildLog(currentBuild) {
Reader reader = currentBuild.getRawBuild().getLogReader()
String logContent = IOUtils.toString(reader);
reader.close();
reader = null
return logContent
}
def nodeAvailable() {
try {
sh "echo 'Node is available!'"
@ -74,3 +104,7 @@ String getIssueCommentTriggerAction() {
return null
}
}
def getJobStartedByUserId() {
return getRawBuild().getCause(hudson.model.Cause.UserIdCause.class)?.getUserId()
}

View File

@ -0,0 +1,137 @@
package com.sap.piper.integration
import com.sap.piper.JsonUtils
class TransportManagementService implements Serializable {
final Script script
final Map config
def jsonUtils = new JsonUtils()
TransportManagementService(Script script, Map config) {
this.script = script
this.config = config
}
def authentication(String uaaUrl, String oauthClientId, String oauthClientSecret) {
echo("OAuth Token retrieval started.")
if (config.verbose) {
echo("UAA-URL: '${uaaUrl}', ClientId: '${oauthClientId}''")
}
def encodedUsernameColonPassword = "${oauthClientId}:${oauthClientSecret}".bytes.encodeBase64().toString()
def urlEncodedFormData = "grant_type=password&" +
"username=${urlEncodeAndReplaceSpace(oauthClientId)}&" +
"password=${urlEncodeAndReplaceSpace(oauthClientSecret)}"
def parameters = [
url : "${uaaUrl}/oauth/token/?grant_type=client_credentials&response_type=token",
httpMode : "POST",
requestBody : urlEncodedFormData,
customHeaders: [
[
maskValue: false,
name : 'Content-Type',
value : 'application/x-www-form-urlencoded'
],
[
maskValue: true,
name : 'authorization',
value : "Basic ${encodedUsernameColonPassword}"
]
]
]
def response = sendApiRequest(parameters)
echo("OAuth Token retrieved successfully.")
return jsonUtils.jsonStringToGroovyObject(response).access_token
}
def uploadFile(String url, String token, String file, String namedUser) {
echo("File upload started.")
if (config.verbose) {
echo("URL: '${url}', File: '${file}'")
}
script.sh """#!/bin/sh -e
curl -H 'Authorization: Bearer ${token}' -F 'file=@${file}' -F 'namedUser=${namedUser}' -o responseFileUpload.txt --fail '${url}/v2/files/upload'
"""
def responseContent = script.readFile("responseFileUpload.txt")
if (config.verbose) {
echo("${responseContent}")
}
echo("File upload successful.")
return jsonUtils.jsonStringToGroovyObject(responseContent)
}
def uploadFileToNode(String url, String token, String nodeName, int fileId, String description, String namedUser) {
echo("Node upload started.")
if (config.verbose) {
echo("URL: '${url}', NodeName: '${nodeName}', FileId: '${fileId}''")
}
def bodyMap = [nodeName: nodeName, contentType: 'MTA', description: description, storageType: 'FILE', namedUser: namedUser, entries: [[uri: fileId]]]
def parameters = [
url : "${url}/v2/nodes/upload",
httpMode : "POST",
contentType : 'APPLICATION_JSON',
requestBody : jsonUtils.groovyObjectToPrettyJsonString(bodyMap),
customHeaders: [
[
maskValue: true,
name : 'authorization',
value : "Bearer ${token}"
]
]
]
def response = sendApiRequest(parameters)
echo("Node upload successful.")
return jsonUtils.jsonStringToGroovyObject(response)
}
private sendApiRequest(parameters) {
def defaultParameters = [
acceptType : 'APPLICATION_JSON',
quiet : !config.verbose,
consoleLogResponseBody: !config.verbose,
ignoreSslErrors : true,
validResponseCodes : "100:399"
]
def response = script.httpRequest(defaultParameters + parameters)
if (config.verbose) {
echo("Received response '${response.content}' with status ${response.status}.")
}
return response.content
}
private echo(message) {
script.echo "[${getClass().getSimpleName()}] ${message}"
}
private static String urlEncodeAndReplaceSpace(String data) {
return URLEncoder.encode(data, "UTF-8").replace('%20', '+')
}
}

View File

@ -0,0 +1,85 @@
#!groovy
package steps
import com.sap.piper.JenkinsUtils
import static org.hamcrest.Matchers.allOf
import static org.hamcrest.Matchers.any
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.hasItem
import static org.hamcrest.Matchers.hasEntry
import static org.hamcrest.Matchers.hasKey
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import static org.junit.Assert.assertThat
import util.BasePiperTest
import util.Rules
import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
import util.JenkinsStepRule
import util.JenkinsShellCallRule
import static com.lesfurets.jenkins.unit.MethodSignature.method
class PiperPublishWarningsTest extends BasePiperTest {
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
def warningsParserSettings
def groovyScriptParserSettings
def warningsPluginOptions
@Rule
public RuleChain ruleChain = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(loggingRule)
.around(shellRule)
.around(stepRule)
@Before
void init() throws Exception {
warningsParserSettings = [:]
groovyScriptParserSettings = [:]
warningsPluginOptions = [:]
// add handler for generic step call
helper.registerAllowedMethod("writeFile", [Map.class], null)
helper.registerAllowedMethod("recordIssues", [Map.class], {
parameters -> warningsPluginOptions = parameters
})
helper.registerAllowedMethod("groovyScript", [Map.class], {
parameters -> groovyScriptParserSettings = parameters
})
JenkinsUtils.metaClass.addWarningsNGParser = { String s1, String s2, String s3, String s4 ->
warningsParserSettings = [id: s1, name: s2, regex: s3, script: s4]
return true
}
JenkinsUtils.metaClass.static.getFullBuildLog = { def currentBuild -> return ""}
JenkinsUtils.metaClass.static.isPluginActive = { id -> return true}
}
@Test
void testPublishWarnings() throws Exception {
stepRule.step.piperPublishWarnings(script: nullScript)
// asserts
assertThat(loggingRule.log, containsString('[piperPublishWarnings] Added warnings-ng plugin parser \'Piper\' configuration.'))
assertThat(warningsParserSettings, hasEntry('id', 'piper'))
assertThat(warningsParserSettings, hasEntry('name', 'Piper'))
assertThat(warningsParserSettings, hasEntry('regex', '\\[(INFO|WARNING|ERROR)\\] (.*) \\(([^) ]*)\\/([^) ]*)\\)'))
assertThat(warningsParserSettings, hasKey('script'))
assertThat(warningsPluginOptions, allOf(
hasEntry('enabledForFailure', true),
hasEntry('blameDisabled', true)
))
assertThat(warningsPluginOptions, hasKey('tools'))
assertThat(groovyScriptParserSettings, hasEntry('parserId', 'piper'))
assertThat(groovyScriptParserSettings, hasEntry('pattern', 'build.log'))
}
}

View File

@ -0,0 +1,182 @@
import com.sap.piper.JenkinsUtils
import com.sap.piper.integration.TransportManagementService
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.*
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.is
import static org.hamcrest.Matchers.not
import static org.junit.Assert.assertThat
public class TmsUploadTest extends BasePiperTest {
private ExpectedException thrown = new ExpectedException()
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
private JenkinsEnvironmentRule envRule = new JenkinsEnvironmentRule(this)
def tmsStub
def jenkinsUtilsStub
def calledTmsMethodsWithArgs = []
def uri = "https://dummy-url.com"
def uaaUrl = "https://oauth.com"
def oauthClientId = "myClientId"
def oauthClientSecret = "myClientSecret"
def serviceKeyContent = """{
"uri": "${uri}",
"uaa": {
"clientid": "${oauthClientId}",
"clientsecret": "${oauthClientSecret}",
"url": "${uaaUrl}"
}
}
"""
class JenkinsUtilsMock extends JenkinsUtils {
def userId
JenkinsUtilsMock(userId) {
this.userId = userId
}
def getJobStartedByUserId(){
return this.userId
}
}
@Rule
public RuleChain ruleChain = Rules.getCommonRules(this)
.around(thrown)
.around(new JenkinsReadYamlRule(this))
.around(stepRule)
.around(loggingRule)
.around(envRule)
.around(new JenkinsCredentialsRule(this)
.withCredentials('TMS_ServiceKey', serviceKeyContent))
@Before
public void setup() {
tmsStub = mockTransportManagementService()
helper.registerAllowedMethod("unstash", [String.class], { s -> return [s] })
}
@After
void tearDown() {
calledTmsMethodsWithArgs.clear()
}
@Test
public void minimalConfig__isSuccessful() {
jenkinsUtilsStub = new JenkinsUtilsMock("Test User")
binding.workspace = "."
envRule.env.gitCommitId = "testCommitId"
stepRule.step.tmsUpload(
script: nullScript,
juStabUtils: utils,
jenkinsUtilsStub: jenkinsUtilsStub,
transportManagementService: tmsStub,
mtaPath: 'dummy.mtar',
nodeName: 'myNode',
credentialsId: 'TMS_ServiceKey'
)
assertThat(calledTmsMethodsWithArgs[0], is("authentication('${uaaUrl}', '${oauthClientId}', '${oauthClientSecret}')"))
assertThat(calledTmsMethodsWithArgs[1], is("uploadFile('${uri}', 'myToken', './dummy.mtar', 'Test User')"))
assertThat(calledTmsMethodsWithArgs[2], is("uploadFileToNode('${uri}', 'myToken', 'myNode', '1234', 'Git CommitId: testCommitId')"))
assertThat(loggingRule.log, containsString("[TransportManagementService] File './dummy.mtar' successfully uploaded to Node 'myNode' (Id: '1000')."))
assertThat(loggingRule.log, containsString("[TransportManagementService] Corresponding Transport Request: 'Git CommitId: testCommitId' (Id: '2000')"))
assertThat(loggingRule.log, not(containsString("[TransportManagementService] CredentialsId: 'TMS_ServiceKey'")))
}
@Test
public void verboseMode__yieldsMoreEchos() {
jenkinsUtilsStub = new JenkinsUtilsMock("Test User")
binding.workspace = "."
envRule.env.gitCommitId = "testCommitId"
stepRule.step.tmsUpload(
script: nullScript,
juStabUtils: utils,
jenkinsUtilsStub: jenkinsUtilsStub,
transportManagementService: tmsStub,
mtaPath: 'dummy.mtar',
nodeName: 'myNode',
credentialsId: 'TMS_ServiceKey',
verbose: true
)
assertThat(loggingRule.log, containsString("[TransportManagementService] CredentialsId: 'TMS_ServiceKey'"))
assertThat(loggingRule.log, containsString("[TransportManagementService] Node name: 'myNode'"))
assertThat(loggingRule.log, containsString("[TransportManagementService] MTA path: 'dummy.mtar'"))
assertThat(loggingRule.log, containsString("[TransportManagementService] Named user: 'Test User'"))
assertThat(loggingRule.log, containsString("[TransportManagementService] UAA URL: '${uaaUrl}'"))
assertThat(loggingRule.log, containsString("[TransportManagementService] TMS URL: '${uri}'"))
assertThat(loggingRule.log, containsString("[TransportManagementService] ClientId: '${oauthClientId}'"))
}
@Test
public void noUserAvailableInCurrentBuild__usesDefaultUser() {
jenkinsUtilsStub = new JenkinsUtilsMock(null)
binding.workspace = "."
envRule.env.gitCommitId = "testCommitId"
stepRule.step.tmsUpload(
script: nullScript,
juStabUtils: utils,
jenkinsUtilsStub: jenkinsUtilsStub,
transportManagementService: tmsStub,
mtaPath: 'dummy.mtar',
nodeName: 'myNode',
credentialsId: 'TMS_ServiceKey'
)
assertThat(calledTmsMethodsWithArgs[1], is("uploadFile('${uri}', 'myToken', './dummy.mtar', 'Piper-Pipeline')"))
}
@Test
public void addCustomDescription__descriptionChanged() {
jenkinsUtilsStub = new JenkinsUtilsMock("Test User")
binding.workspace = "."
envRule.env.gitCommitId = "testCommitId"
stepRule.step.tmsUpload(
script: nullScript,
juStabUtils: utils,
jenkinsUtilsStub: jenkinsUtilsStub,
transportManagementService: tmsStub,
mtaPath: 'dummy.mtar',
nodeName: 'myNode',
credentialsId: 'TMS_ServiceKey',
customDescription: 'My custom description for testing.'
)
assertThat(calledTmsMethodsWithArgs[2], is("uploadFileToNode('${uri}', 'myToken', 'myNode', '1234', 'My custom description for testing.')"))
assertThat(loggingRule.log, containsString("[TransportManagementService] Corresponding Transport Request: 'My custom description for testing.' (Id: '2000')"))
}
def mockTransportManagementService() {
return new TransportManagementService(nullScript, [:]) {
def authentication(String uaaUrl, String oauthClientId, String oauthClientSecret) {
calledTmsMethodsWithArgs << "authentication('${uaaUrl}', '${oauthClientId}', '${oauthClientSecret}')"
return "myToken"
}
def uploadFile(String url, String token, String file, String namedUser) {
calledTmsMethodsWithArgs << "uploadFile('${url}', '${token}', '${file}', '${namedUser}')"
return [fileId: 1234, fileName: file]
}
def uploadFileToNode(String url, String token, String nodeName, int fileId, String description, String namedUser) {
calledTmsMethodsWithArgs << "uploadFileToNode('${url}', '${token}', '${nodeName}', '${fileId}', '${description}')"
return [transportRequestDescription: description, transportRequestId: 2000, queueEntries: [nodeName: 'myNode', nodeId: 1000]]
}
}
}
}

View File

@ -4,7 +4,6 @@ import org.jenkinsci.plugins.workflow.steps.MissingContextVariableException
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.BasePiperTest
import util.JenkinsLoggingRule
@ -33,6 +32,8 @@ class JenkinsUtilsTest extends BasePiperTest {
Map triggerCause
String userId
@Before
void init() throws Exception {
@ -60,7 +61,15 @@ class JenkinsUtilsTest extends BasePiperTest {
return parentMock
}
def getCause(type) {
return triggerCause
if (type == hudson.model.Cause.UserIdCause.class){
def userIdCause = new hudson.model.Cause.UserIdCause()
userIdCause.metaClass.getUserId = {
return userId
}
return userIdCause
} else {
return triggerCause
}
}
}
@ -109,4 +118,16 @@ class JenkinsUtilsTest extends BasePiperTest {
]
assertThat(jenkinsUtils.getIssueCommentTriggerAction(), isEmptyOrNullString())
}
@Test
void testGetUserId() {
userId = 'Test User'
assertThat(jenkinsUtils.getJobStartedByUserId(), is('Test User'))
}
@Test
void testGetUserIdNoUser() {
userId = null
assertThat(jenkinsUtils.getJobStartedByUserId(), isEmptyOrNullString())
}
}

View File

@ -0,0 +1,181 @@
package com.sap.piper.integration
import hudson.AbortException
import org.junit.Rule
import org.junit.Test
import org.junit.Ignore
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.*
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
class TransportManagementServiceTest extends BasePiperTest {
private ExpectedException thrown = ExpectedException.none()
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(new JenkinsErrorRule(this))
.around(new JenkinsReadJsonRule(this))
.around(shellRule)
.around(loggingRule)
.around(new JenkinsReadFileRule(this, 'test/resources/TransportManagementService'))
.around(thrown)
@Test
void retrieveOAuthToken__successfully() {
Map requestParams
helper.registerAllowedMethod('httpRequest', [Map.class], { m ->
requestParams = m
return [content: '{ "access_token": "myOAuthToken" }']
})
def uaaUrl = 'http://dummy.com/oauth'
def clientId = 'myId'
def clientSecret = 'mySecret'
def tms = new TransportManagementService(nullScript, [:])
def token = tms.authentication(uaaUrl, clientId, clientSecret)
assertThat(loggingRule.log, containsString("[TransportManagementService] OAuth Token retrieval started."))
assertThat(loggingRule.log, containsString("[TransportManagementService] OAuth Token retrieved successfully."))
assertThat(token, is('myOAuthToken'))
assertThat(requestParams, hasEntry('url', "${uaaUrl}/oauth/token/?grant_type=client_credentials&response_type=token"))
assertThat(requestParams, hasEntry('requestBody', "grant_type=password&username=${clientId}&password=${clientSecret}".toString()))
assertThat(requestParams.customHeaders[1].value, is("Basic ${"${clientId}:${clientSecret}".bytes.encodeBase64()}"))
}
@Test
void retrieveOAuthToken__inVerboseMode__yieldsMoreEchos() {
Map requestParams
helper.registerAllowedMethod('httpRequest', [Map.class], { m ->
requestParams = m
return [content: '{ "access_token": "myOAuthToken" }', status: 200]
})
def uaaUrl = 'http://dummy.com/oauth'
def clientId = 'myId'
def clientSecret = 'mySecret'
def tms = new TransportManagementService(nullScript, [verbose: true])
tms.authentication(uaaUrl, clientId, clientSecret)
assertThat(loggingRule.log, containsString("[TransportManagementService] OAuth Token retrieval started."))
assertThat(loggingRule.log, containsString("[TransportManagementService] UAA-URL: '${uaaUrl}', ClientId: '${clientId}'"))
assertThat(loggingRule.log, containsString("Received response '{ \"access_token\": \"myOAuthToken\" }' with status 200."))
assertThat(loggingRule.log, containsString("[TransportManagementService] OAuth Token retrieved successfully."))
}
@Test
void uploadFile__successfully() {
def url = 'http://dummy.com/oauth'
def token = 'myToken'
def file = 'myFile.mtar'
def namedUser = 'myUser'
def tms = new TransportManagementService(nullScript, [:])
def responseDetails = tms.uploadFile(url, token, file, namedUser)
def oAuthShellCall = shellRule.shell[0]
assertThat(loggingRule.log, containsString("[TransportManagementService] File upload started."))
assertThat(loggingRule.log, containsString("[TransportManagementService] File upload successful."))
assertThat(oAuthShellCall, startsWith("#!/bin/sh -e "))
assertThat(oAuthShellCall, endsWith("curl -H 'Authorization: Bearer ${token}' -F 'file=@${file}' -F 'namedUser=${namedUser}' -o responseFileUpload.txt --fail '${url}/v2/files/upload'"))
assertThat(responseDetails, hasEntry("fileId", 1234))
}
@Ignore
void uploadFile__withHttpErrorResponse__throwsError() {
def url = 'http://dummy.com/oauth'
def token = 'myWrongToken'
def file = 'myFile.mtar'
def namedUser = 'myUser'
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, ".* curl .*", {throw new AbortException()})
thrown.expect(AbortException.class)
def tms = new TransportManagementService(nullScript, [:])
tms.uploadFile(url, token, file, namedUser)
}
@Test
void uploadFile__inVerboseMode__yieldsMoreEchos() {
def url = 'http://dummy.com/oauth'
def token = 'myToken'
def file = 'myFile.mtar'
def namedUser = 'myUser'
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, ".* curl .*", '200')
def tms = new TransportManagementService(nullScript, [verbose: true])
tms.uploadFile(url, token, file, namedUser)
assertThat(loggingRule.log, containsString("[TransportManagementService] File upload started."))
assertThat(loggingRule.log, containsString("[TransportManagementService] URL: '${url}', File: '${file}'"))
assertThat(loggingRule.log, containsString("\"fileId\": 1234"))
assertThat(loggingRule.log, containsString("[TransportManagementService] File upload successful."))
}
@Test
void uploadFileToNode__successfully() {
Map requestParams
helper.registerAllowedMethod('httpRequest', [Map.class], { m ->
requestParams = m
return [content: '{ "upload": "success" }']
})
def url = 'http://dummy.com/oauth'
def token = 'myToken'
def nodeName = 'myNode'
def fileId = 1234
def description = "My description."
def namedUser = 'myUser'
def tms = new TransportManagementService(nullScript, [:])
def responseDetails = tms.uploadFileToNode(url, token, nodeName, fileId, description, namedUser)
def bodyRegEx = /^\{\s+"nodeName":\s+"myNode",\s+"contentType":\s+"MTA",\s+"description":\s+"My\s+description.",\s+"storageType":\s+"FILE",\s+"namedUser":\s+"myUser",\s+"entries":\s+\[\s+\{\s+"uri":\s+1234\s+}\s+]\s+}$/
assertThat(loggingRule.log, containsString("[TransportManagementService] Node upload started."))
assertThat(loggingRule.log, containsString("[TransportManagementService] Node upload successful."))
assertThat(requestParams, hasEntry('url', "${url}/v2/nodes/upload"))
assert requestParams.requestBody ==~ bodyRegEx
assertThat(requestParams.customHeaders[0].value, is("Bearer ${token}"))
assertThat(responseDetails, hasEntry("upload", "success"))
}
@Test
void uploadFileToNode__inVerboseMode__yieldsMoreEchos() {
Map requestParams
helper.registerAllowedMethod('httpRequest', [Map.class], { m ->
requestParams = m
return [content: '{ "upload": "success" }']
})
def url = 'http://dummy.com/oauth'
def token = 'myToken'
def nodeName = 'myNode'
def fileId = 1234
def description = "My description."
def namedUser = 'myUser'
def tms = new TransportManagementService(nullScript, [verbose: true])
tms.uploadFileToNode(url, token, nodeName, fileId, description, namedUser)
assertThat(loggingRule.log, containsString("[TransportManagementService] Node upload started."))
assertThat(loggingRule.log, containsString("[TransportManagementService] URL: '${url}', NodeName: '${nodeName}', FileId: '${fileId}'"))
assertThat(loggingRule.log, containsString("\"upload\": \"success\""))
assertThat(loggingRule.log, containsString("[TransportManagementService] Node upload successful."))
}
}

View File

@ -37,13 +37,14 @@ class PiperPipelineStagePostTest extends BasePiperTest {
helper.registerAllowedMethod('influxWriteData', [Map.class], {m -> stepsCalled.add('influxWriteData')})
helper.registerAllowedMethod('slackSendNotification', [Map.class], {m -> stepsCalled.add('slackSendNotification')})
helper.registerAllowedMethod('mailSendNotification', [Map.class], {m -> stepsCalled.add('mailSendNotification')})
helper.registerAllowedMethod('piperPublishWarnings', [Map.class], {m -> stepsCalled.add('piperPublishWarnings')})
}
@Test
void testPostDefault() {
jsr.step.piperPipelineStagePost(script: nullScript, juStabUtils: utils)
assertThat(stepsCalled, hasItems('influxWriteData','mailSendNotification'))
assertThat(stepsCalled, hasItems('influxWriteData','mailSendNotification','piperPublishWarnings'))
assertThat(stepsCalled, not(hasItems('slackSendNotification')))
}
@ -53,7 +54,7 @@ class PiperPipelineStagePostTest extends BasePiperTest {
jsr.step.piperPipelineStagePost(script: nullScript, juStabUtils: utils)
assertThat(stepsCalled, hasItems('influxWriteData','mailSendNotification'))
assertThat(stepsCalled, hasItems('influxWriteData','mailSendNotification','piperPublishWarnings'))
assertThat(stepsCalled, not(hasItems('slackSendNotification')))
}
@ -63,6 +64,6 @@ class PiperPipelineStagePostTest extends BasePiperTest {
jsr.step.piperPipelineStagePost(script: nullScript, juStabUtils: utils)
assertThat(stepsCalled, hasItems('influxWriteData','mailSendNotification','slackSendNotification'))
assertThat(stepsCalled, hasItems('influxWriteData','mailSendNotification','slackSendNotification','piperPublishWarnings'))
}
}

View File

@ -31,6 +31,11 @@ class CommandLineMatcher extends BaseMatcher {
return this
}
CommandLineMatcher hasSnippet(String snippet) {
this.args.add(snippet)
return this
}
CommandLineMatcher hasArgument(String arg) {
this.args.add(arg)
return this
@ -58,7 +63,7 @@ class CommandLineMatcher extends BaseMatcher {
for (String arg : args) {
if (!cmd.matches(/.*[\s]*${arg}[\s]*.*/)) {
hint = "A command line having argument '${arg}'."
hint = "A command line having argument/snippet '${arg}'."
matches = false
}
}

View File

@ -26,6 +26,7 @@ class JenkinsFileExistsRule implements TestRule {
void evaluate() throws Throwable {
testInstance.helper.registerAllowedMethod('fileExists', [String.class], {s -> return s in existingFiles})
testInstance.helper.registerAllowedMethod('fileExists', [Map.class], {m -> return m.file in existingFiles})
base.evaluate()
}

View File

@ -0,0 +1,3 @@
{
"access_token": "myOAuthToken"
}

View File

@ -0,0 +1,3 @@
{
"fileId": 1234
}

View File

@ -0,0 +1,3 @@
{
"upload": "success"
}

View File

@ -48,5 +48,6 @@ void call(Map parameters = [:]) {
}
}
mailSendNotification script: script
piperPublishWarnings script: script
}
}

View File

@ -15,6 +15,8 @@ import static com.sap.piper.Prerequisites.checkScript
'healthExecuteCheck',
/** For Neo use-cases: Performs deployment to Neo landscape. */
'neoDeploy',
/** For TMS use-cases: Performs upload to Transport Management Service node*/
'tmsUpload',
/** Publishes release information to GitHub. */
'githubPublishRelease',
]
@ -60,6 +62,12 @@ void call(Map parameters = [:]) {
}
}
if (config.tmsUpload) {
durationMeasure(script: script, measurementName: 'upload_release_tms_duration') {
tmsUpload script: script
}
}
if (config.healthExecuteCheck) {
healthExecuteCheck script: script
}

View File

@ -0,0 +1,96 @@
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 groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field List PLUGIN_ID_LIST = ['warnings-ng']
@Field Set GENERAL_CONFIG_KEYS = []
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* The id of the Groovy script parser. If the id is not present in the current Jenkins configuration it is created.
*/
'parserId',
/**
* The display name for the warnings parsed by the parser.
* Only considered if a new parser is created.
*/
'parserName',
/**
* The pattern used to parse the log file.
* Only considered if a new parser is created.
*/
'parserPattern',
/**
* The script used to parse the matches produced by the pattern into issues.
* Only considered if a new parser is created.
* see https://github.com/jenkinsci/analysis-model/blob/master/src/main/java/edu/hm/hafner/analysis/IssueBuilder.java
*/
'parserScript',
/**
* Settings that are passed to the recordIssues step of the warnings-ng plugin.
* see https://github.com/jenkinsci/warnings-ng-plugin/blob/master/doc/Documentation.md#configuration
*/
'recordIssuesSettings'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS.plus([])
/**
* This step scans the current build log for messages produces by the Piper library steps and publishes them on the Jenkins job run as *Piper warnings* via the warnings-ng plugin.
*
* The default parser detects log entries with the following pattern: `[<SEVERITY>] <MESSAGE> (<LIBRARY>/<STEP>)`
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters, allowBuildFailure: true) {
final script = checkScript(this, parameters) ?: this
for(String id : PLUGIN_ID_LIST){
if (!JenkinsUtils.isPluginActive(id)) {
error("[ERROR][${STEP_NAME}] The step requires the plugin '${id}' to be installed and activated in the Jenkins.")
}
}
// load default & individual configuration
Map configuration = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName ?: env.STAGE_NAME, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
.use()
// report to SWA
new Utils().pushToSWA([
step: STEP_NAME,
stepParamKey1: 'scriptMissing',
stepParam1: parameters?.script == null
], configuration)
// add Piper Notifications parser to config if missing
if(new JenkinsUtils().addWarningsNGParser(
configuration.parserId,
configuration.parserName,
configuration.parserPattern,
configuration.parserScript
)){
echo "[${STEP_NAME}] Added warnings-ng plugin parser '${configuration.parserName}' configuration."
}
writeFile file: 'build.log', text: JenkinsUtils.getFullBuildLog(script.currentBuild)
// parse log for Piper Notifications
recordIssues(
configuration.recordIssuesSettings.plus([
tools: [groovyScript(
parserId: configuration.parserId,
pattern: 'build.log'
)]
])
)
}
}

131
vars/tmsUpload.groovy Normal file
View File

@ -0,0 +1,131 @@
import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateDocumentation
import com.sap.piper.JenkinsUtils
import com.sap.piper.JsonUtils
import com.sap.piper.Utils
import com.sap.piper.integration.TransportManagementService
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
/**
* Print more detailed information into the log.
* @possibleValues `true`, `false`
*/
'verbose'
]
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
/**
* If specific stashes should be considered, their names need to be passed via the parameter `stashContent`.
*/
'stashContent',
/**
* Defines the path to *.mtar for the upload to the Transport Management Service.
*/
'mtaPath',
/**
* Defines the name of the node to which the *.mtar file should be uploaded.
*/
'nodeName',
/**
* Credentials to be used for the file and node uploads to the Transport Management Service.
*/
'credentialsId',
/**
* Can be used as the description of a transport request. Will overwrite the default. (Default: Corresponding Git Commit-ID)
*/
'customDescription'
])
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
* This step allows you to upload an MTA file (multi-target application archive) into a TMS (SAP Cloud Platform Transport Management Service) landscape for further TMS-controlled distribution through a TMS-configured landscape.
* TMS lets you manage transports between SAP Cloud Platform accounts in Neo and Cloud Foundry, such as from DEV to TEST and PROD accounts.
* For more information, see [official documentation of Transport Management Service](https://help.sap.com/viewer/p/TRANSPORT_MANAGEMENT_SERVICE)
*
* !!! note "Prerequisites"
* * You have subscribed to and set up TMS, as described in [Setup and Configuration of SAP Cloud Platform Transport Management](https://help.sap.com/viewer/7f7160ec0d8546c6b3eab72fb5ad6fd8/Cloud/en-US/66fd7283c62f48adb23c56fb48c84a60.html), which includes the configuration of a node to be used for uploading an MTA file.
* * A corresponding service key has been created, as described in [Set Up the Environment to Transport Content Archives directly in an Application](https://help.sap.com/viewer/7f7160ec0d8546c6b3eab72fb5ad6fd8/Cloud/en-US/8d9490792ed14f1bbf8a6ac08a6bca64.html). This service key (JSON) must be stored as a secret text within the Jenkins secure store.
*
*/
@GenerateDocumentation
void call(Map parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
def script = checkScript(this, parameters) ?: this
def utils = parameters.juStabUtils ?: new Utils()
def jenkinsUtils = parameters.jenkinsUtilsStub ?: new JenkinsUtils()
// load default & individual configuration
Map config = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName ?: env.STAGE_NAME, STEP_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
//mandatory parameters
.withMandatoryProperty('mtaPath')
.withMandatoryProperty('nodeName')
.withMandatoryProperty('credentialsId')
.use()
// telemetry reporting
new Utils().pushToSWA([
step : STEP_NAME,
stepParamKey1: 'scriptMissing',
stepParam1 : parameters?.script == null
], config)
def jsonUtilsObject = new JsonUtils()
// 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
config.stashContent = []
def customDescription = config.customDescription ? "${config.customDescription}" : "Git CommitId: ${script.commonPipelineEnvironment.getGitCommitId()}"
def description = customDescription
def namedUser = jenkinsUtils.getJobStartedByUserId() ?: config.namedUser
def nodeName = config.nodeName
def mtaPath = config.mtaPath
if (config.verbose) {
echo "[TransportManagementService] CredentialsId: '${config.credentialsId}'"
echo "[TransportManagementService] Node name: '${nodeName}'"
echo "[TransportManagementService] MTA path: '${mtaPath}'"
echo "[TransportManagementService] Named user: '${namedUser}'"
}
def tms = parameters.transportManagementService ?: new TransportManagementService(script, config)
withCredentials([string(credentialsId: config.credentialsId, variable: 'tmsServiceKeyJSON')]) {
def tmsServiceKey = jsonUtilsObject.jsonStringToGroovyObject(tmsServiceKeyJSON)
def clientId = tmsServiceKey.uaa.clientid
def clientSecret = tmsServiceKey.uaa.clientsecret
def uaaUrl = tmsServiceKey.uaa.url
def uri = tmsServiceKey.uri
if (config.verbose) {
echo "[TransportManagementService] UAA URL: '${uaaUrl}'"
echo "[TransportManagementService] TMS URL: '${uri}'"
echo "[TransportManagementService] ClientId: '${clientId}'"
}
def token = tms.authentication(uaaUrl, clientId, clientSecret)
def fileUploadResponse = tms.uploadFile(uri, token, "${workspace}/${mtaPath}", namedUser)
def uploadFileToNodeResponse = tms.uploadFileToNode(uri, token, nodeName, fileUploadResponse.fileId, description, namedUser)
echo "[TransportManagementService] File '${fileUploadResponse.fileName}' successfully uploaded to Node '${uploadFileToNodeResponse.queueEntries.nodeName}' (Id: '${uploadFileToNodeResponse.queueEntries.nodeId}')."
echo "[TransportManagementService] Corresponding Transport Request: '${uploadFileToNodeResponse.transportRequestDescription}' (Id: '${uploadFileToNodeResponse.transportRequestId}')"
}
}
}