mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-03-03 15:02:35 +02:00
fiori cts upload (#2390)
fiori cts upload Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>
This commit is contained in:
parent
a84692ea97
commit
cdb784aaf4
235
pkg/transportrequest/uploadcts.go
Normal file
235
pkg/transportrequest/uploadcts.go
Normal 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
|
||||
}
|
161
pkg/transportrequest/uploadcts_test.go
Normal file
161
pkg/transportrequest/uploadcts_test.go
Normal 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")
|
||||
})
|
||||
})
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user