You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-07-15 01:34:38 +02:00
ABAP deploy to front end server via existing fiori upload facilities (#1939)
The approach used up to now did not (... never) work since the uploaded content was always assigned to the `$TMP` package. In the meanwhile there is some fiori tooling available for managing such uploads. Now we re-use this fiori tooling. Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>
This commit is contained in:
@ -64,6 +64,80 @@ The properties can also be configured on a per-step basis:
|
|||||||
|
|
||||||
The parameters can also be provided when the step is invoked. For examples see below.
|
The parameters can also be provided when the step is invoked. For examples see below.
|
||||||
|
|
||||||
|
## CTS Uploads
|
||||||
|
|
||||||
|
In order to be able to upload the application, it is required to build the application, e.g. via `npmExecute`
|
||||||
|
or `mtaBuild`. The content of the app needs to be provided in a folder named `dist` in the root level of the project.
|
||||||
|
Although the name of the step `transportRequestUploadFile` might suggest something else, in this case a folder needs
|
||||||
|
to be provided. The application, which is provided in the `dist` folder is zipped and uploaded by the fiori toolset
|
||||||
|
used for performing the upload.
|
||||||
|
|
||||||
|
For `CTS` related uploads we use a node based toolset. When running in a docker environment a standard node
|
||||||
|
image can be used. In this case the required deploy tool dependencies will be installed prior to the deploy.
|
||||||
|
It is also possible to provide a docker image which already contains the required deploy tool
|
||||||
|
dependencies (`config.changeManagement.cts.nodeDocker.image`). In this case an empty list needs to be provided
|
||||||
|
as `config.changeManagement.cts.deployToolDependencies`. Using an already pre-configured docker image speeds-up
|
||||||
|
the deployment step, but comes with the disadvantage of having
|
||||||
|
to maintain and provision the corresponding docker image.
|
||||||
|
|
||||||
|
When running in an environment without docker, it is recommanded to install the deploy tools manually on the
|
||||||
|
system and to provide an empty list for the deploy tool dependencies (`config.changeManagement.cts.deployToolDependencies`).
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
#### Upload based on preconfigured image
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
transportRequestUploadFile script: this,
|
||||||
|
changeManagement: [
|
||||||
|
credentialsId: 'CRED_ID', // credentials needs to be defined inside Jenkins
|
||||||
|
type: 'CTS',
|
||||||
|
endpoint: 'https://example.org:8000',
|
||||||
|
client: '001',
|
||||||
|
cts: [
|
||||||
|
nodeDocker: [
|
||||||
|
image: 'docker-image-name',
|
||||||
|
pullImage: true, // needs to be set to false in case the image is
|
||||||
|
// only available in the local docker cache (not recommended)
|
||||||
|
],
|
||||||
|
npmInstallOpts: [],
|
||||||
|
deployToolDependencies: [], // empty since we use an already preconfigured image
|
||||||
|
],
|
||||||
|
],
|
||||||
|
applicationName: 'APP',
|
||||||
|
abapPackage: 'ABABPACKAGE',
|
||||||
|
transportRequestId: 'XXXK123456', // can be omitted when resolved via commit history
|
||||||
|
applicationDescription: 'An optional description' // only used in case a new application is deployed
|
||||||
|
// description is not updated for re-deployments
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Upload based on a standard node image
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
transportRequestUploadFile script: this,
|
||||||
|
changeManagement: [
|
||||||
|
credentialsId: 'CRED_ID', // credentials needs to be defined inside Jenkins
|
||||||
|
type: 'CTS',
|
||||||
|
endpoint: 'https://example.org:8000',
|
||||||
|
client: '001',
|
||||||
|
cts: [
|
||||||
|
npmInstallOpts: [
|
||||||
|
'--verbose', // might be benefical for troubleshooting
|
||||||
|
'--registry', 'https://your.npmregistry.org/', // an own registry can be specified here
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
applicationName: 'APP',
|
||||||
|
abapPackage: 'ABABPACKAGE',
|
||||||
|
transportRequestId: 'XXXK123456', // can be omitted when resolved via commit history
|
||||||
|
applicationDescription: 'An optional description' // only used in case a new application is deployed
|
||||||
|
// description is not updated for re-deployments
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Exceptions
|
## Exceptions
|
||||||
|
|
||||||
* `IllegalArgumentException`:
|
* `IllegalArgumentException`:
|
||||||
@ -91,15 +165,15 @@ transportRequestUploadFile(
|
|||||||
)
|
)
|
||||||
// CTS
|
// CTS
|
||||||
|
|
||||||
// NOTE: CTS upload currently not supported!
|
|
||||||
|
|
||||||
transportRequestUploadFile(
|
transportRequestUploadFile(
|
||||||
script: this,
|
script: this,
|
||||||
transportRequestId: '001', // typically provided via git commit history
|
transportRequestId: '001', // typically provided via git commit history
|
||||||
filePath: '/path',
|
|
||||||
changeManagement: [
|
changeManagement: [
|
||||||
type: 'CTS'
|
type: 'CTS'
|
||||||
endpoint: 'https://example.org/cm'
|
endpoint: 'https://example.org/cm',
|
||||||
]
|
client: '099',
|
||||||
|
],
|
||||||
|
applicationName: 'myApp',
|
||||||
|
abapPackage: 'MYPACKAGE',
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
@ -24,6 +24,18 @@ general:
|
|||||||
envVars: {}
|
envVars: {}
|
||||||
pullImage: true
|
pullImage: true
|
||||||
cts:
|
cts:
|
||||||
|
osDeployUser: 'node'
|
||||||
|
deployToolDependencies:
|
||||||
|
- '@ui5/cli'
|
||||||
|
- '@sap/ux-ui5-tooling'
|
||||||
|
- '@ui5/logger'
|
||||||
|
- '@ui5/fs'
|
||||||
|
deployConfigFile: 'ui5-deploy.yaml'
|
||||||
|
nodeDocker:
|
||||||
|
image: 'node'
|
||||||
|
options: []
|
||||||
|
envVars: {}
|
||||||
|
pullImage: true
|
||||||
docker:
|
docker:
|
||||||
image: 'ppiper/cm-client'
|
image: 'ppiper/cm-client'
|
||||||
options: []
|
options: []
|
||||||
|
@ -172,29 +172,148 @@ public class ChangeManagement implements Serializable {
|
|||||||
void uploadFileToTransportRequestCTS(
|
void uploadFileToTransportRequestCTS(
|
||||||
Map docker,
|
Map docker,
|
||||||
String transportRequestId,
|
String transportRequestId,
|
||||||
String filePath,
|
|
||||||
String endpoint,
|
String endpoint,
|
||||||
String credentialsId,
|
String client,
|
||||||
String cmclientOpts = '') {
|
String applicationName,
|
||||||
|
String description,
|
||||||
|
String abapPackage, // "package" would be better, but this is a keyword
|
||||||
|
String osDeployUser,
|
||||||
|
def deployToolDependencies,
|
||||||
|
def npmInstallOpts,
|
||||||
|
String deployConfigFile,
|
||||||
|
String credentialsId) {
|
||||||
|
|
||||||
def args = [
|
def script = this.script
|
||||||
'-tID', transportRequestId,
|
|
||||||
"\"$filePath\""
|
|
||||||
]
|
|
||||||
|
|
||||||
int rc = executeWithCredentials(
|
def desc = description ?: 'Deployed with Piper based on SAP Fiori tools'
|
||||||
BackendType.CTS,
|
|
||||||
docker,
|
|
||||||
endpoint,
|
|
||||||
credentialsId,
|
|
||||||
'upload-file-to-transport',
|
|
||||||
args,
|
|
||||||
false,
|
|
||||||
cmclientOpts) as int
|
|
||||||
|
|
||||||
if(rc != 0) {
|
if (deployToolDependencies in List) {
|
||||||
throw new ChangeManagementException(
|
deployToolDependencies = deployToolDependencies.join(' ')
|
||||||
"Cannot upload file into transport request. Return code from cm client: $rc.")
|
}
|
||||||
|
|
||||||
|
if (npmInstallOpts in List) {
|
||||||
|
npmInstallOpts = npmInstallOpts.join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
deployToolDependencies = deployToolDependencies.trim()
|
||||||
|
|
||||||
|
/*
|
||||||
|
In case the configuration has been adjusted so that no deployToolDependencies are provided
|
||||||
|
we assume an image is used, which already contains all dependencies.
|
||||||
|
In this case we don't invoke npm install and we run the image with the standard user
|
||||||
|
already, since there is no need for being root. Hence we don't need to switch user also
|
||||||
|
in the script.
|
||||||
|
*/
|
||||||
|
boolean noInstall = deployToolDependencies.isEmpty()
|
||||||
|
|
||||||
|
Iterable cmd = ['#!/bin/bash -e']
|
||||||
|
|
||||||
|
if (! noInstall) {
|
||||||
|
cmd << "npm install --global ${npmInstallOpts} ${deployToolDependencies}"
|
||||||
|
cmd << "su ${osDeployUser}"
|
||||||
|
} else {
|
||||||
|
script.echo "[INFO] no deploy dependencies provided. Skipping npm install call. Assuming docker image '${docker?.image}' already contains the dependencies for performing the deployment."
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable params = []
|
||||||
|
|
||||||
|
boolean useConfigFile = true, noConfig = false
|
||||||
|
|
||||||
|
if (!deployConfigFile) {
|
||||||
|
useConfigFile = false
|
||||||
|
noConfig = !script.fileExists('ui5-deploy.yaml')
|
||||||
|
} else {
|
||||||
|
if (script.fileExists(deployConfigFile)) {
|
||||||
|
// in this case we will use the config file
|
||||||
|
useConfigFile = true
|
||||||
|
} else {
|
||||||
|
if (deployConfigFile == 'ui5-deploy.yaml') {
|
||||||
|
// in this case this is most likely provided by the piper default config and
|
||||||
|
// it was not explicitly configured. Hence we assume not having a config file
|
||||||
|
useConfigFile = false
|
||||||
|
noConfig = true
|
||||||
|
} else {
|
||||||
|
script.error("Configured deploy config file '${deployConfigFile}' does not exists.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noConfig) {
|
||||||
|
params += ['--noConfig'] // no config file, but we will provide our parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useConfigFile) {
|
||||||
|
params += ['-c', "\"" + deployConfigFile + "\""]
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// All the parameters below encapsulated in an if statement might also be provided in a config file.
|
||||||
|
// In case they are empty we don't add to the command line and we trust in the config file.
|
||||||
|
// In case they are finally missing the fiori deploy toolset will tell us.
|
||||||
|
//
|
||||||
|
|
||||||
|
if (transportRequestId) {
|
||||||
|
params += ['-t', transportRequestId]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endpoint) {
|
||||||
|
params += ['-u', endpoint]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abapPackage) {
|
||||||
|
params += ['-p', abapPackage]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applicationName) {
|
||||||
|
params += ['-n' , applicationName]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client) {
|
||||||
|
params += ['-l', client]
|
||||||
|
}
|
||||||
|
|
||||||
|
params += ['-e', "\"" + desc + "\""]
|
||||||
|
|
||||||
|
params += ['-f'] // failfast --> provide return code != 0 in case of any failure
|
||||||
|
|
||||||
|
params += ['-y'] // autoconfirm --> no need to press 'y' key in order to confirm the params and trigger the deployment
|
||||||
|
|
||||||
|
// Here we provide the names of the environment variable holding username and password. Below we set these values.
|
||||||
|
params += ['--username', 'ABAP_USER', '--password', 'ABAP_PASSWORD']
|
||||||
|
|
||||||
|
def fioriDeployCmd = "fiori deploy ${params.join(' ')}"
|
||||||
|
script.echo "Executing deploy command: '${fioriDeployCmd}'"
|
||||||
|
cmd << fioriDeployCmd
|
||||||
|
|
||||||
|
script.withCredentials([script.usernamePassword(
|
||||||
|
credentialsId: credentialsId,
|
||||||
|
passwordVariable: 'password',
|
||||||
|
usernameVariable: 'username')]) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
After installing the deploy toolset we switch the user. Since we do not `su` with option `-l` the
|
||||||
|
environment variables are preserved. Hence the environment variables for user and password are
|
||||||
|
still available after the user switch.
|
||||||
|
*/
|
||||||
|
def dockerEnvVars = docker.envVars ?: [:]
|
||||||
|
dockerEnvVars += [ABAP_USER: script.username, ABAP_PASSWORD: script.password]
|
||||||
|
|
||||||
|
def dockerOptions = docker.options ?: []
|
||||||
|
if (!noInstall) {
|
||||||
|
// when we install globally we need to be root, after preparing that we can su node` in the bash script.
|
||||||
|
// in case there is already a u provided the latest (... what we set here wins).
|
||||||
|
dockerOptions += ['-u', '0']
|
||||||
|
}
|
||||||
|
|
||||||
|
script.dockerExecute(
|
||||||
|
script: script,
|
||||||
|
dockerImage: docker.image,
|
||||||
|
dockerOptions: dockerOptions,
|
||||||
|
dockerEnvVars: dockerEnvVars,
|
||||||
|
dockerPullImage: docker.pullImage) {
|
||||||
|
|
||||||
|
script.sh script: cmd.join('\n')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,48 +164,75 @@ public class TransportRequestUploadFileTest extends BasePiperTest {
|
|||||||
@Test
|
@Test
|
||||||
public void uploadFileToTransportRequestCTSSuccessTest() {
|
public void uploadFileToTransportRequestCTSSuccessTest() {
|
||||||
|
|
||||||
loggingRule.expect("[INFO] Uploading file '/path' to transport request '002'.")
|
loggingRule.expect("[INFO] Uploading application 'myApp' to transport request '002'.")
|
||||||
loggingRule.expect("[INFO] File '/path' has been successfully uploaded to transport request '002'.")
|
loggingRule.expect("[INFO] Application 'myApp' has been successfully uploaded to transport request '002'.")
|
||||||
|
|
||||||
ChangeManagement cm = new ChangeManagement(nullScript) {
|
ChangeManagement cm = new ChangeManagement(nullScript) {
|
||||||
void uploadFileToTransportRequestCTS(
|
void uploadFileToTransportRequestCTS(
|
||||||
Map docker,
|
Map docker,
|
||||||
String transportRequestId,
|
String transportRequestId,
|
||||||
String filePath,
|
|
||||||
String endpoint,
|
String endpoint,
|
||||||
String credentialsId,
|
String client,
|
||||||
String cmclientOpts) {
|
String appName,
|
||||||
|
String appDescription,
|
||||||
|
String abapPackage,
|
||||||
|
String osDeployUser,
|
||||||
|
def deployToolsDependencies,
|
||||||
|
def npmInstallArgs,
|
||||||
|
String deployConfigFile,
|
||||||
|
String credentialsId) {
|
||||||
|
|
||||||
cmUtilReceivedParams.docker = docker
|
cmUtilReceivedParams.docker = docker
|
||||||
cmUtilReceivedParams.transportRequestId = transportRequestId
|
cmUtilReceivedParams.transportRequestId = transportRequestId
|
||||||
cmUtilReceivedParams.filePath = filePath
|
|
||||||
cmUtilReceivedParams.endpoint = endpoint
|
cmUtilReceivedParams.endpoint = endpoint
|
||||||
|
cmUtilReceivedParams.client = client
|
||||||
|
cmUtilReceivedParams.appName = appName
|
||||||
|
cmUtilReceivedParams.appDescription = appDescription
|
||||||
|
cmUtilReceivedParams.abapPackage = abapPackage
|
||||||
|
cmUtilReceivedParams.osDeployUser = osDeployUser
|
||||||
|
cmUtilReceivedParams.deployToolDependencies = deployToolsDependencies
|
||||||
|
cmUtilReceivedParams.npmInstallOpts = npmInstallArgs
|
||||||
|
cmUtilReceivedParams.deployConfigFile = deployConfigFile
|
||||||
cmUtilReceivedParams.credentialsId = credentialsId
|
cmUtilReceivedParams.credentialsId = credentialsId
|
||||||
cmUtilReceivedParams.cmclientOpts = cmclientOpts
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stepRule.step.transportRequestUploadFile(script: nullScript,
|
stepRule.step.transportRequestUploadFile(script: nullScript,
|
||||||
changeManagement: [type: 'CTS'],
|
changeManagement: [
|
||||||
|
type: 'CTS',
|
||||||
|
client: '001',
|
||||||
|
cts: [
|
||||||
|
osDeployUser: 'node2',
|
||||||
|
deployToolDependencies: ['@ui5/cli', '@sap/ux-ui5-tooling', '@ui5/logger', '@ui5/fs', '@dummy/foo'],
|
||||||
|
npmInstallOpts: ['--verbose'],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
applicationName: 'myApp',
|
||||||
|
applicationDescription: 'the description',
|
||||||
|
abapPackage: 'myPackage',
|
||||||
transportRequestId: '002',
|
transportRequestId: '002',
|
||||||
filePath: '/path',
|
|
||||||
cmUtils: cm)
|
cmUtils: cm)
|
||||||
|
|
||||||
assert cmUtilReceivedParams ==
|
assert cmUtilReceivedParams ==
|
||||||
[
|
[
|
||||||
docker: [
|
docker: [
|
||||||
image: 'ppiper/cm-client',
|
image: 'node',
|
||||||
options:[],
|
options:[],
|
||||||
envVars:[:],
|
envVars:[:],
|
||||||
pullImage:true
|
pullImage:true
|
||||||
],
|
],
|
||||||
transportRequestId: '002',
|
transportRequestId: '002',
|
||||||
filePath: '/path',
|
|
||||||
endpoint: 'https://example.org/cm',
|
endpoint: 'https://example.org/cm',
|
||||||
|
client: '001',
|
||||||
|
appName: 'myApp',
|
||||||
|
appDescription: 'the description',
|
||||||
|
abapPackage: 'myPackage',
|
||||||
|
osDeployUser: 'node2',
|
||||||
|
deployToolDependencies: ['@ui5/cli', '@sap/ux-ui5-tooling', '@ui5/logger', '@ui5/fs', '@dummy/foo'],
|
||||||
|
npmInstallOpts: ['--verbose'],
|
||||||
|
deployConfigFile: 'ui5-deploy.yaml',
|
||||||
credentialsId: 'CM',
|
credentialsId: 'CM',
|
||||||
cmclientOpts: ''
|
|
||||||
]
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -7,6 +7,7 @@ import static org.hamcrest.Matchers.hasItem
|
|||||||
import static org.hamcrest.Matchers.is
|
import static org.hamcrest.Matchers.is
|
||||||
import static org.hamcrest.Matchers.not
|
import static org.hamcrest.Matchers.not
|
||||||
import static org.junit.Assert.assertThat
|
import static org.junit.Assert.assertThat
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals
|
import static org.junit.Assert.assertEquals
|
||||||
|
|
||||||
import org.hamcrest.Matchers
|
import org.hamcrest.Matchers
|
||||||
@ -24,6 +25,7 @@ import util.JenkinsScriptLoaderRule
|
|||||||
import util.JenkinsShellCallRule
|
import util.JenkinsShellCallRule
|
||||||
import util.JenkinsCredentialsRule
|
import util.JenkinsCredentialsRule
|
||||||
import util.JenkinsDockerExecuteRule
|
import util.JenkinsDockerExecuteRule
|
||||||
|
import util.JenkinsFileExistsRule
|
||||||
import util.Rules
|
import util.Rules
|
||||||
|
|
||||||
import hudson.AbortException
|
import hudson.AbortException
|
||||||
@ -35,6 +37,7 @@ public class ChangeManagementTest extends BasePiperTest {
|
|||||||
private JenkinsShellCallRule script = new JenkinsShellCallRule(this)
|
private JenkinsShellCallRule script = new JenkinsShellCallRule(this)
|
||||||
private JenkinsLoggingRule logging = new JenkinsLoggingRule(this)
|
private JenkinsLoggingRule logging = new JenkinsLoggingRule(this)
|
||||||
private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this)
|
private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this)
|
||||||
|
private JenkinsFileExistsRule files = new JenkinsFileExistsRule(this)
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public RuleChain rules = Rules.getCommonRules(this)
|
public RuleChain rules = Rules.getCommonRules(this)
|
||||||
@ -43,6 +46,7 @@ public class ChangeManagementTest extends BasePiperTest {
|
|||||||
.around(logging)
|
.around(logging)
|
||||||
.around(new JenkinsCredentialsRule(this).withCredentials('me','user','password'))
|
.around(new JenkinsCredentialsRule(this).withCredentials('me','user','password'))
|
||||||
.around(dockerExecuteRule)
|
.around(dockerExecuteRule)
|
||||||
|
.around(files)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRetrieveChangeDocumentIdOutsideGitWorkTreeTest() {
|
public void testRetrieveChangeDocumentIdOutsideGitWorkTreeTest() {
|
||||||
@ -291,26 +295,183 @@ public void testGetCommandLineWithCMClientOpts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUploadFileToTransportSucceedsCTS() {
|
public void testUploadFileToTransportSucceedsCTSDeployConfigYamlExists() {
|
||||||
|
|
||||||
// the regex provided below is an implicit check that the command line is fine.
|
files.existingFiles.add('ui5-deploy.yaml')
|
||||||
script.setReturnValue(JenkinsShellCallRule.Type.REGEX, '-t CTS upload-file-to-transport -tID 002 "/path"', 0)
|
|
||||||
|
|
||||||
new ChangeManagement(nullScript).uploadFileToTransportRequestCTS(
|
new ChangeManagement(nullScript).uploadFileToTransportRequestCTS(
|
||||||
[
|
[
|
||||||
image: 'ppiper/cmclient',
|
image: 'node',
|
||||||
pullImage: true
|
pullImage: true
|
||||||
],
|
],
|
||||||
'002',
|
'002',
|
||||||
'/path',
|
|
||||||
'https://example.org/cm',
|
'https://example.org/cm',
|
||||||
'me')
|
'001',
|
||||||
|
'myApp',
|
||||||
|
'the description',
|
||||||
|
'aPackage',
|
||||||
|
'node2',
|
||||||
|
['@ui5/cli', '@sap/ux-ui5-tooling', '@ui5/logger', '@ui5/fs', '@dummy/foo'],
|
||||||
|
['--verbose'],
|
||||||
|
'ui5-deploy.yaml',
|
||||||
|
'me',
|
||||||
|
)
|
||||||
|
|
||||||
assert dockerExecuteRule.getDockerParams().dockerImage == 'ppiper/cmclient'
|
assert script.shell[0].contains('npm install --global --verbose @ui5/cli @sap/ux-ui5-tooling @ui5/logger @ui5/fs @dummy/foo')
|
||||||
|
|
||||||
|
assert script.shell[0].contains("fiori deploy -c \"ui5-deploy.yaml\" -t 002 -u https://example.org/cm")
|
||||||
|
|
||||||
|
assert dockerExecuteRule.getDockerParams().dockerImage == 'node'
|
||||||
assert dockerExecuteRule.getDockerParams().dockerPullImage == true
|
assert dockerExecuteRule.getDockerParams().dockerPullImage == true
|
||||||
|
assert dockerExecuteRule.getDockerParams().dockerEnvVars == [ABAP_USER: "user", ABAP_PASSWORD: 'password']
|
||||||
|
// we launch the container as root (uid 0) in order to be able to install
|
||||||
|
// the deploytool. Before deploying we su to another user.
|
||||||
|
assert dockerExecuteRule.getDockerParams().dockerOptions == ['-u', '0']
|
||||||
|
}
|
||||||
|
|
||||||
// no assert for the shell command required here, since the regex registered
|
@Test
|
||||||
// above to the script rule is an implicit check for the command line.
|
public void testUploadFileToTransportSucceedsCTSDefaultDeployConfigYamlDoesNotExist() {
|
||||||
|
|
||||||
|
// the file does not exist, since it was not explicity added to the files rule
|
||||||
|
|
||||||
|
new ChangeManagement(nullScript).uploadFileToTransportRequestCTS(
|
||||||
|
[
|
||||||
|
image: 'node',
|
||||||
|
pullImage: true
|
||||||
|
],
|
||||||
|
'002',
|
||||||
|
'https://example.org/cm',
|
||||||
|
'001',
|
||||||
|
'myApp',
|
||||||
|
'the description',
|
||||||
|
'aPackage',
|
||||||
|
'node2',
|
||||||
|
['@ui5/cli', '@sap/ux-ui5-tooling', '@ui5/logger', '@ui5/fs', '@dummy/foo'],
|
||||||
|
['--verbose'],
|
||||||
|
'ui5-deploy.yaml',
|
||||||
|
'me',
|
||||||
|
)
|
||||||
|
|
||||||
|
// more details already checked with test "testUploadFileToTransportSucceedsCTSDeployConfigYamlExists"
|
||||||
|
assert script.shell[0].contains("fiori deploy --noConfig -t 002 -u https://example.org/cm")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUploadFileToTransportFailsCTSExplicitlyConfiguredDeployConfigYamlDoesNotExist() {
|
||||||
|
|
||||||
|
// the file does not exist, since it was not explicitly added to the files rule
|
||||||
|
|
||||||
|
thrown.expect(AbortException)
|
||||||
|
thrown.expectMessage('Configured deploy config file \'my-deploy.yaml\' does not exists.')
|
||||||
|
|
||||||
|
new ChangeManagement(nullScript).uploadFileToTransportRequestCTS(
|
||||||
|
[
|
||||||
|
image: 'node',
|
||||||
|
pullImage: true
|
||||||
|
],
|
||||||
|
'002',
|
||||||
|
'https://example.org/cm',
|
||||||
|
'001',
|
||||||
|
'myApp',
|
||||||
|
'the description',
|
||||||
|
'aPackage',
|
||||||
|
'node2',
|
||||||
|
['@ui5/cli', '@sap/ux-ui5-tooling', '@ui5/logger', '@ui5/fs', '@dummy/foo'],
|
||||||
|
['--verbose'],
|
||||||
|
'my-deploy.yaml',
|
||||||
|
'me',
|
||||||
|
)
|
||||||
|
|
||||||
|
// more details already checked with test "testUploadFileToTransportSucceedsCTSDeployConfigYamlExists"
|
||||||
|
assert script.shell[0].contains("fiori deploy --noConfig -t 002 -u https://example.org/cm")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUploadFileToTransportSucceesCTSExplicitlyConfiguredDeployConfigYamExists() {
|
||||||
|
|
||||||
|
files.existingFiles.add('my-deploy.yaml')
|
||||||
|
|
||||||
|
new ChangeManagement(nullScript).uploadFileToTransportRequestCTS(
|
||||||
|
[
|
||||||
|
image: 'node',
|
||||||
|
pullImage: true
|
||||||
|
],
|
||||||
|
'002',
|
||||||
|
'https://example.org/cm',
|
||||||
|
'001',
|
||||||
|
'myApp',
|
||||||
|
'the description',
|
||||||
|
'aPackage',
|
||||||
|
'node2',
|
||||||
|
['@ui5/cli', '@sap/ux-ui5-tooling', '@ui5/logger', '@ui5/fs', '@dummy/foo'],
|
||||||
|
['--verbose'],
|
||||||
|
'my-deploy.yaml',
|
||||||
|
'me',
|
||||||
|
)
|
||||||
|
|
||||||
|
// more details already checked with test "testUploadFileToTransportSucceedsCTSDeployConfigYamlExists"
|
||||||
|
assert script.shell[0].contains("fiori deploy -c \"my-deploy.yaml\" -t 002 -u https://example.org/cm")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUploadFileToTransportSucceedsEmptyDeployToolDependenciesCTS() {
|
||||||
|
|
||||||
|
new ChangeManagement(nullScript).uploadFileToTransportRequestCTS(
|
||||||
|
[
|
||||||
|
image: 'fioriDeployImage',
|
||||||
|
pullImage: true
|
||||||
|
],
|
||||||
|
'002',
|
||||||
|
'https://example.org/cm',
|
||||||
|
'001',
|
||||||
|
'myApp',
|
||||||
|
'aPackage',
|
||||||
|
'the description',
|
||||||
|
'node2',
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
'ui5-deploy.yaml',
|
||||||
|
'me',
|
||||||
|
)
|
||||||
|
|
||||||
|
assert ! script.shell[0].contains('npm install')
|
||||||
|
assert ! script.shell[0].contains('su')
|
||||||
|
|
||||||
|
assert script.shell[0].contains("fiori deploy")
|
||||||
|
|
||||||
|
assert dockerExecuteRule.getDockerParams().dockerImage == 'fioriDeployImage'
|
||||||
|
assert dockerExecuteRule.getDockerParams().dockerPullImage == true
|
||||||
|
assert dockerExecuteRule.getDockerParams().dockerEnvVars == [ABAP_USER: "user", ABAP_PASSWORD: 'password']
|
||||||
|
// we don't start with the root user since there is no need to install something (globally)
|
||||||
|
assert dockerExecuteRule.getDockerParams().dockerOptions == []
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUploadFileToTransportShellFailsCTS() {
|
||||||
|
|
||||||
|
thrown.expect(AbortException)
|
||||||
|
thrown.expectMessage('script returned exit code 1')
|
||||||
|
|
||||||
|
script.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.*fiori deploy.*',
|
||||||
|
{ throw new AbortException('script returned exit code 1') })
|
||||||
|
|
||||||
|
new ChangeManagement(nullScript).uploadFileToTransportRequestCTS(
|
||||||
|
[
|
||||||
|
image: 'node',
|
||||||
|
pullImage: true
|
||||||
|
],
|
||||||
|
'002',
|
||||||
|
'https://example.org/cm',
|
||||||
|
'001',
|
||||||
|
'myApp',
|
||||||
|
'aPackage',
|
||||||
|
'the description',
|
||||||
|
'node',
|
||||||
|
'@ui5/cli @sap/ux-ui5-tooling @ui5/logger @ui5/fs',
|
||||||
|
[],
|
||||||
|
'ui5-deploy.yaml',
|
||||||
|
'me',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -74,16 +74,61 @@ import static com.sap.piper.cm.StepHelpers.getBackendTypeAndLogInfoIfCMIntegrati
|
|||||||
* A pattern used for identifying lines holding the transport request id.
|
* A pattern used for identifying lines holding the transport request id.
|
||||||
* @parentConfigKey changeManagement
|
* @parentConfigKey changeManagement
|
||||||
*/
|
*/
|
||||||
'changeManagement/transportRequestLabel'
|
'transportRequestLabel',
|
||||||
|
/**
|
||||||
|
* Some CTS related transport related steps are cm_client based, others are node based.
|
||||||
|
* For the node based steps the docker image is specified here.
|
||||||
|
* @parentConfigKey changeManagement
|
||||||
|
*/
|
||||||
|
'cts/nodeDocker/image',
|
||||||
|
/**
|
||||||
|
* The ABAP client. Only for `CTS`
|
||||||
|
* @parentConfigKey changeManagement
|
||||||
|
*/
|
||||||
|
'client',
|
||||||
|
/**
|
||||||
|
* By default we use a standard node docker image and prepare some fiori related packages
|
||||||
|
* before performing the deployment. For that we need to launch the image with root privileges.
|
||||||
|
* After that, before actually performing the deployment we swith to a non root user. This user
|
||||||
|
* can be specified here.
|
||||||
|
* @parentConfigKey changeManagement
|
||||||
|
*/
|
||||||
|
'cts/osDeployUser',
|
||||||
|
/**
|
||||||
|
* By default we use a standard node docker iamge and prepare some fiori related packages
|
||||||
|
* performing the deployment. The additional dependencies can be provided here. In case you
|
||||||
|
* use an already prepared docker image which contains the required dependencies, the empty
|
||||||
|
* list can be provide here. Caused hereby installing additional dependencies will be skipped.
|
||||||
|
*
|
||||||
|
* @parentConfigKey changeManagement
|
||||||
|
*/
|
||||||
|
'cts/deployToolDependencies',
|
||||||
|
/**
|
||||||
|
* A list containing additional options for the npm install call. `-g`, `--global` is always assumed.
|
||||||
|
* Can be used for e.g. providing custom registries (`--registry https://your.registry.com`) or
|
||||||
|
* for providing the verbose flag (`--verbose`) for troubleshooting.
|
||||||
|
* @parentConfigKey changeManagement
|
||||||
|
*/
|
||||||
|
'cts/npmInstallOpts',
|
||||||
|
/**
|
||||||
|
* The file handed over to `fiori deploy` with flag `-c --config`.
|
||||||
|
* @parentConfigKey changeManagement
|
||||||
|
*/
|
||||||
|
'cts/deployConfigFile',
|
||||||
]
|
]
|
||||||
|
|
||||||
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
|
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus([
|
||||||
'applicationName', // RFC
|
/** The name of the application. `RFC` and `CTS` only. */
|
||||||
|
'applicationName', // RFC, CTS
|
||||||
/** The id of the application. Only for `SOLMAN`.*/
|
/** The id of the application. Only for `SOLMAN`.*/
|
||||||
'applicationId', // SOLMAN
|
'applicationId', // SOLMAN
|
||||||
|
/** The application description, `RFC` and `CTS` only. For `CTS`: the desription is only
|
||||||
|
taken into account for a new upload. In case of an update the description will not be
|
||||||
|
updated.
|
||||||
|
*/
|
||||||
'applicationDescription',
|
'applicationDescription',
|
||||||
/** The path of the file to upload.*/
|
/** The path of the file to upload, Only for `SOLMAN`.*/
|
||||||
'filePath', // SOLMAN, CTS
|
'filePath', // SOLMAN
|
||||||
/** The URL where to find the UI5 package to upload to the transport request. Only for `RFC`. */
|
/** The URL where to find the UI5 package to upload to the transport request. Only for `RFC`. */
|
||||||
'applicationUrl', // RFC
|
'applicationUrl', // RFC
|
||||||
/** The ABAP package name of your application. */
|
/** The ABAP package name of your application. */
|
||||||
@ -133,11 +178,12 @@ void call(Map parameters = [:]) {
|
|||||||
.withMandatoryProperty('changeManagement/clientOpts')
|
.withMandatoryProperty('changeManagement/clientOpts')
|
||||||
.withMandatoryProperty('changeManagement/credentialsId')
|
.withMandatoryProperty('changeManagement/credentialsId')
|
||||||
.withMandatoryProperty('changeManagement/endpoint')
|
.withMandatoryProperty('changeManagement/endpoint')
|
||||||
|
.withMandatoryProperty('changeManagement/client', null, {backendType == BackendType.CTS})
|
||||||
.withMandatoryProperty('changeManagement/type')
|
.withMandatoryProperty('changeManagement/type')
|
||||||
.withMandatoryProperty('changeManagement/git/from')
|
.withMandatoryProperty('changeManagement/git/from')
|
||||||
.withMandatoryProperty('changeManagement/git/to')
|
.withMandatoryProperty('changeManagement/git/to')
|
||||||
.withMandatoryProperty('changeManagement/git/format')
|
.withMandatoryProperty('changeManagement/git/format')
|
||||||
.withMandatoryProperty('filePath', null, { backendType in [BackendType.SOLMAN, BackendType.CTS] })
|
.withMandatoryProperty('filePath', null, { backendType == BackendType.SOLMAN })
|
||||||
.withMandatoryProperty('applicationUrl', null, { backendType == BackendType.RFC })
|
.withMandatoryProperty('applicationUrl', null, { backendType == BackendType.RFC })
|
||||||
.withMandatoryProperty('codePage', null, { backendType == BackendType.RFC })
|
.withMandatoryProperty('codePage', null, { backendType == BackendType.RFC })
|
||||||
.withMandatoryProperty('acceptUnixStyleLineEndings', null, { backendType == BackendType.RFC })
|
.withMandatoryProperty('acceptUnixStyleLineEndings', null, { backendType == BackendType.RFC })
|
||||||
@ -148,9 +194,9 @@ void call(Map parameters = [:]) {
|
|||||||
.withMandatoryProperty('changeManagement/rfc/docker/envVars', null, {backendType == BackendType.RFC})
|
.withMandatoryProperty('changeManagement/rfc/docker/envVars', null, {backendType == BackendType.RFC})
|
||||||
.withMandatoryProperty('changeManagement/rfc/docker/pullImage', null, {backendType == BackendType.RFC})
|
.withMandatoryProperty('changeManagement/rfc/docker/pullImage', null, {backendType == BackendType.RFC})
|
||||||
.withMandatoryProperty('applicationDescription', null, { backendType == BackendType.RFC })
|
.withMandatoryProperty('applicationDescription', null, { backendType == BackendType.RFC })
|
||||||
.withMandatoryProperty('abapPackage', null, { backendType == BackendType.RFC })
|
.withMandatoryProperty('abapPackage', null, { backendType in [BackendType.RFC, BackendType.CTS] })
|
||||||
.withMandatoryProperty('applicationId', null, {backendType == BackendType.SOLMAN})
|
.withMandatoryProperty('applicationId', null, {backendType == BackendType.SOLMAN})
|
||||||
.withMandatoryProperty('applicationName', null, {backendType == BackendType.RFC})
|
.withMandatoryProperty('applicationName', null, {backendType in [BackendType.RFC, BackendType.CTS]})
|
||||||
.withMandatoryProperty('failOnWarning', null, {backendType == BackendType.RFC})
|
.withMandatoryProperty('failOnWarning', null, {backendType == BackendType.RFC})
|
||||||
.withMandatoryProperty('verbose', null, {backendType == BackendType.RFC})
|
.withMandatoryProperty('verbose', null, {backendType == BackendType.RFC})
|
||||||
|
|
||||||
@ -184,20 +230,15 @@ void call(Map parameters = [:]) {
|
|||||||
"Transport request id not provided (parameter: \'transportRequestId\' provided to the step call or via commit history).")
|
"Transport request id not provided (parameter: \'transportRequestId\' provided to the step call or via commit history).")
|
||||||
.use()
|
.use()
|
||||||
|
|
||||||
def uploadingMessage = ['[INFO] Uploading file ' +
|
|
||||||
"'${backendType == BackendType.RFC ? configuration.applicationUrl : configuration.filePath}' " +
|
|
||||||
"to transport request '${configuration.transportRequestId}'"]
|
|
||||||
if(backendType == BackendType.SOLMAN)
|
|
||||||
uploadingMessage << " of change document '${configuration.changeDocumentId}'"
|
|
||||||
uploadingMessage << '.'
|
|
||||||
|
|
||||||
echo uploadingMessage.join()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
switch(backendType) {
|
switch(backendType) {
|
||||||
|
|
||||||
case BackendType.SOLMAN:
|
case BackendType.SOLMAN:
|
||||||
|
|
||||||
|
echo "[INFO] Uploading file '${configuration.filePath}' to transport request '${configuration.transportRequestId}'" +
|
||||||
|
" of change document '${configuration.changeDocumentId}'."
|
||||||
|
|
||||||
cm.uploadFileToTransportRequestSOLMAN(
|
cm.uploadFileToTransportRequestSOLMAN(
|
||||||
configuration.changeManagement.solman?.docker ?: [:],
|
configuration.changeManagement.solman?.docker ?: [:],
|
||||||
configuration.changeDocumentId,
|
configuration.changeDocumentId,
|
||||||
@ -207,18 +248,36 @@ void call(Map parameters = [:]) {
|
|||||||
configuration.changeManagement.endpoint,
|
configuration.changeManagement.endpoint,
|
||||||
configuration.changeManagement.credentialsId,
|
configuration.changeManagement.credentialsId,
|
||||||
configuration.changeManagement.clientOpts)
|
configuration.changeManagement.clientOpts)
|
||||||
|
|
||||||
|
echo "[INFO] File '${configuration.filePath}' has been successfully uploaded to transport request '${configuration.transportRequestId}'" +
|
||||||
|
" of change document '${configuration.changeDocumentId}'."
|
||||||
|
|
||||||
break
|
break
|
||||||
case BackendType.CTS:
|
case BackendType.CTS:
|
||||||
|
|
||||||
|
echo "[INFO] Uploading application '${configuration.applicationName}' to transport request '${configuration.transportRequestId}'."
|
||||||
|
|
||||||
cm.uploadFileToTransportRequestCTS(
|
cm.uploadFileToTransportRequestCTS(
|
||||||
configuration.changeManagement.cts?.docker ?: [:],
|
configuration.changeManagement.cts?.nodeDocker ?: [:],
|
||||||
configuration.transportRequestId,
|
configuration.transportRequestId,
|
||||||
configuration.filePath,
|
|
||||||
configuration.changeManagement.endpoint,
|
configuration.changeManagement.endpoint,
|
||||||
configuration.changeManagement.credentialsId,
|
configuration.changeManagement.client,
|
||||||
configuration.changeManagement.clientOpts)
|
configuration.applicationName,
|
||||||
|
configuration.applicationDescription,
|
||||||
|
configuration.abapPackage,
|
||||||
|
configuration.changeManagement.cts.osDeployUser,
|
||||||
|
configuration.changeManagement.cts.deployToolDependencies,
|
||||||
|
configuration.changeManagement.cts.npmInstallOpts,
|
||||||
|
configuration.changeManagement.cts.deployConfigFile,
|
||||||
|
configuration.changeManagement.credentialsId)
|
||||||
|
|
||||||
|
echo "[INFO] Application '${configuration.applicationName}' has been successfully uploaded to transport request '${configuration.transportRequestId}'."
|
||||||
|
|
||||||
break
|
break
|
||||||
case BackendType.RFC:
|
case BackendType.RFC:
|
||||||
|
|
||||||
|
echo "[INFO] Uploading file '${configuration.applicationUrl}' to transport request '${configuration.transportRequestId}'."
|
||||||
|
|
||||||
cm.uploadFileToTransportRequestRFC(
|
cm.uploadFileToTransportRequestRFC(
|
||||||
configuration.changeManagement.rfc.docker ?: [:],
|
configuration.changeManagement.rfc.docker ?: [:],
|
||||||
configuration.transportRequestId,
|
configuration.transportRequestId,
|
||||||
@ -236,19 +295,13 @@ void call(Map parameters = [:]) {
|
|||||||
configuration.verbose
|
configuration.verbose
|
||||||
)
|
)
|
||||||
|
|
||||||
break
|
echo "[INFO] File 'configuration.applicationUrl' has been successfully uploaded to transport request '${configuration.transportRequestId}'."
|
||||||
|
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch(ChangeManagementException ex) {
|
} catch(ChangeManagementException ex) {
|
||||||
throw new AbortException(ex.getMessage())
|
throw new AbortException(ex.getMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def uploadedMessage = ["[INFO] File '${backendType == BackendType.RFC ? configuration.applicationUrl : configuration.filePath}' has been successfully uploaded to transport request '${configuration.transportRequestId}'"]
|
|
||||||
if(backendType == BackendType.SOLMAN)
|
|
||||||
uploadedMessage << " of change document '${configuration.changeDocumentId}'"
|
|
||||||
uploadedMessage << '.'
|
|
||||||
echo uploadedMessage.join()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user