mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-03-03 15:02:35 +02:00
Merge pull request #1017 from marcusholl/pr/xsDeployGroovyWithGi
xsDeploy.groovy forwarding to go
This commit is contained in:
commit
e5db600cf4
@ -8,16 +8,21 @@ class DefaultValueCache implements Serializable {
|
|||||||
|
|
||||||
private Map defaultValues
|
private Map defaultValues
|
||||||
|
|
||||||
private DefaultValueCache(Map defaultValues){
|
private List customDefaults = []
|
||||||
|
|
||||||
|
private DefaultValueCache(Map defaultValues, List customDefaults){
|
||||||
this.defaultValues = defaultValues
|
this.defaultValues = defaultValues
|
||||||
|
if(customDefaults) {
|
||||||
|
this.customDefaults.addAll(customDefaults)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(){
|
static getInstance(){
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
static createInstance(Map defaultValues){
|
static createInstance(Map defaultValues, List customDefaults = []){
|
||||||
instance = new DefaultValueCache(defaultValues)
|
instance = new DefaultValueCache(defaultValues, customDefaults)
|
||||||
}
|
}
|
||||||
|
|
||||||
Map getDefaultValues(){
|
Map getDefaultValues(){
|
||||||
@ -28,6 +33,12 @@ class DefaultValueCache implements Serializable {
|
|||||||
instance = null
|
instance = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List getCustomDefaults() {
|
||||||
|
def result = []
|
||||||
|
result.addAll(customDefaults)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
static void prepare(Script steps, Map parameters = [:]) {
|
static void prepare(Script steps, Map parameters = [:]) {
|
||||||
if(parameters == null) parameters = [:]
|
if(parameters == null) parameters = [:]
|
||||||
if(!DefaultValueCache.getInstance() || parameters.customDefaults) {
|
if(!DefaultValueCache.getInstance() || parameters.customDefaults) {
|
||||||
@ -46,7 +57,7 @@ class DefaultValueCache implements Serializable {
|
|||||||
MapUtils.pruneNulls(defaultValues),
|
MapUtils.pruneNulls(defaultValues),
|
||||||
MapUtils.pruneNulls(configuration))
|
MapUtils.pruneNulls(configuration))
|
||||||
}
|
}
|
||||||
DefaultValueCache.createInstance(defaultValues)
|
DefaultValueCache.createInstance(defaultValues, customDefaults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,8 @@ public class CommonStepsTest extends BasePiperTest{
|
|||||||
'handlePipelineStepErrors', // special step (infrastructure)
|
'handlePipelineStepErrors', // special step (infrastructure)
|
||||||
'piperStageWrapper', //intended to be called from within stages
|
'piperStageWrapper', //intended to be called from within stages
|
||||||
'buildSetResult',
|
'buildSetResult',
|
||||||
'githubPublishRelease' //implementing new golang pattern without fields
|
'githubPublishRelease', //implementing new golang pattern without fields
|
||||||
|
'xsDeploy', //implementing new golang pattern without fields
|
||||||
]
|
]
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1,252 +1,106 @@
|
|||||||
|
import static org.hamcrest.Matchers.allOf
|
||||||
|
import static org.hamcrest.Matchers.contains
|
||||||
|
import static org.hamcrest.Matchers.containsInAnyOrder
|
||||||
|
import static org.hamcrest.Matchers.containsString
|
||||||
|
import static org.hamcrest.Matchers.equalTo
|
||||||
|
import static org.hamcrest.Matchers.is
|
||||||
|
import static org.hamcrest.Matchers.not
|
||||||
|
import static org.hamcrest.Matchers.nullValue
|
||||||
import static org.junit.Assert.assertThat
|
import static org.junit.Assert.assertThat
|
||||||
|
|
||||||
import org.hamcrest.Matchers
|
import org.hamcrest.Matchers
|
||||||
|
import org.hamcrest.core.IsNull
|
||||||
import static org.hamcrest.Matchers.allOf
|
import org.junit.Before
|
||||||
import static org.hamcrest.Matchers.contains
|
|
||||||
import static org.hamcrest.Matchers.containsString
|
|
||||||
import static org.hamcrest.Matchers.hasSize
|
|
||||||
import static org.hamcrest.Matchers.is
|
|
||||||
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.ExpectedException
|
import org.junit.rules.ExpectedException
|
||||||
import org.junit.rules.RuleChain
|
import org.junit.rules.RuleChain
|
||||||
|
|
||||||
|
import com.sap.piper.PiperGoUtils
|
||||||
|
|
||||||
|
import hudson.AbortException
|
||||||
import util.BasePiperTest
|
import util.BasePiperTest
|
||||||
import util.CommandLineMatcher
|
import util.CommandLineMatcher
|
||||||
import util.JenkinsCredentialsRule
|
import util.JenkinsCredentialsRule
|
||||||
import util.JenkinsDockerExecuteRule
|
import util.JenkinsDockerExecuteRule
|
||||||
import util.JenkinsFileExistsRule
|
|
||||||
import util.JenkinsLockRule
|
import util.JenkinsLockRule
|
||||||
import util.JenkinsLoggingRule
|
import util.JenkinsReadJsonRule
|
||||||
import util.JenkinsReadYamlRule
|
import util.JenkinsReadYamlRule
|
||||||
import util.JenkinsShellCallRule
|
import util.JenkinsShellCallRule
|
||||||
import util.JenkinsStepRule
|
import util.JenkinsStepRule
|
||||||
|
import util.JenkinsWriteFileRule
|
||||||
import util.Rules
|
import util.Rules
|
||||||
|
|
||||||
import com.sap.piper.JenkinsUtils
|
|
||||||
|
|
||||||
import hudson.AbortException
|
|
||||||
|
|
||||||
class XsDeployTest extends BasePiperTest {
|
class XsDeployTest extends BasePiperTest {
|
||||||
|
|
||||||
private ExpectedException thrown = ExpectedException.none()
|
private ExpectedException thrown = ExpectedException.none()
|
||||||
|
|
||||||
private List existingFiles = [
|
|
||||||
'.xsconfig',
|
|
||||||
'myApp.mta'
|
|
||||||
]
|
|
||||||
|
|
||||||
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
|
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
|
||||||
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
|
private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)
|
||||||
private JenkinsLockRule lockRule = new JenkinsLockRule(this)
|
private JenkinsLockRule lockRule = new JenkinsLockRule(this)
|
||||||
private JenkinsLoggingRule logRule = new JenkinsLoggingRule(this)
|
private JenkinsDockerExecuteRule dockerRule = new JenkinsDockerExecuteRule(this)
|
||||||
|
private JenkinsWriteFileRule writeFileRule = new JenkinsWriteFileRule(this)
|
||||||
|
|
||||||
|
List env
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public RuleChain ruleChain = Rules.getCommonRules(this)
|
public RuleChain ruleChain = Rules.getCommonRules(this)
|
||||||
.around(new JenkinsReadYamlRule(this))
|
.around(new JenkinsReadYamlRule(this))
|
||||||
|
.around(new JenkinsReadJsonRule(this))
|
||||||
.around(stepRule)
|
.around(stepRule)
|
||||||
.around(new JenkinsDockerExecuteRule(this))
|
.around(dockerRule)
|
||||||
|
.around(writeFileRule)
|
||||||
.around(new JenkinsCredentialsRule(this)
|
.around(new JenkinsCredentialsRule(this)
|
||||||
.withCredentials('myCreds', 'cred_xs', 'topSecret'))
|
.withCredentials('myCreds', 'cred_xs', 'topSecret'))
|
||||||
.around(new JenkinsFileExistsRule(this, existingFiles))
|
|
||||||
.around(lockRule)
|
.around(lockRule)
|
||||||
.around(shellRule)
|
.around(shellRule)
|
||||||
.around(logRule)
|
|
||||||
.around(thrown)
|
.around(thrown)
|
||||||
|
|
||||||
@Test
|
private PiperGoUtils goUtils = new PiperGoUtils(null) {
|
||||||
public void testSanityChecks() {
|
void unstashPiperBin() {
|
||||||
|
}
|
||||||
thrown.expect(IllegalArgumentException)
|
|
||||||
thrown.expectMessage(
|
|
||||||
allOf(
|
|
||||||
containsString('ERROR - NO VALUE AVAILABLE FOR:'),
|
|
||||||
containsString('apiUrl'),
|
|
||||||
containsString('org'),
|
|
||||||
containsString('space'),
|
|
||||||
containsString('mtaPath')))
|
|
||||||
|
|
||||||
stepRule.step.xsDeploy(script: nullScript)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Before
|
||||||
public void testLoginFailed() {
|
public void init() {
|
||||||
|
helper.registerAllowedMethod('withEnv', [List, Closure], {l, c -> env = l; c()})
|
||||||
thrown.expect(AbortException)
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*getConfig.*--contextConfig.*', '{"dockerImage": "xs", "dockerPullImage": false, "credentialsId":"myCreds"}')
|
||||||
thrown.expectMessage('xs login failed')
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, 'getConfig.* (?!--contextConfig)', '{"mode": "BG_DEPLOY", "action": "NONE", "apiUrl": "https://example.org/xs", "org": "myOrg", "space": "mySpace"}')
|
||||||
|
nullScript.commonPipelineEnvironment.xsDeploymentId = null
|
||||||
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '#!/bin/bash xs login .*', 1)
|
|
||||||
|
|
||||||
try {
|
|
||||||
stepRule.step.xsDeploy(
|
|
||||||
script: nullScript,
|
|
||||||
apiUrl: 'https://example.org/xs',
|
|
||||||
org: 'myOrg',
|
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
mtaPath: 'myApp.mta'
|
|
||||||
)
|
|
||||||
} catch(AbortException e ) {
|
|
||||||
|
|
||||||
assertThat(shellRule.shell,
|
|
||||||
allOf(
|
|
||||||
// first item: the login attempt
|
|
||||||
// second item: we try to provide the logs
|
|
||||||
hasSize(2),
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog("#!/bin/bash")
|
|
||||||
.hasSnippet('xs login'),
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog('LOG_FOLDER')
|
|
||||||
.hasSnippet('cat \\$\\{LOG_FOLDER\\}/\\*')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeployFailed() {
|
public void testDeployFailed() {
|
||||||
|
|
||||||
thrown.expect(AbortException)
|
thrown.expect(AbortException)
|
||||||
thrown.expectMessage('Failed command(s): [xs deploy]. Check earlier log for details.')
|
thrown.expectMessage('script returned exit code 1')
|
||||||
|
|
||||||
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '#!/bin/bash.*xs deploy .*', {throw new AbortException()})
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*xsDeploy .*', { throw new AbortException('script returned exit code 1')})
|
||||||
|
|
||||||
try {
|
|
||||||
stepRule.step.xsDeploy(
|
|
||||||
script: nullScript,
|
|
||||||
apiUrl: 'https://example.org/xs',
|
|
||||||
org: 'myOrg',
|
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
mtaPath: 'myApp.mta'
|
|
||||||
)
|
|
||||||
} catch(AbortException e ) {
|
|
||||||
|
|
||||||
assertThat(shellRule.shell,
|
|
||||||
allOf(
|
|
||||||
hasSize(4),
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog("#!/bin/bash")
|
|
||||||
.hasSnippet('xs login'),
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog("#!/bin/bash")
|
|
||||||
.hasSnippet('xs deploy'),
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog('#!/bin/bash')
|
|
||||||
.hasSnippet('xs logout'), // logout must be present in case deployment failed.
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog('')
|
|
||||||
.hasSnippet('rm \\$\\{XSCONFIG\\}') // remove the session file after logout
|
|
||||||
)
|
|
||||||
)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNothingHappensWhenModeIsNone() {
|
|
||||||
|
|
||||||
stepRule.step.xsDeploy(
|
stepRule.step.xsDeploy(
|
||||||
script: nullScript,
|
script: nullScript,
|
||||||
mode: 'NONE'
|
piperGoUtils: goUtils,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(logRule.log, containsString('Deployment skipped intentionally.'))
|
|
||||||
assertThat(shellRule.shell, hasSize(0))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeploymentFailsWhenDeployableIsNotPresent() {
|
public void testInvalidDeploymentModeProvided() {
|
||||||
|
|
||||||
thrown.expect(AbortException)
|
|
||||||
thrown.expectMessage('Deployable \'myApp.mta\' does not exist.')
|
|
||||||
|
|
||||||
existingFiles.remove('myApp.mta')
|
|
||||||
|
|
||||||
try {
|
|
||||||
stepRule.step.xsDeploy(
|
|
||||||
script: nullScript,
|
|
||||||
apiUrl: 'https://example.org/xs',
|
|
||||||
org: 'myOrg',
|
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
mtaPath: 'myApp.mta'
|
|
||||||
)
|
|
||||||
} catch(AbortException e) {
|
|
||||||
|
|
||||||
// no shell operation happened in this case.
|
|
||||||
assertThat(shellRule.shell.size(), is(0))
|
|
||||||
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDeployStraighForward() {
|
|
||||||
|
|
||||||
stepRule.step.xsDeploy(
|
|
||||||
script: nullScript,
|
|
||||||
apiUrl: 'https://example.org/xs',
|
|
||||||
org: 'myOrg',
|
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
deployOpts: '-t 60',
|
|
||||||
mtaPath: 'myApp.mta'
|
|
||||||
)
|
|
||||||
|
|
||||||
assertThat(shellRule.shell,
|
|
||||||
allOf(
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog("#!/bin/bash xs login")
|
|
||||||
.hasSnippet('xs login')
|
|
||||||
.hasOption('a', 'https://example.org/xs')
|
|
||||||
.hasOption('u', 'cred_xs')
|
|
||||||
.hasSingleQuotedOption('p', 'topSecret')
|
|
||||||
.hasOption('o', 'myOrg')
|
|
||||||
.hasOption('s', 'mySpace'),
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog("#!/bin/bash")
|
|
||||||
.hasSnippet('xs deploy')
|
|
||||||
.hasOption('t', '60')
|
|
||||||
.hasArgument('\'myApp.mta\''),
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog("#!/bin/bash")
|
|
||||||
.hasSnippet('xs logout')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assertThat(lockRule.getLockResources(), contains('xsDeploy:https://example.org/xs:myOrg:mySpace'))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInvalidDeploymentModeProviced() {
|
|
||||||
|
|
||||||
thrown.expect(IllegalArgumentException)
|
thrown.expect(IllegalArgumentException)
|
||||||
thrown.expectMessage('No enum constant')
|
thrown.expectMessage('No enum constant')
|
||||||
|
|
||||||
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, 'getConfig.* (?!--contextConfig)', '{"mode": "DOES_NOT_EXIST", "action": "NONE", "apiUrl": "https://example.org/xs", "org": "myOrg", "space": "mySpace"}')
|
||||||
|
|
||||||
stepRule.step.xsDeploy(
|
stepRule.step.xsDeploy(
|
||||||
script: nullScript,
|
script: nullScript,
|
||||||
apiUrl: 'https://example.org/xs',
|
piperGoUtils: goUtils,
|
||||||
org: 'myOrg',
|
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
deployOpts: '-t 60',
|
|
||||||
mtaPath: 'myApp.mta',
|
|
||||||
mode: 'DOES_NOT_EXIST'
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testActionProvidedForStandardDeployment() {
|
public void testParametersViaSignature() {
|
||||||
|
|
||||||
thrown.expect(AbortException)
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*xsDeploy .*', '{"operationId": "1234"}')
|
||||||
thrown.expectMessage(
|
|
||||||
'Cannot perform action \'resume\' in mode \'deploy\'. Only action \'none\' is allowed.')
|
|
||||||
|
|
||||||
stepRule.step.xsDeploy(
|
stepRule.step.xsDeploy(
|
||||||
script: nullScript,
|
script: nullScript,
|
||||||
@ -256,109 +110,93 @@ class XsDeployTest extends BasePiperTest {
|
|||||||
credentialsId: 'myCreds',
|
credentialsId: 'myCreds',
|
||||||
deployOpts: '-t 60',
|
deployOpts: '-t 60',
|
||||||
mtaPath: 'myApp.mta',
|
mtaPath: 'myApp.mta',
|
||||||
mode: 'DEPLOY', // this is the default anyway
|
mode: 'DEPLOY',
|
||||||
action: 'RESUME'
|
action: 'NONE',
|
||||||
|
piperGoUtils: goUtils
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// nota bene: script and piperGoUtils are not contained in the json below.
|
||||||
|
assertThat(env*.toString(), contains('PIPER_parametersJSON={"apiUrl":"https://example.org/xs","org":"myOrg","space":"mySpace","credentialsId":"myCreds","deployOpts":"-t 60","mtaPath":"myApp.mta","mode":"DEPLOY","action":"NONE"}'))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBlueGreenDeployFailes() {
|
public void testBlueGreenDeployInit() {
|
||||||
|
|
||||||
thrown.expect(AbortException)
|
//
|
||||||
thrown.expectMessage('Failed command(s): [xs bg-deploy]')
|
// Only difference between bg deploy and standard deploy is in the config.
|
||||||
|
// The surrounding behavior is the same. Hence there is no dedicated test here
|
||||||
|
// in the groovy layer for standard deploy
|
||||||
|
//
|
||||||
|
|
||||||
logRule.expect('Something went wrong')
|
boolean unstashCalled
|
||||||
|
|
||||||
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '#!/bin/bash.*xs bg-deploy .*',
|
assertThat(nullScript.commonPipelineEnvironment.xsDeploymentId, nullValue())
|
||||||
{ throw new AbortException('Something went wrong.') })
|
|
||||||
|
|
||||||
try {
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*xsDeploy .*', '{"operationId": "1234"}')
|
||||||
stepRule.step.xsDeploy(
|
|
||||||
script: nullScript,
|
|
||||||
apiUrl: 'https://example.org/xs',
|
|
||||||
org: 'myOrg',
|
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
mtaPath: 'myApp.mta',
|
|
||||||
mode: 'BG_DEPLOY'
|
|
||||||
)
|
|
||||||
} catch(AbortException e) {
|
|
||||||
|
|
||||||
// in case there is a deployment failure we have to logout also for bg-deployments
|
goUtils = new PiperGoUtils(null) {
|
||||||
assertThat(shellRule.shell,
|
void unstashPiperBin() {
|
||||||
new CommandLineMatcher()
|
unstashCalled = true
|
||||||
.hasProlog('#!/bin/bash')
|
}
|
||||||
.hasSnippet('xs logout')
|
|
||||||
)
|
|
||||||
|
|
||||||
throw e
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBlueGreenDeployStraighForward() {
|
|
||||||
|
|
||||||
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '#!/bin/bash.*xs bg-deploy .*',
|
|
||||||
((CharSequence)''' |
|
|
||||||
|
|
|
||||||
|Uploading 1 files:
|
|
||||||
|/myFolder/my.mtar
|
|
||||||
|File upload finished
|
|
||||||
|
|
|
||||||
|Detected MTA schema version: "3.1.0"
|
|
||||||
|Detected deploy target as "myOrg mySpace"
|
|
||||||
|Detected deployed MTA with ID "my_mta" and version "0.0.1"
|
|
||||||
|Deployed MTA color: blue
|
|
||||||
|New MTA color: green
|
|
||||||
|Detected new MTA version: "0.0.1"
|
|
||||||
|Deployed MTA version: 0.0.1
|
|
||||||
|Service "xxx" is not modified and will not be updated
|
|
||||||
|Creating application "db-green" from MTA module "xx"...
|
|
||||||
|Uploading application "xx-green"...
|
|
||||||
|Staging application "xx-green"...
|
|
||||||
|Application "xx-green" staged
|
|
||||||
|Executing task "deploy" on application "xx-green"...
|
|
||||||
|Task execution status: succeeded
|
|
||||||
|Process has entered validation phase. After testing your new deployment you can resume or abort the process.
|
|
||||||
|Use "xs bg-deploy -i 1234 -a resume" to resume the process.
|
|
||||||
|Use "xs bg-deploy -i 1234 -a abort" to abort the process.
|
|
||||||
|Hint: Use the '--no-confirm' option of the bg-deploy command to skip this phase.
|
|
||||||
|''').stripMargin())
|
|
||||||
|
|
||||||
stepRule.step.xsDeploy(
|
stepRule.step.xsDeploy(
|
||||||
script: nullScript,
|
script: nullScript,
|
||||||
apiUrl: 'https://example.org/xs',
|
piperGoUtils: goUtils
|
||||||
org: 'myOrg',
|
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
deployOpts: '-t 60',
|
|
||||||
mtaPath: 'myApp.mta',
|
|
||||||
mode: 'BG_DEPLOY'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assertThat(unstashCalled, equalTo(true))
|
||||||
|
|
||||||
assertThat(nullScript.commonPipelineEnvironment.xsDeploymentId, is('1234'))
|
assertThat(nullScript.commonPipelineEnvironment.xsDeploymentId, is('1234'))
|
||||||
|
|
||||||
|
assertThat(writeFileRule.files.keySet(), containsInAnyOrder(
|
||||||
|
'.pipeline/additionalConfigs/default_pipeline_environment.yml',
|
||||||
|
'.pipeline/metadata/xsDeploy.yaml',
|
||||||
|
))
|
||||||
|
|
||||||
|
assertThat(dockerRule.dockerParams.dockerImage, equalTo('xs'))
|
||||||
|
assertThat(dockerRule.dockerParams.dockerPullImage, equalTo(false))
|
||||||
|
|
||||||
assertThat(shellRule.shell,
|
assertThat(shellRule.shell,
|
||||||
allOf(
|
allOf(
|
||||||
new CommandLineMatcher()
|
new CommandLineMatcher()
|
||||||
.hasProlog("#!/bin/bash xs login")
|
.hasProlog('./piper version'),
|
||||||
.hasOption('a', 'https://example.org/xs')
|
|
||||||
.hasOption('u', 'cred_xs')
|
|
||||||
.hasSingleQuotedOption('p', 'topSecret')
|
|
||||||
.hasOption('o', 'myOrg')
|
|
||||||
.hasOption('s', 'mySpace'),
|
|
||||||
new CommandLineMatcher()
|
new CommandLineMatcher()
|
||||||
.hasProlog("#!/bin/bash")
|
.hasProlog('./piper getConfig')
|
||||||
.hasOption('t', '60')
|
.hasArgument('--contextConfig'),
|
||||||
.hasArgument('\'myApp.mta\''),
|
|
||||||
new CommandLineMatcher()
|
new CommandLineMatcher()
|
||||||
.hasProlog("#!/bin/bash")
|
.hasProlog('./piper getConfig --stepMetadata \'.pipeline/metadata/xsDeploy.yaml\''),
|
||||||
|
new CommandLineMatcher()
|
||||||
|
.hasProlog('#!/bin/bash ./piper xsDeploy --defaultConfig ".pipeline/additionalConfigs/default_pipeline_environment.yml" --user \\$\\{USERNAME\\} --password \\$\\{PASSWORD\\}'),
|
||||||
|
not(new CommandLineMatcher()
|
||||||
|
.hasProlog('#!/bin/bash ./piper xsDeploy')
|
||||||
|
.hasOption('operationId', '1234'))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(lockRule.getLockResources(), contains('xsDeploy:https://example.org/xs:myOrg:mySpace'))
|
assertThat(lockRule.getLockResources(), contains('xsDeploy:https://example.org/xs:myOrg:mySpace'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlueGreenDeployResume() {
|
||||||
|
|
||||||
|
nullScript.commonPipelineEnvironment.xsDeploymentId = '1234'
|
||||||
|
|
||||||
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, 'getConfig.* (?!--contextConfig)', '{"mode": "BG_DEPLOY", "action": "RESUME", "apiUrl": "https://example.org/xs", "org": "myOrg", "space": "mySpace"}')
|
||||||
|
|
||||||
|
stepRule.step.xsDeploy(
|
||||||
|
script: nullScript,
|
||||||
|
piperGoUtils: goUtils
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(shellRule.shell,
|
||||||
|
new CommandLineMatcher()
|
||||||
|
.hasProlog('#!/bin/bash ./piper xsDeploy')
|
||||||
|
.hasOption('operationId', '1234')
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(lockRule.getLockResources(), contains('xsDeploy:https://example.org/xs:myOrg:mySpace'))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBlueGreenDeployResumeWithoutDeploymentId() {
|
public void testBlueGreenDeployResumeWithoutDeploymentId() {
|
||||||
|
|
||||||
@ -367,114 +205,96 @@ class XsDeployTest extends BasePiperTest {
|
|||||||
thrown.expect(IllegalArgumentException)
|
thrown.expect(IllegalArgumentException)
|
||||||
thrown.expectMessage(
|
thrown.expectMessage(
|
||||||
allOf(
|
allOf(
|
||||||
containsString('No deployment id provided'),
|
containsString('No operationId provided'),
|
||||||
containsString('Was there a deployment before?')))
|
containsString('Was there a deployment before?')))
|
||||||
|
|
||||||
nullScript.commonPipelineEnvironment.xsDeploymentId = null // is null anyway, just for clarification
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, 'getConfig.* (?!--contextConfig)', '{"mode": "BG_DEPLOY", "action": "RESUME", "apiUrl": "https://example.org/xs", "org": "myOrg", "space": "mySpace"}')
|
||||||
|
|
||||||
|
assertThat(nullScript.commonPipelineEnvironment.xsDeploymentId, nullValue())
|
||||||
|
|
||||||
stepRule.step.xsDeploy(
|
stepRule.step.xsDeploy(
|
||||||
script: nullScript,
|
script: nullScript,
|
||||||
apiUrl: 'https://example.org/xs',
|
piperGoUtils: goUtils,
|
||||||
org: 'myOrg',
|
failOnError: true,
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
mode: 'BG_DEPLOY',
|
|
||||||
action: 'RESUME'
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBlueGreenDeployWithoutExistingSession() {
|
public void testBlueGreenDeployResumeOperationIdViaSignature() {
|
||||||
|
|
||||||
thrown.expect(AbortException)
|
// this happens in case we would like to complete a deployment without having a (successful) deployments before.
|
||||||
thrown.expectMessage(
|
|
||||||
'For the current configuration an already existing session is required.' +
|
|
||||||
' But there is no already existing session')
|
|
||||||
|
|
||||||
existingFiles.remove('.xsconfig')
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, 'getConfig.* (?!--contextConfig)', '{"mode": "BG_DEPLOY", "action": "RESUME", "apiUrl": "https://example.org/xs", "org": "myOrg", "space": "mySpace"}')
|
||||||
|
|
||||||
|
assertThat(nullScript.commonPipelineEnvironment.xsDeploymentId, nullValue())
|
||||||
|
|
||||||
stepRule.step.xsDeploy(
|
stepRule.step.xsDeploy(
|
||||||
script: nullScript,
|
script: nullScript,
|
||||||
apiUrl: 'https://example.org/xs',
|
piperGoUtils: goUtils,
|
||||||
org: 'myOrg',
|
failOnError: true,
|
||||||
space: 'mySpace',
|
operationId: '1357'
|
||||||
credentialsId: 'myCreds',
|
|
||||||
mode: 'BG_DEPLOY',
|
|
||||||
action: 'RESUME'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assertThat(shellRule.shell,
|
||||||
|
new CommandLineMatcher()
|
||||||
|
.hasProlog('#!/bin/bash ./piper xsDeploy')
|
||||||
|
.hasOption('operationId', '1357')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBlueGreenDeployResumeFails() {
|
public void testAdditionalCustomConfigLayers() {
|
||||||
|
|
||||||
// e.g. we try to resume a deployment which did not succeed or which was already resumed or aborted.
|
def resources = ['a.yml': '- x: y}', 'b.yml' : '- a: b}']
|
||||||
|
|
||||||
thrown.expect(AbortException)
|
helper.registerAllowedMethod('libraryResource', [String], {
|
||||||
thrown.expectMessage('Failed command(s): [xs bg-deploy -a resume].')
|
|
||||||
|
|
||||||
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, 'xs bg-deploy -i .*', 1)
|
r ->
|
||||||
|
|
||||||
nullScript.commonPipelineEnvironment.xsDeploymentId = '1234'
|
def resource = resources[r]
|
||||||
|
if(resource) return resource
|
||||||
|
|
||||||
try {
|
File res = new File(new File('resources'), r)
|
||||||
stepRule.step.xsDeploy(
|
if (res.exists()) {
|
||||||
script: nullScript,
|
return res.getText()
|
||||||
apiUrl: 'https://example.org/xs',
|
}
|
||||||
org: 'myOrg',
|
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
mode: 'BG_DEPLOY',
|
|
||||||
action: 'RESUME'
|
|
||||||
)
|
|
||||||
} catch(AbortException e) {
|
|
||||||
|
|
||||||
// logout must happen also in case of a failed deployment
|
throw new RuntimeException("Resource '${r}' not found.")
|
||||||
assertThat(shellRule.shell,
|
})
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog('')
|
assertThat(nullScript.commonPipelineEnvironment.xsDeploymentId, nullValue())
|
||||||
.hasSnippet('xs logout'))
|
|
||||||
throw e
|
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*xsDeploy .*', '{"operationId": "1234"}')
|
||||||
|
|
||||||
|
nullScript.commonPipelineEnvironment = ['reset': {}, 'getCustomDefaults': {['a.yml', 'b.yml']}]
|
||||||
|
|
||||||
|
goUtils = new PiperGoUtils(null) {
|
||||||
|
void unstashPiperBin() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBlueGreenDeployResume() {
|
|
||||||
|
|
||||||
shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, 'xs bg-deploy -i .*', 0)
|
|
||||||
|
|
||||||
nullScript.commonPipelineEnvironment.xsDeploymentId = '1234'
|
|
||||||
|
|
||||||
stepRule.step.xsDeploy(
|
stepRule.step.xsDeploy(
|
||||||
script: nullScript,
|
script: nullScript,
|
||||||
apiUrl: 'https://example.org/xs',
|
piperGoUtils: goUtils
|
||||||
org: 'myOrg',
|
|
||||||
space: 'mySpace',
|
|
||||||
credentialsId: 'myCreds',
|
|
||||||
mode: 'BG_DEPLOY',
|
|
||||||
action: 'RESUME'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// there is no login in case of a resume since we have to use the old session which triggered the deployment.
|
assertThat(nullScript.commonPipelineEnvironment.xsDeploymentId, is('1234'))
|
||||||
|
|
||||||
|
assertThat(writeFileRule.files.keySet(), containsInAnyOrder(
|
||||||
|
'.pipeline/additionalConfigs/a.yml',
|
||||||
|
'.pipeline/additionalConfigs/b.yml',
|
||||||
|
'.pipeline/additionalConfigs/default_pipeline_environment.yml',
|
||||||
|
'.pipeline/metadata/xsDeploy.yaml',
|
||||||
|
))
|
||||||
|
|
||||||
assertThat(shellRule.shell,
|
assertThat(shellRule.shell,
|
||||||
allOf(
|
allOf(
|
||||||
hasSize(3),
|
|
||||||
new CommandLineMatcher()
|
new CommandLineMatcher()
|
||||||
.hasProlog('#!/bin/bash')
|
.hasProlog('./piper getConfig')
|
||||||
.hasSnippet('xs bg-deploy')
|
.hasArgument('--contextConfig')
|
||||||
.hasOption('i', '1234')
|
.hasArgument('--defaultConfig ".pipeline/additionalConfigs/b.yml" ".pipeline/additionalConfigs/a.yml" ".pipeline/additionalConfigs/default_pipeline_environment.yml"'),
|
||||||
.hasOption('a', 'resume'),
|
|
||||||
new CommandLineMatcher()
|
new CommandLineMatcher()
|
||||||
.hasProlog("#!/bin/bash")
|
.hasProlog('./piper getConfig --stepMetadata \'.pipeline/metadata/xsDeploy.yaml\''),
|
||||||
.hasSnippet('xs logout'),
|
|
||||||
new CommandLineMatcher()
|
|
||||||
.hasProlog('')
|
|
||||||
.hasSnippet('rm \\$\\{XSCONFIG\\}') // delete the session file after logout
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(lockRule.getLockResources(), contains('xsDeploy:https://example.org/xs:myOrg:mySpace'))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import com.sap.piper.ConfigurationLoader
|
import com.sap.piper.ConfigurationLoader
|
||||||
import com.sap.piper.ConfigurationMerger
|
import com.sap.piper.ConfigurationMerger
|
||||||
|
import com.sap.piper.DefaultValueCache
|
||||||
import com.sap.piper.analytics.InfluxData
|
import com.sap.piper.analytics.InfluxData
|
||||||
|
|
||||||
class commonPipelineEnvironment implements Serializable {
|
class commonPipelineEnvironment implements Serializable {
|
||||||
@ -143,4 +144,8 @@ class commonPipelineEnvironment implements Serializable {
|
|||||||
config = ConfigurationMerger.merge(configuration.get('stages')?.get(stageName) ?: [:], null, config)
|
config = ConfigurationMerger.merge(configuration.get('stages')?.get(stageName) ?: [:], null, config)
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
List getCustomDefaults() {
|
||||||
|
DefaultValueCache.getInstance().getCustomDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,21 @@
|
|||||||
import com.sap.piper.JenkinsUtils
|
|
||||||
|
|
||||||
import static com.sap.piper.Prerequisites.checkScript
|
import static com.sap.piper.Prerequisites.checkScript
|
||||||
|
|
||||||
import com.sap.piper.BashUtils
|
import com.sap.piper.DefaultValueCache
|
||||||
import com.sap.piper.ConfigurationHelper
|
import com.sap.piper.JenkinsUtils
|
||||||
|
import com.sap.piper.PiperGoUtils
|
||||||
|
|
||||||
|
|
||||||
import com.sap.piper.GenerateDocumentation
|
import com.sap.piper.GenerateDocumentation
|
||||||
import com.sap.piper.Utils
|
import com.sap.piper.Utils
|
||||||
|
|
||||||
import groovy.transform.Field
|
import groovy.transform.Field
|
||||||
|
|
||||||
import hudson.AbortException
|
@Field String METADATA_FILE = 'metadata/xsDeploy.yaml'
|
||||||
|
@Field String PIPER_DEFAULTS = 'default_pipeline_environment.yml'
|
||||||
@Field String STEP_NAME = getClass().getName()
|
@Field String STEP_NAME = getClass().getName()
|
||||||
|
@Field String METADATA_FOLDER = '.pipeline' // metadata file contains already the "metadata" folder level, hence we end up in a folder ".pipeline/metadata"
|
||||||
|
@Field String ADDITIONAL_CONFIGS_FOLDER='.pipeline/additionalConfigs'
|
||||||
|
|
||||||
@Field Set GENERAL_CONFIG_KEYS = STEP_CONFIG_KEYS
|
|
||||||
|
|
||||||
@Field Set STEP_CONFIG_KEYS = [
|
|
||||||
'action',
|
|
||||||
'apiUrl',
|
|
||||||
'credentialsId',
|
|
||||||
'deploymentId',
|
|
||||||
'deployIdLogPattern',
|
|
||||||
'deployOpts',
|
|
||||||
/** A map containing properties forwarded to dockerExecute. For more details see [here][dockerExecute] */
|
|
||||||
'docker',
|
|
||||||
'loginOpts',
|
|
||||||
'mode',
|
|
||||||
'mtaPath',
|
|
||||||
'org',
|
|
||||||
'space',
|
|
||||||
'xsSessionFile',
|
|
||||||
]
|
|
||||||
|
|
||||||
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
|
|
||||||
|
|
||||||
enum DeployMode {
|
enum DeployMode {
|
||||||
DEPLOY,
|
DEPLOY,
|
||||||
@ -55,284 +38,142 @@ enum Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs an XS deployment
|
|
||||||
*
|
|
||||||
* In case of blue-green deployments the step is called for the deployment in the narrower sense
|
|
||||||
* and later again for resuming or aborting. In this case both calls needs to be performed from the
|
|
||||||
* same directory.
|
|
||||||
*/
|
|
||||||
@GenerateDocumentation
|
|
||||||
void call(Map parameters = [:]) {
|
void call(Map parameters = [:]) {
|
||||||
|
|
||||||
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
|
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
|
||||||
|
|
||||||
|
final script = checkScript(this, parameters) ?: null
|
||||||
|
|
||||||
|
if(! script) {
|
||||||
|
error "Reference to surrounding pipeline script not provided (script: this)."
|
||||||
|
}
|
||||||
|
|
||||||
def utils = parameters.juStabUtils ?: new Utils()
|
def utils = parameters.juStabUtils ?: new Utils()
|
||||||
|
def piperGoUtils = parameters.piperGoUtils ?: new PiperGoUtils(utils)
|
||||||
|
|
||||||
final script = checkScript(this, parameters) ?: this
|
//
|
||||||
|
// The parameters map in provided from outside. That map might be used elsewhere in the pipeline
|
||||||
|
// hence we should not modify it here. So we create a new map based on the parameters map.
|
||||||
|
parameters = [:] << parameters
|
||||||
|
|
||||||
ConfigurationHelper configHelper = ConfigurationHelper.newInstance(this)
|
// hard to predict how these parameters looks like in its serialized form. Anyhow it is better
|
||||||
.loadStepDefaults()
|
// not to have these parameters forwarded somehow to the go layer.
|
||||||
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
|
parameters.remove('juStabUtils')
|
||||||
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
|
parameters.remove('piperGoUtils')
|
||||||
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
|
parameters.remove('script')
|
||||||
.addIfEmpty('mtaPath', script.commonPipelineEnvironment.getMtarFilePath())
|
|
||||||
.addIfEmpty('deploymentId', script.commonPipelineEnvironment.xsDeploymentId)
|
|
||||||
.mixin(parameters, PARAMETER_KEYS)
|
|
||||||
|
|
||||||
Map config = configHelper.use()
|
piperGoUtils.unstashPiperBin()
|
||||||
|
|
||||||
DeployMode mode = config.mode
|
//
|
||||||
|
// Printing the piper-go version. Should not be done here, but somewhere during materializing
|
||||||
if(mode == DeployMode.NONE) {
|
// the piper binary. As long as we don't have it elsewhere we should keep it here.
|
||||||
echo "Deployment skipped intentionally. Deploy mode '${mode.toString()}'."
|
def piperGoVersion = sh(returnStdout: true, script: "./piper version")
|
||||||
return
|
echo "PiperGoVersion: ${piperGoVersion}"
|
||||||
}
|
|
||||||
|
|
||||||
Action action = config.action
|
|
||||||
|
|
||||||
if(mode == DeployMode.DEPLOY && action != Action.NONE) {
|
|
||||||
error "Cannot perform action '${action.toString()}' in mode '${mode.toString()}'. Only action '${Action.NONE.toString()}' is allowed."
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean performLogin = ((mode == DeployMode.DEPLOY) || (mode == DeployMode.BG_DEPLOY && !(action in [Action.RESUME, Action.ABORT])))
|
|
||||||
boolean performLogout = ((mode == DeployMode.DEPLOY) || (mode == DeployMode.BG_DEPLOY && action != Action.NONE))
|
|
||||||
|
|
||||||
boolean sessionExists = fileExists file: config.xsSessionFile
|
|
||||||
|
|
||||||
if( (! performLogin) && (! sessionExists) ) {
|
|
||||||
error 'For the current configuration an already existing session is required. But there is no already existing session.'
|
|
||||||
}
|
|
||||||
|
|
||||||
configHelper
|
|
||||||
.collectValidationFailures()
|
|
||||||
/**
|
|
||||||
* Used for finalizing the blue-green deployment.
|
|
||||||
* @possibleValues RESUME, ABORT, RETRY
|
|
||||||
*/
|
|
||||||
.withMandatoryProperty('action')
|
|
||||||
/** The file name of the file representing the sesssion after `xs login`. Should not be changed normally. */
|
|
||||||
.withMandatoryProperty('xsSessionFile')
|
|
||||||
/** Regex pattern for retrieving the ID of the deployment. */
|
|
||||||
.withMandatoryProperty('deployIdLogPattern')
|
|
||||||
/**
|
|
||||||
* Controls if there is a standard deployment or a blue green deployment
|
|
||||||
* @possibleValues DEPLOY, BG_DEPLOY
|
|
||||||
*/
|
|
||||||
.withMandatoryProperty('mode')
|
|
||||||
/** The endpoint */
|
|
||||||
.withMandatoryProperty('apiUrl')
|
|
||||||
/** The organization */
|
|
||||||
.withMandatoryProperty('org')
|
|
||||||
/** The space */
|
|
||||||
.withMandatoryProperty('space')
|
|
||||||
/** Additional options appended to the login command. Only needed for sophisticated cases.
|
|
||||||
* When provided it is the duty of the provider to ensure proper quoting / escaping.
|
|
||||||
*/
|
|
||||||
.withMandatoryProperty('loginOpts')
|
|
||||||
/** Additional options appended to the deploy command. Only needed for sophisticated cases.
|
|
||||||
* When provided it is the duty of the provider to ensure proper quoting / escaping.
|
|
||||||
*/
|
|
||||||
.withMandatoryProperty('deployOpts')
|
|
||||||
/** The credentialsId */
|
|
||||||
.withMandatoryProperty('credentialsId')
|
|
||||||
/** The path to the deployable. If not provided explicitly it is retrieved from the common pipeline environment
|
|
||||||
* (Parameter `mtarFilePath`).
|
|
||||||
*/
|
|
||||||
.withMandatoryProperty('mtaPath', null, {action == Action.NONE})
|
|
||||||
.withMandatoryProperty('deploymentId',
|
|
||||||
'No deployment id provided, neither via parameters nor via common pipeline environment. Was there a deployment before?',
|
|
||||||
{action in [Action.RESUME, Action.ABORT, Action.RETRY]})
|
|
||||||
.use()
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// since there is no valid config provided (... null) telemetry is disabled (same for other go releated steps at the moment).
|
||||||
utils.pushToSWA([
|
utils.pushToSWA([
|
||||||
step: STEP_NAME,
|
step: STEP_NAME,
|
||||||
], config)
|
], null)
|
||||||
|
|
||||||
if(action == Action.NONE) {
|
String configFiles = prepareConfigurations([PIPER_DEFAULTS].plus(script.commonPipelineEnvironment.getCustomDefaults()), ADDITIONAL_CONFIGS_FOLDER)
|
||||||
boolean deployableExists = fileExists file: config.mtaPath
|
|
||||||
if(! deployableExists)
|
|
||||||
error "Deployable '${config.mtaPath}' does not exist."
|
|
||||||
}
|
|
||||||
|
|
||||||
if(performLogin) {
|
writeFile(file: "${METADATA_FOLDER}/${METADATA_FILE}", text: libraryResource(METADATA_FILE))
|
||||||
login(script, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
def failures = []
|
withEnv([
|
||||||
|
"PIPER_parametersJSON=${groovy.json.JsonOutput.toJson(parameters)}",
|
||||||
|
]) {
|
||||||
|
|
||||||
if(action in [Action.RESUME, Action.ABORT, Action.RETRY]) {
|
//
|
||||||
|
// context config gives us e.g. the docker image name. --> How does this work for customer maintained images?
|
||||||
|
// There is a name provided in the metadata file. But we do not provide a docker image for that.
|
||||||
|
// The user has to build that for her/his own. How do we expect to configure this?
|
||||||
|
|
||||||
complete(script, mode, action, config, failures)
|
String projectConfigScript = "./piper getConfig --stepMetadata '${METADATA_FOLDER}/${METADATA_FILE}' --defaultConfig ${configFiles}"
|
||||||
|
String contextConfigScript = projectConfigScript + " --contextConfig"
|
||||||
|
Map projectConfig = readJSON (text: sh(returnStdout: true, script: projectConfigScript))
|
||||||
|
Map contextConfig = readJSON (text: sh(returnStdout: true, script: contextConfigScript))
|
||||||
|
|
||||||
} else {
|
Action action = projectConfig.action
|
||||||
|
DeployMode mode = projectConfig.mode
|
||||||
|
|
||||||
deploy(script, mode, config, failures)
|
if(parameters.verbose) {
|
||||||
}
|
echo "[INFO] ContextConfig: ${contextConfig}"
|
||||||
|
echo "[INFO] ProjectConfig: ${projectConfig}"
|
||||||
|
}
|
||||||
|
|
||||||
if (performLogout || failures) {
|
def operationId = parameters.operationId
|
||||||
logout(script, config, failures)
|
if(! operationId && mode == DeployMode.BG_DEPLOY && action != Action.NONE) {
|
||||||
|
operationId = script.commonPipelineEnvironment.xsDeploymentId
|
||||||
} else {
|
if (! operationId) {
|
||||||
echo "Skipping logout in order to be able to resume or abort later."
|
throw new IllegalArgumentException('No operationId provided. Was there a deployment before?')
|
||||||
}
|
|
||||||
|
|
||||||
if(failures) {
|
|
||||||
error "Failed command(s): ${failures}. Check earlier log for details."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void login(Script script, Map config) {
|
|
||||||
|
|
||||||
withCredentials([usernamePassword(
|
|
||||||
credentialsId: config.credentialsId,
|
|
||||||
passwordVariable: 'password',
|
|
||||||
usernameVariable: 'username'
|
|
||||||
)]) {
|
|
||||||
|
|
||||||
def returnCode = executeXSCommand([script: script].plus(config.docker),
|
|
||||||
[
|
|
||||||
"xs login -a ${config.apiUrl} -u ${username} -p ${BashUtils.quoteAndEscape(password)} -o ${config.org} -s ${config.space} ${config.loginOpts}",
|
|
||||||
'RC=$?',
|
|
||||||
"[ \$RC == 0 ] && cp \"\${HOME}/${config.xsSessionFile}\" .",
|
|
||||||
'exit $RC'
|
|
||||||
])
|
|
||||||
|
|
||||||
if(returnCode != 0)
|
|
||||||
error "xs login failed."
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean existsXsSessionFileAfterLogin = fileExists file: config.xsSessionFile
|
|
||||||
if(! existsXsSessionFileAfterLogin)
|
|
||||||
error "Session file ${config.xsSessionFile} not found in current working directory after login."
|
|
||||||
}
|
|
||||||
|
|
||||||
void deploy(Script script, DeployMode mode, Map config, def failures) {
|
|
||||||
|
|
||||||
def deploymentLog
|
|
||||||
|
|
||||||
try {
|
|
||||||
lock(getLockIdentifier(config)) {
|
|
||||||
deploymentLog = executeXSCommand([script: script].plus(config.docker),
|
|
||||||
[
|
|
||||||
"cp ${config.xsSessionFile} \${HOME}",
|
|
||||||
"xs ${mode.toString()} '${config.mtaPath}' -f ${config.deployOpts}"
|
|
||||||
], true)
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Deploy log: ${deploymentLog}"
|
|
||||||
|
|
||||||
} catch(AbortException e) {
|
|
||||||
echo "deployment failed. Message: ${e.getMessage()}, Log: ${deploymentLog}}"
|
|
||||||
failures << "xs ${mode.toString()}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mode == DeployMode.BG_DEPLOY) {
|
|
||||||
|
|
||||||
if(! failures.isEmpty()) {
|
|
||||||
|
|
||||||
echo "Retrieval of deploymentId skipped since prior deployment was not successfull."
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
for (def logLine : deploymentLog.readLines()) {
|
|
||||||
def matcher = logLine =~ config.deployIdLogPattern
|
|
||||||
if(matcher.find()) {
|
|
||||||
script.commonPipelineEnvironment.xsDeploymentId = matcher[0][1]
|
|
||||||
echo "DeploymentId: ${script.commonPipelineEnvironment.xsDeploymentId}."
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(script.commonPipelineEnvironment.xsDeploymentId == null) {
|
|
||||||
failures << "Cannot lookup deploymentId. Search pattern was: '${config.deployIdLogPattern}'."
|
def xsDeployStdout
|
||||||
|
|
||||||
|
lock(getLockIdentifier(projectConfig)) {
|
||||||
|
|
||||||
|
withCredentials([usernamePassword(
|
||||||
|
credentialsId: contextConfig.credentialsId,
|
||||||
|
passwordVariable: 'PASSWORD',
|
||||||
|
usernameVariable: 'USERNAME')]) {
|
||||||
|
|
||||||
|
dockerExecute([script: this].plus([dockerImage: contextConfig.dockerImage, dockerPullImage: contextConfig.dockerPullImage])) {
|
||||||
|
xsDeployStdout = sh returnStdout: true, script: """#!/bin/bash
|
||||||
|
./piper xsDeploy --defaultConfig ${configFiles} --user \${USERNAME} --password \${PASSWORD} ${operationId ? "--operationId " + operationId : "" }
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mode == DeployMode.BG_DEPLOY && action == Action.NONE) {
|
||||||
|
script.commonPipelineEnvironment.xsDeploymentId = readJSON(text: xsDeployStdout).operationId
|
||||||
|
if (!script.commonPipelineEnvironment.xsDeploymentId) {
|
||||||
|
error "No Operation id returned from xs deploy step. This is required for mode '${mode}' and action '${action}'."
|
||||||
|
}
|
||||||
|
echo "[INFO] OperationId for subsequent resume or abort: '${script.commonPipelineEnvironment.xsDeploymentId}'."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void complete(Script script, DeployMode mode, Action action, Map config, def failures) {
|
|
||||||
|
|
||||||
if(mode != DeployMode.BG_DEPLOY)
|
|
||||||
error "Action '${action.toString()}' can only be performed for mode '${DeployMode.BG_DEPLOY.toString()}'. Current mode is: '${mode.toString()}'."
|
|
||||||
|
|
||||||
def returnCode = 1
|
|
||||||
|
|
||||||
lock(getLockIdentifier(config)) {
|
|
||||||
returnCode = executeXSCommand([script: script].plus(config.docker),
|
|
||||||
[
|
|
||||||
"cp ${config.xsSessionFile} \${HOME}",
|
|
||||||
"xs ${mode.toString()} -i ${config.deploymentId} -a ${action.toString()}"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
if(returnCode != 0) {
|
|
||||||
echo "${mode.toString()} with action '${action.toString()}' failed with return code ${returnCode}."
|
|
||||||
failures << "xs ${mode.toString()} -a ${action.toString()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void logout(Script script, Map config, def failures) {
|
|
||||||
|
|
||||||
def returnCode = executeXSCommand([script: script].plus(config.docker),
|
|
||||||
[
|
|
||||||
"cp ${config.xsSessionFile} \${HOME}",
|
|
||||||
'xs logout'
|
|
||||||
])
|
|
||||||
|
|
||||||
if(returnCode != 0) {
|
|
||||||
failures << 'xs logout'
|
|
||||||
}
|
|
||||||
|
|
||||||
sh "XSCONFIG=${config.xsSessionFile}; [ -f \${XSCONFIG} ] && rm \${XSCONFIG}"
|
|
||||||
}
|
|
||||||
|
|
||||||
String getLockIdentifier(Map config) {
|
String getLockIdentifier(Map config) {
|
||||||
"$STEP_NAME:${config.apiUrl}:${config.org}:${config.space}"
|
"$STEP_NAME:${config.apiUrl}:${config.org}:${config.space}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def executeXSCommand(Map dockerOptions, List commands, boolean returnStdout = false) {
|
/*
|
||||||
|
* The returned string can be used directly in the command line for retrieving the configuration via go
|
||||||
|
*/
|
||||||
|
String prepareConfigurations(List configs, String configCacheFolder) {
|
||||||
|
|
||||||
def r
|
for(def customDefault : configs) {
|
||||||
|
writeFile(file: "${ADDITIONAL_CONFIGS_FOLDER}/${customDefault}", text: libraryResource(customDefault))
|
||||||
dockerExecute(dockerOptions) {
|
|
||||||
|
|
||||||
// in case there are credentials contained in the commands we assume
|
|
||||||
// the call is properly wrapped by withCredentials(./.)
|
|
||||||
echo "Executing: '${commands}'."
|
|
||||||
|
|
||||||
List prelude = [
|
|
||||||
'#!/bin/bash'
|
|
||||||
]
|
|
||||||
|
|
||||||
List script = (prelude + commands)
|
|
||||||
|
|
||||||
params = [
|
|
||||||
script: script.join('\n')
|
|
||||||
]
|
|
||||||
|
|
||||||
if(returnStdout) {
|
|
||||||
params << [ returnStdout: true ]
|
|
||||||
} else {
|
|
||||||
params << [ returnStatus: true ]
|
|
||||||
}
|
|
||||||
|
|
||||||
r = sh params
|
|
||||||
|
|
||||||
if( (! returnStdout ) && r != 0) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
echo "xs logs:"
|
|
||||||
|
|
||||||
sh 'LOG_FOLDER=${HOME}/.xs_logs; [ -d ${LOG_FOLDER} ] && cat ${LOG_FOLDER}/*'
|
|
||||||
|
|
||||||
} catch(Exception e) {
|
|
||||||
|
|
||||||
echo "Cannot provide xs logs: ${e.getMessage()}."
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Executing of commands '${commands}' failed. Check earlier logs for details."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
r
|
joinAndQuote(configs.reverse(), configCacheFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* prefix is supposed to be provided without trailing slash
|
||||||
|
*/
|
||||||
|
String joinAndQuote(List l, String prefix = '') {
|
||||||
|
_l = []
|
||||||
|
|
||||||
|
if(prefix == null) {
|
||||||
|
prefix = ''
|
||||||
|
}
|
||||||
|
if(prefix.endsWith('/') || prefix.endsWith('\\'))
|
||||||
|
throw new IllegalArgumentException("Provide prefix (${prefix}) without trailing slash")
|
||||||
|
|
||||||
|
for(def e : l) {
|
||||||
|
def _e = ''
|
||||||
|
if(prefix.length() > 0) {
|
||||||
|
_e += prefix
|
||||||
|
_e += '/'
|
||||||
|
}
|
||||||
|
_e += e
|
||||||
|
_l << '"' + _e + '"'
|
||||||
|
}
|
||||||
|
_l.join(' ')
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user