2021-01-18 14:20:30 +02:00
package cts
2020-12-21 10:05:49 +02:00
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 { }
2021-01-18 14:20:30 +02:00
// Connection Everything wee need for connecting to CTS
type Connection struct {
2020-12-21 10:05:49 +02:00
// 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
}
2021-01-18 14:20:30 +02:00
// Application The details of the application
type Application struct {
2020-12-21 10:05:49 +02:00
// 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
}
2021-01-18 14:20:30 +02:00
// Node The details for configuring the node image
type Node struct {
2020-12-21 10:05:49 +02:00
// 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
}
2021-01-18 14:20:30 +02:00
// UploadAction Collects all the properties we need for the deployment
type UploadAction struct {
Connection Connection
Application Application
Node Node
2020-12-21 10:05:49 +02:00
TransportRequestID string
ConfigFile string
DeployUser string
}
const (
abapUserKey = "ABAP_USER"
abapPasswordKey = "ABAP_PASSWORD"
defaultConfigFileName = "ui5-deploy.yaml"
)
// WithConnection ...
2021-01-18 14:20:30 +02:00
func ( action * UploadAction ) WithConnection ( connection Connection ) {
2020-12-21 10:05:49 +02:00
action . Connection = connection
}
// WithApplication ...
2021-01-18 14:20:30 +02:00
func ( action * UploadAction ) WithApplication ( app Application ) {
2020-12-21 10:05:49 +02:00
action . Application = app
}
// WithNodeProperties ...
2021-01-18 14:20:30 +02:00
func ( action * UploadAction ) WithNodeProperties ( node Node ) {
2020-12-21 10:05:49 +02:00
action . Node = node
}
// WithTransportRequestID ...
2021-01-18 14:20:30 +02:00
func ( action * UploadAction ) WithTransportRequestID ( id string ) {
2020-12-21 10:05:49 +02:00
action . TransportRequestID = id
}
// WithConfigFile ...
2021-01-18 14:20:30 +02:00
func ( action * UploadAction ) WithConfigFile ( configFile string ) {
2020-12-21 10:05:49 +02:00
action . ConfigFile = configFile
}
// WithDeployUser ...
2021-01-18 14:20:30 +02:00
func ( action * UploadAction ) WithDeployUser ( deployUser string ) {
2020-12-21 10:05:49 +02:00
action . DeployUser = deployUser
}
// Perform Performs the upload
2021-01-18 14:20:30 +02:00
func ( action * UploadAction ) Perform ( command command . ShellRunner ) error {
2020-12-21 10:05:49 +02:00
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 ,
2021-01-18 14:20:30 +02:00
app Application ,
cts Connection ,
2020-12-21 10:05:49 +02:00
) ( 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
}