Merge branch 'master' into fix/rolebackNeoDeployChange

This commit is contained in:
Marcus Holl
2018-07-31 16:41:36 +02:00
committed by GitHub
6 changed files with 554 additions and 8 deletions
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# this is simply testing if the application root returns HTTP STATUS_CODE
curl -so /dev/null -w '%{response_code}' https://$1 | grep $STATUS_CODE
@@ -84,6 +84,23 @@ steps:
fail:
high: '0'
archive: false
cloudFoundryDeploy:
cloudFoundry:
apiEndpoint: 'https://api.cf.eu10.hana.ondemand.com'
deployTool: 'cf_native'
deployType: 'standard'
mtaDeployParameters: '-f'
mtaExtensionDescriptor: ''
mtaPath: ''
smokeTestScript: 'blueGreenCheckScript.sh'
smokeTestStatusCode: 200
stashContent: []
cf_native:
dockerImage: 's4sdk/docker-cf-cli'
dockerWorkspace: '/home/piper'
mtaDeployPlugin:
dockerImage: 's4sdk/docker-cf-cli'
dockerWorkspace: '/home/piper'
influxWriteData:
influxServer: 'jenkins'
mavenExecute:
+33 -7
View File
@@ -26,27 +26,53 @@ class ConfigurationHelper implements Serializable {
return this
}
ConfigurationHelper mixinGeneralConfig(commonPipelineEnvironment, Set filter = null){
ConfigurationHelper mixinGeneralConfig(commonPipelineEnvironment, Set filter = null, Script step = null, Map compatibleParameters = [:]){
Map stepConfiguration = ConfigurationLoader.generalConfiguration([commonPipelineEnvironment: commonPipelineEnvironment])
return mixin(stepConfiguration, filter)
return mixin(stepConfiguration, filter, step, compatibleParameters)
}
ConfigurationHelper mixinStageConfig(commonPipelineEnvironment, stageName, Set filter = null){
ConfigurationHelper mixinStageConfig(commonPipelineEnvironment, stageName, Set filter = null, Script step = null, Map compatibleParameters = [:]){
Map stageConfiguration = ConfigurationLoader.stageConfiguration([commonPipelineEnvironment: commonPipelineEnvironment], stageName)
return mixin(stageConfiguration, filter)
return mixin(stageConfiguration, filter, step, compatibleParameters)
}
ConfigurationHelper mixinStepConfig(commonPipelineEnvironment, Set filter = null){
ConfigurationHelper mixinStepConfig(commonPipelineEnvironment, Set filter = null, Script step = null, Map compatibleParameters = [:]){
if(!name) throw new IllegalArgumentException('Step has no public name property!')
Map stepConfiguration = ConfigurationLoader.stepConfiguration([commonPipelineEnvironment: commonPipelineEnvironment], name)
return mixin(stepConfiguration, filter)
return mixin(stepConfiguration, filter, step, compatibleParameters)
}
ConfigurationHelper mixin(Map parameters, Set filter = null){
ConfigurationHelper mixin(Map parameters, Set filter = null, Script step = null, Map compatibleParameters = [:]){
if (parameters.size() > 0 && compatibleParameters.size() > 0) {
parameters = ConfigurationMerger.merge(handleCompatibility(step, compatibleParameters, parameters), null, parameters)
}
config = ConfigurationMerger.merge(parameters, filter, config)
return this
}
private Map handleCompatibility(Script step, Map compatibleParameters, String paramStructure = '', Map configMap ) {
Map newConfig = [:]
compatibleParameters.each {entry ->
if (entry.getValue() instanceof Map) {
paramStructure = (paramStructure ? paramStructure + '.' : '') + entry.getKey()
newConfig[entry.getKey()] = handleCompatibility(step, entry.getValue(), paramStructure, configMap)
} else {
def configSubMap = configMap
for(String key in paramStructure.tokenize('.')){
configSubMap = configSubMap?.get(key)
}
if (configSubMap == null || (configSubMap != null && configSubMap[entry.getKey()] == null)) {
newConfig[entry.getKey()] = configMap[entry.getValue()]
def paramName = (paramStructure ? paramStructure + '.' : '') + entry.getKey()
if (step && configMap[entry.getValue()] != null) {
step.echo ("[INFO] The parameter '${entry.getValue()}' is COMPATIBLE to the parameter '${paramName}'")
}
}
}
}
return newConfig
}
Map dependingOn(dependentKey){
return [
mixin: {key ->
+271
View File
@@ -0,0 +1,271 @@
#!groovy
import groovy.json.JsonSlurperClassic
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import org.yaml.snakeyaml.Yaml
import util.BasePiperTest
import util.JenkinsEnvironmentRule
import util.JenkinsDockerExecuteRule
import util.JenkinsLoggingRule
import util.JenkinsShellCallRule
import util.JenkinsStepRule
import util.JenkinsWriteFileRule
import util.Rules
import static org.junit.Assert.assertTrue
import static org.junit.Assert.assertEquals
class CloudFoundryDeployTest extends BasePiperTest {
private ExpectedException thrown = ExpectedException.none()
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
private JenkinsShellCallRule jscr = new JenkinsShellCallRule(this)
private JenkinsWriteFileRule jwfr = new JenkinsWriteFileRule(this)
private JenkinsDockerExecuteRule jedr = new JenkinsDockerExecuteRule(this)
private JenkinsStepRule jsr = new JenkinsStepRule(this)
private JenkinsEnvironmentRule jer = new JenkinsEnvironmentRule(this)
@Rule
public RuleChain rules = Rules
.getCommonRules(this)
.around(thrown)
.around(jlr)
.around(jscr)
.around(jwfr)
.around(jedr)
.around(jer)
.around(jsr) // needs to be activated after jedr, otherwise executeDocker is not mocked
@Before
void init() throws Throwable {
helper.registerAllowedMethod('usernamePassword', [Map], { m -> return m })
helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c ->
if(l[0].credentialsId == 'test_cfCredentialsId') {
binding.setProperty('username', 'test_cf')
binding.setProperty('password', '********')
} else if(l[0].credentialsId == 'test_camCredentialsId') {
binding.setProperty('username', 'test_cam')
binding.setProperty('password', '********')
}
try {
c()
} finally {
binding.setProperty('username', null)
binding.setProperty('password', null)
}
})
}
@Test
void testNoTool() throws Exception {
nullScript.commonPipelineEnvironment.configuration = [
general: [
camSystemRole: 'testRole',
cfCredentialsId: 'myCreds'
],
stages: [
acceptance: [
cfOrg: 'testOrg',
cfSpace: 'testSpace',
deployUser: 'testUser',
]
],
steps: [
cloudFoundryDeploy: []
]
]
jsr.step.cloudFoundryDeploy([
script: nullScript,
juStabUtils: utils,
deployTool: '',
stageName: 'acceptance',
])
assertTrue(jlr.log.contains('[cloudFoundryDeploy] General parameters: deployTool=, deployType=standard, cfApiEndpoint=https://api.cf.eu10.hana.ondemand.com, cfOrg=testOrg, cfSpace=testSpace, cfCredentialsId=myCreds, deployUser=testUser'))
}
@Test
void testNotAvailableTool() throws Exception {
nullScript.commonPipelineEnvironment.configuration = [
general: [
cfCredentialsId: 'myCreds'
],
stages: [
acceptance: [
cfOrg: 'testOrg',
cfSpace: 'testSpace',
deployUser: 'testUser',
]
],
steps: [
cloudFoundryDeploy: []
]
]
jsr.step.cloudFoundryDeploy([
script: nullScript,
juStabUtils: utils,
deployTool: 'notAvailable',
stageName: 'acceptance'
])
assertTrue(jlr.log.contains('[cloudFoundryDeploy] General parameters: deployTool=notAvailable, deployType=standard, cfApiEndpoint=https://api.cf.eu10.hana.ondemand.com, cfOrg=testOrg, cfSpace=testSpace, cfCredentialsId=myCreds, deployUser=testUser'))
}
@Test
void testCfNativeWithAppName() {
jsr.step.cloudFoundryDeploy([
script: nullScript,
juStabUtils: utils,
deployTool: 'cf_native',
cfOrg: 'testOrg',
cfSpace: 'testSpace',
cfCredentialsId: 'test_cfCredentialsId',
cfAppName: 'testAppName',
cfManifest: 'test.yml'
])
assertEquals('s4sdk/docker-cf-cli', jedr.dockerParams.dockerImage)
assertEquals('/home/piper', jedr.dockerParams.dockerWorkspace)
assertEquals('200', jedr.dockerParams.dockerEnvVars.STATUS_CODE.toString())
assertTrue(jscr.shell[1].contains('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))
assertTrue(jscr.shell[1].contains('cf push "testAppName" -f "test.yml"'))
}
@Test
void testCfNativeWithAppNameCustomApi() {
jsr.step.cloudFoundryDeploy([
script: nullScript,
juStabUtils: utils,
deployTool: 'cf_native',
cfApiEndpoint: 'https://customApi',
cfOrg: 'testOrg',
cfSpace: 'testSpace',
cfCredentialsId: 'test_cfCredentialsId',
cfAppName: 'testAppName',
cfManifest: 'test.yml'
])
assertTrue(jscr.shell[1].contains('cf login -u "test_cf" -p \'********\' -a https://customApi -o "testOrg" -s "testSpace"'))
}
@Test
void testCfNativeWithAppNameCompatible() {
jsr.step.cloudFoundryDeploy([
script: nullScript,
juStabUtils: utils,
deployTool: 'cf_native',
cloudFoundry: [
org: 'testOrg',
space: 'testSpace',
credentialsId: 'test_cfCredentialsId',
appName: 'testAppName',
manifest: 'test.yml'
]
])
assertEquals('s4sdk/docker-cf-cli', jedr.dockerParams.dockerImage)
assertEquals('/home/piper', jedr.dockerParams.dockerWorkspace)
assertEquals('200', jedr.dockerParams.dockerEnvVars.STATUS_CODE.toString())
assertTrue(jscr.shell[1].contains('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))
assertTrue(jscr.shell[1].contains('cf push "testAppName" -f "test.yml"'))
}
@Test
void testCfNativeAppNameFromManifest() {
helper.registerAllowedMethod('fileExists', [String.class], { s -> return true })
helper.registerAllowedMethod("readYaml", [Map], { Map m ->
if(m.text) {
return new Yaml().load(m.text)
} else if(m.file == 'test.yml') {
return [applications: [[name: 'manifestAppName']]]
} else if(m.file) {
return new Yaml().load((m.file as File).text)
} else {
throw new IllegalArgumentException("Key 'text' is missing in map ${m}.")
}
})
jsr.step.cloudFoundryDeploy([
script: nullScript,
juStabUtils: utils,
deployTool: 'cf_native',
cfOrg: 'testOrg',
cfSpace: 'testSpace',
cfCredentialsId: 'test_cfCredentialsId',
cfManifest: 'test.yml'
])
assertTrue(jscr.shell[1].contains('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))
assertTrue(jscr.shell[1].contains('cf push -f "test.yml"'))
}
@Test
void testCfNativeWithoutAppName() {
helper.registerAllowedMethod('fileExists', [String.class], { s -> return true })
helper.registerAllowedMethod("readYaml", [Map], { Map m ->
if(m.text) {
return new Yaml().load(m.text)
} else if(m.file == 'test.yml') {
return [applications: [[]]]
} else if(m.file) {
return new Yaml().load((m.file as File).text)
} else {
throw new IllegalArgumentException("Key 'text' is missing in map ${m}.")
}
})
thrown.expect(hudson.AbortException)
thrown.expectMessage('[cloudFoundryDeploy] ERROR: No appName available in manifest test.yml.')
jsr.step.cloudFoundryDeploy([
script: nullScript,
juStabUtils: utils,
deployTool: 'cf_native',
cfOrg: 'testOrg',
cfSpace: 'testSpace',
cfCredentialsId: 'test_cfCredentialsId',
cfManifest: 'test.yml'
])
}
@Test
void testMta() {
jsr.step.cloudFoundryDeploy([
script: nullScript,
juStabUtils: utils,
cfOrg: 'testOrg',
cfSpace: 'testSpace',
cfCredentialsId: 'test_cfCredentialsId',
deployTool: 'mtaDeployPlugin',
mtaPath: 'target/test.mtar'
])
assertEquals('s4sdk/docker-cf-cli', jedr.dockerParams.dockerImage)
assertEquals('/home/piper', jedr.dockerParams.dockerWorkspace)
assertTrue(jscr.shell[0].contains('cf login -u test_cf -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"'))
assertTrue(jscr.shell[0].contains("cf deploy target/test.mtar -f".toString()))
}
}
@@ -158,6 +158,69 @@ class ConfigurationHelperTest {
Assert.assertThat(config, hasEntry('executeDocker3', false))
}
@Test
void testHandleCompatibility() {
def configuration = new ConfigurationHelper()
.mixin([old1: 'oldValue1', old2: 'oldValue2', test: 'testValue'], null, null, [newStructure: [new1: 'old1', new2: 'old2']])
.use()
Assert.assertThat(configuration.size(), is(4))
Assert.assertThat(configuration.newStructure.new1, is('oldValue1'))
Assert.assertThat(configuration.newStructure.new2, is('oldValue2'))
}
@Test
void testHandleCompatibilityFlat() {
def configuration = new ConfigurationHelper()
.mixin([old1: 'oldValue1', old2: 'oldValue2', test: 'testValue'], null, null, [new1: 'old1', new2: 'old2'])
.use()
Assert.assertThat(configuration.size(), is(5))
Assert.assertThat(configuration.new1, is('oldValue1'))
Assert.assertThat(configuration.new2, is('oldValue2'))
}
@Test
void testHandleCompatibilityDeep() {
def configuration = new ConfigurationHelper()
.mixin([old1: 'oldValue1', old2: 'oldValue2', test: 'testValue'], null, null, [deep:[deeper:[newStructure: [new1: 'old1', new2: 'old2']]]])
.use()
Assert.assertThat(configuration.size(), is(4))
Assert.assertThat(configuration.deep.deeper.newStructure.new1, is('oldValue1'))
Assert.assertThat(configuration.deep.deeper.newStructure.new2, is('oldValue2'))
}
@Test
void testHandleCompatibilityNewAvailable() {
def configuration = new ConfigurationHelper([old1: 'oldValue1', newStructure: [new1: 'newValue1'], test: 'testValue'])
.mixin([old1: 'oldValue1', newStructure: [new1: 'newValue1'], test: 'testValue'], null, null, [newStructure: [new1: 'old1', new2: 'old2']])
.use()
Assert.assertThat(configuration.size(), is(3))
Assert.assertThat(configuration.newStructure.new1, is('newValue1'))
}
@Test
void testHandleCompatibilityOldNotSet() {
def configuration = new ConfigurationHelper([old1: null, test: 'testValue'])
.mixin([old1: null, test: 'testValue'], null, null, [newStructure: [new1: 'old1', new2: 'old2']])
.use()
Assert.assertThat(configuration.size(), is(2))
Assert.assertThat(configuration.newStructure.new1, is(null))
}
@Test
void testHandleCompatibilityNoneAvailable() {
def configuration = new ConfigurationHelper([old1: null, test: 'testValue'])
.mixin([test: 'testValue'], null, null, [newStructure: [new1: 'old1', new2: 'old2']])
.use()
Assert.assertThat(configuration.size(), is(2))
Assert.assertThat(configuration.newStructure.new1, is(null))
}
@Test
public void testWithMandoryParameterReturnDefaultFailureMessage() {
@@ -185,5 +248,4 @@ class ConfigurationHelperTest {
public void testWithMandoryParameterDefaultCustomFailureMessageNotProvidedSucceeds() {
new ConfigurationHelper([myKey: 'myValue']).withMandatoryProperty('myKey')
}
}
+167
View File
@@ -0,0 +1,167 @@
import com.sap.piper.Utils
import com.sap.piper.ConfigurationHelper
import groovy.transform.Field
@Field String STEP_NAME = 'cloudFoundryDeploy'
@Field Set STEP_CONFIG_KEYS = [
'cloudFoundry',
'deployUser',
'deployTool',
'deployType',
'dockerImage',
'dockerWorkspace',
'mtaDeployParameters',
'mtaExtensionDescriptor',
'mtaPath',
'smokeTestScript',
'smokeTestStatusCode',
'stashContent']
@Field Map CONFIG_KEY_COMPATIBILITY = [cloudFoundry: [apiEndpoint: 'cfApiEndpoint', appName:'cfAppName', credentialsId: 'cfCredentialsId', manifest: 'cfManifest', org: 'cfOrg', space: 'cfSpace']]
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
def call(Map parameters = [:]) {
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
def utils = parameters.juStabUtils
if (utils == null) {
utils = new Utils()
}
def script = parameters.script
if (script == null)
script = [commonPipelineEnvironment: commonPipelineEnvironment]
Map config = ConfigurationHelper
.loadStepDefaults(this)
.mixinGeneralConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS, this, CONFIG_KEY_COMPATIBILITY)
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS, this, CONFIG_KEY_COMPATIBILITY)
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS, this, CONFIG_KEY_COMPATIBILITY)
.mixin(parameters, PARAMETER_KEYS, this, CONFIG_KEY_COMPATIBILITY)
.dependingOn('deployTool').mixin('dockerImage')
.dependingOn('deployTool').mixin('dockerWorkspace')
.withMandatoryProperty('cloudFoundry/org')
.withMandatoryProperty('cloudFoundry/space')
.withMandatoryProperty('cloudFoundry/credentialsId')
.use()
echo "[${STEP_NAME}] General parameters: deployTool=${config.deployTool}, deployType=${config.deployType}, cfApiEndpoint=${config.cloudFoundry.apiEndpoint}, cfOrg=${config.cloudFoundry.org}, cfSpace=${config.cloudFoundry.space}, cfCredentialsId=${config.cloudFoundry.credentialsId}, deployUser=${config.deployUser}"
utils.unstash 'deployDescriptor'
if (config.deployTool == 'mtaDeployPlugin') {
// set default mtar path
config = new ConfigurationHelper(config)
.addIfEmpty('mtaPath', config.mtaPath?:findMtar())
.use()
dockerExecute(dockerImage: config.dockerImage, dockerWorkspace: config.dockerWorkspace, stashContent: config.stashContent) {
deployMta(config)
}
return
}
if (config.deployTool == 'cf_native') {
config.smokeTest = ''
if (config.smokeTestScript == 'blueGreenCheckScript.sh') {
writeFile file: config.smokeTestScript, text: libraryResource(config.smokeTestScript)
} else {
utils.unstash 'pipelineConfigAndTests'
}
config.smokeTest = '--smoke-test $(pwd)/' + config.smokeTestScript
sh "chmod +x ${config.smokeTestScript}"
echo "[${STEP_NAME}] CF native deployment (${config.deployType}) with cfAppName=${config.cloudFoundry.appName}, cfManifest=${config.cloudFoundry.manifest}, smokeTestScript=${config.smokeTestScript}"
dockerExecute (
dockerImage: config.dockerImage,
dockerWorkspace: config.dockerWorkspace,
stashContent: config.stashContent,
dockerEnvVars: [CF_HOME:"${config.dockerWorkspace}", CF_PLUGIN_HOME:"${config.dockerWorkspace}", STATUS_CODE: "${config.smokeTestStatusCode}"]
) {
deployCfNative(config)
}
return
}
}
}
def findMtar(){
def mtarPath = ''
def mtarFiles = findFiles(glob: '**/target/*.mtar')
if(mtarFiles.length > 1){
error 'Found multiple *.mtar files, please specify file via mtaPath parameter! ${mtarFiles}'
}
if(mtarFiles.length == 1){
return mtarFiles[0].path
}
error 'No *.mtar file found!'
}
def deployCfNative (config) {
withCredentials([usernamePassword(
credentialsId: config.cloudFoundry.credentialsId,
passwordVariable: 'password',
usernameVariable: 'username'
)]) {
def deployCommand = 'push'
if (config.deployType == 'blue-green') {
deployCommand = 'blue-green-deploy'
} else {
config.smokeTest = ''
}
// check if appName is available
if (config.cloudFoundry.appName == null || config.cloudFoundry.appName == '') {
if (fileExists(config.cloudFoundry.manifest)) {
def manifest = readYaml file: config.cloudFoundry.manifest
if (!manifest || !manifest.applications || !manifest.applications[0].name)
error "[${STEP_NAME}] ERROR: No appName available in manifest ${config.cloudFoundry.manifest}."
} else {
error "[${STEP_NAME}] ERROR: No manifest file ${config.cloudFoundry.manifest} found."
}
}
sh """#!/bin/bash
set +x
export HOME=${config.dockerWorkspace}
cf login -u \"${username}\" -p '${password}' -a ${config.cloudFoundry.apiEndpoint} -o \"${config.cloudFoundry.org}\" -s \"${config.cloudFoundry.space}\"
cf plugins
cf ${deployCommand} ${config.cloudFoundry.appName?"\"${config.cloudFoundry.appName}\"":''} -f \"${config.cloudFoundry.manifest}\" ${config.smokeTest}"""
def retVal = sh script: "cf app \"${config.cloudFoundry.appName}-old\"", returnStatus: true
if (retVal == 0) {
sh "cf delete \"${config.cloudFoundry.appName}-old\" -r -f"
}
sh "cf logout"
}
}
def deployMta (config) {
if (config.mtaExtensionDescriptor == null) config.mtaExtensionDescriptor = ''
if (!config.mtaExtensionDescriptor.isEmpty() && !config.mtaExtensionDescriptor.startsWith('-e ')) config.mtaExtensionDescriptor = "-e ${config.mtaExtensionDescriptor}"
def deployCommand = 'deploy'
if (config.deployType == 'blue-green')
deployCommand = 'bg-deploy'
withCredentials([usernamePassword(
credentialsId: config.cloudFoundry.credentialsId,
passwordVariable: 'password',
usernameVariable: 'username'
)]) {
echo "[${STEP_NAME}] Deploying MTA (${config.mtaPath}) with following parameters: ${config.mtaExtensionDescriptor} ${config.mtaDeployParameters}"
sh """#!/bin/bash
export HOME=${config.dockerWorkspace}
set +x
cf api ${config.cloudFoundry.apiEndpoint}
cf login -u ${username} -p '${password}' -a ${config.cloudFoundry.apiEndpoint} -o \"${config.cloudFoundry.org}\" -s \"${config.cloudFoundry.space}\"
cf plugins
cf ${deployCommand} ${config.mtaPath} ${config.mtaDeployParameters} ${config.mtaExtensionDescriptor}"""
sh "cf logout"
}
}