diff --git a/documentation/docs/steps/cloudFoundryDeploy.md b/documentation/docs/steps/cloudFoundryDeploy.md index f8c5cebf1..cb13fe4b9 100644 --- a/documentation/docs/steps/cloudFoundryDeploy.md +++ b/documentation/docs/steps/cloudFoundryDeploy.md @@ -61,6 +61,10 @@ Deployment can be done - cfOrg - cfSpace +!!! note + Due to [an incompatible change](https://github.com/cloudfoundry/cli/issues/1445) in the Cloud Foundry CLI, multiple buildpacks are not supported by this step. + If your `application` contains a list of `buildpacks` instead a single `buildpack`, this will be automatically re-written by the step when blue-green deployment is used. + * `deployTool` defines the tool which should be used for deployment. * `deployType` defines the type of deployment, either `standard` deployment which results in a system downtime or a zero-downtime `blue-green` deployment. * `dockerImage` defines the Docker image containing the deployment tools (like cf cli, ...) and `dockerWorkspace` defines the home directory of the default user of the `dockerImage` @@ -106,7 +110,11 @@ The following parameters can also be specified as step/stage/general parameters ## Example ```groovy -artifactSetVersion script: this, buildTool: 'maven' +cloudFoundryDeploy( + script: script, + deployType: 'blue-green', + cloudFoundry: [apiEndpoint: 'https://test.server.com', appName:'cfAppName', credentialsId: 'cfCredentialsId', manifest: 'cfManifest', org: 'cfOrg', space: 'cfSpace'], + deployTool: 'cf_native' +) ``` - diff --git a/src/com/sap/piper/CfManifestUtils.groovy b/src/com/sap/piper/CfManifestUtils.groovy new file mode 100644 index 000000000..a65a3b2eb --- /dev/null +++ b/src/com/sap/piper/CfManifestUtils.groovy @@ -0,0 +1,26 @@ +package com.sap.piper + +import com.cloudbees.groovy.cps.NonCPS + +class CfManifestUtils { + @NonCPS + static Map transform(Map manifest) { + if (manifest.applications[0].buildpacks) { + manifest['applications'].each { Map application -> + def buildpacks = application['buildpacks'] + if (buildpacks) { + if (buildpacks instanceof List) { + if (buildpacks.size > 1) { + throw new RuntimeException('More than one Cloud Foundry Buildpack is not supported. Please check your manifest.yaml file.') + } + application['buildpack'] = buildpacks[0] + application.remove('buildpacks') + } else { + throw new RuntimeException('"buildpacks" in manifest.yaml is not a list. Please check your manifest.yaml file.') + } + } + } + } + return manifest + } +} diff --git a/test/groovy/CloudFoundryDeployTest.groovy b/test/groovy/CloudFoundryDeployTest.groovy index f4e5d7326..a22a76f50 100644 --- a/test/groovy/CloudFoundryDeployTest.groovy +++ b/test/groovy/CloudFoundryDeployTest.groovy @@ -105,6 +105,11 @@ class CloudFoundryDeployTest extends BasePiperTest { @Test void testCfNativeWithAppName() { + jryr.registerYaml('test.yml', "applications: [[name: 'manifestAppName']]") + helper.registerAllowedMethod('writeYaml', [Map], { Map parameters -> + generatedFile = parameters.file + data = parameters.data + }) jsr.step.cloudFoundryDeploy([ script: nullScript, juStabUtils: utils, @@ -125,6 +130,11 @@ class CloudFoundryDeployTest extends BasePiperTest { @Test void testCfNativeWithAppNameCustomApi() { + jryr.registerYaml('test.yml', "applications: [[name: 'manifestAppName']]") + helper.registerAllowedMethod('writeYaml', [Map], { Map parameters -> + generatedFile = parameters.file + data = parameters.data + }) jsr.step.cloudFoundryDeploy([ script: nullScript, juStabUtils: utils, @@ -142,6 +152,11 @@ class CloudFoundryDeployTest extends BasePiperTest { @Test void testCfNativeWithAppNameCompatible() { + jryr.registerYaml('test.yml', "applications: [[name: 'manifestAppName']]") + helper.registerAllowedMethod('writeYaml', [Map], { Map parameters -> + generatedFile = parameters.file + data = parameters.data + }) jsr.step.cloudFoundryDeploy([ script: nullScript, juStabUtils: utils, @@ -165,7 +180,11 @@ class CloudFoundryDeployTest extends BasePiperTest { @Test void testCfNativeAppNameFromManifest() { helper.registerAllowedMethod('fileExists', [String.class], { s -> return true }) - jryr.registerYaml('test.yml', "[applications: [[name: 'manifestAppName']]]") + jryr.registerYaml('test.yml', "applications: [[name: 'manifestAppName']]") + helper.registerAllowedMethod('writeYaml', [Map], { Map parameters -> + generatedFile = parameters.file + data = parameters.data + }) jsr.step.cloudFoundryDeploy([ script: nullScript, @@ -185,6 +204,10 @@ class CloudFoundryDeployTest extends BasePiperTest { void testCfNativeWithoutAppName() { helper.registerAllowedMethod('fileExists', [String.class], { s -> return true }) jryr.registerYaml('test.yml', "applications: [[]]") + helper.registerAllowedMethod('writeYaml', [Map], { Map parameters -> + generatedFile = parameters.file + data = parameters.data + }) thrown.expect(hudson.AbortException) thrown.expectMessage('[cloudFoundryDeploy] ERROR: No appName available in manifest test.yml.') diff --git a/test/groovy/com/sap/piper/CfManifestUtilsTest.groovy b/test/groovy/com/sap/piper/CfManifestUtilsTest.groovy new file mode 100644 index 000000000..f2feadc7a --- /dev/null +++ b/test/groovy/com/sap/piper/CfManifestUtilsTest.groovy @@ -0,0 +1,30 @@ +package com.sap.piper + +import org.junit.Test + +import static org.junit.Assert.* + +class CfManifestUtilsTest { + + @Test + void testManifestTransform() { + Map testFixture = [applications: [[buildpacks: ['sap_java_buildpack']]]] + Map expected = [applications: [[buildpack: 'sap_java_buildpack']]] + def actual = CfManifestUtils.transform(testFixture) + assertEquals(expected, actual) + } + + @Test(expected = RuntimeException) + void testManifestTransformMultipleBuildpacks() { + Map testFixture = [applications: [[buildpacks: ['sap_java_buildpack', 'another_buildpack']]]] + CfManifestUtils.transform(testFixture) + } + + @Test + void testManifestTransformShouldNotChange() { + Map testFixture = [applications: [[buildpack: 'sap_java_buildpack']]] + Map expected = [applications: [[buildpack: 'sap_java_buildpack']]] + def actual = CfManifestUtils.transform(testFixture) + assertEquals(expected, actual) + } +} diff --git a/vars/cloudFoundryDeploy.groovy b/vars/cloudFoundryDeploy.groovy index 811f13dd0..d4d27e709 100644 --- a/vars/cloudFoundryDeploy.groovy +++ b/vars/cloudFoundryDeploy.groovy @@ -1,5 +1,6 @@ import com.sap.piper.Utils import com.sap.piper.ConfigurationHelper +import com.sap.piper.CfManifestUtils import groovy.transform.Field @@ -112,6 +113,7 @@ def deployCfNative (config) { def deployCommand = 'push' if (config.deployType == 'blue-green') { deployCommand = 'blue-green-deploy' + handleLegacyCfManifest(config) } else { config.smokeTest = '' } @@ -129,7 +131,7 @@ def deployCfNative (config) { } sh """#!/bin/bash - set +x + 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 @@ -166,3 +168,18 @@ def deployMta (config) { sh "cf logout" } } + +def handleLegacyCfManifest(config) { + def manifest = readYaml file: config.cloudFoundry.manifest + String originalManifest = manifest.toString() + manifest = CfManifestUtils.transform(manifest) + String transformedManifest = manifest.toString() + if (originalManifest != transformedManifest) { + echo """The file ${config.cloudFoundry.manifest} is not compatible with the Cloud Foundry blue-green deployment plugin. Re-writing inline. +See this issue if you are interested in the background: https://github.com/cloudfoundry/cli/issues/1445.\n +Original manifest file content: $originalManifest\n +Transformed manifest file content: $transformedManifest""" + sh "rm ${config.cloudFoundry.manifest}" + writeYaml file: config.cloudFoundry.manifest, data: manifest + } +}