1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-03-05 15:15:44 +02:00

fiori cts upload (#2390)

fiori cts upload


Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>
This commit is contained in:
Marcus Holl 2020-12-21 09:05:49 +01:00 committed by GitHub
parent a84692ea97
commit cdb784aaf4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 396 additions and 0 deletions

View File

@ -0,0 +1,235 @@
package transportrequest
import (
"fmt"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"strings"
)
type fileUtils interface {
FileExists(string) (bool, error)
}
var files fileUtils = piperutils.Files{}
// CTSConnection Everything wee need for connecting to CTS
type CTSConnection struct {
// The endpoint in for form <protocol>://<host>:<port>, no path
Endpoint string
// The ABAP client, like e.g. "001"
Client string
User string
Password string
}
// CTSApplication The details of the application
type CTSApplication struct {
// Name of the application
Name string
// The ABAP package
Pack string
// A description. Only taken into account for initial upload, not
// in case of a re-deployment.
Desc string
}
// CTSNode The details for configuring the node image
type CTSNode struct {
// The dependencies which are installed on a basic node image in order
// to enable it for fiori deployment. If left empty we assume the
// provided base image has already everything installed.
DeployDependencies []string
// Additional options for the npm install command. Useful e.g.
// for providing additional registries or for triggering verbose mode
InstallOpts []string
}
// CTSUploadAction Collects all the properties we need for the deployment
type CTSUploadAction struct {
Connection CTSConnection
Application CTSApplication
Node CTSNode
TransportRequestID string
ConfigFile string
DeployUser string
}
const (
abapUserKey = "ABAP_USER"
abapPasswordKey = "ABAP_PASSWORD"
defaultConfigFileName = "ui5-deploy.yaml"
)
// WithConnection ...
func (action *CTSUploadAction) WithConnection(connection CTSConnection) {
action.Connection = connection
}
// WithApplication ...
func (action *CTSUploadAction) WithApplication(app CTSApplication) {
action.Application = app
}
// WithNodeProperties ...
func (action *CTSUploadAction) WithNodeProperties(node CTSNode) {
action.Node = node
}
// WithTransportRequestID ...
func (action *CTSUploadAction) WithTransportRequestID(id string) {
action.TransportRequestID = id
}
// WithConfigFile ...
func (action *CTSUploadAction) WithConfigFile(configFile string) {
action.ConfigFile = configFile
}
// WithDeployUser ...
func (action *CTSUploadAction) WithDeployUser(deployUser string) {
action.DeployUser = deployUser
}
// Perform Performs the upload
func (action *CTSUploadAction) Perform(command command.ShellRunner) error {
command.AppendEnv(
[]string{
fmt.Sprintf("%s=%s", abapUserKey, action.Connection.User),
fmt.Sprintf("%s=%s", abapPasswordKey, action.Connection.Password),
})
cmd := []string{"#!/bin/bash -e"}
noInstall := len(action.Node.DeployDependencies) == 0
if !noInstall {
cmd = append(cmd, "echo \"Current user is '$(whoami)'\"")
cmd = append(cmd, getPrepareFioriEnvironmentStatement(action.Node.DeployDependencies, action.Node.InstallOpts))
cmd = append(cmd, getSwitchUserStatement(action.DeployUser))
} else {
log.Entry().Info("No deploy dependencies provided. Skipping npm install call. Assuming current docker image already contains the dependencies for performing the deployment.")
}
deployStatement, err := getFioriDeployStatement(action.TransportRequestID, action.ConfigFile, action.Application, action.Connection)
if err != nil {
return err
}
cmd = append(cmd, deployStatement)
return command.RunShell("/bin/bash", strings.Join(cmd, "\n"))
}
func getPrepareFioriEnvironmentStatement(deps []string, npmInstallOpts []string) string {
cmd := []string{
"npm",
"install",
"--global",
}
cmd = append(cmd, npmInstallOpts...)
cmd = append(cmd, deps...)
return strings.Join(cmd, " ")
}
func getFioriDeployStatement(
transportRequestID string,
configFile string,
app CTSApplication,
cts CTSConnection,
) (string, error) {
desc := app.Desc
if len(desc) == 0 {
desc = "Deployed with Piper based on SAP Fiori tools"
}
useConfigFileOptionInCommandInvocation, useNoConfigFileOptionInCommandInvocation, err := handleConfigFileOptions(configFile)
if err != nil {
return "", err
}
cmd := []string{
"fiori",
"deploy",
"--failfast", // provide return code != 0 in case of any failure
"--yes", // autoconfirm --> no need to press 'y' key in order to confirm the params and trigger the deployment
"--username", abapUserKey,
"--password", abapPasswordKey,
"--description", fmt.Sprintf("\"%s\"", desc),
}
if useNoConfigFileOptionInCommandInvocation {
cmd = append(cmd, "--noConfig") // no config file, but we will provide our parameters
}
if useConfigFileOptionInCommandInvocation {
cmd = append(cmd, "--config", fmt.Sprintf("\"%s\"", configFile))
}
if len(cts.Endpoint) > 0 {
log.Entry().Debugf("Endpoint '%s' used from piper config", cts.Endpoint)
cmd = append(cmd, "--url", cts.Endpoint)
} else {
log.Entry().Debug("No endpoint found in piper config.")
}
if len(cts.Client) > 0 {
log.Entry().Debugf("Client '%s' used from piper config", cts.Client)
cmd = append(cmd, "--client", cts.Client)
} else {
log.Entry().Debug("No client found in piper config.")
}
if len(transportRequestID) > 0 {
log.Entry().Debugf("TransportRequestID '%s' used from piper config", transportRequestID)
cmd = append(cmd, "--transport", transportRequestID)
} else {
log.Entry().Debug("No transportRequestID found in piper config.")
}
if len(app.Pack) > 0 {
log.Entry().Debugf("application package '%s' used from piper config", app.Pack)
cmd = append(cmd, "--package", app.Pack)
} else {
log.Entry().Debug("No application package found in piper config.")
}
if len(app.Name) > 0 {
log.Entry().Debugf("application name '%s' used from piper config", app.Name)
cmd = append(cmd, "--name", app.Name)
} else {
log.Entry().Debug("No application name found in piper config.")
}
return strings.Join(cmd, " "), nil
}
func getSwitchUserStatement(user string) string {
return fmt.Sprintf("su %s", user)
}
func handleConfigFileOptions(path string) (useConfigFileOptionInCommandInvocation, useNoConfigFileOptionInCommandInvoction bool, err error) {
exists := false
if len(path) == 0 {
exists, err = files.FileExists(defaultConfigFileName)
if err != nil {
return
}
useConfigFileOptionInCommandInvocation = false
useNoConfigFileOptionInCommandInvoction = !exists
return
}
exists, err = files.FileExists(path)
if err != nil {
return
}
if exists {
useConfigFileOptionInCommandInvocation = true
useNoConfigFileOptionInCommandInvoction = false
} else {
if path != defaultConfigFileName {
err = fmt.Errorf("Configured deploy config file '%s' does not exists", path)
return
}
// 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
useConfigFileOptionInCommandInvocation = false
useNoConfigFileOptionInCommandInvoction = true
}
return
}

View File

@ -0,0 +1,161 @@
package transportrequest
import (
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/stretchr/testify/assert"
"testing"
)
func TestUploadCTS(t *testing.T) {
fMock := &mock.FilesMock{}
files = fMock
defer func() { files = piperutils.Files{} }()
t.Run("npm install command tests", func(t *testing.T) {
cmd := mock.ShellMockRunner{}
action := CTSUploadAction{
Connection: CTSConnection{Endpoint: "", Client: "", User: "me", Password: "******"},
Application: CTSApplication{Pack: "", Name: "", Desc: ""},
Node: CTSNode{
DeployDependencies: []string{"@sap/my-dep"},
InstallOpts: []string{"--verbose", "--registry", "https://registry.example.org"},
},
TransportRequestID: "12345678",
ConfigFile: "ui5-deploy.yaml",
DeployUser: "node",
}
err := action.Perform(&cmd)
if assert.NoError(t, err) {
assert.Regexp(
t,
"(?m)^npm install --global --verbose --registry https://registry.example.org @sap/my-dep$",
cmd.Calls[0],
"Expected npm install command not found",
)
assert.Regexp(
t,
"(?m)^su node$",
cmd.Calls[0],
"Expected switch user statement not found",
)
}
})
t.Run("deploy command tests", func(t *testing.T) {
t.Run("all possible values provided", func(t *testing.T) {
cmd := mock.ShellMockRunner{}
action := CTSUploadAction{
Connection: CTSConnection{Endpoint: "https://example.org:8080/cts", Client: "001", User: "me", Password: "******"},
Application: CTSApplication{Pack: "abapPackage", Name: "appName", Desc: "the Desc"},
Node: CTSNode{
DeployDependencies: []string{},
InstallOpts: []string{},
},
TransportRequestID: "12345678",
ConfigFile: "ui5-deploy.yaml",
DeployUser: "doesNotMatterInThisCase",
}
err := action.Perform(&cmd)
if assert.NoError(t, err) {
assert.Regexp(
t,
"(?m)^fiori deploy --failfast --yes --username ABAP_USER --password ABAP_PASSWORD --description \"the Desc\" --noConfig --url https://example.org:8080/cts --client 001 --transport 12345678 --package abapPackage --name appName$",
cmd.Calls[0],
"Expected fiori deploy command not found",
)
assert.Equal(t, []string{"ABAP_USER=me", "ABAP_PASSWORD=******"}, cmd.Env)
}
})
t.Run("all possible values omitted", func(t *testing.T) {
// In this case the values are expected inside the fiori deploy config file
cmd := mock.ShellMockRunner{}
action := CTSUploadAction{
Connection: CTSConnection{Endpoint: "", Client: "", User: "me", Password: "******"},
Application: CTSApplication{Pack: "", Name: "", Desc: ""},
Node: CTSNode{
DeployDependencies: []string{},
InstallOpts: []string{},
},
TransportRequestID: "12345678",
ConfigFile: "ui5-deploy.yaml",
DeployUser: "doesNotMatterInThisCase",
}
err := action.Perform(&cmd)
if assert.NoError(t, err) {
assert.Regexp(
t,
"(?m)^fiori deploy --failfast --yes --username ABAP_USER --password ABAP_PASSWORD --description \"Deployed with Piper based on SAP Fiori tools\" --noConfig --transport 12345678$",
cmd.Calls[0],
"Expected fiori deploy command not found",
)
assert.Equal(t, []string{"ABAP_USER=me", "ABAP_PASSWORD=******"}, cmd.Env)
}
})
})
t.Run("config file releated tests", func(t *testing.T) {
connection := CTSConnection{Endpoint: "", Client: "", User: "me", Password: "******"}
app := CTSApplication{Pack: "", Name: "", Desc: ""}
node := CTSNode{
DeployDependencies: []string{},
InstallOpts: []string{},
}
t.Run("default config file exists", func(t *testing.T) {
filesMock := mock.FilesMock{}
filesMock.AddFile("ui5-deploy.yaml", []byte{})
files = &filesMock
defer func() { files = fMock }()
cmd := mock.ShellMockRunner{}
action := CTSUploadAction{
Connection: connection,
Application: app,
Node: node,
TransportRequestID: "12345678",
ConfigFile: "ui5-deploy.yaml",
DeployUser: "doesNotMatterInThisCase",
}
err := action.Perform(&cmd)
if assert.NoError(t, err) {
assert.Contains(t, cmd.Calls[0], " --config \"ui5-deploy.yaml\" ")
}
})
t.Run("Config file exists", func(t *testing.T) {
filesMock := mock.FilesMock{}
filesMock.AddFile("my-ui5-deploy.yaml", []byte{})
files = &filesMock
defer func() { files = fMock }()
cmd := mock.ShellMockRunner{}
action := CTSUploadAction{
Connection: connection,
Application: app,
Node: node,
TransportRequestID: "12345678",
ConfigFile: "my-ui5-deploy.yaml",
DeployUser: "doesNotMatterInThisCase",
}
err := action.Perform(&cmd)
if assert.NoError(t, err) {
assert.Contains(t, cmd.Calls[0], " --config \"my-ui5-deploy.yaml\" ")
}
})
t.Run("Config file missing", func(t *testing.T) {
cmd := mock.ShellMockRunner{}
action := CTSUploadAction{
Connection: connection,
Application: app,
Node: node,
TransportRequestID: "12345678",
ConfigFile: "my-ui5-deploy.yaml",
DeployUser: "doesNotMatterInThisCase",
}
err := action.Perform(&cmd)
assert.EqualError(t, err, "Configured deploy config file 'my-ui5-deploy.yaml' does not exists")
})
})
}