mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-03-03 15:02:35 +02:00
cloud foundry deploy go (#1743)
Provide cloudFoundryDeploy step in GO layer. Groovy part untouched. Groovy-Stub needs to be provided later (with a feature toggle in order to be able to switch back) Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
This commit is contained in:
parent
4b5fa80980
commit
e41e43611b
852
cmd/cloudFoundryDeploy.go
Normal file
852
cmd/cloudFoundryDeploy.go
Normal file
@ -0,0 +1,852 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"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/elliotchance/orderedmap"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type cfFileUtil interface {
|
||||
FileExists(string) (bool, error)
|
||||
FileWrite(path string, content []byte, perm os.FileMode) error
|
||||
Getwd() (string, error)
|
||||
Glob(string) ([]string, error)
|
||||
Chmod(string, os.FileMode) error
|
||||
}
|
||||
|
||||
var _now = time.Now
|
||||
var _cfLogin = cfLogin
|
||||
var _cfLogout = cfLogout
|
||||
var _getManifest = getManifest
|
||||
var _replaceVariables = yaml.Substitute
|
||||
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()
|
||||
}
|
||||
|
||||
const defaultSmokeTestScript = `#!/usr/bin/env bash
|
||||
# this is simply testing if the application root returns HTTP STATUS_CODE
|
||||
curl -so /dev/null -w '%{response_code}' https://$1 | grep $STATUS_CODE`
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return fmt.Errorf(strings.Join(message, " "))
|
||||
}
|
||||
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"
|
||||
|
||||
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 {
|
||||
return errors.Wrapf(err, "Cannot check if file path '%s' exists %w", mtarFilePath, err)
|
||||
}
|
||||
|
||||
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 {
|
||||
DeployCommand string
|
||||
DeployOptions []string
|
||||
AppName string
|
||||
ManifestFile string
|
||||
SmokeTestScript []string
|
||||
}
|
||||
|
||||
func handleCFNativeDeployment(config *cloudFoundryDeployOptions, command command.ExecRunner) error {
|
||||
|
||||
deployType, err := checkAndUpdateDeployTypeForNotSupportedManifest(config)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var deployCommand string
|
||||
var smokeTestScript []string
|
||||
var deployOptions []string
|
||||
|
||||
// deploy command will be provided by the prepare functions below
|
||||
|
||||
if deployType == "blue-green" {
|
||||
deployCommand, deployOptions, smokeTestScript, err = prepareBlueGreenCfNativeDeploy(config)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Cannot prepare cf native deployment. DeployType '%s'", deployType)
|
||||
}
|
||||
} else if deployType == "standard" {
|
||||
deployCommand, deployOptions, smokeTestScript, err = prepareCfPushCfNativeDeploy(config)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Cannot prepare cf push native deployment. DeployType '%s'", deployType)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Invalid deploy type received: '%s'. Supported values: %v", deployType, []string{"blue-green", "standard"})
|
||||
}
|
||||
|
||||
appName, err := getAppName(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Entry().Infof("CF native deployment ('%s') with:", config.DeployType)
|
||||
log.Entry().Infof("cfAppName='%s'", appName)
|
||||
log.Entry().Infof("cfManifest='%s'", config.Manifest)
|
||||
log.Entry().Infof("cfManifestVariables: '%v'", config.ManifestVariables)
|
||||
log.Entry().Infof("cfManifestVariablesFiles: '%v'", config.ManifestVariablesFiles)
|
||||
log.Entry().Infof("cfdeployDockerImage: '%s'", config.DeployDockerImage)
|
||||
log.Entry().Infof("smokeTestScript: '%s'", config.SmokeTestScript)
|
||||
|
||||
additionalEnvironment := []string{
|
||||
"STATUS_CODE=" + strconv.FormatInt(int64(config.SmokeTestStatusCode), 10),
|
||||
}
|
||||
|
||||
if len(config.DockerPassword) > 0 {
|
||||
additionalEnvironment = append(additionalEnvironment, "CF_DOCKER_PASSWORD="+config.DockerPassword)
|
||||
}
|
||||
|
||||
myDeployConfig := deployConfig{
|
||||
DeployCommand: deployCommand,
|
||||
DeployOptions: deployOptions,
|
||||
AppName: config.AppName,
|
||||
ManifestFile: config.Manifest,
|
||||
SmokeTestScript: smokeTestScript,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if len(config.DeployDockerImage) > 0 && config.DeployType != "blue-green" {
|
||||
deployStatement = append(deployStatement, "--docker-image", config.DeployDockerImage)
|
||||
}
|
||||
|
||||
if len(config.DockerUsername) > 0 && config.DeployType != "blue-green" {
|
||||
deployStatement = append(deployStatement, "--docker-username", config.DockerUsername)
|
||||
}
|
||||
|
||||
if len(deployConfig.SmokeTestScript) > 0 {
|
||||
deployStatement = append(deployStatement, deployConfig.SmokeTestScript...)
|
||||
}
|
||||
|
||||
if len(config.CfNativeDeployParameters) > 0 {
|
||||
deployStatement = append(deployStatement, strings.Fields(config.CfNativeDeployParameters)...)
|
||||
}
|
||||
|
||||
stopOldAppIfRunning := func(_cmd command.ExecRunner) error {
|
||||
|
||||
if config.KeepOldInstance && config.DeployType == "blue-green" {
|
||||
oldAppName := deployConfig.AppName + "-old"
|
||||
|
||||
var buff bytes.Buffer
|
||||
|
||||
_cmd.Stdout(&buff)
|
||||
|
||||
defer func() {
|
||||
_cmd.Stdout(log.Writer())
|
||||
}()
|
||||
|
||||
err := _cmd.RunExecutable("cf", "stop", oldAppName)
|
||||
|
||||
if err != nil {
|
||||
|
||||
cfStopLog := buff.String()
|
||||
|
||||
if !strings.Contains(cfStopLog, oldAppName+" not found") {
|
||||
return fmt.Errorf("Could not stop application '%s'. Error: %s", oldAppName, cfStopLog)
|
||||
}
|
||||
log.Entry().Infof("Cannot stop application '%s' since this appliation was not found.", oldAppName)
|
||||
|
||||
} else {
|
||||
log.Entry().Infof("Old application '%s' has been stopped.", oldAppName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return cfDeploy(config, deployStatement, additionalEnvironment, stopOldAppIfRunning, cmd)
|
||||
}
|
||||
|
||||
func getManifest(name string) (cloudfoundry.Manifest, error) {
|
||||
return cloudfoundry.ReadManifest(name)
|
||||
}
|
||||
|
||||
func getAppName(config *cloudFoundryDeployOptions) (string, error) {
|
||||
|
||||
if len(config.AppName) > 0 {
|
||||
return config.AppName, nil
|
||||
}
|
||||
if config.DeployType == "blue-green" {
|
||||
return "", fmt.Errorf("Blue-green plugin requires app name to be passed (see https://github.com/bluemixgaragelondon/cf-blue-green-deploy/issues/27)")
|
||||
}
|
||||
if len(config.Manifest) == 0 {
|
||||
return "", fmt.Errorf("Manifest file not provided in configuration. Cannot retrieve app name")
|
||||
}
|
||||
fileExists, err := fileUtils.FileExists(config.Manifest)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "Cannot check if file '%s' exists", config.Manifest)
|
||||
}
|
||||
if !fileExists {
|
||||
return "", fmt.Errorf("Manifest file '%s' not found. Cannot retrieve app name", config.Manifest)
|
||||
}
|
||||
manifest, err := _getManifest(config.Manifest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
apps, err := manifest.GetApplications()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(apps) == 0 {
|
||||
return "", fmt.Errorf("No apps declared in manifest '%s'", config.Manifest)
|
||||
}
|
||||
namePropertyExists, err := manifest.ApplicationHasProperty(0, "name")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !namePropertyExists {
|
||||
return "", fmt.Errorf("No appName available in manifest '%s'", config.Manifest)
|
||||
}
|
||||
appName, err := manifest.GetApplicationProperty(0, "name")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var name string
|
||||
var ok bool
|
||||
if name, ok = appName.(string); !ok {
|
||||
return "", fmt.Errorf("appName from manifest '%s' has wrong type", config.Manifest)
|
||||
}
|
||||
if len(name) == 0 {
|
||||
return "", fmt.Errorf("appName from manifest '%s' is empty", config.Manifest)
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func handleSmokeTestScript(smokeTestScript string) ([]string, error) {
|
||||
|
||||
if smokeTestScript == "blueGreenCheckScript.sh" {
|
||||
// what should we do if there is already a script with the given name? Should we really overwrite ...
|
||||
err := fileUtils.FileWrite(smokeTestScript, []byte(defaultSmokeTestScript), 0755)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("failed to write default smoke-test script: %w", err)
|
||||
}
|
||||
log.Entry().Debugf("smoke test script '%s' has been written.", smokeTestScript)
|
||||
}
|
||||
|
||||
if len(smokeTestScript) > 0 {
|
||||
err := fileUtils.Chmod(smokeTestScript, 0755)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("failed to make smoke-test script executable: %w", err)
|
||||
}
|
||||
pwd, err := fileUtils.Getwd()
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("failed to get current working directory for execution of smoke-test script: %w", err)
|
||||
}
|
||||
|
||||
return []string{"--smoke-test", fmt.Sprintf("%s/%s", pwd, smokeTestScript)}, nil
|
||||
}
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func prepareBlueGreenCfNativeDeploy(config *cloudFoundryDeployOptions) (string, []string, []string, error) {
|
||||
|
||||
smokeTest, err := handleSmokeTestScript(config.SmokeTestScript)
|
||||
if err != nil {
|
||||
return "", []string{}, []string{}, err
|
||||
}
|
||||
|
||||
var deployOptions = []string{}
|
||||
|
||||
if !config.KeepOldInstance {
|
||||
deployOptions = append(deployOptions, "--delete-old-apps")
|
||||
}
|
||||
|
||||
if len(config.Manifest) > 0 {
|
||||
manifestFileExists, err := fileUtils.FileExists(config.Manifest)
|
||||
if err != nil {
|
||||
return "", []string{}, []string{}, errors.Wrapf(err, "Cannot check if file '%s' exists", config.Manifest)
|
||||
}
|
||||
|
||||
if !manifestFileExists {
|
||||
|
||||
log.Entry().Infof("Manifest file '%s' does not exist", config.Manifest)
|
||||
|
||||
} else {
|
||||
|
||||
manifestVariables, err := toStringInterfaceMap(toParameterMap(config.ManifestVariables))
|
||||
if err != nil {
|
||||
return "", []string{}, []string{}, errors.Wrapf(err, "Cannot prepare manifest variables: '%v'", config.ManifestVariables)
|
||||
}
|
||||
|
||||
manifestVariablesFiles, err := validateManifestVariablesFiles(config.ManifestVariablesFiles)
|
||||
if err != nil {
|
||||
return "", []string{}, []string{}, errors.Wrapf(err, "Cannot validate manifest variables files '%v'", config.ManifestVariablesFiles)
|
||||
}
|
||||
|
||||
modified, err := _replaceVariables(config.Manifest, manifestVariables, manifestVariablesFiles)
|
||||
if err != nil {
|
||||
return "", []string{}, []string{}, errors.Wrap(err, "Cannot prepare manifest file")
|
||||
}
|
||||
|
||||
if modified {
|
||||
log.Entry().Infof("Manifest file '%s' has been updated (variable substitution)", config.Manifest)
|
||||
} else {
|
||||
log.Entry().Infof("Manifest file '%s' has not been updated (no variable substitution)", config.Manifest)
|
||||
}
|
||||
|
||||
err = handleLegacyCfManifest(config.Manifest)
|
||||
if err != nil {
|
||||
return "", []string{}, []string{}, errors.Wrapf(err, "Cannot handle legacy manifest '%s'", config.Manifest)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.Entry().Info("No manifest file configured")
|
||||
}
|
||||
return "blue-green-deploy", deployOptions, smokeTest, nil
|
||||
}
|
||||
|
||||
// validateManifestVariablesFiles: in case the only provided file is 'manifest-variables.yml' and this file does not
|
||||
// exist we ignore that file. For any other file there is no check if that file exists. In case several files are
|
||||
// provided we also do not check for the default file 'manifest-variables.yml'
|
||||
func validateManifestVariablesFiles(manifestVariablesFiles []string) ([]string, error) {
|
||||
|
||||
const defaultManifestVariableFileName = "manifest-variables.yml"
|
||||
if len(manifestVariablesFiles) == 1 && manifestVariablesFiles[0] == defaultManifestVariableFileName {
|
||||
// we have only the default file. Most likely this is not configured, but we simply have the default.
|
||||
// In case this file does not exist we ignore that file.
|
||||
exists, err := fileUtils.FileExists(defaultManifestVariableFileName)
|
||||
if err != nil {
|
||||
return []string{}, errors.Wrapf(err, "Cannot check if file '%s' exists", defaultManifestVariableFileName)
|
||||
}
|
||||
if !exists {
|
||||
return []string{}, nil
|
||||
}
|
||||
}
|
||||
return manifestVariablesFiles, nil
|
||||
}
|
||||
|
||||
func toParameterMap(parameters []string) (*orderedmap.OrderedMap, error) {
|
||||
|
||||
parameterMap := orderedmap.NewOrderedMap()
|
||||
|
||||
for _, p := range parameters {
|
||||
keyVal := strings.Split(p, "=")
|
||||
if len(keyVal) != 2 {
|
||||
return nil, fmt.Errorf("Invalid parameter provided (expected format <key>=<val>: '%s'", p)
|
||||
}
|
||||
parameterMap.Set(keyVal[0], keyVal[1])
|
||||
}
|
||||
return parameterMap, nil
|
||||
}
|
||||
|
||||
func handleLegacyCfManifest(manifestFile string) error {
|
||||
manifest, err := _getManifest(manifestFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = manifest.Transform()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if manifest.IsModified() {
|
||||
|
||||
err = manifest.WriteManifest()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Entry().Infof("Manifest file '%s' was in legacy format has been transformed and updated.", manifestFile)
|
||||
} else {
|
||||
log.Entry().Debugf("Manifest file '%s' was not in legacy format. No transformation needed, no update performed.", manifestFile)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareCfPushCfNativeDeploy(config *cloudFoundryDeployOptions) (string, []string, []string, error) {
|
||||
|
||||
deployOptions := []string{}
|
||||
varOptions, err := getVarOptions(config.ManifestVariables)
|
||||
if err != nil {
|
||||
return "", []string{}, []string{}, errors.Wrapf(err, "Cannot prepare var-options: '%v'", config.ManifestVariables)
|
||||
}
|
||||
|
||||
varFileOptions, err := getVarFileOptions(config.ManifestVariablesFiles)
|
||||
if err != nil {
|
||||
return "", []string{}, []string{}, errors.Wrapf(err, "Cannot prepare var-file-options: '%v'", config.ManifestVariablesFiles)
|
||||
}
|
||||
|
||||
deployOptions = append(deployOptions, varOptions...)
|
||||
deployOptions = append(deployOptions, varFileOptions...)
|
||||
|
||||
return "push", deployOptions, []string{}, nil
|
||||
}
|
||||
|
||||
func toStringInterfaceMap(in *orderedmap.OrderedMap, err error) (map[string]interface{}, error) {
|
||||
|
||||
out := map[string]interface{}{}
|
||||
|
||||
if err == nil {
|
||||
for _, key := range in.Keys() {
|
||||
if k, ok := key.(string); ok {
|
||||
val, exists := in.Get(key)
|
||||
if exists {
|
||||
out[k] = val
|
||||
} else {
|
||||
return nil, fmt.Errorf("No entry found for '%v'", key)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Cannot cast key '%v' to string", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out, err
|
||||
}
|
||||
func getVarOptions(vars []string) ([]string, error) {
|
||||
|
||||
varsMap, err := toParameterMap(vars)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
varsResult := []string{}
|
||||
|
||||
for _, key := range varsMap.Keys() {
|
||||
val, _ := varsMap.Get(key)
|
||||
if v, ok := val.(string); ok {
|
||||
varsResult = append(varsResult, "--var", fmt.Sprintf("%s=%s", key, v))
|
||||
} else {
|
||||
return []string{}, fmt.Errorf("Cannot cast '%v' to string", val)
|
||||
}
|
||||
}
|
||||
return varsResult, nil
|
||||
}
|
||||
|
||||
func getVarFileOptions(manifestVariableFiles []string) ([]string, error) {
|
||||
|
||||
varFiles, err := validateManifestVariablesFiles(manifestVariableFiles)
|
||||
if err != nil {
|
||||
return []string{}, errors.Wrapf(err, "Cannot validate manifest variables files '%v'", manifestVariableFiles)
|
||||
}
|
||||
|
||||
varFilesResult := []string{}
|
||||
|
||||
for _, varFile := range varFiles {
|
||||
fExists, err := fileUtils.FileExists(varFile)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
if !fExists {
|
||||
log.Entry().Warningf("We skip adding not-existing file '%s' as a vars-file to the cf create-service-push call", varFile)
|
||||
continue
|
||||
}
|
||||
|
||||
varFilesResult = append(varFilesResult, "--vars-file", varFile)
|
||||
}
|
||||
|
||||
if len(varFilesResult) > 0 {
|
||||
log.Entry().Infof("We will add the following string to the cf push call: '%s'", strings.Join(varFilesResult, " "))
|
||||
}
|
||||
return varFilesResult, nil
|
||||
}
|
||||
|
||||
func checkAndUpdateDeployTypeForNotSupportedManifest(config *cloudFoundryDeployOptions) (string, error) {
|
||||
|
||||
manifestFile := config.Manifest
|
||||
if len(manifestFile) == 0 {
|
||||
manifestFile = "manifest.yml"
|
||||
}
|
||||
|
||||
manifestFileExists, err := fileUtils.FileExists(manifestFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if config.DeployType == "blue-green" && manifestFileExists {
|
||||
|
||||
manifest, _ := _getManifest(manifestFile)
|
||||
|
||||
apps, err := manifest.GetApplications()
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to obtain applications from manifest: %w", err)
|
||||
}
|
||||
if len(apps) > 1 {
|
||||
return "", fmt.Errorf("Your manifest contains more than one application. For blue green deployments your manifest file may contain only one application")
|
||||
}
|
||||
|
||||
hasNoRouteProperty, err := manifest.ApplicationHasProperty(0, "no-route")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to obtain 'no-route' property from manifest")
|
||||
}
|
||||
if len(apps) == 1 && hasNoRouteProperty {
|
||||
|
||||
const deployTypeStandard = "standard"
|
||||
log.Entry().Warningf("Blue green deployment is not possible for application without route. Using deployment type '%s' instead.", deployTypeStandard)
|
||||
return deployTypeStandard, nil
|
||||
}
|
||||
}
|
||||
|
||||
return config.DeployType, nil
|
||||
}
|
||||
|
||||
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"
|
||||
if !piperutils.ContainsString(deployParams, noConfirmFlag) {
|
||||
deployParams = append(deployParams, noConfirmFlag)
|
||||
}
|
||||
}
|
||||
|
||||
cfDeployParams := []string{
|
||||
deployCommand,
|
||||
mtarFilePath,
|
||||
}
|
||||
|
||||
if len(deployParams) > 0 {
|
||||
cfDeployParams = append(cfDeployParams, deployParams...)
|
||||
}
|
||||
|
||||
cfDeployParams = append(cfDeployParams, handleMtaExtensionDescriptors(config.MtaExtensionDescriptor)...)
|
||||
|
||||
return cfDeploy(config, cfDeployParams, nil, nil, command)
|
||||
}
|
||||
|
||||
func handleMtaExtensionDescriptors(mtaExtensionDescriptor string) []string {
|
||||
var result = []string{}
|
||||
for _, part := range strings.Fields(strings.Trim(mtaExtensionDescriptor, " ")) {
|
||||
if part == "-e" || part == "" {
|
||||
continue
|
||||
}
|
||||
// REVISIT: maybe check if the extension descriptor exists
|
||||
result = append(result, "-e", part)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func cfDeploy(
|
||||
config *cloudFoundryDeployOptions,
|
||||
cfDeployParams []string,
|
||||
additionalEnvironment []string,
|
||||
postDeployAction func(command command.ExecRunner) error,
|
||||
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)
|
||||
|
||||
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),
|
||||
})
|
||||
|
||||
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 err == nil && postDeployAction != nil {
|
||||
err = postDeployAction(command)
|
||||
}
|
||||
|
||||
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{}
|
||||
for _, mtar := range mtars {
|
||||
sMtars = append(sMtars, mtar)
|
||||
}
|
||||
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
|
||||
}
|
404
cmd/cloudFoundryDeploy_generated.go
Normal file
404
cmd/cloudFoundryDeploy_generated.go
Normal file
@ -0,0 +1,404 @@
|
||||
// Code generated by piper's step-generator. DO NOT EDIT.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/piperenv"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type cloudFoundryDeployOptions struct {
|
||||
APIEndpoint string `json:"apiEndpoint,omitempty"`
|
||||
AppName string `json:"appName,omitempty"`
|
||||
ArtifactVersion string `json:"artifactVersion,omitempty"`
|
||||
CfHome string `json:"cfHome,omitempty"`
|
||||
CfNativeDeployParameters string `json:"cfNativeDeployParameters,omitempty"`
|
||||
CfPluginHome string `json:"cfPluginHome,omitempty"`
|
||||
DeployDockerImage string `json:"deployDockerImage,omitempty"`
|
||||
DeployTool string `json:"deployTool,omitempty"`
|
||||
DeployType string `json:"deployType,omitempty"`
|
||||
DockerPassword string `json:"dockerPassword,omitempty"`
|
||||
DockerUsername string `json:"dockerUsername,omitempty"`
|
||||
KeepOldInstance bool `json:"keepOldInstance,omitempty"`
|
||||
LoginParameters string `json:"loginParameters,omitempty"`
|
||||
Manifest string `json:"manifest,omitempty"`
|
||||
ManifestVariables []string `json:"manifestVariables,omitempty"`
|
||||
ManifestVariablesFiles []string `json:"manifestVariablesFiles,omitempty"`
|
||||
MtaDeployParameters string `json:"mtaDeployParameters,omitempty"`
|
||||
MtaExtensionDescriptor string `json:"mtaExtensionDescriptor,omitempty"`
|
||||
MtaPath string `json:"mtaPath,omitempty"`
|
||||
Org string `json:"org,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
SmokeTestScript string `json:"smokeTestScript,omitempty"`
|
||||
SmokeTestStatusCode int `json:"smokeTestStatusCode,omitempty"`
|
||||
Space string `json:"space,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
}
|
||||
|
||||
type cloudFoundryDeployInflux struct {
|
||||
deployment_data struct {
|
||||
fields struct {
|
||||
artifactURL string
|
||||
deployTime string
|
||||
jobTrigger string
|
||||
}
|
||||
tags struct {
|
||||
artifactVersion string
|
||||
deployUser string
|
||||
deployResult string
|
||||
cfAPIEndpoint string
|
||||
cfOrg string
|
||||
cfSpace string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *cloudFoundryDeployInflux) persist(path, resourceName string) {
|
||||
measurementContent := []struct {
|
||||
measurement string
|
||||
valType string
|
||||
name string
|
||||
value string
|
||||
}{
|
||||
{valType: config.InfluxField, measurement: "deployment_data", name: "artifactUrl", value: i.deployment_data.fields.artifactURL},
|
||||
{valType: config.InfluxField, measurement: "deployment_data", name: "deployTime", value: i.deployment_data.fields.deployTime},
|
||||
{valType: config.InfluxField, measurement: "deployment_data", name: "jobTrigger", value: i.deployment_data.fields.jobTrigger},
|
||||
{valType: config.InfluxTag, measurement: "deployment_data", name: "artifactVersion", value: i.deployment_data.tags.artifactVersion},
|
||||
{valType: config.InfluxTag, measurement: "deployment_data", name: "deployUser", value: i.deployment_data.tags.deployUser},
|
||||
{valType: config.InfluxTag, measurement: "deployment_data", name: "deployResult", value: i.deployment_data.tags.deployResult},
|
||||
{valType: config.InfluxTag, measurement: "deployment_data", name: "cfApiEndpoint", value: i.deployment_data.tags.cfAPIEndpoint},
|
||||
{valType: config.InfluxTag, measurement: "deployment_data", name: "cfOrg", value: i.deployment_data.tags.cfOrg},
|
||||
{valType: config.InfluxTag, measurement: "deployment_data", name: "cfSpace", value: i.deployment_data.tags.cfSpace},
|
||||
}
|
||||
|
||||
errCount := 0
|
||||
for _, metric := range measurementContent {
|
||||
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(metric.measurement, fmt.Sprintf("%vs", metric.valType), metric.name), metric.value)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Error("Error persisting influx environment.")
|
||||
errCount++
|
||||
}
|
||||
}
|
||||
if errCount > 0 {
|
||||
log.Entry().Fatal("failed to persist Influx environment")
|
||||
}
|
||||
}
|
||||
|
||||
// CloudFoundryDeployCommand Deploys an application to cloud foundry
|
||||
func CloudFoundryDeployCommand() *cobra.Command {
|
||||
const STEP_NAME = "cloudFoundryDeploy"
|
||||
|
||||
metadata := cloudFoundryDeployMetadata()
|
||||
var stepConfig cloudFoundryDeployOptions
|
||||
var startTime time.Time
|
||||
var influx cloudFoundryDeployInflux
|
||||
|
||||
var createCloudFoundryDeployCmd = &cobra.Command{
|
||||
Use: STEP_NAME,
|
||||
Short: "Deploys an application to cloud foundry",
|
||||
Long: `Deploys an application to a test or production space within Cloud Foundry.`,
|
||||
PreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||
startTime = time.Now()
|
||||
log.SetStepName(STEP_NAME)
|
||||
log.SetVerbose(GeneralConfig.Verbose)
|
||||
|
||||
path, _ := os.Getwd()
|
||||
fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path}
|
||||
log.RegisterHook(fatalHook)
|
||||
|
||||
err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return err
|
||||
}
|
||||
log.RegisterSecret(stepConfig.DockerPassword)
|
||||
log.RegisterSecret(stepConfig.Password)
|
||||
log.RegisterSecret(stepConfig.Username)
|
||||
|
||||
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
|
||||
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
|
||||
log.RegisterHook(&sentryHook)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
telemetryData := telemetry.CustomData{}
|
||||
telemetryData.ErrorCode = "1"
|
||||
handler := func() {
|
||||
influx.persist(GeneralConfig.EnvRootPath, "influx")
|
||||
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
|
||||
telemetry.Send(&telemetryData)
|
||||
}
|
||||
log.DeferExitHandler(handler)
|
||||
defer handler()
|
||||
telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
|
||||
cloudFoundryDeploy(stepConfig, &telemetryData, &influx)
|
||||
telemetryData.ErrorCode = "0"
|
||||
log.Entry().Info("SUCCESS")
|
||||
},
|
||||
}
|
||||
|
||||
addCloudFoundryDeployFlags(createCloudFoundryDeployCmd, &stepConfig)
|
||||
return createCloudFoundryDeployCmd
|
||||
}
|
||||
|
||||
func addCloudFoundryDeployFlags(cmd *cobra.Command, stepConfig *cloudFoundryDeployOptions) {
|
||||
cmd.Flags().StringVar(&stepConfig.APIEndpoint, "apiEndpoint", `https://api.cf.eu10.hana.ondemand.com`, "Cloud Foundry API endpoint")
|
||||
cmd.Flags().StringVar(&stepConfig.AppName, "appName", os.Getenv("PIPER_appName"), "Defines the name of the application to be deployed to the Cloud Foundry space")
|
||||
cmd.Flags().StringVar(&stepConfig.ArtifactVersion, "artifactVersion", os.Getenv("PIPER_artifactVersion"), "The artifact version, used for influx reporting")
|
||||
cmd.Flags().StringVar(&stepConfig.CfHome, "cfHome", os.Getenv("PIPER_cfHome"), "The cf home folder used by the cf cli. If not provided the default assumed by the cf cli is used.")
|
||||
cmd.Flags().StringVar(&stepConfig.CfNativeDeployParameters, "cfNativeDeployParameters", os.Getenv("PIPER_cfNativeDeployParameters"), "Additional parameters passed to cf native deployment command")
|
||||
cmd.Flags().StringVar(&stepConfig.CfPluginHome, "cfPluginHome", os.Getenv("PIPER_cfPluginHome"), "The cf plugin home folder used by the cf cli. If not provided the default assumed by the cf cli is used.")
|
||||
cmd.Flags().StringVar(&stepConfig.DeployDockerImage, "deployDockerImage", os.Getenv("PIPER_deployDockerImage"), "Docker image deployments are supported (via manifest file in general)[https://docs.cloudfoundry.org/devguide/deploy-apps/manifest-attributes.html#docker]. If no manifest is used, this parameter defines the image to be deployed. The specified name of the image is passed to the `--docker-image` parameter of the cf CLI and must adhere it's naming pattern (e.g. REPO/IMAGE:TAG). See (cf CLI documentation)[https://docs.cloudfoundry.org/devguide/deploy-apps/push-docker.html] for details. Note: The used Docker registry must be visible for the targeted Cloud Foundry instance.")
|
||||
cmd.Flags().StringVar(&stepConfig.DeployTool, "deployTool", os.Getenv("PIPER_deployTool"), "Defines the tool which should be used for deployment.")
|
||||
cmd.Flags().StringVar(&stepConfig.DeployType, "deployType", `standard`, "Defines the type of deployment, either `standard` deployment which results in a system downtime or a zero-downtime `blue-green` deployment.If 'cf_native' as deployType and 'blue-green' as deployTool is used in combination, your manifest.yaml may only contain one application. If this application has the option 'no-route' active the deployType will be changed to 'standard'.")
|
||||
cmd.Flags().StringVar(&stepConfig.DockerPassword, "dockerPassword", os.Getenv("PIPER_dockerPassword"), "dockerPassword")
|
||||
cmd.Flags().StringVar(&stepConfig.DockerUsername, "dockerUsername", os.Getenv("PIPER_dockerUsername"), "dockerUserName")
|
||||
cmd.Flags().BoolVar(&stepConfig.KeepOldInstance, "keepOldInstance", false, "In case of a `blue-green` deployment the old instance will be deleted by default. If this option is set to true the old instance will remain stopped in the Cloud Foundry space.")
|
||||
cmd.Flags().StringVar(&stepConfig.LoginParameters, "loginParameters", os.Getenv("PIPER_loginParameters"), "Addition command line options for cf login command. No escaping/quoting is performed. Not recommanded for productive environments.")
|
||||
cmd.Flags().StringVar(&stepConfig.Manifest, "manifest", os.Getenv("PIPER_manifest"), "Defines the manifest to be used for deployment to Cloud Foundry.")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.ManifestVariables, "manifestVariables", []string{}, "Defines a list of variables as key-value Map objects used for variable substitution within the file given by manifest. Defaults to an empty list, if not specified otherwise. This can be used to set variables like it is provided by 'cf push --var key=value'. The order of the maps of variables given in the list is relevant in case there are conflicting variable names and value between maps contained within the list. In case of conflicts, the last specified map in the list will win. Though each map entry in the list can contain more than one key-value pair for variable substitution, it is recommended to stick to one entry per map, and rather declare more maps within the list. The reason is that if a map in the list contains more than one key-value entry, and the entries are conflicting, the conflict resolution behavior is undefined (since map entries have no sequence). Note: variables defined via 'manifestVariables' always win over conflicting variables defined via any file given by 'manifestVariablesFiles' - no matter what is declared before. This is the same behavior as can be observed when using 'cf push --var' in combination with 'cf push --vars-file'.")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.ManifestVariablesFiles, "manifestVariablesFiles", []string{`manifest-variables.yml`}, "path(s) of the Yaml file(s) containing the variable values to use as a replacement in the manifest file. The order of the files is relevant in case there are conflicting variable names and values within variable files. In such a case, the values of the last file win.")
|
||||
cmd.Flags().StringVar(&stepConfig.MtaDeployParameters, "mtaDeployParameters", `-f`, "Additional parameters passed to mta deployment command")
|
||||
cmd.Flags().StringVar(&stepConfig.MtaExtensionDescriptor, "mtaExtensionDescriptor", os.Getenv("PIPER_mtaExtensionDescriptor"), "Defines additional extension descriptor file for deployment with the mtaDeployPlugin")
|
||||
cmd.Flags().StringVar(&stepConfig.MtaPath, "mtaPath", os.Getenv("PIPER_mtaPath"), "Defines the path to *.mtar for deployment with the mtaDeployPlugin")
|
||||
cmd.Flags().StringVar(&stepConfig.Org, "org", os.Getenv("PIPER_org"), "Cloud Foundry target organization.")
|
||||
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password")
|
||||
cmd.Flags().StringVar(&stepConfig.SmokeTestScript, "smokeTestScript", `blueGreenCheckScript.sh`, "Allows to specify a script which performs a check during blue-green deployment. The script gets the FQDN as parameter and returns `exit code 0` in case check returned `smokeTestStatusCode`. More details can be found [here](https://github.com/bluemixgaragelondon/cf-blue-green-deploy#how-to-use). Currently this option is only considered for deployTool `cf_native`.")
|
||||
cmd.Flags().IntVar(&stepConfig.SmokeTestStatusCode, "smokeTestStatusCode", 200, "Expected status code returned by the check.")
|
||||
cmd.Flags().StringVar(&stepConfig.Space, "space", os.Getenv("PIPER_space"), "Cloud Foundry target space")
|
||||
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User")
|
||||
|
||||
cmd.MarkFlagRequired("apiEndpoint")
|
||||
cmd.MarkFlagRequired("deployTool")
|
||||
cmd.MarkFlagRequired("org")
|
||||
cmd.MarkFlagRequired("password")
|
||||
cmd.MarkFlagRequired("space")
|
||||
cmd.MarkFlagRequired("username")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func cloudFoundryDeployMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Metadata: config.StepMetadata{
|
||||
Name: "cloudFoundryDeploy",
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "apiEndpoint",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{{Name: "cfApiEndpoint"}, {Name: "cloudFoundry/apiEndpoint"}},
|
||||
},
|
||||
{
|
||||
Name: "appName",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cfAppName"}, {Name: "cloudFoundry/appName"}},
|
||||
},
|
||||
{
|
||||
Name: "artifactVersion",
|
||||
ResourceRef: []config.ResourceReference{{Name: "commonPipelineEnvironment", Param: "artifactVersion"}},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "cfHome",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "cfNativeDeployParameters",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "cfPluginHome",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "deployDockerImage",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "deployTool",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "deployType",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "dockerPassword",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "dockerUsername",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "keepOldInstance",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "bool",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "loginParameters",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "manifest",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cfManifest"}, {Name: "cloudFoundry/manifest"}},
|
||||
},
|
||||
{
|
||||
Name: "manifestVariables",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "[]string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cfManifestVariables"}, {Name: "cloudFoundry/manifestVariables"}},
|
||||
},
|
||||
{
|
||||
Name: "manifestVariablesFiles",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "[]string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cfManifestVariablesFiles"}, {Name: "cloudFoundry/manifestVariablesFiles"}},
|
||||
},
|
||||
{
|
||||
Name: "mtaDeployParameters",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "mtaExtensionDescriptor",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "cloudFoundry/mtaExtensionDescriptor"}},
|
||||
},
|
||||
{
|
||||
Name: "mtaPath",
|
||||
ResourceRef: []config.ResourceReference{{Name: "commonPipelineEnvironment", Param: "mtarFilePath"}},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "org",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{{Name: "cfOrg"}, {Name: "cloudFoundry/org"}},
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "smokeTestScript",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "smokeTestStatusCode",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "int",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "space",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{{Name: "cfSpace"}, {Name: "cloudFoundry/space"}},
|
||||
},
|
||||
{
|
||||
Name: "username",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
16
cmd/cloudFoundryDeploy_generated_test.go
Normal file
16
cmd/cloudFoundryDeploy_generated_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCloudFoundryDeployCommand(t *testing.T) {
|
||||
|
||||
testCmd := CloudFoundryDeployCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "cloudFoundryDeploy", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
1279
cmd/cloudFoundryDeploy_test.go
Normal file
1279
cmd/cloudFoundryDeploy_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -91,6 +91,7 @@ func Execute() {
|
||||
rootCmd.AddCommand(GctsExecuteABAPUnitTestsCommand())
|
||||
rootCmd.AddCommand(GctsDeployCommand())
|
||||
rootCmd.AddCommand(MalwareExecuteScanCommand())
|
||||
rootCmd.AddCommand(CloudFoundryDeployCommand())
|
||||
rootCmd.AddCommand(GctsRollbackCommand())
|
||||
rootCmd.AddCommand(WhitesourceExecuteScanCommand())
|
||||
rootCmd.AddCommand(GctsCloneRepositoryCommand())
|
||||
|
1
go.mod
1
go.mod
@ -12,6 +12,7 @@ require (
|
||||
github.com/bmatcuk/doublestar v1.3.1
|
||||
github.com/containerd/containerd v1.3.6 // indirect
|
||||
github.com/docker/docker v1.4.2-0.20200114201811-16a3519d870b // indirect
|
||||
github.com/elliotchance/orderedmap v1.2.2
|
||||
github.com/evanphx/json-patch v4.5.0+incompatible
|
||||
github.com/getsentry/sentry-go v0.6.1
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
|
2
go.sum
2
go.sum
@ -242,6 +242,8 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elliotchance/orderedmap v1.2.2 h1:U5tjNwkj4PjuySqnbkIiiGrj8Ovw83domXHeLeb7OgY=
|
||||
github.com/elliotchance/orderedmap v1.2.2/go.mod h1:8hdSl6jmveQw8ScByd3AaNHNk51RhbTazdqtTty+NFw=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
|
270
resources/metadata/cloudFoundryDeploy.yaml
Normal file
270
resources/metadata/cloudFoundryDeploy.yaml
Normal file
@ -0,0 +1,270 @@
|
||||
metadata:
|
||||
name: cloudFoundryDeploy
|
||||
description: Deploys an application to cloud foundry
|
||||
longDescription: |
|
||||
Deploys an application to a test or production space within Cloud Foundry.
|
||||
spec:
|
||||
inputs:
|
||||
params:
|
||||
- name: apiEndpoint
|
||||
type: string
|
||||
description: "Cloud Foundry API endpoint"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
default: "https://api.cf.eu10.hana.ondemand.com"
|
||||
aliases:
|
||||
- name: cfApiEndpoint
|
||||
- name: cloudFoundry/apiEndpoint
|
||||
- name: appName
|
||||
type: string
|
||||
description: "Defines the name of the application to be deployed to the Cloud Foundry space"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cfAppName
|
||||
- name: cloudFoundry/appName
|
||||
- name: artifactVersion
|
||||
type: string
|
||||
description: "The artifact version, used for influx reporting"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
resourceRef:
|
||||
- name: commonPipelineEnvironment
|
||||
param: artifactVersion
|
||||
- name: cfHome
|
||||
type: string
|
||||
description: "The cf home folder used by the cf cli. If not provided the default assumed by the cf cli is used."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
- name: cfNativeDeployParameters
|
||||
type: string
|
||||
description: "Additional parameters passed to cf native deployment command"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
- name: cfPluginHome
|
||||
type: string
|
||||
description: "The cf plugin home folder used by the cf cli. If not provided the default assumed by the cf cli is used."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
- name: deployDockerImage
|
||||
type: string
|
||||
description: "Docker image deployments are supported (via manifest file in general)[https://docs.cloudfoundry.org/devguide/deploy-apps/manifest-attributes.html#docker]. If no manifest is used, this parameter defines the image to be deployed. The specified name of the image is passed to the `--docker-image` parameter of the cf CLI and must adhere it's naming pattern (e.g. REPO/IMAGE:TAG). See (cf CLI documentation)[https://docs.cloudfoundry.org/devguide/deploy-apps/push-docker.html] for details. Note: The used Docker registry must be visible for the targeted Cloud Foundry instance."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
- name: deployTool
|
||||
type: string
|
||||
description: "Defines the tool which should be used for deployment."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: deployType
|
||||
type: string
|
||||
description: "Defines the type of deployment, either `standard` deployment which results in a system downtime or a zero-downtime `blue-green` deployment.If 'cf_native' as deployType and 'blue-green' as deployTool is used in combination, your manifest.yaml may only contain one application. If this application has the option 'no-route' active the deployType will be changed to 'standard'."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
default: "standard"
|
||||
- name: dockerPassword
|
||||
type: string
|
||||
description: "dockerPassword"
|
||||
secret: true
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
- name: dockerUsername
|
||||
type: string
|
||||
description: "dockerUserName"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
- name: keepOldInstance
|
||||
type: bool
|
||||
description: "In case of a `blue-green` deployment the old instance will be deleted by default. If this option is set to true the old instance will remain stopped in the Cloud Foundry space."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
- name: loginParameters
|
||||
type: string
|
||||
description: "Addition command line options for cf login command. No escaping/quoting is performed. Not recommanded for productive environments."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
- name: manifest
|
||||
type: string
|
||||
description: "Defines the manifest to be used for deployment to Cloud Foundry."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cfManifest
|
||||
- name: cloudFoundry/manifest
|
||||
- name: manifestVariables
|
||||
type: "[]string"
|
||||
description: "Defines a list of variables as key-value Map objects used for variable substitution within the file given by manifest. Defaults to an empty list, if not specified otherwise. This can be used to set variables like it is provided by 'cf push --var key=value'. The order of the maps of variables given in the list is relevant in case there are conflicting variable names and value between maps contained within the list. In case of conflicts, the last specified map in the list will win. Though each map entry in the list can contain more than one key-value pair for variable substitution, it is recommended to stick to one entry per map, and rather declare more maps within the list. The reason is that if a map in the list contains more than one key-value entry, and the entries are conflicting, the conflict resolution behavior is undefined (since map entries have no sequence). Note: variables defined via 'manifestVariables' always win over conflicting variables defined via any file given by 'manifestVariablesFiles' - no matter what is declared before. This is the same behavior as can be observed when using 'cf push --var' in combination with 'cf push --vars-file'."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cfManifestVariables
|
||||
- name: cloudFoundry/manifestVariables
|
||||
- name: manifestVariablesFiles
|
||||
type: "[]string"
|
||||
description: "path(s) of the Yaml file(s) containing the variable values to use as a replacement in the manifest file. The order of the files is relevant in case there are conflicting variable names and values within variable files. In such a case, the values of the last file win."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: "manifest-variables.yml"
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cfManifestVariablesFiles
|
||||
- name: cloudFoundry/manifestVariablesFiles
|
||||
- name: mtaDeployParameters
|
||||
type: string
|
||||
description: "Additional parameters passed to mta deployment command"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
default: "-f"
|
||||
- name: mtaExtensionDescriptor
|
||||
type: string
|
||||
description: "Defines additional extension descriptor file for deployment with the mtaDeployPlugin"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
aliases:
|
||||
- name: cloudFoundry/mtaExtensionDescriptor
|
||||
- name: mtaPath
|
||||
type: string
|
||||
description: "Defines the path to *.mtar for deployment with the mtaDeployPlugin"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
resourceRef:
|
||||
- name: commonPipelineEnvironment
|
||||
param: mtarFilePath
|
||||
- name: org
|
||||
type: string
|
||||
description: "Cloud Foundry target organization."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
aliases:
|
||||
- name: cfOrg
|
||||
- name: cloudFoundry/org
|
||||
mandatory: true
|
||||
secret: false
|
||||
- name: password
|
||||
type: string
|
||||
description: "Password"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
secret: true
|
||||
- name: smokeTestScript
|
||||
type: string
|
||||
description: "Allows to specify a script which performs a check during blue-green deployment. The script gets the FQDN as parameter and returns `exit code 0` in case check returned `smokeTestStatusCode`. More details can be found [here](https://github.com/bluemixgaragelondon/cf-blue-green-deploy#how-to-use). Currently this option is only considered for deployTool `cf_native`."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
default: "blueGreenCheckScript.sh"
|
||||
- name: smokeTestStatusCode
|
||||
type: int
|
||||
description: "Expected status code returned by the check."
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: false
|
||||
default: 200
|
||||
- name: space
|
||||
type: string
|
||||
description: "Cloud Foundry target space"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
aliases:
|
||||
- name: cfSpace
|
||||
- name: cloudFoundry/space
|
||||
mandatory: true
|
||||
secret: false
|
||||
- name: username
|
||||
type: string
|
||||
description: "User"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
secret: true
|
||||
containers:
|
||||
- name: cfDeploy
|
||||
image: ppiper/cf-cli
|
||||
imagePullPolicy: Always
|
||||
outputs:
|
||||
resources:
|
||||
- name: influx
|
||||
type: influx
|
||||
params:
|
||||
- name: deployment_data
|
||||
fields:
|
||||
- name: artifactUrl
|
||||
- name: deployTime
|
||||
- name: jobTrigger
|
||||
tags:
|
||||
- name: artifactVersion
|
||||
- name: deployUser
|
||||
- name: deployResult
|
||||
- name: cfApiEndpoint
|
||||
- name: cfOrg
|
||||
- name: cfSpace
|
||||
|
Loading…
x
Reference in New Issue
Block a user