From c8ca3ea5224af294674f9eca1c4406b49551478a Mon Sep 17 00:00:00 2001 From: Kevin Hudemann Date: Mon, 6 Jul 2020 16:16:48 +0200 Subject: [PATCH] Add mta extension credentials handling to cloudFoundryDeploy step and infer deployTool default from buildTool (#1761) --- resources/default_pipeline_environment.yml | 11 ++- test/groovy/CloudFoundryDeployTest.groovy | 107 +++++++++++++++++++-- test/groovy/MulticloudDeployTest.groovy | 19 ---- vars/cloudFoundryDeploy.groovy | 59 +++++++++++- vars/multicloudDeploy.groovy | 10 +- 5 files changed, 171 insertions(+), 35 deletions(-) diff --git a/resources/default_pipeline_environment.yml b/resources/default_pipeline_environment.yml index af3678a3a..9e7b8d750 100644 --- a/resources/default_pipeline_environment.yml +++ b/resources/default_pipeline_environment.yml @@ -164,7 +164,6 @@ steps: apiEndpoint: 'https://api.cf.eu10.hana.ondemand.com' apiParameters: '' loginParameters: '' - deployTool: 'cf_native' deployType: 'standard' keepOldInstance: false cfNativeDeployParameters: '' @@ -182,6 +181,16 @@ steps: mtaDeployPlugin: dockerImage: 'ppiper/cf-cli' dockerWorkspace: '/home/piper' + mta: + deployTool: 'mtaDeployPlugin' + maven: + deployTool: 'cf_native' + npm: + deployTool: 'cf_native' + docker: + deployTool: 'cf_native' + kaniko: + deployTool: 'cf_native' containerExecuteStructureTests: containerCommand: '/busybox/tail -f /dev/null' containerShell: '/busybox/sh' diff --git a/test/groovy/CloudFoundryDeployTest.groovy b/test/groovy/CloudFoundryDeployTest.groovy index cb93ad303..58ecea38e 100644 --- a/test/groovy/CloudFoundryDeployTest.groovy +++ b/test/groovy/CloudFoundryDeployTest.groovy @@ -93,7 +93,7 @@ class CloudFoundryDeployTest extends BasePiperTest { nullScript.commonPipelineEnvironment.configuration = [ general: [ camSystemRole: 'testRole', - cfCredentialsId: 'myCreds' + cfCredentialsId: 'test_cfCredentialsId' ], stages: [ acceptance: [ @@ -106,17 +106,16 @@ class CloudFoundryDeployTest extends BasePiperTest { cloudFoundryDeploy: [] ] ] - + nullScript.commonPipelineEnvironment.setBuildTool('mta') stepRule.step.cloudFoundryDeploy([ script: nullScript, juStabUtils: utils, jenkinsUtilsStub: new JenkinsUtilsMock(), - deployTool: '', + mtaPath: 'target/test.mtar', stageName: 'acceptance', ]) // asserts - assertThat(loggingRule.log, containsString('[cloudFoundryDeploy] General parameters: deployTool=, deployType=standard, cfApiEndpoint=https://api.cf.eu10.hana.ondemand.com, cfOrg=testOrg, cfSpace=testSpace, cfCredentialsId=myCreds')) - assertThat(loggingRule.log, containsString('[cloudFoundryDeploy] WARNING! Found unsupported deployTool. Skipping deployment.')) + assertThat(loggingRule.log, containsString('[cloudFoundryDeploy] General parameters: deployTool=mtaDeployPlugin, deployType=standard, cfApiEndpoint=https://api.cf.eu10.hana.ondemand.com, cfOrg=testOrg, cfSpace=testSpace, cfCredentialsId=test_cfCredentialsId')) } @Test @@ -979,6 +978,7 @@ class CloudFoundryDeployTest extends BasePiperTest { space: 'testSpace', manifest: 'test.yml', ], + deployTool: 'cf_native', cfCredentialsId: 'test_cfCredentialsId', verbose: true ]) @@ -1006,6 +1006,7 @@ class CloudFoundryDeployTest extends BasePiperTest { space: 'testSpace', manifest: 'test.yml', ], + deployTool: 'cf_native', cfCredentialsId: 'test_cfCredentialsId', verbose: true ]) @@ -1149,6 +1150,7 @@ class CloudFoundryDeployTest extends BasePiperTest { space: 'irrelevant', appName: 'myValidAppName123' ], + deployTool: 'cf_native', cfCredentialsId: 'test_cfCredentialsId', mtaPath: 'irrelevant' ]) @@ -1167,6 +1169,7 @@ class CloudFoundryDeployTest extends BasePiperTest { space: 'irrelevant', appName: 'my-Valid-AppName123' ], + deployTool: 'cf_native', cfCredentialsId: 'test_cfCredentialsId', mtaPath: 'irrelevant' ]) @@ -1176,7 +1179,9 @@ class CloudFoundryDeployTest extends BasePiperTest { @Test void testMtaExtensionDescriptor() { - + fileExistsRule.existingFiles.addAll( + 'globalMtaDescriptor.mtaext', + ) stepRule.step.cloudFoundryDeploy([ script: nullScript, juStabUtils: utils, @@ -1198,6 +1203,9 @@ class CloudFoundryDeployTest extends BasePiperTest { @Test void testTargetMtaExtensionDescriptor() { + fileExistsRule.existingFiles.addAll( + 'targetMtaDescriptor.mtaext', + ) stepRule.step.cloudFoundryDeploy([ script: nullScript, juStabUtils: utils, @@ -1215,4 +1223,91 @@ class CloudFoundryDeployTest extends BasePiperTest { ]) assertThat(shellRule.shell, hasItem(containsString("-e targetMtaDescriptor.mtaext"))) } + + @Test + void testMtaExtensionCredentials() { + fileExistsRule.existingFiles.addAll( + 'mtaext.mtaext', + ) + credentialsRule.withCredentials("mtaExtCredTest","token") + + helper.registerAllowedMethod('readFile', [String], {file -> + if (file == 'mtaext.mtaext') { + return '_schema-version: \'3.1\'\n' + + 'ID: test.ext\n' + + 'extends: test\n' + + '\n' + + 'parameters:\n' + + ' test-credentials: "<%= testCred %>"' + } + return '' + }) + + stepRule.step.cloudFoundryDeploy([ + script: nullScript, + juStabUtils: utils, + jenkinsUtilsStub: new JenkinsUtilsMock(), + cfOrg: 'testOrg', + cfSpace: 'testSpace', + cfCredentialsId: 'test_cfCredentialsId', + deployTool: 'mtaDeployPlugin', + mtaPath: 'target/test.mtar', + mtaExtensionDescriptor: "mtaext.mtaext", + mtaExtensionCredentials: [ + testCred: 'mtaExtCredTest' + ] + ]) + + assertThat(shellRule.shell, hasItem(containsString('cp mtaext.mtaext mtaext.mtaext.original'))) + assertThat(shellRule.shell, hasItem(containsString('mv --force mtaext.mtaext.original mtaext.mtaext'))) + assertThat(writeFileRule.files['mtaext.mtaext'], is('_schema-version: \'3.1\'\n' + + 'ID: test.ext\n' + + 'extends: test\n' + + '\n' + + 'parameters:\n' + + ' test-credentials: "token"')) + } + + @Test + void testMtaExtensionDescriptorNotFound() { + thrown.expect(hudson.AbortException) + thrown.expectMessage('[cloudFoundryDeploy] The mta extension descriptor file mtaext.mtaext does not exist at the configured location.') + + stepRule.step.cloudFoundryDeploy([ + script: nullScript, + juStabUtils: utils, + jenkinsUtilsStub: new JenkinsUtilsMock(), + cfOrg: 'testOrg', + cfSpace: 'testSpace', + cfCredentialsId: 'test_cfCredentialsId', + deployTool: 'mtaDeployPlugin', + mtaPath: 'target/test.mtar', + mtaExtensionDescriptor: "mtaext.mtaext" + ]) + } + + @Test + void testMtaExtensionDescriptorReadFails() { + fileExistsRule.existingFiles.addAll( + 'mtaext.mtaext', + ) + + thrown.expect(Exception) + thrown.expectMessage('[cloudFoundryDeploy] Unable to read mta extension file mtaext.mtaext.') + + stepRule.step.cloudFoundryDeploy([ + script: nullScript, + juStabUtils: utils, + jenkinsUtilsStub: new JenkinsUtilsMock(), + cfOrg: 'testOrg', + cfSpace: 'testSpace', + cfCredentialsId: 'test_cfCredentialsId', + deployTool: 'mtaDeployPlugin', + mtaPath: 'target/test.mtar', + mtaExtensionDescriptor: "mtaext.mtaext", + mtaExtensionCredentials: [ + testCred: 'mtaExtCredTest' + ], + ]) + } } diff --git a/test/groovy/MulticloudDeployTest.groovy b/test/groovy/MulticloudDeployTest.groovy index 1664c4734..7e1ec5707 100644 --- a/test/groovy/MulticloudDeployTest.groovy +++ b/test/groovy/MulticloudDeployTest.groovy @@ -207,7 +207,6 @@ class MulticloudDeployTest extends BasePiperTest { assert cloudFoundryDeployRule.hasParameter('deployType', 'standard') assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry) assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath) - assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native') } @Test @@ -225,7 +224,6 @@ class MulticloudDeployTest extends BasePiperTest { assert cloudFoundryDeployRule.hasParameter('deployType', 'blue-green') assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry1) assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath) - assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native') } @Test @@ -247,7 +245,6 @@ class MulticloudDeployTest extends BasePiperTest { assert cloudFoundryDeployRule.hasParameter('deployType', 'blue-green') assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry1) assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath) - assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native') assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry2) } @@ -271,7 +268,6 @@ class MulticloudDeployTest extends BasePiperTest { assert cloudFoundryDeployRule.hasParameter('deployType', 'blue-green') assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry1) assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath) - assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native') assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry2) } @@ -372,19 +368,4 @@ class MulticloudDeployTest extends BasePiperTest { assertFalse(executedOnKubernetes) } - - @Test - void multicloudParallelCfStandardDeployTest() { - stepRule.step.multicloudDeploy([ - script : nullScript, - enableZeroDowntimeDeployment: false, - parallelExecution : true, - source : 'file.mtar' - ]) - - assertTrue(executedInParallel) - assertFalse(executedOnNode) - assertFalse(executedOnKubernetes) - - } } diff --git a/vars/cloudFoundryDeploy.groovy b/vars/cloudFoundryDeploy.groovy index c2f8ed7bd..7c4e17ce2 100644 --- a/vars/cloudFoundryDeploy.groovy +++ b/vars/cloudFoundryDeploy.groovy @@ -11,6 +11,7 @@ import static com.sap.piper.Prerequisites.checkScript @Field String STEP_NAME = getClass().getName() @Field Set GENERAL_CONFIG_KEYS = [ + 'buildTool', 'cloudFoundry', /** * Cloud Foundry API endpoint. @@ -73,6 +74,7 @@ import static com.sap.piper.Prerequisites.checkScript 'space', /** * Defines the tool which should be used for deployment. + * If it is not set it will be inferred automatically based on the buildTool, i.e., for MTA projects `mtaDeployPlugin` will be used and `cf_native` for other types of projects. * @possibleValues 'cf_native', 'mtaDeployPlugin' */ 'deployTool', @@ -112,6 +114,11 @@ import static com.sap.piper.Prerequisites.checkScript * Additional parameters passed to mta deployment command. */ 'mtaDeployParameters', + /** + * Defines a map of credentials that need to be replaced in the `mtaExtensionDescriptor`. + * This map needs to be created as `value-to-be-replaced`:`id-of-a-credential-in-jenkins` + */ + 'mtaExtensionCredentials', /** * Defines additional extension descriptor file for deployment with the mtaDeployPlugin. */ @@ -206,6 +213,8 @@ void call(Map parameters = [:]) { .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) + .addIfEmpty('buildTool', script.commonPipelineEnvironment.getBuildTool()) + .dependingOn('buildTool').mixin('deployTool') .dependingOn('deployTool').mixin('dockerImage') .dependingOn('deployTool').mixin('dockerWorkspace') .withMandatoryProperty('cloudFoundry/org') @@ -306,8 +315,19 @@ def findMtar(){ } def deployMta(config) { - if (config.mtaExtensionDescriptor == null) config.mtaExtensionDescriptor = '' - if (!config.mtaExtensionDescriptor.isEmpty() && !config.mtaExtensionDescriptor.startsWith('-e ')) config.mtaExtensionDescriptor = "-e ${config.mtaExtensionDescriptor}" + String mtaExtensionDescriptorParam = '' + + if (config.mtaExtensionDescriptor) { + if (!fileExists(config.mtaExtensionDescriptor)) { + error "[${STEP_NAME}] The mta extension descriptor file ${config.mtaExtensionDescriptor} does not exist at the configured location." + } + + mtaExtensionDescriptorParam = "-e ${config.mtaExtensionDescriptor}" + + if (config.mtaExtensionCredentials) { + handleMtaExtensionCredentials(config) + } + } def deployCommand = 'deploy' if (config.deployType == 'blue-green') { @@ -317,11 +337,40 @@ def deployMta(config) { } } - def deployStatement = "cf ${deployCommand} ${config.mtaPath} ${config.mtaDeployParameters} ${config.mtaExtensionDescriptor}" + def deployStatement = "cf ${deployCommand} ${config.mtaPath} ${config.mtaDeployParameters} ${mtaExtensionDescriptorParam}" def apiStatement = "cf api ${config.cloudFoundry.apiEndpoint} ${config.apiParameters}" - echo "[${STEP_NAME}] Deploying MTA (${config.mtaPath}) with following parameters: ${config.mtaExtensionDescriptor} ${config.mtaDeployParameters}" - deploy(apiStatement, deployStatement, config, null) + echo "[${STEP_NAME}] Deploying MTA (${config.mtaPath}) with following parameters: ${mtaExtensionDescriptorParam} ${config.mtaDeployParameters}" + try { + deploy(apiStatement, deployStatement, config, null) + } finally { + if (config.mtaExtensionCredentials && config.mtaExtensionDescriptor && fileExists(config.mtaExtensionDescriptor)) { + sh "mv --force ${config.mtaExtensionDescriptor}.original ${config.mtaExtensionDescriptor} || echo 'The file ${config.mtaExtensionDescriptor}.original could not be renamed.'" + } + } +} + +private void handleMtaExtensionCredentials(Map config) { + echo "[${STEP_NAME}] Modifying ${config.mtaExtensionDescriptor}. Adding credential values from Jenkins." + sh "cp ${config.mtaExtensionDescriptor} ${config.mtaExtensionDescriptor}.original" + + Map mtaExtensionCredentials = config.mtaExtensionCredentials + + String fileContent = '' + + try { + fileContent = readFile config.mtaExtensionDescriptor + } catch (Exception e) { + error("[${STEP_NAME}] Unable to read mta extension file ${config.mtaExtensionDescriptor}. (${e.getMessage()})") + } + + mtaExtensionCredentials.each { key, credentialsId -> + withCredentials([string(credentialsId: credentialsId, variable: 'mtaExtensionCredential')]) { + fileContent = fileContent.replace('<%= ' + key.toString() + ' %>', mtaExtensionCredential.toString()) + } + } + + writeFile file: config.mtaExtensionDescriptor, text: fileContent } private checkAndUpdateDeployTypeForNotSupportedManifest(Map config){ diff --git a/vars/multicloudDeploy.groovy b/vars/multicloudDeploy.groovy index b749047dc..dffe3c446 100644 --- a/vars/multicloudDeploy.groovy +++ b/vars/multicloudDeploy.groovy @@ -99,11 +99,12 @@ void call(parameters = [:]) { if (config.cfTargets) { def deploymentType = DeploymentType.selectFor(CloudPlatform.CLOUD_FOUNDRY, config.enableZeroDowntimeDeployment).toString() - def deployTool = script.commonPipelineEnvironment.configuration.isMta ? 'mtaDeployPlugin' : 'cf_native' - // An isolated workspace is only required when using blue-green deployment with multiple cfTargets, + // An isolated workspace is required when using blue-green deployment with multiple cfTargets, // since the cloudFoundryDeploy step might edit the manifest.yml file in that case. - Boolean runInIsolatedWorkspace = config.cfTargets.size() > 1 && deploymentType == "blue-green" + // It is also required in case of parallel execution and use of mtaExtensionCredentials, since the + // credentials are inserted in the mtaExtensionDescriptor file. + Boolean runInIsolatedWorkspace = config.cfTargets.size() > 1 && (deploymentType == "blue-green" || config.parallelExecution) for (int i = 0; i < config.cfTargets.size(); i++) { @@ -121,8 +122,9 @@ void call(parameters = [:]) { jenkinsUtilsStub: jenkinsUtils, deployType: deploymentType, cloudFoundry: target, + mtaExtensionDescriptor: target.mtaExtensionDescriptor, + mtaExtensionCredentials: target.mtaExtensionCredentials, mtaPath: script.commonPipelineEnvironment.mtarFilePath, - deployTool: deployTool ) if (runInIsolatedWorkspace) { deploymentUtils.stashStageFiles(script, stageName)