1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-04 04:07:16 +02:00

Generic build step (#3323)

* new step abapEnvironmentBuild

* Update piper.go

* Update abapEnvironmentBuild.go

* update yaml file

* Logging for debugging

* Update abaputils.go

* Update connector.go

* assigning connector

* delete debugging logging

* Update abapEnvironmentBuild.go

* certificate to yaml

* Update abapEnvironmentBuild.go

* add scope

* Update abapEnvironmentBuild.go

* Update abapEnvironmentBuild.yaml

* change certificate name in yaml

* test my new gitscript

* logging for debugging

* debugging...

* adding options to client.

* skip verification

* debugging

* debugging...

* switch of transportskipverification

* changing connector return

* deleting additional set options

* fixed timeout error

* adding certificate

* testing without certificate set

* testing with certificate set

* download, publish and value logic

* write values to cpe

* logging

* adding condition on string length

* change publishmethod and some logging

* change download method -> using references

* evaluation of parameter for download

* add case for empty string

* adding unittests

* Update mockClient.go

* make abapEnvironmentBuildUtilsBundle powerful

* refactor abapEnvironmentBuild into pieces

* check error message

* check error message 2

* check error message 3

* check error message 4

* remove check error message

* cleanup

* adding unittests

* unittests and docu

* docu

* docu

* Update abapEnvironmentBuild.md

* removing trailing spaces and adding empty lines in docu

* Update abapEnvironmentBuild.md

* fixing unittest and PR recommen

* Update abapEnvironmentPipelineStageBuild.groovy

* Update abapEnvironmentPipelineStageBuild.groovy

* Update abapEnvironmentPipelineStageBuild.groovy

* Update abapEnvironmentPipelineStageBuild.groovy

* changes derived from pull request

Co-authored-by: tiloKo <70266685+tiloKo@users.noreply.github.com>
This commit is contained in:
rosemarieB 2021-12-06 14:43:37 +01:00 committed by GitHub
parent 24d8584c3f
commit e90856d5bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1824 additions and 24 deletions

270
cmd/abapEnvironmentBuild.go Normal file
View File

@ -0,0 +1,270 @@
package cmd
import (
"encoding/json"
"time"
abapbuild "github.com/SAP/jenkins-library/pkg/abap/build"
"github.com/SAP/jenkins-library/pkg/abaputils"
"github.com/SAP/jenkins-library/pkg/command"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/pkg/errors"
)
type abapEnvironmentBuildUtils interface {
command.ExecRunner
abaputils.Communication
abapbuild.Publish
abapbuild.HTTPSendLoader
getMaxRuntime() time.Duration
getPollingIntervall() time.Duration
}
type abapEnvironmentBuildUtilsBundle struct {
*command.Command
*piperhttp.Client
*abaputils.AbapUtils
maxRuntime time.Duration
pollingIntervall time.Duration
}
func (aEBUB *abapEnvironmentBuildUtilsBundle) getMaxRuntime() time.Duration {
return aEBUB.maxRuntime
}
func (aEBUB *abapEnvironmentBuildUtilsBundle) getPollingIntervall() time.Duration {
return aEBUB.pollingIntervall
}
func (aEBUB *abapEnvironmentBuildUtilsBundle) PersistReportsAndLinks(stepName, workspace string, reports, links []piperutils.Path) {
abapbuild.PersistReportsAndLinks(stepName, workspace, reports, links)
}
func newAbapEnvironmentBuildUtils(maxRuntime time.Duration, pollingIntervall time.Duration) abapEnvironmentBuildUtils {
utils := abapEnvironmentBuildUtilsBundle{
Command: &command.Command{},
Client: &piperhttp.Client{},
AbapUtils: &abaputils.AbapUtils{
Exec: &command.Command{},
},
maxRuntime: maxRuntime * time.Minute,
pollingIntervall: pollingIntervall * time.Second,
}
// Reroute command output to logging framework
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
}
func abapEnvironmentBuild(config abapEnvironmentBuildOptions, telemetryData *telemetry.CustomData, cpe *abapEnvironmentBuildCommonPipelineEnvironment) {
utils := newAbapEnvironmentBuildUtils(time.Duration(config.MaxRuntimeInMinutes), time.Duration(config.PollingIntervallInSeconds))
if err := runAbapEnvironmentBuild(&config, telemetryData, &utils, cpe); err != nil {
log.Entry().WithError(err).Fatal("step execution failed")
}
}
func runAbapEnvironmentBuild(config *abapEnvironmentBuildOptions, telemetryData *telemetry.CustomData, utils *abapEnvironmentBuildUtils, cpe *abapEnvironmentBuildCommonPipelineEnvironment) error {
values, err := generateValues(config)
if err != nil {
return errors.Wrap(err, "Generating the values from config failed")
}
conn := new(abapbuild.Connector)
if err := initConnection(conn, config, utils); err != nil {
return errors.Wrap(err, "Connector initialization for communication with the ABAP system failed")
}
finalValues, err := runBuild(conn, config, utils, values)
if err != nil {
return errors.Wrap(err, "Error during execution of build framework")
}
cpe.abap.buildValues = finalValues
return nil
}
func initConnection(conn *abapbuild.Connector, config *abapEnvironmentBuildOptions, utils *abapEnvironmentBuildUtils) error {
var connConfig abapbuild.ConnectorConfiguration
connConfig.CfAPIEndpoint = config.CfAPIEndpoint
connConfig.CfOrg = config.CfOrg
connConfig.CfSpace = config.CfSpace
connConfig.CfServiceInstance = config.CfServiceInstance
connConfig.CfServiceKeyName = config.CfServiceKeyName
connConfig.Host = config.Host
connConfig.Username = config.Username
connConfig.Password = config.Password
connConfig.MaxRuntimeInMinutes = config.MaxRuntimeInMinutes
connConfig.CertificateNames = config.CertificateNames
if err := conn.InitBuildFramework(connConfig, *utils, *utils); err != nil {
return err
}
conn.MaxRuntime = (*utils).getMaxRuntime()
conn.PollingInterval = (*utils).getPollingIntervall()
return nil
}
// ***********************************Run Build***************************************************************
func runBuild(conn *abapbuild.Connector, config *abapEnvironmentBuildOptions, utils *abapEnvironmentBuildUtils, values abapbuild.Values) (string, error) {
build := myBuild{
Build: abapbuild.Build{
Connector: *conn,
},
abapEnvironmentBuildOptions: config,
}
if err := build.Start(values); err != nil {
return "", err
}
if err := build.Poll(); err != nil {
return "", errors.Wrap(err, "Error during the polling for the final state of the build run")
}
if err := build.PrintLogs(); err != nil {
return "", errors.Wrap(err, "Error printing the logs")
}
if err := build.EvaluteIfBuildSuccessful(); err != nil {
return "", err
}
if err := build.Download(); err != nil {
return "", err
}
if err := build.Publish(utils); err != nil {
return "", err
}
finalValues, err := build.GetFinalValues()
if err != nil {
return "", err
}
return finalValues, nil
}
type myBuild struct {
abapbuild.Build
*abapEnvironmentBuildOptions
}
func (b *myBuild) Start(values abapbuild.Values) error {
if err := b.Build.Start(b.abapEnvironmentBuildOptions.Phase, values); err != nil {
return errors.Wrap(err, "Error starting the build framework")
}
return nil
}
func (b *myBuild) EvaluteIfBuildSuccessful() error {
if err := b.Build.EvaluteIfBuildSuccessful(b.TreatWarningsAsError); err != nil {
return errors.Wrap(err, "Build ended without success")
}
return nil
}
func (b *myBuild) Download() error {
if b.DownloadAllResultFiles {
if err := b.DownloadAllResults(b.SubDirectoryForDownload, b.FilenamePrefixForDownload); err != nil {
return errors.Wrap(err, "Error during the download of the result files")
}
} else {
if err := b.DownloadResults(b.DownloadResultFilenames, b.SubDirectoryForDownload, b.FilenamePrefixForDownload); err != nil {
return errors.Wrapf(err, "Error during the download of the result files %s", b.DownloadResultFilenames)
}
}
return nil
}
func (b *myBuild) Publish(utils *abapEnvironmentBuildUtils) error {
if b.PublishAllDownloadedResultFiles {
b.PublishAllDownloadedResults("abapEnvironmentBuild", *utils)
} else {
if err := b.PublishDownloadedResults("abapEnvironmentBuild", b.PublishResultFilenames, *utils); err != nil {
return errors.Wrapf(err, "Error during the publish of the result files %s", b.PublishResultFilenames)
}
}
return nil
}
func (b *myBuild) GetFinalValues() (string, error) {
type cpeValue struct {
ValueID string `json:"value_id"`
Value string `json:"value"`
}
if err := b.GetValues(); err != nil {
return "", errors.Wrapf(err, "Error getting the values from build framework")
}
var cpeValues []cpeValue
byt, err := json.Marshal(&b.Build.Values)
if err != nil {
return "", errors.Wrapf(err, "Error converting the values from the build framework")
}
if err := json.Unmarshal(byt, &cpeValues); err != nil {
return "", errors.Wrapf(err, "Error converting the values from the build framework into the structure for the commonPipelineEnvironment")
}
jsonBytes, err := json.Marshal(cpeValues)
if err != nil {
return "", errors.Wrapf(err, "Error converting the converted values")
}
return string(jsonBytes), nil
}
// **********************************Generate Values**************************************************************
func generateValues(config *abapEnvironmentBuildOptions) (abapbuild.Values, error) {
var values abapbuild.Values
vE := valuesEvaluator{}
if err := vE.initialize(config.Values); err != nil {
return values, err
}
if err := vE.appendValues(config.CpeValues); err != nil {
return values, err
}
values.Values = vE.values
return values, nil
}
type valuesEvaluator struct {
values []abapbuild.Value
m map[string]string
}
func (vE *valuesEvaluator) initialize(stringValues string) error {
if err := json.Unmarshal([]byte(stringValues), &vE.values); err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrapf(err, "Could not convert the values %s from the config", stringValues)
}
vE.m = make(map[string]string)
for _, value := range vE.values {
if (len(value.ValueID) == 0) || (len(value.Value) == 0) {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Errorf("Values %s from config have not the right format", stringValues)
}
_, present := vE.m[value.ValueID]
if present {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Errorf("Value_id %s is not unique in the config", value.ValueID)
}
vE.m[value.ValueID] = value.Value
}
return nil
}
func (vE *valuesEvaluator) appendValues(stringValues string) error {
var values []abapbuild.Value
if len(stringValues) > 0 {
if err := json.Unmarshal([]byte(stringValues), &values); err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrapf(err, "Could not convert the values %s from the commonPipelineEnvironment", stringValues)
}
for i := len(values) - 1; i >= 0; i-- {
_, present := vE.m[values[i].ValueID]
if present || (values[i].ValueID == "PHASE") {
log.Entry().Infof("Value %s already exists in config -> discard this value", values[i])
values = append(values[:i], values[i+1:]...)
}
}
vE.values = append(vE.values, values...)
}
return nil
}

