2020-07-31 12:38:00 +02:00
package cmd
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
2024-12-12 17:32:12 +02:00
"slices"
2021-01-12 09:39:04 +01:00
"sort"
2020-07-31 12:38:00 +02:00
"strings"
"time"
2022-02-09 09:33:12 +01:00
"github.com/SAP/jenkins-library/pkg/cloudfoundry"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/yaml"
"github.com/pkg/errors"
2020-07-31 12:38:00 +02:00
)
type cfFileUtil interface {
FileExists ( string ) ( bool , error )
2021-01-12 09:39:04 +01:00
FileRename ( string , string ) error
FileRead ( string ) ( [ ] byte , error )
2020-07-31 12:38:00 +02:00
FileWrite ( path string , content [ ] byte , perm os . FileMode ) error
Getwd ( ) ( string , error )
Glob ( string ) ( [ ] string , error )
Chmod ( string , os . FileMode ) error
2021-01-12 09:39:04 +01:00
Copy ( string , string ) ( int64 , error )
Stat ( path string ) ( os . FileInfo , error )
2020-07-31 12:38:00 +02:00
}
var _now = time . Now
var _cfLogin = cfLogin
var _cfLogout = cfLogout
var _getManifest = getManifest
var _replaceVariables = yaml . Substitute
2020-09-24 09:39:18 +02:00
var _getVarsOptions = cloudfoundry . GetVarsOptions
var _getVarsFileOptions = cloudfoundry . GetVarsFileOptions
2021-01-12 09:39:04 +01:00
var _environ = os . Environ
2020-07-31 12:38:00 +02:00
var fileUtils cfFileUtil = piperutils . Files { }
// for simplify mocking. Maybe we find a more elegant way (mock for CFUtils)
func cfLogin ( c command . ExecRunner , options cloudfoundry . LoginOptions ) error {
cf := & cloudfoundry . CFUtils { Exec : c }
return cf . Login ( options )
}
// for simplify mocking. Maybe we find a more elegant way (mock for CFUtils)
func cfLogout ( c command . ExecRunner ) error {
cf := & cloudfoundry . CFUtils { Exec : c }
return cf . Logout ( )
}
func cloudFoundryDeploy ( config cloudFoundryDeployOptions , telemetryData * telemetry . CustomData , influxData * cloudFoundryDeployInflux ) {
// for command execution use Command
c := command . Command { }
// reroute command output to logging framework
c . Stdout ( log . Writer ( ) )
c . Stderr ( log . Writer ( ) )
// for http calls import piperhttp "github.com/SAP/jenkins-library/pkg/http"
// and use a &piperhttp.Client{} in a custom system
// Example: step checkmarxExecuteScan.go
// error situations should stop execution through log.Entry().Fatal() call which leads to an os.Exit(1) in the end
err := runCloudFoundryDeploy ( & config , telemetryData , influxData , & c )
if err != nil {
log . Entry ( ) . WithError ( err ) . Fatalf ( "step execution failed: %s" , err )
}
}
func runCloudFoundryDeploy ( config * cloudFoundryDeployOptions , telemetryData * telemetry . CustomData , influxData * cloudFoundryDeployInflux , command command . ExecRunner ) error {
log . Entry ( ) . Infof ( "General parameters: deployTool='%s', deployType='%s', cfApiEndpoint='%s', cfOrg='%s', cfSpace='%s'" ,
config . DeployTool , config . DeployType , config . APIEndpoint , config . Org , config . Space )
err := validateAppName ( config . AppName )
if err != nil {
return err
}
2020-08-24 18:10:45 +02:00
validateDeployTool ( config )
2020-07-31 12:38:00 +02:00
var deployTriggered bool
if config . DeployTool == "mtaDeployPlugin" {
deployTriggered = true
err = handleMTADeployment ( config , command )
} else if config . DeployTool == "cf_native" {
deployTriggered = true
err = handleCFNativeDeployment ( config , command )
} else {
log . Entry ( ) . Warningf ( "Found unsupported deployTool ('%s'). Skipping deployment. Supported deploy tools: 'mtaDeployPlugin', 'cf_native'" , config . DeployTool )
}
if deployTriggered {
prepareInflux ( err == nil , config , influxData )
}
return err
}
2020-08-24 18:10:45 +02:00
func validateDeployTool ( config * cloudFoundryDeployOptions ) {
if config . DeployTool != "" || config . BuildTool == "" {
return
}
switch config . BuildTool {
case "mta" :
config . DeployTool = "mtaDeployPlugin"
default :
config . DeployTool = "cf_native"
}
log . Entry ( ) . Infof ( "Parameter deployTool not specified - deriving from buildTool '%s': '%s'" ,
config . BuildTool , config . DeployTool )
}
2020-07-31 12:38:00 +02:00
func validateAppName ( appName string ) error {
// for the sake of brevity we consider the empty string as valid app name here
isValidAppName , err := regexp . MatchString ( "^$|^[a-zA-Z0-9]$|^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$" , appName )
if err != nil {
return err
}
if isValidAppName {
return nil
}
const (
underscore = "_"
dash = "-"
docuLink = "https://docs.cloudfoundry.org/devguide/deploy-apps/deploy-app.html#basic-settings"
)
log . Entry ( ) . Warningf ( "Your application name '%s' contains non-alphanumeric characters which may lead to errors in the future, " +
"as they are not supported by CloudFoundry. For more details please visit %s" , appName , docuLink )
var fail bool
message := [ ] string { fmt . Sprintf ( "Your application name '%s'" , appName ) }
if strings . Contains ( appName , underscore ) {
message = append ( message , fmt . Sprintf ( "contains a '%s' (underscore) which is not allowed, only letters, dashes and numbers can be used." , underscore ) )
fail = true
}
if strings . HasPrefix ( appName , dash ) || strings . HasSuffix ( appName , dash ) {
message = append ( message , fmt . Sprintf ( "starts or ends with a '%s' (dash) which is not allowed, only letters and numbers can be used." , dash ) )
fail = true
}
message = append ( message , fmt . Sprintf ( "Please change the name to fit this requirement(s). For more details please visit %s." , docuLink ) )
if fail {
2024-08-15 10:20:01 +02:00
return errors . New ( strings . Join ( message , " " ) )
2020-07-31 12:38:00 +02:00
}
return nil
}
func prepareInflux ( success bool , config * cloudFoundryDeployOptions , influxData * cloudFoundryDeployInflux ) {
if influxData == nil {
return
}
result := "FAILURE"
if success {
result = "SUCCESS"
}
influxData . deployment_data . tags . artifactVersion = config . ArtifactVersion
influxData . deployment_data . tags . deployUser = config . Username
influxData . deployment_data . tags . deployResult = result
influxData . deployment_data . tags . cfAPIEndpoint = config . APIEndpoint
influxData . deployment_data . tags . cfOrg = config . Org
influxData . deployment_data . tags . cfSpace = config . Space
// n/a (literally) is also reported in groovy
influxData . deployment_data . fields . artifactURL = "n/a"
2021-08-05 17:03:51 +02:00
influxData . deployment_data . fields . commitHash = config . CommitHash
2020-07-31 12:38:00 +02:00
influxData . deployment_data . fields . deployTime = strings . ToUpper ( _now ( ) . Format ( "Jan 02 2006 15:04:05" ) )
// we should discuss how we handle the job trigger
// 1.) outside Jenkins
// 2.) inside Jenkins (how to get)
influxData . deployment_data . fields . jobTrigger = "n/a"
}
func handleMTADeployment ( config * cloudFoundryDeployOptions , command command . ExecRunner ) error {
mtarFilePath := config . MtaPath
if len ( mtarFilePath ) == 0 {
var err error
mtarFilePath , err = findMtar ( )
if err != nil {
return err
}
log . Entry ( ) . Debugf ( "Using mtar file '%s' found in workspace" , mtarFilePath )
} else {
exists , err := fileUtils . FileExists ( mtarFilePath )
if err != nil {
2020-08-06 17:20:26 +02:00
return errors . Wrapf ( err , "Cannot check if file path '%s' exists" , mtarFilePath )
2020-07-31 12:38:00 +02:00
}
if ! exists {
return fmt . Errorf ( "mtar file '%s' retrieved from configuration does not exist" , mtarFilePath )
}
log . Entry ( ) . Debugf ( "Using mtar file '%s' from configuration" , mtarFilePath )
}
return deployMta ( config , mtarFilePath , command )
}
type deployConfig struct {
2024-07-04 11:13:36 +02:00
DeployCommand string
DeployOptions [ ] string
AppName string
ManifestFile string
2020-07-31 12:38:00 +02:00
}
func handleCFNativeDeployment ( config * cloudFoundryDeployOptions , command command . ExecRunner ) error {
var deployCommand string
var deployOptions [ ] string
2024-07-04 11:13:36 +02:00
var err error
2020-07-31 12:38:00 +02:00
// deploy command will be provided by the prepare functions below
2024-07-04 11:13:36 +02:00
if config . DeployType == "blue-green" {
return fmt . Errorf ( "Blue-green deployment type is deprecated for cf native builds." +
2023-10-30 12:40:01 +01:00
"Instead set parameter `cfNativeDeployParameters: '--strategy rolling'`. " +
"Please refer to the Cloud Foundry documentation for further information: " +
2024-03-15 10:32:28 +01:00
"https://docs.cloudfoundry.org/devguide/deploy-apps/rolling-deploy.html." +
"Or alternatively, switch to mta build tool. Please refer to mta build tool" +
"documentation for further information: https://sap.github.io/cloud-mta-build-tool/configuration/." )
2024-07-04 11:13:36 +02:00
} else if config . DeployType == "standard" {
deployCommand , deployOptions , err = prepareCfPushCfNativeDeploy ( config )
2020-07-31 12:38:00 +02:00
if err != nil {
2024-07-04 11:13:36 +02:00
return errors . Wrapf ( err , "Cannot prepare cf push native deployment. DeployType '%s'" , config . DeployType )
2020-07-31 12:38:00 +02:00
}
} else {
2024-07-04 11:13:36 +02:00
return fmt . Errorf ( "Invalid deploy type received: '%s'. Supported value: standard" , config . DeployType )
2020-07-31 12:38:00 +02:00
}
appName , err := getAppName ( config )
if err != nil {
return err
}
2022-10-11 14:12:35 +02:00
manifestFile , err := getManifestFileName ( config )
2020-07-31 12:38:00 +02:00
log . Entry ( ) . Infof ( "CF native deployment ('%s') with:" , config . DeployType )
log . Entry ( ) . Infof ( "cfAppName='%s'" , appName )
2022-10-11 14:12:35 +02:00
log . Entry ( ) . Infof ( "cfManifest='%s'" , manifestFile )
2020-07-31 12:38:00 +02:00
log . Entry ( ) . Infof ( "cfManifestVariables: '%v'" , config . ManifestVariables )
log . Entry ( ) . Infof ( "cfManifestVariablesFiles: '%v'" , config . ManifestVariablesFiles )
log . Entry ( ) . Infof ( "cfdeployDockerImage: '%s'" , config . DeployDockerImage )
2024-07-04 11:13:36 +02:00
var additionalEnvironment [ ] string
2020-07-31 12:38:00 +02:00
if len ( config . DockerPassword ) > 0 {
2024-07-04 11:13:36 +02:00
additionalEnvironment = [ ] string { ( "CF_DOCKER_PASSWORD=" + config . DockerPassword ) }
2020-07-31 12:38:00 +02:00
}
myDeployConfig := deployConfig {
2024-07-04 11:13:36 +02:00
DeployCommand : deployCommand ,
DeployOptions : deployOptions ,
AppName : config . AppName ,
ManifestFile : config . Manifest ,
2020-07-31 12:38:00 +02:00
}
log . Entry ( ) . Infof ( "DeployConfig: %v" , myDeployConfig )
return deployCfNative ( myDeployConfig , config , additionalEnvironment , command )
}
func deployCfNative ( deployConfig deployConfig , config * cloudFoundryDeployOptions , additionalEnvironment [ ] string , cmd command . ExecRunner ) error {
deployStatement := [ ] string {
deployConfig . DeployCommand ,
}
if len ( deployConfig . AppName ) > 0 {
deployStatement = append ( deployStatement , deployConfig . AppName )
}
if len ( deployConfig . DeployOptions ) > 0 {
deployStatement = append ( deployStatement , deployConfig . DeployOptions ... )
}
if len ( deployConfig . ManifestFile ) > 0 {
deployStatement = append ( deployStatement , "-f" )
deployStatement = append ( deployStatement , deployConfig . ManifestFile )
}
2024-07-04 11:13:36 +02:00
if len ( config . DeployDockerImage ) > 0 {
2020-07-31 12:38:00 +02:00
deployStatement = append ( deployStatement , "--docker-image" , config . DeployDockerImage )
}
2024-07-04 11:13:36 +02:00
if len ( config . DockerUsername ) > 0 {
2020-07-31 12:38:00 +02:00
deployStatement = append ( deployStatement , "--docker-username" , config . DockerUsername )
}
if len ( config . CfNativeDeployParameters ) > 0 {
deployStatement = append ( deployStatement , strings . Fields ( config . CfNativeDeployParameters ) ... )
}
2024-07-04 11:13:36 +02:00
return cfDeploy ( config , deployStatement , additionalEnvironment , cmd )
2020-07-31 12:38:00 +02:00
}
func getManifest ( name string ) ( cloudfoundry . Manifest , error ) {
return cloudfoundry . ReadManifest ( name )
}
2022-10-11 14:12:35 +02:00
func getManifestFileName ( config * cloudFoundryDeployOptions ) ( string , error ) {
manifestFileName := config . Manifest
if len ( manifestFileName ) == 0 {
manifestFileName = "manifest.yml"
}
return manifestFileName , nil
}
2020-07-31 12:38:00 +02:00
func getAppName ( config * cloudFoundryDeployOptions ) ( string , error ) {
if len ( config . AppName ) > 0 {
return config . AppName , nil
}
2024-07-04 11:13:36 +02:00
2022-10-11 14:12:35 +02:00
manifestFile , err := getManifestFileName ( config )
fileExists , err := fileUtils . FileExists ( manifestFile )
2020-07-31 12:38:00 +02:00
if err != nil {
2022-10-11 14:12:35 +02:00
return "" , errors . Wrapf ( err , "Cannot check if file '%s' exists" , manifestFile )
2020-07-31 12:38:00 +02:00
}
if ! fileExists {
2022-10-11 14:12:35 +02:00
return "" , fmt . Errorf ( "Manifest file '%s' not found. Cannot retrieve app name" , manifestFile )
2020-07-31 12:38:00 +02:00
}
2022-10-11 14:12:35 +02:00
manifest , err := _getManifest ( manifestFile )
2020-07-31 12:38:00 +02:00
if err != nil {
return "" , err
}
apps , err := manifest . GetApplications ( )
if err != nil {
return "" , err
}
if len ( apps ) == 0 {
2022-10-11 14:12:35 +02:00
return "" , fmt . Errorf ( "No apps declared in manifest '%s'" , manifestFile )
2020-07-31 12:38:00 +02:00
}
namePropertyExists , err := manifest . ApplicationHasProperty ( 0 , "name" )
if err != nil {
return "" , err
}
if ! namePropertyExists {
2022-10-11 14:12:35 +02:00
return "" , fmt . Errorf ( "No appName available in manifest '%s'" , manifestFile )
2020-07-31 12:38:00 +02:00
}
appName , err := manifest . GetApplicationProperty ( 0 , "name" )
if err != nil {
return "" , err
}
var name string
var ok bool
if name , ok = appName . ( string ) ; ! ok {
2022-10-11 14:12:35 +02:00
return "" , fmt . Errorf ( "appName from manifest '%s' has wrong type" , manifestFile )
2020-07-31 12:38:00 +02:00
}
if len ( name ) == 0 {
2022-10-11 14:12:35 +02:00
return "" , fmt . Errorf ( "appName from manifest '%s' is empty" , manifestFile )
2020-07-31 12:38:00 +02:00
}
return name , nil
}
2024-07-04 11:13:36 +02:00
func prepareCfPushCfNativeDeploy ( config * cloudFoundryDeployOptions ) ( string , [ ] string , error ) {
2020-07-31 12:38:00 +02:00
deployOptions := [ ] string { }
2020-09-24 09:39:18 +02:00
varOptions , err := _getVarsOptions ( config . ManifestVariables )
2020-07-31 12:38:00 +02:00
if err != nil {
2024-07-04 11:13:36 +02:00
return "" , [ ] string { } , errors . Wrapf ( err , "Cannot prepare var-options: '%v'" , config . ManifestVariables )
2020-07-31 12:38:00 +02:00
}
2020-09-24 09:39:18 +02:00
varFileOptions , err := _getVarsFileOptions ( config . ManifestVariablesFiles )
2020-07-31 12:38:00 +02:00
if err != nil {
2020-09-24 09:39:18 +02:00
if e , ok := err . ( * cloudfoundry . VarsFilesNotFoundError ) ; ok {
for _ , missingVarFile := range e . MissingFiles {
log . Entry ( ) . Warningf ( "We skip adding not-existing file '%s' as a vars-file to the cf create-service-push call" , missingVarFile )
}
} else {
2024-07-04 11:13:36 +02:00
return "" , [ ] string { } , errors . Wrapf ( err , "Cannot prepare var-file-options: '%v'" , config . ManifestVariablesFiles )
2020-09-24 09:39:18 +02:00
}
2020-07-31 12:38:00 +02:00
}
deployOptions = append ( deployOptions , varOptions ... )
deployOptions = append ( deployOptions , varFileOptions ... )
2024-07-04 11:13:36 +02:00
return "push" , deployOptions , nil
2020-07-31 12:38:00 +02:00
}
func deployMta ( config * cloudFoundryDeployOptions , mtarFilePath string , command command . ExecRunner ) error {
deployCommand := "deploy"
deployParams := [ ] string { }
if len ( config . MtaDeployParameters ) > 0 {
deployParams = append ( deployParams , strings . Split ( config . MtaDeployParameters , " " ) ... )
}
if config . DeployType == "bg-deploy" || config . DeployType == "blue-green" {
deployCommand = "bg-deploy"
const noConfirmFlag = "--no-confirm"
2024-12-12 17:32:12 +02:00
if ! slices . Contains ( deployParams , noConfirmFlag ) {
2020-07-31 12:38:00 +02:00
deployParams = append ( deployParams , noConfirmFlag )
}
}
cfDeployParams := [ ] string {
deployCommand ,
mtarFilePath ,
}
if len ( deployParams ) > 0 {
cfDeployParams = append ( cfDeployParams , deployParams ... )
}
2021-01-12 09:39:04 +01:00
extFileParams , extFiles := handleMtaExtensionDescriptors ( config . MtaExtensionDescriptor )
2020-07-31 12:38:00 +02:00
2021-01-12 09:39:04 +01:00
for _ , extFile := range extFiles {
_ , err := fileUtils . Copy ( extFile , extFile + ".original" )
if err != nil {
return fmt . Errorf ( "Cannot prepare mta extension files: %w" , err )
}
2022-02-09 09:33:12 +01:00
_ , _ , err = handleMtaExtensionCredentials ( extFile , config . MtaExtensionCredentials )
2021-01-12 09:39:04 +01:00
if err != nil {
return fmt . Errorf ( "Cannot handle credentials inside mta extension files: %w" , err )
}
}
cfDeployParams = append ( cfDeployParams , extFileParams ... )
2024-07-04 11:13:36 +02:00
err := cfDeploy ( config , cfDeployParams , nil , command )
2021-01-12 09:39:04 +01:00
for _ , extFile := range extFiles {
renameError := fileUtils . FileRename ( extFile + ".original" , extFile )
if err == nil && renameError != nil {
return renameError
}
}
return err
}
2022-02-09 09:33:12 +01:00
func handleMtaExtensionCredentials ( extFile string , credentials map [ string ] interface { } ) ( updated , containsUnresolved bool , err error ) {
2021-01-12 09:39:04 +01:00
log . Entry ( ) . Debugf ( "Inserting credentials into extension file '%s'" , extFile )
b , err := fileUtils . FileRead ( extFile )
if err != nil {
2022-02-09 09:33:12 +01:00
return false , false , errors . Wrapf ( err , "Cannot handle credentials for mta extension file '%s'" , extFile )
2021-01-12 09:39:04 +01:00
}
content := string ( b )
env , err := toMap ( _environ ( ) , "=" )
if err != nil {
2022-02-09 09:33:12 +01:00
return false , false , errors . Wrap ( err , "Cannot handle mta extension credentials." )
2021-01-12 09:39:04 +01:00
}
missingCredentials := [ ] string { }
for name , credentialKey := range credentials {
credKey , ok := credentialKey . ( string )
if ! ok {
2022-02-09 09:33:12 +01:00
return false , false , fmt . Errorf ( "cannot handle mta extension credentials: Cannot cast '%v' (type %T) to string" , credentialKey , credentialKey )
2021-01-12 09:39:04 +01:00
}
2022-02-09 09:33:12 +01:00
const allowedVariableNamePattern = "^[-_A-Za-z0-9]+$"
alphaNumOnly := regexp . MustCompile ( allowedVariableNamePattern )
if ! alphaNumOnly . MatchString ( name ) {
return false , false , fmt . Errorf ( "credential key name '%s' contains unsupported character. Must contain only %s" , name , allowedVariableNamePattern )
}
pattern := regexp . MustCompile ( "<%=\\s*" + name + "\\s*%>" )
if pattern . MatchString ( content ) {
2021-01-12 09:39:04 +01:00
cred := env [ toEnvVarKey ( credKey ) ]
if len ( cred ) == 0 {
missingCredentials = append ( missingCredentials , credKey )
continue
}
2022-02-11 09:36:19 +01:00
content = pattern . ReplaceAllLiteralString ( content , cred )
2021-01-12 09:39:04 +01:00
updated = true
log . Entry ( ) . Debugf ( "Mta extension credentials handling: Placeholder '%s' has been replaced by credential denoted by '%s'/'%s' in file '%s'" , name , credKey , toEnvVarKey ( credKey ) , extFile )
2022-02-09 09:33:12 +01:00
} else {
log . Entry ( ) . Debugf ( "Mta extension credentials handling: Variable '%s' is not used in file '%s'" , name , extFile )
2021-01-12 09:39:04 +01:00
}
}
if len ( missingCredentials ) > 0 {
missinCredsEnvVarKeyCompatible := [ ] string { }
for _ , missingKey := range missingCredentials {
missinCredsEnvVarKeyCompatible = append ( missinCredsEnvVarKeyCompatible , toEnvVarKey ( missingKey ) )
}
// ensure stable order of the entries. Needed e.g. for the tests.
sort . Strings ( missingCredentials )
sort . Strings ( missinCredsEnvVarKeyCompatible )
2022-02-09 09:33:12 +01:00
return false , false , fmt . Errorf ( "cannot handle mta extension credentials: No credentials found for '%s'/'%s'. Are these credentials maintained?" , missingCredentials , missinCredsEnvVarKeyCompatible )
2021-01-12 09:39:04 +01:00
}
if ! updated {
log . Entry ( ) . Debugf ( "Mta extension credentials handling: Extension file '%s' has not been updated. Seems to contain no credentials." , extFile )
} else {
fInfo , err := fileUtils . Stat ( extFile )
fMode := fInfo . Mode ( )
if err != nil {
2022-02-09 09:33:12 +01:00
return false , false , errors . Wrap ( err , "Cannot handle mta extension credentials." )
2021-01-12 09:39:04 +01:00
}
err = fileUtils . FileWrite ( extFile , [ ] byte ( content ) , fMode )
if err != nil {
2022-02-09 09:33:12 +01:00
return false , false , errors . Wrap ( err , "Cannot handle mta extension credentials." )
2021-01-12 09:39:04 +01:00
}
log . Entry ( ) . Debugf ( "Mta extension credentials handling: Extension file '%s' has been updated." , extFile )
}
2022-02-09 09:33:12 +01:00
re := regexp . MustCompile ( ` <%=.+%> ` )
2021-01-12 09:39:04 +01:00
placeholders := re . FindAll ( [ ] byte ( content ) , - 1 )
2022-02-09 09:33:12 +01:00
containsUnresolved = ( len ( placeholders ) > 0 )
if containsUnresolved {
2021-01-12 09:39:04 +01:00
log . Entry ( ) . Warningf ( "mta extension credential handling: Unresolved placeholders found after inserting credentials: %s" , placeholders )
}
2022-02-09 09:33:12 +01:00
return updated , containsUnresolved , nil
2021-01-12 09:39:04 +01:00
}
func toEnvVarKey ( key string ) string {
key = regexp . MustCompile ( ` [^A-Za-z0-9] ` ) . ReplaceAllString ( key , "_" )
2021-11-15 12:59:49 +01:00
return strings . ToUpper ( regexp . MustCompile ( ` ([a-z0-9])([A-Z]) ` ) . ReplaceAllString ( key , "${1}_${2}" ) )
2021-01-12 09:39:04 +01:00
}
func toMap ( keyValue [ ] string , separator string ) ( map [ string ] string , error ) {
result := map [ string ] string { }
for _ , entry := range keyValue {
kv := strings . Split ( entry , separator )
if len ( kv ) < 2 {
return map [ string ] string { } , fmt . Errorf ( "Cannot convert to map: separator '%s' not found in entry '%s'" , separator , entry )
}
result [ kv [ 0 ] ] = strings . Join ( kv [ 1 : ] , separator )
}
return result , nil
2020-07-31 12:38:00 +02:00
}
2021-01-12 09:39:04 +01:00
func handleMtaExtensionDescriptors ( mtaExtensionDescriptor string ) ( [ ] string , [ ] string ) {
2020-07-31 12:38:00 +02:00
var result = [ ] string { }
2021-01-12 09:39:04 +01:00
var extFiles = [ ] string { }
2020-07-31 12:38:00 +02:00
for _ , part := range strings . Fields ( strings . Trim ( mtaExtensionDescriptor , " " ) ) {
if part == "-e" || part == "" {
continue
}
// REVISIT: maybe check if the extension descriptor exists
2021-01-12 09:39:04 +01:00
extFiles = append ( extFiles , part )
2020-07-31 12:38:00 +02:00
}
2022-01-14 16:13:24 +01:00
if len ( extFiles ) > 0 {
result = append ( result , "-e" )
result = append ( result , strings . Join ( extFiles , "," ) )
}
2021-01-12 09:39:04 +01:00
return result , extFiles
2020-07-31 12:38:00 +02:00
}
func cfDeploy (
config * cloudFoundryDeployOptions ,
cfDeployParams [ ] string ,
additionalEnvironment [ ] string ,
command command . ExecRunner ) error {
const cfLogFile = "cf.log"
var err error
var loginPerformed bool
additionalEnvironment = append ( additionalEnvironment , "CF_TRACE=" + cfLogFile )
if len ( config . CfHome ) > 0 {
additionalEnvironment = append ( additionalEnvironment , "CF_HOME=" + config . CfHome )
}
if len ( config . CfPluginHome ) > 0 {
additionalEnvironment = append ( additionalEnvironment , "CF_PLUGIN_HOME=" + config . CfPluginHome )
}
log . Entry ( ) . Infof ( "Using additional environment variables: %s" , additionalEnvironment )
// TODO set HOME to config.DockerWorkspace
command . SetEnv ( additionalEnvironment )
2021-02-05 08:24:41 +01:00
err = command . RunExecutable ( "cf" , "version" )
if err == nil {
err = _cfLogin ( command , cloudfoundry . LoginOptions {
CfAPIEndpoint : config . APIEndpoint ,
CfOrg : config . Org ,
CfSpace : config . Space ,
Username : config . Username ,
Password : config . Password ,
CfLoginOpts : strings . Fields ( config . LoginParameters ) ,
} )
}
2020-07-31 12:38:00 +02:00
if err == nil {
loginPerformed = true
err = command . RunExecutable ( "cf" , [ ] string { "plugins" } ... )
if err != nil {
log . Entry ( ) . WithError ( err ) . Errorf ( "Command '%s' failed." , [ ] string { "plugins" } )
}
}
if err == nil {
err = command . RunExecutable ( "cf" , cfDeployParams ... )
if err != nil {
log . Entry ( ) . WithError ( err ) . Errorf ( "Command '%s' failed." , cfDeployParams )
}
}
if loginPerformed {
logoutErr := _cfLogout ( command )
if logoutErr != nil {
log . Entry ( ) . WithError ( logoutErr ) . Errorf ( "Cannot perform cf logout" )
if err == nil {
err = logoutErr
}
}
}
if err != nil || GeneralConfig . Verbose {
e := handleCfCliLog ( cfLogFile )
if e != nil {
log . Entry ( ) . WithError ( err ) . Errorf ( "Error reading cf log file '%s'." , cfLogFile )
}
}
return err
}
func findMtar ( ) ( string , error ) {
const pattern = "**/*.mtar"
mtars , err := fileUtils . Glob ( pattern )
if err != nil {
return "" , err
}
if len ( mtars ) == 0 {
return "" , fmt . Errorf ( "No mtar file matching pattern '%s' found" , pattern )
}
if len ( mtars ) > 1 {
sMtars := [ ] string { }
2022-07-21 09:04:21 +02:00
sMtars = append ( sMtars , mtars ... )
2020-07-31 12:38:00 +02:00
return "" , fmt . Errorf ( "Found multiple mtar files matching pattern '%s' (%s), please specify file via parameter 'mtarPath'" , pattern , strings . Join ( sMtars , "," ) )
}
return mtars [ 0 ] , nil
}
func handleCfCliLog ( logFile string ) error {
fExists , err := fileUtils . FileExists ( logFile )
if err != nil {
return err
}
log . Entry ( ) . Info ( "### START OF CF CLI TRACE OUTPUT ###" )
if fExists {
f , err := os . Open ( logFile )
if err != nil {
return err
}
defer f . Close ( )
bReader := bufio . NewReader ( f )
for {
line , err := bReader . ReadString ( '\n' )
if err == nil || err == io . EOF {
// maybe inappropriate to log as info. Maybe the line from the
// log indicates an error, but that is something like a project
// standard.
log . Entry ( ) . Info ( strings . TrimSuffix ( line , "\n" ) )
}
if err != nil {
break
}
}
} else {
log . Entry ( ) . Warningf ( "No trace file found at '%s'" , logFile )
}
log . Entry ( ) . Info ( "### END OF CF CLI TRACE OUTPUT ###" )
return err
}