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

feat(newman): fetch xsuaa credentials in newman execution (#1325)

* fetch xsuaa credentials in newman execution

* refactor CfUtils and moving to integration package

* removing CfUtils from old package com.sap.piper

* avoid config.verbose as "null"

* variable renamed for consistent naming

* handling NPE and other comments incorp

* added test cases

Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
This commit is contained in:
sudeepsukumar 2020-06-12 13:34:03 +05:30 committed by GitHub
parent 41c1653a06
commit 702664645c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 323 additions and 4 deletions

View File

@ -0,0 +1,68 @@
package com.sap.piper.integration
class CloudFoundry {
final Script script
CloudFoundry(Script script) {
this.script = script
}
def getXsuaaCredentials(String apiEndpoint, String org, String space, String credentialsId, String appName, boolean verbose){
def env = getAppEnvironment(apiEndpoint, org, space, credentialsId, appName, verbose)
if (!env.system_env_json.VCAP_SERVICES.xsuaa[0]) throw new Exception("Cannot find xsuaa credentials")
return env.system_env_json.VCAP_SERVICES.xsuaa[0].credentials
}
def getAppEnvironment(String apiEndpoint, String org, String space, String credentialsId, String appName, boolean verbose){
def authEndpoint = getAuthEndPoint(apiEndpoint, verbose)
def bearerToken = getBearerToken(authEndpoint, credentialsId, verbose)
def appUrl = getAppRefUrl(apiEndpoint, org, space, bearerToken, appName, verbose)
def response = script.httpRequest url: "${appUrl}/env", quiet: !verbose,
customHeaders:[[name: 'Authorization', value: "${bearerToken}"]]
def envJson = script.readJSON text:"${response.content}"
return envJson
}
def getAuthEndPoint(String apiEndpoint, boolean verbose){
def info = script.httpRequest url: "${apiEndpoint}/v2/info", quiet: !verbose
def responseJson = script.readJSON text:"${info.content}"
return responseJson.authorization_endpoint
}
def getBearerToken(String authorizationEndpoint, String credentialsId, boolean verbose){
script.withCredentials([script.usernamePassword(credentialsId: credentialsId, usernameVariable: 'usercf', passwordVariable: 'passwordcf')]) {
def token = script.httpRequest url:"${authorizationEndpoint}/oauth/token", quiet: !verbose,
httpMode:'POST',
requestBody: "username=${script.usercf}&password=${script.passwordcf}&client_id=cf&grant_type=password&response_type=token",
customHeaders: [[name: 'Content-Type', value: 'application/x-www-form-urlencoded'],[name: 'Authorization', value: 'Basic Y2Y6']]
def responseJson = script.readJSON text:"${token.content}"
return "Bearer ${responseJson.access_token.trim()}"
}
}
def getAppRefUrl(String apiEndpoint, String org, String space, String bearerToken, String appName, boolean verbose){
def orgGuid = getOrgGuid(apiEndpoint, org, bearerToken, verbose)
def spaceGuid = getSpaceGuid(apiEndpoint, orgGuid, space, bearerToken, verbose)
def appInfo = script.httpRequest url:"${apiEndpoint}/v3/apps?names=${appName},${appName}_blue,${appName}_green,space_guids=${spaceGuid}", quiet: !verbose,
customHeaders:[[name: 'Authorization', value: "${bearerToken}"]]
def responseJson = script.readJSON text:"${appInfo.content}"
if (!responseJson.resources[0]) throw new Exception("Cannot find Application")
return responseJson.resources[0].links.self.href.trim()
}
def getOrgGuid(String apiEndpoint, String org, String bearerToken, boolean verbose){
def organizationInfo = script.httpRequest url: "${apiEndpoint}/v3/organizations?names=${org}", quiet: !verbose,
customHeaders:[[name: 'Authorization', value: "${bearerToken}"]]
def responseJson = script.readJSON text:"${organizationInfo.content}"
if (!responseJson.resources[0]) throw new Exception("Cannot find Organization")
return responseJson.resources[0].guid
}
def getSpaceGuid(String apiEndpoint, String orgGuid, String space, String bearerToken, boolean verbose){
def spaceInfo = script.httpRequest url: "${apiEndpoint}/v3/spaces?names=${space},organization_guids=${orgGuid}", quiet: !verbose,
customHeaders:[[name: 'Authorization', value: "${bearerToken}"]]
def responseJson = script.readJSON text:"${spaceInfo.content}"
if (!responseJson.resources[0]) throw new Exception("Cannot find Space")
return responseJson.resources[0].guid
}
}

View File

@ -8,6 +8,7 @@ import static org.hamcrest.Matchers.is
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.endsWith
import static org.hamcrest.Matchers.startsWith
import groovy.json.JsonSlurper
import static org.junit.Assert.assertThat
@ -19,6 +20,7 @@ import util.JenkinsShellCallRule
import util.JenkinsDockerExecuteRule
import util.Rules
import org.junit.rules.ExpectedException
import util.JenkinsCredentialsRule
class NewmanExecuteTest extends BasePiperTest {
private ExpectedException thrown = ExpectedException.none()
@ -26,6 +28,7 @@ class NewmanExecuteTest extends BasePiperTest {
private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this)
private JenkinsCredentialsRule jenkinsCredentialsRule = new JenkinsCredentialsRule(this)
@Rule
public RuleChain rules = Rules
@ -34,6 +37,7 @@ class NewmanExecuteTest extends BasePiperTest {
.around(thrown)
.around(dockerExecuteRule)
.around(shellRule)
.around(jenkinsCredentialsRule)
.around(loggingRule)
.around(stepRule) // needs to be activated after dockerExecuteRule, otherwise executeDocker is not mocked
@ -162,4 +166,32 @@ class NewmanExecuteTest extends BasePiperTest {
assertThat(shellRule.shell, hasItem(endsWith('newman run testCollectionsFolder'+File.separatorChar+'B.postman_collection.json --iteration-data testDataFile --reporters junit,html --reporter-junit-export target/newman/TEST-testCollectionsFolder_B.xml --reporter-html-export target/newman/TEST-testCollectionsFolder_B.html')))
assertJobStatusSuccess()
}
@Test
void testExecuteNewmanCfAppsWithSecrets() throws Exception {
def jsonResponse = '{ "system_env_json":{"VCAP_SERVICES":{"xsuaa":[{"credentials":{"clientid":"myclientid", "clientsecret":"myclientsecret"}}]}}, "authorization_endpoint": "myAuthEndPoint", "access_token": "myAccessToken", "resources":[{"guid":"myGuid", "links":{"self":{"href":"myAppUrl"}}}] }'
jenkinsCredentialsRule.withCredentials('credentialsId', 'myuser', 'topsecret')
helper.registerAllowedMethod('httpRequest', [Map.class] , {
return [content: jsonResponse, status: 200]
})
helper.registerAllowedMethod('readJSON', [Map.class] , {
return new JsonSlurper().parseText(jsonResponse)
})
stepRule.step.newmanExecute(
script: nullScript,
juStabUtils: utils,
newmanCollection: 'testCollection',
newmanEnvironment: 'testEnvironment',
newmanGlobals: 'testGlobals',
dockerImage: 'testImage',
testRepository: 'testRepo',
failOnError: false,
cloudFoundry: ["apiEndpoint": "http://fake.endpoint.com", "org":"myOrg", "space": "mySpace", "credentialsId": "credentialsId"],
cfAppsWithSecrets: ['app1', 'app2']
)
// asserts
assertThat(shellRule.shell, hasItem(endsWith('--env-var app1_clientid=myclientid --env-var app1_clientsecret=myclientsecret --env-var app2_clientid=myclientid --env-var app2_clientsecret=myclientsecret')))
assertJobStatusSuccess()
}
}

View File

@ -0,0 +1,158 @@
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 groovy.json.JsonSlurper
import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
import util.JenkinsCredentialsRule
class CloudFoundryTest extends BasePiperTest {
public ExpectedException exception = ExpectedException.none()
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
private JenkinsCredentialsRule jenkinsCredentialsRule = new JenkinsCredentialsRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(exception)
.around(new JenkinsErrorRule(this))
.around(new JenkinsReadJsonRule(this))
.around(shellRule)
.around(jenkinsCredentialsRule)
@Test
void getAuthEndPoint_test() {
helper.registerAllowedMethod('httpRequest', [Map.class] , {
return [content: '{ "authorization_endpoint": "myAuthEndPoint" }', status: 200]
})
String apiEndPoint = 'http://dummy.sap.com'
boolean verbose = true
def cf = new CloudFoundry(nullScript)
def endPoint = cf.getAuthEndPoint(apiEndPoint, verbose)
assertThat(endPoint, is('myAuthEndPoint'))
}
@Test
void getBearerToken_test(){
String authorizationEndpoint = 'http://dummy.sap.com'
String credentialsId = 'credentialsId'
boolean verbose = true
jenkinsCredentialsRule.withCredentials(credentialsId, 'myuser', 'topsecret')
helper.registerAllowedMethod('httpRequest', [Map.class] , {
return [content: '{ "access_token": "myAccessToken" }', status: 200]
})
def cf = new CloudFoundry(nullScript)
def token = cf.getBearerToken(authorizationEndpoint, credentialsId, verbose)
assertThat(token.toString(), is('Bearer myAccessToken'))
}
@Test
void getAppRefUrl_test(){
String apiEndpoint = 'http://dummy.sap.com'
String org = 'myOrg'
String space = 'mySpace'
String bearerToken = 'myAccessToken'
String appName = 'myAppName'
boolean verbose = true
helper.registerAllowedMethod('httpRequest', [Map.class] , {
return [content: '{ "resources":[{"guid":"myGuid", "links":{"self":{"href":"myAppUrl"}}}] }', status: 200]
})
def cf = new CloudFoundry(nullScript)
def appUrl = cf.getAppRefUrl(apiEndpoint, org, space, bearerToken, appName, verbose)
assertThat(appUrl.toString(), is('myAppUrl'))
}
@Test
void getOrgGuid_test(){
String apiEndpoint = 'http://dummy.sap.com'
String org = 'myOrg'
String bearerToken = 'myToken'
boolean verbose = true
helper.registerAllowedMethod('httpRequest', [Map.class] , {
return [content: '{ "resources":[{"guid":"myOrgGuid"}] }', status: 200]
})
def cf = new CloudFoundry(nullScript)
def orgGuid = cf.getOrgGuid(apiEndpoint, org, bearerToken, verbose)
assertThat(orgGuid.toString(), is('myOrgGuid'))
}
@Test
void getSpaceGuid_test(){
String apiEndpoint ='http://dummy.sap.com'
String orgGuid = 'myOrgGuid'
String space = 'mySpace'
String bearerToken = 'myToken'
boolean verbose = true
helper.registerAllowedMethod('httpRequest', [Map.class] , {
return [content: '{ "resources":[{"guid":"mySpaceGuid"}] }', status: 200]
})
def cf = new CloudFoundry(nullScript)
def spaceGuid = cf.getSpaceGuid(apiEndpoint, orgGuid, space, bearerToken, verbose)
assertThat(spaceGuid.toString(), is('mySpaceGuid'))
}
@Test
void getAppEnvironment_test(){
String apiEndpoint = 'http://dummy.sap.com'
String org = 'myOrg'
String space = 'mySpace'
String credentialsId = 'credentialsId'
String appName = 'myAppName'
boolean verbose = true
jenkinsCredentialsRule.withCredentials(credentialsId, 'myuser', 'topsecret')
helper.registerAllowedMethod('httpRequest', [Map.class] , {
return [content: '{ "authorization_endpoint": "myAuthEndPoint", "access_token": "myAccessToken", "resources":[{"guid":"myGuid", "links":{"self":{"href":"myAppUrl"}}}] }', status: 200]
})
def cf = new CloudFoundry(nullScript)
def appEnv = cf.getAppEnvironment(apiEndpoint, org, space, credentialsId, appName, verbose)
assertThat(appEnv.toString(), is('{authorization_endpoint=myAuthEndPoint, access_token=myAccessToken, resources=[{guid=myGuid, links={self={href=myAppUrl}}}]}'))
}
@Test
void getXsuaaCredentials_test(){
String apiEndpoint = 'http://dummy.sap.com'
String org = 'myOrg'
String space = 'mySpace'
String credentialsId = 'credentialsId'
String appName = 'myAppName'
boolean verbose = true
jenkinsCredentialsRule.withCredentials(credentialsId, 'myuser', 'topsecret')
helper.registerAllowedMethod('httpRequest', [Map.class] , {
return [content: '{ "system_env_json":{"VCAP_SERVICES":{"xsuaa":[{"credentials":"myCredentials"}]}}, "authorization_endpoint": "myAuthEndPoint", "access_token": "myAccessToken", "resources":[{"guid":"myGuid", "links":{"self":{"href":"myAppUrl"}}}] }', status: 200]
})
def cf = new CloudFoundry(nullScript)
def xsuaaCred = cf.getXsuaaCredentials(apiEndpoint, org, space, credentialsId, appName, verbose)
assertThat(xsuaaCred.toString(), is('myCredentials'))
}
}

View File

@ -4,6 +4,8 @@ import com.sap.piper.ConfigurationHelper
import com.sap.piper.GenerateDocumentation
import com.sap.piper.GitUtils
import com.sap.piper.Utils
import com.sap.piper.integration.CloudFoundry
import groovy.text.GStringTemplateEngine
import groovy.transform.Field
@ -64,9 +66,43 @@ import groovy.transform.Field
* Define an additional repository where the test implementation is located.
* For protected repositories the `testRepository` needs to contain the ssh git url.
*/
'testRepository'
'testRepository',
/**
* Define name array of cloud foundry apps deployed for which secrets (clientid and clientsecret) will be appended
* to the newman command that overrides the environment json entries
* (--env-var <appName_clientid>=${clientid} & --env-var <appName_clientsecret>=${clientsecret})
*/
'cfAppsWithSecrets',
'cloudFoundry',
/**
* Cloud Foundry API endpoint.
* @parentConfigKey cloudFoundry
*/
'apiEndpoint',
/**
* Credentials to be used for deployment.
* @parentConfigKey cloudFoundry
*/
'credentialsId',
/**
* Cloud Foundry target organization.
* @parentConfigKey cloudFoundry
*/
'org',
/**
* Cloud Foundry target space.
* @parentConfigKey cloudFoundry
*/
'space',
/**
* Print more detailed information into the log.
* @possibleValues `true`, `false`
*/
'verbose'
]
@Field Map CONFIG_KEY_COMPATIBILITY = [cloudFoundry: [apiEndpoint: 'cfApiEndpoint', credentialsId: 'cfCredentialsId', org: 'cfOrg', space: 'cfSpace']]
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
/**
@ -86,7 +122,7 @@ void call(Map parameters = [:]) {
.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)
.mixin(parameters, PARAMETER_KEYS, CONFIG_KEY_COMPATIBILITY)
.use()
new Utils().pushToSWA([
@ -128,10 +164,35 @@ void call(Map parameters = [:]) {
config: config.plus([newmanCollection: collection]),
collectionDisplayName: collectionDisplayName
]).toString()
def command_secrets = ''
if(config.cfAppsWithSecrets){
CloudFoundry cfUtils = new CloudFoundry(script);
config.cfAppsWithSecrets.each { appName ->
def xsuaaCredentials = cfUtils.getXsuaaCredentials(config.cloudFoundry.apiEndpoint,
config.cloudFoundry.org,
config.cloudFoundry.space,
config.cloudFoundry.credentialsId,
appName,
config.verbose ? true : false ) //to avoid config.verbose as "null" if undefined in yaml and since function parameter boolean
command_secrets += " --env-var ${appName}_clientid=${xsuaaCredentials.clientid} --env-var ${appName}_clientsecret=${xsuaaCredentials.clientsecret}"
echo "Exposing client id and secret for ${appName}: as ${appName}_clientid and ${appName}_clientsecret to newman"
}
}
if(!config.failOnError) command += ' --suppress-exit-code'
try {
sh "PATH=\$PATH:~/.npm-global/bin newman ${command}"
} catch (e) {
if (config.cfAppsWithSecrets && command_secrets){
echo "PATH=\$PATH:~/.npm-global/bin newman ${command} **env/secrets**"
sh """
set +x
PATH=\$PATH:~/.npm-global/bin newman ${command} ${command_secrets}
"""
}
else{
sh "PATH=\$PATH:~/.npm-global/bin newman ${command}"
}
}
catch (e) {
error "[${STEP_NAME}] ERROR: The execution of the newman tests failed, see the log for details."
}
}