1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-04 04:07:16 +02:00

add multicloud deploy step

This commit is contained in:
Alejandra Ferreiro Vidal 2019-03-12 15:48:35 +01:00
parent 4af3a2a861
commit 5448385985
9 changed files with 531 additions and 0 deletions

View File

@ -0,0 +1,18 @@
# ${docGenStepName}
## ${docGenDescription}
## ${docGenParameters}
## ${docGenConfiguration}
## Examples
```groovy
multicloudDeploy(
script: script,
cfTargets: [[apiEndpoint: 'https://test.server.com', appName:'cfAppName', credentialsId: 'cfCredentialsId', manifest: 'cfManifest', org: 'cfOrg', space: 'cfSpace']],
neoTargets: [[credentialsId: 'my-credentials-id', host: hana.example.org, account: 'trialuser1']],
enableZeroDowntimeDeployment: 'true'
)
```

View File

@ -0,0 +1,5 @@
package com.sap.piper
enum CloudPlatform {
NEO, CLOUD_FOUNDRY
}

View File

@ -0,0 +1,34 @@
package com.sap.piper
enum DeploymentType {
NEO_ROLLING_UPDATE('rolling-update'), CF_BLUE_GREEN('blue-green'), CF_STANDARD('standard'), NEO_DEPLOY('deploy')
private String value
public DeploymentType(String value){
this.value = value
}
@Override
public String toString(){
return value
}
static DeploymentType selectFor(CloudPlatform cloudPlatform, boolean enableZeroDowntimeDeployment) {
switch (cloudPlatform) {
case CloudPlatform.NEO:
if (enableZeroDowntimeDeployment) return NEO_ROLLING_UPDATE
return NEO_DEPLOY
case CloudPlatform.CLOUD_FOUNDRY:
if (enableZeroDowntimeDeployment) return CF_BLUE_GREEN
return CF_STANDARD
default:
throw new RuntimeException("Unknown cloud platform: ${cloudPlatform}")
}
}
}

View File