View File

@ -0,0 +1,424 @@
// 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/splunk"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/validation"
"github.com/spf13/cobra"
)
type abapEnvironmentBuildOptions struct {
CfAPIEndpoint string `json:"cfApiEndpoint,omitempty"`
CfOrg string `json:"cfOrg,omitempty"`
CfSpace string `json:"cfSpace,omitempty"`
CfServiceInstance string `json:"cfServiceInstance,omitempty"`
CfServiceKeyName string `json:"cfServiceKeyName,omitempty"`
Host string `json:"host,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Phase string `json:"phase,omitempty"`
Values string `json:"values,omitempty"`
DownloadAllResultFiles bool `json:"downloadAllResultFiles,omitempty"`
DownloadResultFilenames []string `json:"downloadResultFilenames,omitempty"`
PublishAllDownloadedResultFiles bool `json:"publishAllDownloadedResultFiles,omitempty"`
PublishResultFilenames []string `json:"publishResultFilenames,omitempty"`
SubDirectoryForDownload string `json:"subDirectoryForDownload,omitempty"`
FilenamePrefixForDownload string `json:"filenamePrefixForDownload,omitempty"`
TreatWarningsAsError bool `json:"treatWarningsAsError,omitempty"`
MaxRuntimeInMinutes int `json:"maxRuntimeInMinutes,omitempty"`
PollingIntervallInSeconds int `json:"pollingIntervallInSeconds,omitempty"`
CertificateNames []string `json:"certificateNames,omitempty"`
CpeValues string `json:"cpeValues,omitempty"`
}
type abapEnvironmentBuildCommonPipelineEnvironment struct {
abap struct {
buildValues string
}
}
func (p *abapEnvironmentBuildCommonPipelineEnvironment) persist(path, resourceName string) {
content := []struct {
category string
name string
value interface{}
}{
{category: "abap", name: "buildValues", value: p.abap.buildValues},
}
errCount := 0
for _, param := range content {
err := piperenv.SetResourceParameter(path, resourceName, filepath.Join(param.category, param.name), param.value)
if err != nil {
log.Entry().WithError(err).Error("Error persisting piper environment.")
errCount++
}
}
if errCount > 0 {
log.Entry().Fatal("failed to persist Piper environment")
}
}
// AbapEnvironmentBuildCommand Executes builds as defined with the build framework
func AbapEnvironmentBuildCommand() *cobra.Command {
const STEP_NAME = "abapEnvironmentBuild"
metadata := abapEnvironmentBuildMetadata()
var stepConfig abapEnvironmentBuildOptions
var startTime time.Time
var commonPipelineEnvironment abapEnvironmentBuildCommonPipelineEnvironment
var logCollector *log.CollectorHook
var splunkClient *splunk.Splunk
telemetryClient := &telemetry.Telemetry{}
var createAbapEnvironmentBuildCmd = &cobra.Command{
Use: STEP_NAME,
Short: "Executes builds as defined with the build framework",
Long: `Executes builds as defined with the build framework. Transaction overview /n/BUILD/OVERVIEW`,
PreRunE: func(cmd *cobra.Command, _ []string) error {
startTime = time.Now()
log.SetStepName(STEP_NAME)
log.SetVerbose(GeneralConfig.Verbose)
GeneralConfig.GitHubAccessTokens = ResolveAccessTokens(GeneralConfig.GitHubTokens)
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.Username)
log.RegisterSecret(stepConfig.Password)
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
log.RegisterHook(&sentryHook)
}
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
splunkClient = &splunk.Splunk{}
logCollector = &log.CollectorHook{CorrelationID: GeneralConfig.CorrelationID}
log.RegisterHook(logCollector)
}
validation, err := validation.New(validation.WithJSONNamesForStructFields(), validation.WithPredefinedErrorMessages())
if err != nil {
return err
}
if err = validation.ValidateStruct(stepConfig); err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return err
}
return nil
},
Run: func(_ *cobra.Command, _ []string) {
stepTelemetryData := telemetry.CustomData{}
stepTelemetryData.ErrorCode = "1"
handler := func() {
config.RemoveVaultSecretFiles()
commonPipelineEnvironment.persist(GeneralConfig.EnvRootPath, "commonPipelineEnvironment")
stepTelemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
stepTelemetryData.ErrorCategory = log.GetErrorCategory().String()
stepTelemetryData.PiperCommitHash = GitCommit
telemetryClient.SetData(&stepTelemetryData)
telemetryClient.Send()
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
splunkClient.Send(telemetryClient.GetData(), logCollector)
}
}
log.DeferExitHandler(handler)
defer handler()
telemetryClient.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
if len(GeneralConfig.HookConfig.SplunkConfig.Dsn) > 0 {
splunkClient.Initialize(GeneralConfig.CorrelationID,
GeneralConfig.HookConfig.SplunkConfig.Dsn,
GeneralConfig.HookConfig.SplunkConfig.Token,
GeneralConfig.HookConfig.SplunkConfig.Index,
GeneralConfig.HookConfig.SplunkConfig.SendLogs)
}
abapEnvironmentBuild(stepConfig, &stepTelemetryData, &commonPipelineEnvironment)
stepTelemetryData.ErrorCode = "0"
log.Entry().Info("SUCCESS")
},
}
addAbapEnvironmentBuildFlags(createAbapEnvironmentBuildCmd, &stepConfig)
return createAbapEnvironmentBuildCmd
}
func addAbapEnvironmentBuildFlags(cmd *cobra.Command, stepConfig *abapEnvironmentBuildOptions) {
cmd.Flags().StringVar(&stepConfig.CfAPIEndpoint, "cfApiEndpoint", os.Getenv("PIPER_cfApiEndpoint"), "Cloud Foundry API endpoint")
cmd.Flags().StringVar(&stepConfig.CfOrg, "cfOrg", os.Getenv("PIPER_cfOrg"), "Cloud Foundry target organization")
cmd.Flags().StringVar(&stepConfig.CfSpace, "cfSpace", os.Getenv("PIPER_cfSpace"), "Cloud Foundry target space")
cmd.Flags().StringVar(&stepConfig.CfServiceInstance, "cfServiceInstance", os.Getenv("PIPER_cfServiceInstance"), "Cloud Foundry Service Instance")
cmd.Flags().StringVar(&stepConfig.CfServiceKeyName, "cfServiceKeyName", os.Getenv("PIPER_cfServiceKeyName"), "Cloud Foundry Service Key")
cmd.Flags().StringVar(&stepConfig.Host, "host", os.Getenv("PIPER_host"), "Specifies the host address of the SAP Cloud Platform ABAP Environment system")
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User")
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password")
cmd.Flags().StringVar(&stepConfig.Phase, "phase", os.Getenv("PIPER_phase"), "Phase as specified in the build script in the backend system")
cmd.Flags().StringVar(&stepConfig.Values, "values", os.Getenv("PIPER_values"), "Input values for the build framework")
cmd.Flags().BoolVar(&stepConfig.DownloadAllResultFiles, "downloadAllResultFiles", false, "If true, all build artefacts are downloaded")
cmd.Flags().StringSliceVar(&stepConfig.DownloadResultFilenames, "downloadResultFilenames", []string{}, "Only the specified files are downloaded, downloadAllResultFiles is true, this parameter is ignored")
cmd.Flags().BoolVar(&stepConfig.PublishAllDownloadedResultFiles, "publishAllDownloadedResultFiles", false, "If true, it publishes all downloaded files")
cmd.Flags().StringSliceVar(&stepConfig.PublishResultFilenames, "publishResultFilenames", []string{}, "Only the specified files get published, in case the file was not downloaded before an error occures")
cmd.Flags().StringVar(&stepConfig.SubDirectoryForDownload, "subDirectoryForDownload", os.Getenv("PIPER_subDirectoryForDownload"), "Target directory to store the downloaded files, {buildID} and {taskID} can be used and will be resolved accordingly")
cmd.Flags().StringVar(&stepConfig.FilenamePrefixForDownload, "filenamePrefixForDownload", os.Getenv("PIPER_filenamePrefixForDownload"), "Filename prefix for the downloaded files, {buildID} and {taskID} can be used and will be resolved accordingly")
cmd.Flags().BoolVar(&stepConfig.TreatWarningsAsError, "treatWarningsAsError", false, "If a warrning occures, the step will be set to unstable")
cmd.Flags().IntVar(&stepConfig.MaxRuntimeInMinutes, "maxRuntimeInMinutes", 360, "maximal runtime of the step in minutes")
cmd.Flags().IntVar(&stepConfig.PollingIntervallInSeconds, "pollingIntervallInSeconds", 60, "wait time in seconds till next status request in the backend system")
cmd.Flags().StringSliceVar(&stepConfig.CertificateNames, "certificateNames", []string{}, "certificates for the backend system, this certificates needs to be stored in .pipeline/trustStore")
cmd.Flags().StringVar(&stepConfig.CpeValues, "cpeValues", os.Getenv("PIPER_cpeValues"), "Values taken from the previous step, if a value was also specified in the config file, the value from cpe will be discarded")
cmd.MarkFlagRequired("username")
cmd.MarkFlagRequired("password")
cmd.MarkFlagRequired("phase")
cmd.MarkFlagRequired("downloadAllResultFiles")
cmd.MarkFlagRequired("publishAllDownloadedResultFiles")
cmd.MarkFlagRequired("treatWarningsAsError")
cmd.MarkFlagRequired("maxRuntimeInMinutes")
cmd.MarkFlagRequired("pollingIntervallInSeconds")
}
// retrieve step metadata
func abapEnvironmentBuildMetadata() config.StepData {
var theMetaData = config.StepData{
Metadata: config.StepMetadata{
Name: "abapEnvironmentBuild",
Aliases: []config.Alias{},
Description: "Executes builds as defined with the build framework",
},
Spec: config.StepSpec{
Inputs: config.StepInputs{
Secrets: []config.StepSecrets{
{Name: "abapCredentialsId", Description: "Jenkins credentials ID containing user and password to authenticate to the Cloud Platform ABAP Environment system or the Cloud Foundry API", Type: "jenkins", Aliases: []config.Alias{{Name: "cfCredentialsId", Deprecated: false}, {Name: "credentialsId", Deprecated: false}}},
},
Parameters: []config.StepParameters{
{
Name: "cfApiEndpoint",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "cloudFoundry/apiEndpoint"}},
Default: os.Getenv("PIPER_cfApiEndpoint"),
},
{
Name: "cfOrg",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "cloudFoundry/org"}},
Default: os.Getenv("PIPER_cfOrg"),
},
{
Name: "cfSpace",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "cloudFoundry/space"}},
Default: os.Getenv("PIPER_cfSpace"),
},
{
Name: "cfServiceInstance",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "cloudFoundry/serviceInstance"}},
Default: os.Getenv("PIPER_cfServiceInstance"),
},
{
Name: "cfServiceKeyName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "cloudFoundry/serviceKey"}, {Name: "cloudFoundry/serviceKeyName"}, {Name: "cfServiceKey"}},
Default: os.Getenv("PIPER_cfServiceKeyName"),
},
{
Name: "host",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_host"),
},
{
Name: "username",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_username"),
},
{
Name: "password",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_password"),
},
{
Name: "phase",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_phase"),
},
{
Name: "values",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_values"),
},
{
Name: "downloadAllResultFiles",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: true,
Aliases: []config.Alias{},
Default: false,
},
{
Name: "downloadResultFilenames",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Default: []string{},
},
{
Name: "publishAllDownloadedResultFiles",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: true,
Aliases: []config.Alias{},
Default: false,
},
{
Name: "publishResultFilenames",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Default: []string{},
},
{
Name: "subDirectoryForDownload",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_subDirectoryForDownload"),
},
{
Name: "filenamePrefixForDownload",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_filenamePrefixForDownload"),
},
{
Name: "treatWarningsAsError",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "bool",
Mandatory: true,
Aliases: []config.Alias{},
Default: false,
},
{
Name: "maxRuntimeInMinutes",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "int",
Mandatory: true,
Aliases: []config.Alias{},
Default: 360,
},
{
Name: "pollingIntervallInSeconds",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "int",
Mandatory: true,
Aliases: []config.Alias{},
Default: 60,
},
{
Name: "certificateNames",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS", "GENERAL"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
Default: []string{},
},
{
Name: "cpeValues",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "abap/buildValues",
},
},
Scope: []string{},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_cpeValues"),
},
},
},
Containers: []config.Container{
{Name: "cf", Image: "ppiper/cf-cli:7"},
},
Outputs: config.StepOutputs{
Resources: []config.StepResources{
{
Name: "commonPipelineEnvironment",
Type: "piperEnvironment",
Parameters: []map[string]interface{}{
{"Name": "abap/buildValues"},
},
},
},
},
},
}
return theMetaData
}

View File

@ -0,0 +1,17 @@
package cmd
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAbapEnvironmentBuildCommand(t *testing.T) {
t.Parallel()
testCmd := AbapEnvironmentBuildCommand()
// only high level testing performed - details are tested in step generation procedure
assert.Equal(t, "abapEnvironmentBuild", testCmd.Use, "command name incorrect")
}

View File

@ -0,0 +1,150 @@
package cmd
import (
"testing"
"time"
abapbuild "github.com/SAP/jenkins-library/pkg/abap/build"
"github.com/SAP/jenkins-library/pkg/abaputils"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/stretchr/testify/assert"
)
type abapEnvironmentBuildMockUtils struct {
*mock.ExecMockRunner
*abapbuild.MockClient
}
func newAbapEnvironmentBuildTestsUtils() abapEnvironmentBuildUtils {
mC := abapbuild.GetBuildMockClient()
utils := abapEnvironmentBuildMockUtils{
ExecMockRunner: &mock.ExecMockRunner{},
MockClient: &mC,
}
return &utils
}
func (mB abapEnvironmentBuildMockUtils) PersistReportsAndLinks(stepName, workspace string, reports, links []piperutils.Path) {
}
func (mB abapEnvironmentBuildMockUtils) GetAbapCommunicationArrangementInfo(options abaputils.AbapEnvironmentOptions, oDataURL string) (abaputils.ConnectionDetailsHTTP, error) {
var cd abaputils.ConnectionDetailsHTTP
cd.URL = "/sap/opu/odata/BUILD/CORE_SRV"
return cd, nil
}
func (mB abapEnvironmentBuildMockUtils) GetPollIntervall() time.Duration {
return 1 * time.Microsecond
}
func (mB abapEnvironmentBuildMockUtils) getMaxRuntime() time.Duration {
return 1 * time.Second
}
func (mB abapEnvironmentBuildMockUtils) getPollingIntervall() time.Duration {
return 1 * time.Microsecond
}
func TestRunAbapEnvironmentBuild(t *testing.T) {
t.Parallel()
t.Run("happy path", func(t *testing.T) {
t.Parallel()
// init
cpe := abapEnvironmentBuildCommonPipelineEnvironment{}
config := abapEnvironmentBuildOptions{}
config.Values = `[{"value_id":"PACKAGES","value":"/BUILD/AUNIT_DUMMY_TESTS"},{"value_id":"MyId1","value":"Value1"}]`
config.DownloadAllResultFiles = true
config.PublishAllDownloadedResultFiles = true
utils := newAbapEnvironmentBuildTestsUtils()
// test
err := runAbapEnvironmentBuild(&config, nil, &utils, &cpe)
// assert
finalValues := `[{"value_id":"PHASE","value":"AUNIT"},{"value_id":"PACKAGES","value":"/BUILD/AUNIT_DUMMY_TESTS"},{"value_id":"MyId1","value":"AunitValue1"},{"value_id":"MyId2","value":"AunitValue2"},{"value_id":"BUILD_FRAMEWORK_MODE","value":"P"}]`
assert.NoError(t, err)
assert.Equal(t, finalValues, cpe.abap.buildValues)
})
t.Run("happy path, download only one", func(t *testing.T) {
t.Parallel()
// init
cpe := abapEnvironmentBuildCommonPipelineEnvironment{}
config := abapEnvironmentBuildOptions{}
config.Values = `[{"value_id":"PACKAGES","value":"/BUILD/AUNIT_DUMMY_TESTS"},{"value_id":"MyId1","value":"Value1"}]`
config.DownloadResultFilenames = []string{"SAR_XML"}
config.PublishResultFilenames = []string{"SAR_XML"}
utils := newAbapEnvironmentBuildTestsUtils()
// test
err := runAbapEnvironmentBuild(&config, nil, &utils, &cpe)
// assert
assert.NoError(t, err)
})
t.Run("error path, try to publish file, which was not downloaded", func(t *testing.T) {
t.Parallel()
// init
cpe := abapEnvironmentBuildCommonPipelineEnvironment{}
config := abapEnvironmentBuildOptions{}
config.Values = `[{"value_id":"PACKAGES","value":"/BUILD/AUNIT_DUMMY_TESTS"},{"value_id":"MyId1","value":"Value1"}]`
config.DownloadResultFilenames = []string{"DELIVERY_LOGS.ZIP"}
config.PublishResultFilenames = []string{"SAR_XML"}
utils := newAbapEnvironmentBuildTestsUtils()
// test
err := runAbapEnvironmentBuild(&config, nil, &utils, &cpe)
// assert
assert.Error(t, err)
})
t.Run("error path, try to download file which does not exist", func(t *testing.T) {
t.Parallel()
// init
cpe := abapEnvironmentBuildCommonPipelineEnvironment{}
config := abapEnvironmentBuildOptions{}
config.Values = `[{"value_id":"PACKAGES","value":"/BUILD/AUNIT_DUMMY_TESTS"},{"value_id":"MyId1","value":"Value1"}]`
config.DownloadResultFilenames = []string{"DOES_NOT_EXIST"}
config.PublishAllDownloadedResultFiles = true
utils := newAbapEnvironmentBuildTestsUtils()
// test
err := runAbapEnvironmentBuild(&config, nil, &utils, &cpe)
// assert
assert.Error(t, err)
})
}
func TestGenerateValues(t *testing.T) {
t.Parallel()
t.Run("happy path", func(t *testing.T) {
t.Parallel()
// init
config := abapEnvironmentBuildOptions{}
config.Values = `[{"value_id":"PACKAGES","value":"/BUILD/AUNIT_DUMMY_TESTS"},{"value_id":"MyId1","value":"Value1"}]`
config.CpeValues = `[{"value_id":"PHASE","value":"AUNIT"},{"value_id":"PACKAGES","value":"CPE_PACKAGE"},{"value_id":"MyId2","value":"Value2"}]`
// test
values, err := generateValues(&config)
// assert
assert.NoError(t, err)
assert.Equal(t, 3, len(values.Values))
assert.Equal(t, "/BUILD/AUNIT_DUMMY_TESTS", values.Values[0].Value)
assert.Equal(t, "Value1", values.Values[1].Value)
assert.Equal(t, "Value2", values.Values[2].Value)
})
t.Run("error path - duplicate in config", func(t *testing.T) {
t.Parallel()
// init
config := abapEnvironmentBuildOptions{}
config.Values = `[{"value_id":"PACKAGES","value":"/BUILD/AUNIT_DUMMY_TESTS"},{"value_id":"MyId1","value":"Value1"},{"value_id":"MyId1","value":"Value1"}]`
// test
values, err := generateValues(&config)
// assert
assert.Error(t, err)
assert.Equal(t, 0, len(values.Values))
})
t.Run("error path - bad formating in config.Values", func(t *testing.T) {
t.Parallel()
// init
config := abapEnvironmentBuildOptions{}
config.Values = `[{"task_id":"PACKAGES","task":"/BUILD/AUNIT_DUMMY_TESTS"},{"value_id":"MyId1","value":"Value1"}]`
// test
_, err := generateValues(&config)
// assert
assert.Error(t, err)
})
}

View File

@ -16,6 +16,7 @@ func GetAllStepMetadata() map[string]config.StepData {
"abapAddonAssemblyKitReserveNextPackages": abapAddonAssemblyKitReserveNextPackagesMetadata(),
"abapEnvironmentAssembleConfirm": abapEnvironmentAssembleConfirmMetadata(),
"abapEnvironmentAssemblePackages": abapEnvironmentAssemblePackagesMetadata(),
"abapEnvironmentBuild": abapEnvironmentBuildMetadata(),
"abapEnvironmentCheckoutBranch": abapEnvironmentCheckoutBranchMetadata(),
"abapEnvironmentCloneGitRepo": abapEnvironmentCloneGitRepoMetadata(),
"abapEnvironmentCreateSystem": abapEnvironmentCreateSystemMetadata(),

View File

@ -128,6 +128,7 @@ func Execute() {
rootCmd.AddCommand(JsonApplyPatchCommand())
rootCmd.AddCommand(KanikoExecuteCommand())
rootCmd.AddCommand(CnbBuildCommand())
rootCmd.AddCommand(AbapEnvironmentBuildCommand())
rootCmd.AddCommand(AbapEnvironmentAssemblePackagesCommand())
rootCmd.AddCommand(AbapAddonAssemblyKitCheckCVsCommand())
rootCmd.AddCommand(AbapAddonAssemblyKitCheckPVCommand())

View File

@ -0,0 +1,86 @@
# ${docGenStepName}
## ${docGenDescription}
## Prerequisites SAP BTP, ABAP environment
* A SAP BTP, ABAP environment system is available.
* This can be created manually on Cloud Foundry.
* In a pipeline, you can do this, for example, with the step [cloudFoundryCreateService](https://sap.github.io/jenkins-library/steps/cloudFoundryCreateService/).
* Communication Scenario [“SAP BTP, ABAP Environment - Software Assembly Integration (SAP_COM_0582)“](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/26b8df5435c649aa8ea7b3688ad5bb0a.html) is setup for this system.
* E.g. a [Communication User](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/0377adea0401467f939827242c1f4014.html), a [Communication System](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/1bfe32ae08074b7186e375ab425fb114.html) and a [Communication Arrangement](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/a0771f6765f54e1c8193ad8582a32edb.html) are configured.
* This can be done manually through the respective applications on the SAP BTP, ABAP environment system,
* or through creating a service key for the system on cloud foundry with the parameters {“scenario_id”: “SAP_COM_0582", “type”: “basic”}.
* In a pipeline, you can do this, for example, with the step [cloudFoundryCreateServiceKey](https://sap.github.io/jenkins-library/steps/cloudFoundryCreateServiceKey/).
* You have following options to provide the ABAP endpoint configuration:
* The host and credentials the SAP BTP, ABAP environment system itself. The credentials must be configured for the Communication Scenario SAP_COM_0582.
* The Cloud Foundry parameters (API endpoint, organization, space), credentials, the service instance for the ABAP service and the service key for the Communication Scenario SAP_COM_0582.
* Only provide one of those options with the respective credentials. If all values are provided, the direct communication (via host) has priority.
## Prerequisites On Premise
* You need to specify the host and credentials to your system
* A certificate for the system needs to be stored in .pipeline/trustStore and the name of the certificate needs to be handed over via the configuration
## ${docGenParameters}
## ${docGenConfiguration}
## ${docJenkinsPluginDependencies}
## Examples
### Configuration in the config.yml
If you want to use this step several time in one pipeline with different phases, the steps have to be put in different stages as it is not allowed to run the same step repeatedly in one stage.
The recommended way to configure your pipeline is via the config.yml file. In this case, calling the step in the Jenkinsfile is reduced to one line:
```groovy
stage('MyPhase') {
steps {
abapEnvironmentBuild script: this
}
}
```
If you want to provide the host and credentials of the Communication Arrangement directly or you want to run in on premise, the configuration could look as follows:
```yaml
stages:
MyPhase:
abapCredentialsId: 'abapCredentialsId',
host: 'https://myABAPendpoint.com',
```
Or by authenticating against Cloud Foundry and reading the Service Key details from there:
```yaml
stages:
MyPhase:
abapCredentialsId: 'cfCredentialsId',
cfApiEndpoint : 'https://test.server.com',
cfOrg : 'cfOrg',
cfSpace: 'cfSpace',
cfServiceInstance: 'myServiceInstance',
cfServiceKeyName: 'myServiceKey',
```
One possible complete config example. Please note that the values are handed over as a string, which has inside a json structure:
```yaml
stages:
MyPhase:
abapCredentialsId: 'abapCredentialsId'
host: 'https://myABAPendpoint.com'
certificateNames: ['myCert.cer']
phase: 'MyPhase'
values: '[{"value_id":"ID1","value":"Value1"},{"value_id":"ID2","value":"Value2"}]'
downloadResultFilenames: ['File1','File2']
publishResultFilenames: ['File2']
subDirectoryForDownload: 'MyDir'
filenamePrefixForDownload: 'MyPrefix'
treatWarningsAsError: true
maxRuntimeInMinutes: 360
pollingIntervallInSeconds: 15
```

View File

@ -57,6 +57,7 @@ nav:
- abapAddonAssemblyKitRegisterPackages: steps/abapAddonAssemblyKitRegisterPackages.md
- abapAddonAssemblyKitReleasePackages: steps/abapAddonAssemblyKitReleasePackages.md
- abapAddonAssemblyKitReserveNextPackages: steps/abapAddonAssemblyKitReserveNextPackages.md
- abapEnvironmentBuild: steps/abapEnvironmentBuild.md
- abapEnvironmentAssemblePackages: steps/abapEnvironmentAssemblePackages.md
- abapEnvironmentAssembleConfirm: steps/abapEnvironmentAssembleConfirm.md
- abapEnvironmentCheckoutBranch: steps/abapEnvironmentCheckoutBranch.md

View File

@ -2,11 +2,17 @@ package build
import (
"encoding/json"
"errors"
"fmt"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/pkg/errors"
)
// RunState : Current Status of the Build
@ -28,11 +34,12 @@ const (
// Finished : Build Framework ended successful
Finished RunState = "FINISHED"
// Failed : Build Framework endded with error
Failed RunState = "FAILED"
loginfo msgty = "I"
logwarning msgty = "W"
logerror msgty = "E"
logaborted msgty = "A"
Failed RunState = "FAILED"
loginfo msgty = "I"
logwarning msgty = "W"
logerror msgty = "E"
logaborted msgty = "A"
dummyResultName string = "Dummy"
)
//******** structs needed for json convertion ********
@ -113,6 +120,8 @@ type Result struct {
Name string `json:"name"`
AdditionalInfo string `json:"additional_info"`
Mimetype string `json:"mimetype"`
SavedFilename string
DownloadPath string
}
// Value : Returns Build Runtime Value
@ -165,6 +174,39 @@ func (b *Build) Start(phase string, inputValues Values) error {
return nil
}
// Poll : waits for the build framework to be finished
func (b *Build) Poll() error {
timeout := time.After(b.Connector.MaxRuntime)
ticker := time.Tick(b.Connector.PollingInterval)
for {
select {
case <-timeout:
return errors.Errorf("Timed out: (max Runtime %v reached)", b.Connector.MaxRuntime)
case <-ticker:
b.Get()
if !b.IsFinished() {
log.Entry().Infof("Build is not yet finished, check again in %s", b.Connector.PollingInterval)
} else {
return nil
}
}
}
}
// EvaluteIfBuildSuccessful : Checks the finale state of the build framework
func (b *Build) EvaluteIfBuildSuccessful(treatWarningsAsError bool) error {
if b.RunState == Failed {
return errors.Errorf("Build ended with runState failed")
}
if treatWarningsAsError && b.ResultState == warning {
return errors.Errorf("Build ended with resultState warning, setting to failed as configured")
}
if (b.ResultState == aborted) || (b.ResultState == erroneous) {
return errors.Errorf("Build ended with resultState %s", b.ResultState)
}
return nil
}
// Get : Get all Build tasks
func (b *Build) Get() error {
appendum := "/builds('" + b.BuildID + "')"
@ -204,7 +246,8 @@ func (b *Build) getTasks() error {
return nil
}
func (b *Build) getValues() error {
// GetValues : Gets all Build values
func (b *Build) GetValues() error {
if len(b.Values) == 0 {
appendum := "/builds('" + b.BuildID + "')/values"
body, err := b.Connector.Get(appendum)
@ -246,7 +289,8 @@ func (b *Build) PrintLogs() error {
return nil
}
func (b *Build) getResults() error {
// GetResults : Gets all Build results
func (b *Build) GetResults() error {
if err := b.getTasks(); err != nil {
return err
}
@ -269,26 +313,26 @@ func (t *task) printLogs() error {
}
// GetResult : Returns the last Build artefact created from build step
func (b *Build) GetResult(name string) (Result, error) {
var Results []Result
func (b *Build) GetResult(name string) (*Result, error) {
var Results []*Result
var returnResult Result
if err := b.getResults(); err != nil {
return returnResult, err
if err := b.GetResults(); err != nil {
return &returnResult, err
}
for _, task := range b.Tasks {
for _, result := range task.Results {
if result.Name == name {
Results = append(Results, result)
for i_task := range b.Tasks {
for i_result := range b.Tasks[i_task].Results {
if b.Tasks[i_task].Results[i_result].Name == name {
Results = append(Results, &b.Tasks[i_task].Results[i_result])
}
}
}
switch len(Results) {
case 0:
return returnResult, errors.New("No result named " + name + " was found")
return &returnResult, errors.New("No result named " + name + " was found")
case 1:
return Results[0], nil
default:
return returnResult, errors.New("More than one result with the name " + name + " was found")
return &returnResult, errors.New("More than one result with the name " + name + " was found")
}
}
@ -329,12 +373,83 @@ func (t *task) getResults() error {
}
if len(t.Results) == 0 {
//prevent 2nd GET request - no new results will occure...
t.Results = append(t.Results, Result{Name: "Dummy"})
t.Results = append(t.Results, Result{Name: dummyResultName})
}
}
return nil
}
// DownloadAllResults : Downloads all build artefacts, saves it to basePath and the filenames can be modified with the filenamePrefix
func (b *Build) DownloadAllResults(basePath string, filenamePrefix string) error {
if err := b.GetResults(); err != nil {
return err
}
for i_task := range b.Tasks {
//in case there was no result, there is only one entry with dummyResultName, obviously we don't want to download this
if b.Tasks[i_task].Results[0].Name != dummyResultName {
for i_result := range b.Tasks[i_task].Results {
if err := b.Tasks[i_task].Results[i_result].DownloadWithFilenamePrefixAndTargetDirectory(basePath, filenamePrefix); err != nil {
return errors.Wrapf(err, "Error during the download of file %s", b.Tasks[i_task].Results[i_result].Name)
}
}
}
}
return nil
}
// DownloadResults : Download results which are specified in filenames
func (b *Build) DownloadResults(filenames []string, basePath string, filenamePrefix string) error {
for _, name := range filenames {
result, err := b.GetResult(name)
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrapf(err, "Problems finding the file %s, please check your config whether this file is really a result file", name)
}
if err := result.DownloadWithFilenamePrefixAndTargetDirectory(basePath, filenamePrefix); err != nil {
return errors.Wrapf(err, "Error during the download of file %s", name)
}
}
return nil
}
// PublishAllDownloadedResults : publishes all build artefacts which were downloaded before
func (b *Build) PublishAllDownloadedResults(stepname string, publish Publish) {
var filesToPublish []piperutils.Path
for i_task := range b.Tasks {
for i_result := range b.Tasks[i_task].Results {
if b.Tasks[i_task].Results[i_result].wasDownloaded() {
filesToPublish = append(filesToPublish, piperutils.Path{Target: b.Tasks[i_task].Results[i_result].DownloadPath,
Name: b.Tasks[i_task].Results[i_result].SavedFilename, Mandatory: true})
}
}
}
if len(filesToPublish) > 0 {
publish.PersistReportsAndLinks(stepname, "", filesToPublish, nil)
}
}
// PublishDownloadedResults : Publishes build artefacts specified in filenames
func (b *Build) PublishDownloadedResults(stepname string, filenames []string, publish Publish) error {
var filesToPublish []piperutils.Path
for i := range filenames {
result, err := b.GetResult(filenames[i])
if err != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Wrapf(err, "Problems finding the file %s, please check your config whether this file is really a result file", filenames[i])
}
if result.wasDownloaded() {
filesToPublish = append(filesToPublish, piperutils.Path{Target: result.DownloadPath, Name: result.SavedFilename, Mandatory: true})
} else {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Errorf("Trying to publish the file %s which was not downloaded", result.Name)
}
}
if len(filesToPublish) > 0 {
publish.PersistReportsAndLinks(stepname, "", filesToPublish, nil)
}
return nil
}
// Download : Provides the atrefact of build step
func (result *Result) Download(downloadPath string) error {
appendum := fmt.Sprint("/results(build_id='", result.BuildID, "',task_id=", result.TaskID, ",name='", result.Name, "')/$value")
@ -342,6 +457,57 @@ func (result *Result) Download(downloadPath string) error {
return err
}
// DownloadWithFilenamePrefixAndTargetDirectory : downloads build artefact, saves it to basePath and the filename can be modified with the filenamePrefix
func (result *Result) DownloadWithFilenamePrefixAndTargetDirectory(basePath string, filenamePrefix string) error {
basePath, err := result.resolveParamter(basePath)
if err != nil {
return errors.Wrapf(err, "Could not resolve parameter %s for the target directory", basePath)
}
filenamePrefix, err = result.resolveParamter(filenamePrefix)
if err != nil {
return errors.Wrapf(err, "Could not resolve parameter %s for the filename prefix", filenamePrefix)
}
appendum := fmt.Sprint("/results(build_id='", result.BuildID, "',task_id=", result.TaskID, ",name='", result.Name, "')/$value")
filename := filenamePrefix + result.Name
downloadPath := filepath.Join(path.Base(basePath), path.Base(filename))
if err := result.connector.Download(appendum, downloadPath); err != nil {
log.SetErrorCategory(log.ErrorInfrastructure)
return errors.Wrapf(err, "Could not download %s", result.Name)
}
result.SavedFilename = filename
result.DownloadPath = downloadPath
log.Entry().Infof("Saved file %s as %s to %s", result.Name, result.SavedFilename, result.DownloadPath)
return nil
}
func (result *Result) resolveParamter(parameter string) (string, error) {
if len(parameter) == 0 {
return parameter, nil
}
if (string(parameter[0]) == "{") && string(parameter[len(parameter)-1]) == "}" {
trimmedParam := strings.ToLower(parameter[1 : len(parameter)-1])
switch trimmedParam {
case "buildid":
return result.BuildID, nil
case "taskid":
return strconv.Itoa(result.TaskID), nil
default:
log.SetErrorCategory(log.ErrorConfiguration)
return "", errors.Errorf("Unknown parameter %s", parameter)
}
} else {
return parameter, nil
}
}
func (result *Result) wasDownloaded() bool {
if len(result.DownloadPath) > 0 && len(result.SavedFilename) > 0 {
return true
} else {
return false
}
}
func (logging *logStruct) print() {
switch logging.Msgty {
case loginfo:
@ -376,3 +542,12 @@ func (vs Values) String() string {
func (in inputForPost) String() string {
return fmt.Sprintf(`{ "phase": "%s", "values": [%s]}`, in.phase, in.values.String())
}
// *****************publish *******************************
type Publish interface {
PersistReportsAndLinks(stepName, workspace string, reports, links []piperutils.Path)
}
func PersistReportsAndLinks(stepName, workspace string, reports, links []piperutils.Path) {
piperutils.PersistReportsAndLinks(stepName, workspace, reports, links)
}

View File

@ -343,3 +343,44 @@ var responseGetValues = `{
]
}
}`
func GetMockBuildTestDownloadPublish() Build {
conn := new(Connector)
conn.DownloadClient = &DownloadClientMock{}
results0 := []Result{
{
connector: *conn,
Name: dummyResultName,
},
}
results1 := []Result{
{
connector: *conn,
Name: "File1",
},
{
connector: *conn,
Name: "File2",
},
{
connector: *conn,
Name: "File3",
},
}
build := Build{
BuildID: "123",
Tasks: []task{
{
TaskID: 0,
Results: results0,
},
{
TaskID: 1,
Results: results1,
},
},
}
return build
}

View File

@ -1,12 +1,24 @@
package build
import (
"path"
"path/filepath"
"testing"
"time"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/stretchr/testify/assert"
)
type mockPublish struct {
reports []piperutils.Path
}
func (mP *mockPublish) PersistReportsAndLinks(stepName, workspace string, reports, links []piperutils.Path) {
mP.reports = reports
}
func testSetup(client piperhttp.Sender, buildID string) Build {
conn := new(Connector)
conn.Client = client
@ -82,7 +94,7 @@ func TestGetValues(t *testing.T) {
t.Run("Run getValues", func(t *testing.T) {
b := testSetup(&ClMock{}, "ABIFNLDCSQPOVMXK4DNPBDRW2M")
assert.Equal(t, 0, len(b.Values))
err := b.getValues()
err := b.GetValues()
assert.NoError(t, err)
assert.Equal(t, 4, len(b.Values))
assert.Equal(t, "PHASE", b.Values[0].ValueID)
@ -99,7 +111,7 @@ func TestGetValues(t *testing.T) {
func TestGetResults(t *testing.T) {
t.Run("Run getResults", func(t *testing.T) {
b := testSetup(&ClMock{}, "ABIFNLDCSQPOVMXK4DNPBDRW2M")
err := b.getResults()
err := b.GetResults()
assert.NoError(t, err)
assert.Equal(t, 1, len(b.Tasks[0].Results))
assert.Equal(t, 2, len(b.Tasks[1].Results))
@ -113,3 +125,321 @@ func TestGetResults(t *testing.T) {
assert.NoError(t, err)
})
}
func TestPoll(t *testing.T) {
//arrange global
build := new(Build)
conn := new(Connector)
conn.MaxRuntime = time.Duration(1 * time.Second)
conn.PollingInterval = time.Duration(1 * time.Microsecond)
conn.Baseurl = "/sap/opu/odata/BUILD/CORE_SRV"
t.Run("Normal Poll", func(t *testing.T) {
//arrange
build.BuildID = "AKO22FYOFYPOXHOBVKXUTX3A3Q"
mc := NewMockClient()
mc.AddData(buildGet1)
mc.AddData(buildGet2)
conn.Client = &mc
build.Connector = *conn
//act
err := build.Poll()
//assert
assert.NoError(t, err)
})
t.Run("Poll runstate failed", func(t *testing.T) {
//arrange
build.BuildID = "AKO22FYOFYPOXHOBVKXUTX3A3Q"
mc := NewMockClient()
mc.AddData(buildGet1)
mc.AddData(buildGetRunStateFailed)
conn.Client = &mc
build.Connector = *conn
//act
err := build.Poll()
//assert
assert.NoError(t, err)
})
t.Run("Poll timeout", func(t *testing.T) {
//arrange
build.BuildID = "AKO22FYOFYPOXHOBVKXUTX3A3Q"
conn.MaxRuntime = time.Duration(2 * time.Microsecond)
conn.PollingInterval = time.Duration(1 * time.Microsecond)
mc := NewMockClient()
mc.AddData(buildGet1)
mc.AddData(buildGet1)
mc.AddData(buildGet2)
conn.Client = &mc
build.Connector = *conn
//act
err := build.Poll()
//assert
assert.Error(t, err)
})
}
func TestEvaluteIfBuildSuccessful(t *testing.T) {
//arrange global
build := new(Build)
treatWarningsAsError := false
t.Run("No error", func(t *testing.T) {
//arrange
build.RunState = Finished
build.ResultState = successful
//act
err := build.EvaluteIfBuildSuccessful(treatWarningsAsError)
//assert
assert.NoError(t, err)
})
t.Run("RunState failed => Error", func(t *testing.T) {
//arrange
build.RunState = Failed
//act
err := build.EvaluteIfBuildSuccessful(treatWarningsAsError)
//assert
assert.Error(t, err)
})
t.Run("ResultState aborted => Error", func(t *testing.T) {
//arrange
build.RunState = Finished
build.ResultState = aborted
//act
err := build.EvaluteIfBuildSuccessful(treatWarningsAsError)
//assert
assert.Error(t, err)
})
t.Run("ResultState erroneous => Error", func(t *testing.T) {
//arrange
build.RunState = Finished
build.ResultState = erroneous
//act
err := build.EvaluteIfBuildSuccessful(treatWarningsAsError)
//assert
assert.Error(t, err)
})
t.Run("ResultState warning, treatWarningsAsError false => No error", func(t *testing.T) {
//arrange
build.RunState = Finished
build.ResultState = warning
//act
err := build.EvaluteIfBuildSuccessful(treatWarningsAsError)
//assert
assert.NoError(t, err)
})
t.Run("ResultState warning, treatWarningsAsError true => error", func(t *testing.T) {
//arrange
build.RunState = Finished
build.ResultState = warning
treatWarningsAsError = true
//act
err := build.EvaluteIfBuildSuccessful(treatWarningsAsError)
//assert
assert.Error(t, err)
})
}
func TestDownloadWithFilenamePrefixAndTargetDirectory(t *testing.T) {
//arrange global
result := new(Result)
result.BuildID = "123456789"
result.TaskID = 1
result.Name = "MyFile"
conn := new(Connector)
conn.DownloadClient = &DownloadClientMock{}
result.connector = *conn
t.Run("Download without extension", func(t *testing.T) {
//arrange
basePath := ""
filenamePrefix := ""
//act
err := result.DownloadWithFilenamePrefixAndTargetDirectory(basePath, filenamePrefix)
//assert
assert.NoError(t, err)
assert.Equal(t, "MyFile", result.SavedFilename)
assert.Equal(t, "MyFile", result.DownloadPath)
})
t.Run("Download with extensions", func(t *testing.T) {
//arrange
basePath := "MyDir"
filenamePrefix := "SuperFile_"
//act
err := result.DownloadWithFilenamePrefixAndTargetDirectory(basePath, filenamePrefix)
//assert
assert.NoError(t, err)
assert.Equal(t, "SuperFile_MyFile", result.SavedFilename)
downloadPath := filepath.Join(path.Base(basePath), path.Base("SuperFile_MyFile"))
assert.Equal(t, downloadPath, result.DownloadPath)
})
t.Run("Download with parameter", func(t *testing.T) {
//arrange
basePath := "{BuildID}"
filenamePrefix := "{taskid}"
//act
err := result.DownloadWithFilenamePrefixAndTargetDirectory(basePath, filenamePrefix)
//assert
assert.NoError(t, err)
assert.Equal(t, "1MyFile", result.SavedFilename)
downloadPath := filepath.Join(path.Base("123456789"), path.Base("1MyFile"))
assert.Equal(t, downloadPath, result.DownloadPath)
})
}
func TestDownloadAllResults(t *testing.T) {
//arrange global
build := GetMockBuildTestDownloadPublish()
t.Run("Download without extension", func(t *testing.T) {
//arrange
basePath := ""
filenamePrefix := ""
//act
err := build.DownloadAllResults(basePath, filenamePrefix)
//assert
assert.NoError(t, err)
assert.Equal(t, "", build.Tasks[0].Results[0].SavedFilename)
assert.Equal(t, "", build.Tasks[0].Results[0].DownloadPath)
assert.Equal(t, "File1", build.Tasks[1].Results[0].SavedFilename)
assert.Equal(t, "File1", build.Tasks[1].Results[0].DownloadPath)
assert.Equal(t, "File2", build.Tasks[1].Results[1].SavedFilename)
assert.Equal(t, "File2", build.Tasks[1].Results[1].DownloadPath)
})
t.Run("Download with extension", func(t *testing.T) {
//arrange
basePath := ""
filenamePrefix := "SuperFile_"
//act
err := build.DownloadAllResults(basePath, filenamePrefix)
//assert
assert.NoError(t, err)
assert.Equal(t, "", build.Tasks[0].Results[0].SavedFilename)
assert.Equal(t, "", build.Tasks[0].Results[0].DownloadPath)
assert.Equal(t, "SuperFile_File1", build.Tasks[1].Results[0].SavedFilename)
assert.Equal(t, "SuperFile_File1", build.Tasks[1].Results[0].DownloadPath)
assert.Equal(t, "SuperFile_File2", build.Tasks[1].Results[1].SavedFilename)
assert.Equal(t, "SuperFile_File2", build.Tasks[1].Results[1].DownloadPath)
})
}
func TestDownloadResults(t *testing.T) {
//arrange global
build := GetMockBuildTestDownloadPublish()
t.Run("Download existing", func(t *testing.T) {
//arrange
basePath := ""
filenamePrefix := ""
filenames := []string{"File1", "File3"}
//act
err := build.DownloadResults(filenames, basePath, filenamePrefix)
//assert
assert.NoError(t, err)
assert.Equal(t, "", build.Tasks[0].Results[0].SavedFilename)
assert.Equal(t, "", build.Tasks[0].Results[0].DownloadPath)
assert.Equal(t, "File1", build.Tasks[1].Results[0].SavedFilename)
assert.Equal(t, "File1", build.Tasks[1].Results[0].DownloadPath)
assert.Equal(t, "", build.Tasks[1].Results[1].SavedFilename)
assert.Equal(t, "", build.Tasks[1].Results[1].DownloadPath)
assert.Equal(t, "File3", build.Tasks[1].Results[2].SavedFilename)
assert.Equal(t, "File3", build.Tasks[1].Results[2].DownloadPath)
})
t.Run("Try to download non existing", func(t *testing.T) {
//arrange
basePath := ""
filenamePrefix := ""
filenames := []string{"File1", "File4"}
//act
err := build.DownloadResults(filenames, basePath, filenamePrefix)
//assert
assert.Error(t, err)
assert.Equal(t, "", build.Tasks[0].Results[0].SavedFilename)
assert.Equal(t, "", build.Tasks[0].Results[0].DownloadPath)
assert.Equal(t, "File1", build.Tasks[1].Results[0].SavedFilename)
assert.Equal(t, "File1", build.Tasks[1].Results[0].DownloadPath)
assert.Equal(t, "", build.Tasks[1].Results[1].SavedFilename)
assert.Equal(t, "", build.Tasks[1].Results[1].DownloadPath)
})
}
func TestPublishAllDownloadedResults(t *testing.T) {
t.Run("Something was downloaded", func(t *testing.T) {
//arrange
build := GetMockBuildTestDownloadPublish()
mP := mockPublish{}
build.Tasks[1].Results[0].SavedFilename = "File1"
build.Tasks[1].Results[0].DownloadPath = "Dir1/File1"
build.Tasks[1].Results[2].SavedFilename = "File3"
build.Tasks[1].Results[2].DownloadPath = "File3"
//act
build.PublishAllDownloadedResults("MyStep", &mP)
//assert
publishedFiles := []piperutils.Path{
{
Target: "Dir1/File1",
Name: "File1",
Mandatory: true,
},
{
Target: "File3",
Name: "File3",
Mandatory: true,
},
}
assert.Equal(t, publishedFiles, mP.reports)
})
t.Run("Nothing was downloaded", func(t *testing.T) {
//arrange
build := GetMockBuildTestDownloadPublish()
mP := mockPublish{}
//act
build.PublishAllDownloadedResults("MyStep", &mP)
//assert
assert.Equal(t, 0, len(mP.reports))
})
}
func TestPublishDownloadedResults(t *testing.T) {
filenames := []string{"File1", "File3"}
t.Run("Publish downloaded files", func(t *testing.T) {
//arrange
build := GetMockBuildTestDownloadPublish()
mP := mockPublish{}
build.Tasks[1].Results[0].SavedFilename = "SuperFile_File1"
build.Tasks[1].Results[0].DownloadPath = "Dir1/SuperFile_File1"
build.Tasks[1].Results[2].SavedFilename = "File3"
build.Tasks[1].Results[2].DownloadPath = "File3"
//act
err := build.PublishDownloadedResults("MyStep", filenames, &mP)
//assert
assert.NoError(t, err)
publishedFiles := []piperutils.Path{
{
Target: "Dir1/SuperFile_File1",
Name: "SuperFile_File1",
Mandatory: true,
},
{
Target: "File3",
Name: "File3",
Mandatory: true,
},
}
assert.Equal(t, publishedFiles, mP.reports)
})
t.Run("Try to publish file which was not downloaded", func(t *testing.T) {
//arrange
build := GetMockBuildTestDownloadPublish()
mP := mockPublish{}
build.Tasks[1].Results[0].SavedFilename = "SuperFile_File1"
build.Tasks[1].Results[0].DownloadPath = "Dir1/SuperFile_File1"
//act
err := build.PublishDownloadedResults("MyStep", filenames, &mP)
//assert
assert.Error(t, err)
})
}

View File

@ -36,6 +36,7 @@ type ConnectorConfiguration struct {
Password string
AddonDescriptor string
MaxRuntimeInMinutes int
CertificateNames []string
}
// HTTPSendLoader : combine both interfaces [sender, downloader]
@ -170,9 +171,10 @@ func (conn *Connector) InitBuildFramework(config ConnectorConfiguration, com aba
})
cookieJar, _ := cookiejar.New(nil)
conn.Client.SetOptions(piperhttp.ClientOptions{
Username: connectionDetails.User,
Password: connectionDetails.Password,
CookieJar: cookieJar,
Username: connectionDetails.User,
Password: connectionDetails.Password,
CookieJar: cookieJar,
TrustedCerts: config.CertificateNames,
})
conn.Baseurl = connectionDetails.URL

View File

@ -143,6 +143,7 @@ func GetBuildMockClient() MockClient {
mc.AddData(buildGetTask11Result)
mc.AddData(buildGetTask12Result)
mc.AddData(buildGetTask11ResultMedia)
mc.AddData(buildGetValues)
return mc
}
@ -254,6 +255,39 @@ var buildGet2 = MockData{
StatusCode: 200,
}
var buildGetRunStateFailed = MockData{
Method: `GET`,
Url: `/sap/opu/odata/BUILD/CORE_SRV/builds('AKO22FYOFYPOXHOBVKXUTX3A3Q')`,
Body: `{
"d" : {
"__metadata" : {
"id" : "https://7aa9d1a3-a876-464e-b59a-f26104452461.abap.stagingaws.hanavlab.ondemand.com/sap/opu/odata/BUILD/CORE_SRV/builds('AKO22FYOFYPOXHOBVKXUTX3A3Q')",
"uri" : "https://7aa9d1a3-a876-464e-b59a-f26104452461.abap.stagingaws.hanavlab.ondemand.com/sap/opu/odata/BUILD/CORE_SRV/builds('AKO22FYOFYPOXHOBVKXUTX3A3Q')",
"type" : "BUILD.CORE_SRV.xBUILDxVIEW_BUILDSType"
},
"build_id" : "AKO22FYOFYPOXHOBVKXUTX3A3Q",
"run_state" : "FAILED",
"result_state" : "SUCCESSFUL",
"phase" : "BUILD_AOI",
"entitytype" : "C",
"startedby" : "CC0000000001",
"started_at" : "\/Date(1614108520862+0000)\/",
"finished_at" : "\/Date(1614108535350+0000)\/",
"tasks" : {
"__deferred" : {
"uri" : "https://7aa9d1a3-a876-464e-b59a-f26104452461.abap.stagingaws.hanavlab.ondemand.com/sap/opu/odata/BUILD/CORE_SRV/builds('AKO22FYOFYPOXHOBVKXUTX3A3Q')/tasks"
}
},
"values" : {
"__deferred" : {
"uri" : "https://7aa9d1a3-a876-464e-b59a-f26104452461.abap.stagingaws.hanavlab.ondemand.com/sap/opu/odata/BUILD/CORE_SRV/builds('AKO22FYOFYPOXHOBVKXUTX3A3Q')/values"
}
}
}
}`,
StatusCode: 200,
}
var buildGetTasks = MockData{
Method: `GET`,
Url: `/sap/opu/odata/BUILD/CORE_SRV/builds('AKO22FYOFYPOXHOBVKXUTX3A3Q')/tasks`,
@ -1513,6 +1547,43 @@ var buildGetTask11ResultMedia = MockData{
StatusCode: 200,
}
var buildGetValues = MockData{
Method: `GET`,
Url: `/sap/opu/odata/BUILD/CORE_SRV/builds('AKO22FYOFYPOXHOBVKXUTX3A3Q')/values`,
Body: `{
"d": {
"results": [
{
"build_id": "AKO22FYOFYPOXHOBVKXUTX3A3Q",
"value_id": "PHASE",
"value": "AUNIT"
},
{
"build_id": "AKO22FYOFYPOXHOBVKXUTX3A3Q",
"value_id": "PACKAGES",
"value": "/BUILD/AUNIT_DUMMY_TESTS"
},
{
"build_id": "AKO22FYOFYPOXHOBVKXUTX3A3Q",
"value_id": "MyId1",
"value": "AunitValue1"
},
{
"build_id": "AKO22FYOFYPOXHOBVKXUTX3A3Q",
"value_id": "MyId2",
"value": "AunitValue2"
},
{
"build_id": "AKO22FYOFYPOXHOBVKXUTX3A3Q",
"value_id": "BUILD_FRAMEWORK_MODE",
"value": "P"
}
]
}
}`,
StatusCode: 200,
}
var template = MockData{
Method: `GET`,
Url: ``,

View File

@ -0,0 +1,219 @@
metadata:
name: abapEnvironmentBuild
description: "Executes builds as defined with the build framework"
longDescription: |
Executes builds as defined with the build framework. Transaction overview /n/BUILD/OVERVIEW
spec:
inputs:
secrets:
- name: abapCredentialsId
description: Jenkins credentials ID containing user and password to authenticate to the Cloud Platform ABAP Environment system or the Cloud Foundry API
type: jenkins
aliases:
- name: cfCredentialsId
- name: credentialsId
params:
- name: cfApiEndpoint
type: string
description: Cloud Foundry API endpoint
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
mandatory: false
aliases:
- name: cloudFoundry/apiEndpoint
- name: cfOrg
type: string
description: Cloud Foundry target organization
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
mandatory: false
aliases:
- name: cloudFoundry/org
- name: cfSpace
type: string
description: Cloud Foundry target space
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
mandatory: false
aliases:
- name: cloudFoundry/space
- name: cfServiceInstance
type: string
description: Cloud Foundry Service Instance
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
mandatory: false
aliases:
- name: cloudFoundry/serviceInstance
- name: cfServiceKeyName
type: string
description: Cloud Foundry Service Key
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
mandatory: false
aliases:
- name: cloudFoundry/serviceKey
- name: cloudFoundry/serviceKeyName
- name: cfServiceKey
- name: host
description: Specifies the host address of the SAP Cloud Platform ABAP Environment system
type: string
mandatory: false
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: username
type: string
description: User
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
secret: true
- name: password
type: string
description: Password
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: true
secret: true
- name: phase
type: string
mandatory: true
description: Phase as specified in the build script in the backend system
scope:
- PARAMETERS
- STAGES
- STEPS
- name: values
type: string
description: Input values for the build framework
scope:
- PARAMETERS
- STAGES
- STEPS
mandatory: false
- name: downloadAllResultFiles
type: bool
mandatory: true
default: false
description: If true, all build artefacts are downloaded
scope:
- PARAMETERS
- STAGES
- STEPS
- name: downloadResultFilenames
type: "[]string"
mandatory: false
description: Only the specified files are downloaded, downloadAllResultFiles is true, this parameter is ignored
scope:
- PARAMETERS
- STAGES
- STEPS
- name: publishAllDownloadedResultFiles
type: bool
mandatory: true
default: false
description: If true, it publishes all downloaded files
scope:
- PARAMETERS
- STAGES
- STEPS
- name: publishResultFilenames
type: "[]string"
mandatory: false
description: Only the specified files get published, in case the file was not downloaded before an error occures
scope:
- PARAMETERS
- STAGES
- STEPS
- name: subDirectoryForDownload
type: string
mandatory: false
description: Target directory to store the downloaded files, {buildID} and {taskID} can be used and will be resolved accordingly
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: filenamePrefixForDownload
type: string
mandatory: false
description: Filename prefix for the downloaded files, {buildID} and {taskID} can be used and will be resolved accordingly
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: treatWarningsAsError
type: bool
mandatory: true
default: false
description: If a warrning occures, the step will be set to unstable
scope:
- PARAMETERS
- STAGES
- STEPS
- name: maxRuntimeInMinutes
type: int
description: maximal runtime of the step in minutes
mandatory: true
default: 360
scope:
- PARAMETERS
- STAGES
- STEPS
- name: pollingIntervallInSeconds
type: int
description: wait time in seconds till next status request in the backend system
mandatory: true
default: 60
scope:
- PARAMETERS
- STAGES
- STEPS
- name: certificateNames
type: "[]string"
description: certificates for the backend system, this certificates needs to be stored in .pipeline/trustStore
mandatory: false
scope:
- PARAMETERS
- STAGES
- STEPS
- GENERAL
- name: cpeValues
type: string
description: Values taken from the previous step, if a value was also specified in the config file, the value from cpe will be discarded
mandatory: false
resourceRef:
- name: commonPipelineEnvironment
param: abap/buildValues
outputs:
resources:
- name: commonPipelineEnvironment
type: piperEnvironment
params:
- name: abap/buildValues
containers:
- name: cf
image: ppiper/cf-cli:7

View File

@ -113,6 +113,7 @@ public class CommonStepsTest extends BasePiperTest{
'abapAddonAssemblyKitRegisterPackages', //implementing new golang pattern without fields
'abapAddonAssemblyKitReleasePackages', //implementing new golang pattern without fields
'abapAddonAssemblyKitReserveNextPackages', //implementing new golang pattern without fields
'abapEnvironmentBuild', //implementing new golang pattern without fields
'abapEnvironmentAssemblePackages', //implementing new golang pattern without fields
'abapEnvironmentAssembleConfirm', //implementing new golang pattern without fields
'abapEnvironmentCheckoutBranch', //implementing new golang pattern without fields

View File

@ -0,0 +1,11 @@
import groovy.transform.Field
@Field String STEP_NAME = getClass().getName()
@Field String METADATA_FILE = 'metadata/abapEnvironmentBuild.yaml'
void call(Map parameters = [:]) {
List credentials = [
[type: 'usernamePassword', id: 'abapCredentialsId', env: ['PIPER_username', 'PIPER_password']]
]
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, credentials, false, false, true)
}