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:
parent
4af3a2a861
commit
5448385985
18
documentation/docs/steps/multicloudDeploy.md
Normal file
18
documentation/docs/steps/multicloudDeploy.md
Normal 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'
|
||||
)
|
||||
```
|
5
src/com/sap/piper/CloudPlatform.groovy
Normal file
5
src/com/sap/piper/CloudPlatform.groovy
Normal file
@ -0,0 +1,5 @@
|
||||
package com.sap.piper
|
||||
|
||||
enum CloudPlatform {
|
||||
NEO, CLOUD_FOUNDRY
|
||||
}
|
34
src/com/sap/piper/DeploymentType.groovy
Normal file
34
src/com/sap/piper/DeploymentType.groovy
Normal 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}")
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
256
test/groovy/MulticloudDeployTest.groovy
Normal file
256
test/groovy/MulticloudDeployTest.groovy
Normal 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')
|
||||
}
|
||||
|
||||
}
|
55
test/groovy/util/JenkinsMockStepRule.groovy
Normal file
55
test/groovy/util/JenkinsMockStepRule.groovy
Normal 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()
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
8
test/resources/deploy/pom.xml
Normal file
8
test/resources/deploy/pom.xml
Normal 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>
|
141
vars/multicloudDeploy.groovy
Normal file
141
vars/multicloudDeploy.groovy
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user