@ -23,6 +23,16 @@ def stash(name, include = '**/*.*', exclude = '', useDefaultExcludes = true) {
steps.stash stashParams
}
@NonCPS
def runClosures(Map closures) {
def closuresToRun = closures.values().asList()
Collections.shuffle(closuresToRun) // Shuffle the list so no one tries to rely on the order of execution
for (int i = 0; i < closuresToRun.size(); i++) {
(closuresToRun[i] as Closure).run()
}
}
def stashList(script, List stashes) {
for (def stash : stashes) {
def name = stash.name

View File

@ -0,0 +1,256 @@
import com.sap.piper.JenkinsUtils
import com.sap.piper.Utils
import hudson.AbortException
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.RuleChain
import util.*
class MulticloudDeployTest extends BasePiperTest {
private ExpectedException thrown = new ExpectedException().none()
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
private JenkinsMockStepRule neoDeployRule = new JenkinsMockStepRule(this, 'neoDeploy')
private JenkinsMockStepRule cloudFoundryDeployRule = new JenkinsMockStepRule(this, 'cloudFoundryDeploy')
private JenkinsReadMavenPomRule readMavenPomRule = new JenkinsReadMavenPomRule(this, 'test/resources/deploy')
private Map neo1 = [:]
private Map neo2 = [:]
private Map cloudFoundry1 = [:]
private Map cloudFoundry2 = [:]
@Rule
public RuleChain ruleChain = Rules
.getCommonRules(this)
.around(new JenkinsReadYamlRule(this))
.around(thrown)
.around(stepRule)
.around(neoDeployRule)
.around(cloudFoundryDeployRule)
.around(readMavenPomRule)
private Map neoDeployParameters = [:]
private Map cloudFoundryDeployParameters = [:]
@Before
void init() {
neo1 = [
host: 'test.deploy.host1.com',
account: 'trialuser1',
credentialsId: 'credentialsId1'
]
neo2 = [
host: 'test.deploy.host2.com',
account: 'trialuser2',
credentialsId: 'credentialsId2'
]
cloudFoundry1 = [
appName:'testAppName1',
manifest: 'test.yml',
org: 'testOrg1',
space: 'testSpace1',
credentialsId: 'cfCredentialsId1'
]
cloudFoundry2 = [
appName:'testAppName2',
manifest: 'test.yml',
org: 'testOrg2',
space: 'testSpace2',
credentialsId: 'cfCredentialsId2'
]
nullScript.commonPipelineEnvironment.configuration = [
general: [
neoTargets: [
neo1, neo2
],
cfTargets: [
cloudFoundry1, cloudFoundry2
]
],
stages: [
acceptance: [
org: 'testOrg',
space: 'testSpace',
deployUser: 'testUser'
]
],
steps: [
cloudFoundryDeploy: [
deployTool: 'cf_native',
deployType: 'blue-green',
keepOldInstance: true,
cf_native: [
dockerImage: 's4sdk/docker-cf-cli',
dockerWorkspace: '/home/piper'
]
]
]
]
}
@Test
void errorNoTargetsDefined() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = []
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
thrown.expect(Exception)
thrown.expectMessage('Deployment skipped because no targets defined!')
stepRule.step.multicloudDeploy(
script: nullScript,
stage: 'test'
)
}
@Test
void errorNoSourceForNeoDeploymentTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = [neo1]
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
thrown.expect(Exception)
thrown.expectMessage('ERROR - NO VALUE AVAILABLE FOR source')
stepRule.step.multicloudDeploy(
script: nullScript,
stage: 'test'
)
}
@Test
void neoDeploymentTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = [neo1]
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
stepRule.step.multicloudDeploy(
script: nullScript,
stage: 'test',
source: 'file.mtar'
)
assert neoDeployRule.hasParameter('script', nullScript)
assert neoDeployRule.hasParameter('warAction', 'deploy')
assert neoDeployRule.hasParameter('source', 'file.mtar')
assert neoDeployRule.hasParameter('neo', neo1)
}
@Test
void neoRollingUpdateTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = []
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
def neoParam = [
host: 'test.param.deploy.host.com',
account: 'trialparamNeoUser',
credentialsId: 'paramNeoCredentialsId'
]
stepRule.step.multicloudDeploy(
script: nullScript,
stage: 'test',
neoTargets: [neoParam],
source: 'file.mtar',
enableZeroDowntimeDeployment: true
)
assert neoDeployRule.hasParameter('script', nullScript)
assert neoDeployRule.hasParameter('warAction', 'rolling-update')
assert neoDeployRule.hasParameter('source', 'file.mtar')
assert neoDeployRule.hasParameter('neo', neoParam)
}
@Test
void cfDeploymentTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = []
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = []
def cloudFoundry = [
appName:'paramTestAppName',
manifest: 'test.yml',
org: 'paramTestOrg',
space: 'paramTestSpace',
credentialsId: 'paramCfCredentialsId'
]
stepRule.step.multicloudDeploy([
script: nullScript,
stage: 'acceptance',
cfTargets: [cloudFoundry]
])
assert cloudFoundryDeployRule.hasParameter('script', nullScript)
assert cloudFoundryDeployRule.hasParameter('deployType', 'standard')
assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry)
assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath)
assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native')
}
@Test
void cfBlueGreenDeploymentTest() {
nullScript.commonPipelineEnvironment.configuration.general.neoTargets = []
nullScript.commonPipelineEnvironment.configuration.general.cfTargets = [cloudFoundry1]
stepRule.step.multicloudDeploy([
script: nullScript,
stage: 'acceptance',
enableZeroDowntimeDeployment: true
])
assert cloudFoundryDeployRule.hasParameter('script', nullScript)
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
void multicloudDeploymentTest() {
stepRule.step.multicloudDeploy([
script: nullScript,
stage: 'acceptance',
enableZeroDowntimeDeployment: true,
source: 'file.mtar'
])
assert neoDeployRule.hasParameter('script', nullScript)
assert neoDeployRule.hasParameter('warAction', 'rolling-update')
assert neoDeployRule.hasParameter('source', 'file.mtar')
assert neoDeployRule.hasParameter('neo', neo1)
assert neoDeployRule.hasParameter('script', nullScript)
assert neoDeployRule.hasParameter('warAction', 'rolling-update')
assert neoDeployRule.hasParameter('source', 'file.mtar')
assert neoDeployRule.hasParameter('neo', neo2)
assert cloudFoundryDeployRule.hasParameter('script', nullScript)
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('script', nullScript)
assert cloudFoundryDeployRule.hasParameter('deployType', 'blue-green')
assert cloudFoundryDeployRule.hasParameter('cloudFoundry', cloudFoundry2)
assert cloudFoundryDeployRule.hasParameter('mtaPath', nullScript.commonPipelineEnvironment.mtarFilePath)
assert cloudFoundryDeployRule.hasParameter('deployTool', 'cf_native')
}
}

View File

@ -0,0 +1,55 @@
package util
import com.lesfurets.jenkins.unit.BasePipelineTest
import java.beans.Introspector
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class JenkinsMockStepRule implements TestRule {
final BasePipelineTest testInstance
final String stepName
def callsIndex = 0
def callsParameters = [:]
JenkinsMockStepRule(BasePipelineTest testInstance, String stepName) {
this.testInstance = testInstance
this.stepName = stepName
}
boolean hasParameter(def key, def value){
for ( def parameters : callsParameters) {
for ( def parameter : parameters.value.entrySet()) {
if (parameter.key.equals(key) && parameter.value.equals(value)) return true
}
}
return false
}
@Override
Statement apply(Statement base, Description description) {
return new Statement() {
@Override
void evaluate() throws Throwable {
testInstance.helper.registerAllowedMethod(this.stepName, [Map], { Map m ->
this.callsIndex += 1
this.callsParameters.put(callsIndex, m)
})
base.evaluate()
}
}
}
@Override
String toString() {
return callsParameters.toString()
}
}

