1
0
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:
Marcus Holl 2020-07-31 12:38:00 +02:00 committed by GitHub
parent 4b5fa80980
commit e41e43611b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 2825 additions and 0 deletions

852
cmd/cloudFoundryDeploy.go Normal file
View 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
}

View 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
}

View 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")
}

File diff suppressed because it is too large Load Diff

View File

@ -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
View File

@ -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
View File

@ -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=

View 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