View File

@ -8,7 +8,9 @@ import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
class JenkinsStepRule implements TestRule {
final BasePipelineTest testInstance
def step
@ -22,9 +24,11 @@ class JenkinsStepRule implements TestRule {
return new Statement() {
@Override
void evaluate() throws Throwable {
def testClassName = testInstance.getClass().getSimpleName()
def stepName = Introspector.decapitalize(testClassName.replaceAll('Test$', ''))
this.step = testInstance.loadScript("${stepName}.groovy")
base.evaluate()
}
}

View File

@ -0,0 +1,8 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sap.piper</groupId>
<artifactId>library-test</artifactId>
<packaging>war</packaging>
<version>1.2.3</version>
<name>library-test</name>
</project>

View File

@ -0,0 +1,141 @@
import com.sap.piper.GenerateDocumentation
import com.sap.piper.CloudPlatform
import com.sap.piper.DeploymentType
import com.sap.piper.k8s.ContainerMap
import com.sap.piper.ConfigurationHelper
import com.sap.piper.Utils
import com.sap.piper.JenkinsUtils
import groovy.transform.Field
import static com.sap.piper.Prerequisites.checkScript
@Field String STEP_NAME = getClass().getName()
@Field Set GENERAL_CONFIG_KEYS = [
/** Defines the targets to deploy on cloudFoundry.*/
'cfTargets',
/** Defines the targets to deploy on neo.*/
'neoTargets'
]
@Field Set STEP_CONFIG_KEYS = []
@Field Set PARAMETER_KEYS = GENERAL_CONFIG_KEYS.plus([
/** The stage name. If the stage name is not provided, it will be taken from the environment variable 'STAGE_NAME'.*/
'stage',
/** Defines the deployment type.*/
'enableZeroDowntimeDeployment',
/** The source file to deploy to the SAP Cloud Platform.*/
'source'
])
/**
* Deploys an application to multiple platforms (cloudFoundry, SAP Cloud Platform) or to multiple instances of multiple platforms or the same platform.
*/
@GenerateDocumentation
void call(parameters = [:]) {
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
def stageName = parameters.stage ?: env.STAGE_NAME
def enableZeroDowntimeDeployment = parameters.enableZeroDowntimeDeployment ?: false
def script = checkScript(this, parameters) ?: this
def utils = parameters.utils ?: new Utils()
def jenkinsUtils = parameters.jenkinsUtils ?: new JenkinsUtils()
ConfigurationHelper configHelper = ConfigurationHelper.newInstance(this)
.loadStepDefaults()
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
.mixin(parameters, PARAMETER_KEYS)
Map config = configHelper.use()
configHelper
.withMandatoryProperty('source', null, { config.neoTargets })
utils.pushToSWA([
step: STEP_NAME,
stepParamKey1: 'stage',
stepParam1: stageName,
stepParamKey2: 'enableZeroDowntimeDeployment',
stepParam2: enableZeroDowntimeDeployment
], config)
def index = 1
def deployments = [:]
def deploymentType
def deployTool
if (config.cfTargets) {
deploymentType = DeploymentType.selectFor(CloudPlatform.CLOUD_FOUNDRY, enableZeroDowntimeDeployment).toString()
deployTool = script.commonPipelineEnvironment.configuration.isMta ? 'mtaDeployPlugin' : 'cf_native'
for (int i = 0; i < config.cfTargets.size(); i++) {
def target = config.cfTargets[i]
Closure deployment = {
cloudFoundryDeploy(
script: script,
juStabUtils: utils,
jenkinsUtilsStub: jenkinsUtils,
deployType: deploymentType,
cloudFoundry: target,
mtaPath: script.commonPipelineEnvironment.mtarFilePath,
deployTool: deployTool
)
}
setDeployment(deployments, deployment, index, script, stageName)
index++
}
utils.runClosures(deployments)
}
if (config.neoTargets) {
deploymentType = DeploymentType.selectFor(CloudPlatform.NEO, enableZeroDowntimeDeployment)
for (int i = 0; i < config.neoTargets.size(); i++) {
def target = config.neoTargets[i]
Closure deployment = {
neoDeploy (
script: script,
warAction: deploymentType.toString(),
source: config.source,
neo: target
)
}
setDeployment(deployments, deployment, index, script, stageName)
index++
}
utils.runClosures(deployments)
}
if (!config.cfTargets && !config.neoTargets) {
error "Deployment skipped because no targets defined!"
}
}
}
void setDeployment(deployments, deployment, index, script, stageName) {
deployments["Deployment ${index > 1 ? index : ''}"] = {
if (env.POD_NAME) {
dockerExecuteOnKubernetes(script: script, containerMap: ContainerMap.instance.getMap().get(stageName) ?: [:]) {
deployment.run()
}
} else {
node(env.NODE_NAME) {
deployment.run()
}
}
}
}