mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
Merge remote-tracking branch 'upstream/master' into harmonize-docker-arguments
This commit is contained in:
commit
1dbe7352c7
@ -1,3 +1,7 @@
|
||||
version: "2"
|
||||
checks:
|
||||
return-statements:
|
||||
enabled: false
|
||||
plugins:
|
||||
codenarc:
|
||||
enabled: true
|
||||
|
@ -22,6 +22,6 @@ indent_size = none
|
||||
[cfg/id_rsa.enc]
|
||||
indent_style = none
|
||||
indent_size = none
|
||||
[{go.mod,go.sum,*.go}]
|
||||
[{go.mod,go.sum,*.go,*.golden}]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
41
.github/CONTRIBUTING.md
vendored
41
.github/CONTRIBUTING.md
vendored
@ -1,5 +1,13 @@
|
||||
# Guidance on how to contribute
|
||||
|
||||
**Table of contents:**
|
||||
|
||||
1. [Using the issue tracker](#using-the-issue-tracker)
|
||||
1. [Changing the code-base](#changing-the-code-base)
|
||||
1. [Jenkins credential handling](#jenkins-credentials)
|
||||
1. [Code Style](#code-style)
|
||||
1. [References](#references)
|
||||
|
||||
There are two primary ways to help:
|
||||
|
||||
* Using the issue tracker, and
|
||||
@ -36,14 +44,6 @@ Implementation of a functionality and its documentation shall happen within the
|
||||
|
||||
Pipeline steps must not make use of return values. The pattern for sharing parameters between pipeline steps or between a pipeline step and a pipeline script is sharing values via the [`commonPipelineEnvironment`](../vars/commonPipelineEnvironment.groovy). Since there is no return value from a pipeline step the return value of a pipeline step is already `void` rather than `def`.
|
||||
|
||||
### Code Style
|
||||
|
||||
The code should follow any stylistic and architectural guidelines prescribed by the project. In the absence of guidelines, mimic the styles and patterns in the existing code-base.
|
||||
|
||||
Variables, methods, types and so on shall have meaningful self describing names. Doing so makes understanding code easier and requires less commenting. It helps people who did not write the code to understand it better.
|
||||
|
||||
Code shall contain comments to explain the intention of the code when it is unclear what the intention of the author was. In such cases, comments should describe the "why" and not the "what" (that is in the code already).
|
||||
|
||||
#### EditorConfig
|
||||
|
||||
To ensure a common file format, there is a `.editorConfig` file [in place](../.editorconfig). To respect this file, [check](http://editorconfig.org/#download) if your editor does support it natively or you need to download a plugin.
|
||||
@ -54,8 +54,25 @@ Write [meaningful commit messages](http://who-t.blogspot.de/2009/12/on-commit-me
|
||||
|
||||
Good commit messages speed up the review process and help to keep this project maintainable in the long term.
|
||||
|
||||
## Jenkins credential handling
|
||||
|
||||
References to Jenkins credentials should have meaningful names.
|
||||
|
||||
We are using the following approach for naming Jenkins credentials:
|
||||
|
||||
For username/password credentials:
|
||||
`<tool>CredentialsId` like e.g. `neoCredentialsId`
|
||||
|
||||
For other cases we add further information to the name like:
|
||||
|
||||
* `gitSshCredentialsId` for ssh credentials
|
||||
* `githubTokenCredentialsId`for token/string credentials
|
||||
* `gcpFileCredentialsId` for file credentials
|
||||
|
||||
## Code Style
|
||||
|
||||
Generally, the code should follow any stylistic and architectural guidelines prescribed by the project. In the absence of guidelines, mimic the styles and patterns in the existing code-base.
|
||||
|
||||
The intention of this section is to describe the code style for this project. As reference document, the [Groovy's style guide](http://groovy-lang.org/style-guide.html) was taken. For further reading about Groovy's syntax and examples, please refer to this guide.
|
||||
|
||||
This project is intended to run in Jenkins [[2]](https://jenkins.io/doc/book/getting-started/) as part of a Jenkins Pipeline [[3]](https://jenkins.io/doc/book/pipeline/). It is composed by Jenkins Pipeline's syntax, Groovy's syntax and Java's syntax.
|
||||
@ -64,6 +81,12 @@ Some Groovy's syntax is not yet supported by Jenkins. It is also the intention o
|
||||
|
||||
As Groovy supports 99% of Java’s syntax [[1]](http://groovy-lang.org/style-guide.html), many Java developers tend to write Groovy code using Java's syntax. Such a developer should also consider the following code style for this project.
|
||||
|
||||
### General remarks
|
||||
|
||||
Variables, methods, types and so on shall have meaningful self describing names. Doing so makes understanding code easier and requires less commenting. It helps people who did not write the code to understand it better.
|
||||
|
||||
Code shall contain comments to explain the intention of the code when it is unclear what the intention of the author was. In such cases, comments should describe the "why" and not the "what" (that is in the code already).
|
||||
|
||||
### Omit semicolons
|
||||
|
||||
### Use the return keyword
|
||||
@ -177,7 +200,7 @@ If the type of the exception thrown inside a try block is not important, catch a
|
||||
|
||||
To check parameters, return values, and more, use the assert statement.
|
||||
|
||||
## Reference
|
||||
## References
|
||||
|
||||
[1] Groovy's syntax: [http://groovy-lang.org/style-guide.html](http://groovy-lang.org/style-guide.html)
|
||||
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -19,3 +19,5 @@ documentation/docs-gen
|
||||
consumer-test/**/workspace
|
||||
|
||||
*.code-workspace
|
||||
piper
|
||||
piper.exe
|
||||
|
@ -8,6 +8,8 @@ RUN go test ./... -cover
|
||||
## ONLY tests so far, building to be added later
|
||||
# execute build
|
||||
# RUN go build -o piper
|
||||
RUN export GIT_COMMIT=$(git rev-parse HEAD) && \
|
||||
go build -ldflags "-X github.com/SAP/jenkins-library/cmd.GitCommit=${GIT_COMMIT}" -o piper
|
||||
|
||||
# FROM gcr.io/distroless/base:latest
|
||||
# COPY --from=build-env /build/piper /piper
|
||||
|
120
cmd/getConfig.go
Normal file
120
cmd/getConfig.go
Normal file
@ -0,0 +1,120 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type configCommandOptions struct {
|
||||
output string //output format, so far only JSON
|
||||
parametersJSON string //parameters to be considered in JSON format
|
||||
stepMetadata string //metadata to be considered, can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
|
||||
stepName string
|
||||
contextConfig bool
|
||||
openFile func(s string) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
var configOptions configCommandOptions
|
||||
|
||||
// ConfigCommand is the entry command for loading the configuration of a pipeline step
|
||||
func ConfigCommand() *cobra.Command {
|
||||
|
||||
configOptions.openFile = openPiperFile
|
||||
var createConfigCmd = &cobra.Command{
|
||||
Use: "getConfig",
|
||||
Short: "Loads the project 'Piper' configuration respecting defaults and parameters.",
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
return generateConfig()
|
||||
},
|
||||
}
|
||||
|
||||
addConfigFlags(createConfigCmd)
|
||||
return createConfigCmd
|
||||
}
|
||||
|
||||
func generateConfig() error {
|
||||
|
||||
var myConfig config.Config
|
||||
var stepConfig config.StepConfig
|
||||
|
||||
var metadata config.StepData
|
||||
metadataFile, err := configOptions.openFile(configOptions.stepMetadata)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "metadata: open failed")
|
||||
}
|
||||
|
||||
err = metadata.ReadPipelineStepData(metadataFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "metadata: read failed")
|
||||
}
|
||||
|
||||
customConfig, err := configOptions.openFile(generalConfig.customConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "config: open failed")
|
||||
}
|
||||
|
||||
defaultConfig, paramFilter, err := defaultsAndFilters(&metadata)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "defaults: retrieving step defaults failed")
|
||||
}
|
||||
|
||||
for _, f := range generalConfig.defaultConfig {
|
||||
fc, err := configOptions.openFile(f)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "config: getting defaults failed: '%v'", f)
|
||||
}
|
||||
defaultConfig = append(defaultConfig, fc)
|
||||
}
|
||||
|
||||
var flags map[string]interface{}
|
||||
|
||||
params := []config.StepParameters{}
|
||||
if !configOptions.contextConfig {
|
||||
params = metadata.Spec.Inputs.Parameters
|
||||
}
|
||||
|
||||
stepConfig, err = myConfig.GetStepConfig(flags, generalConfig.parametersJSON, customConfig, defaultConfig, paramFilter, params, generalConfig.stageName, configOptions.stepName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting step config failed")
|
||||
}
|
||||
|
||||
//ToDo: Check for mandatory parameters
|
||||
|
||||
myConfigJSON, _ := config.GetJSON(stepConfig.Config)
|
||||
|
||||
fmt.Println(myConfigJSON)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addConfigFlags(cmd *cobra.Command) {
|
||||
|
||||
//ToDo: support more output options, like https://kubernetes.io/docs/reference/kubectl/overview/#formatting-output
|
||||
cmd.Flags().StringVar(&configOptions.output, "output", "json", "Defines the output format")
|
||||
|
||||
cmd.Flags().StringVar(&configOptions.parametersJSON, "parametersJSON", os.Getenv("PIPER_parametersJSON"), "Parameters to be considered in JSON format")
|
||||
cmd.Flags().StringVar(&configOptions.stepMetadata, "stepMetadata", "", "Step metadata, passed as path to yaml")
|
||||
cmd.Flags().StringVar(&configOptions.stepName, "stepName", "", "Name of the step for which configuration should be included")
|
||||
cmd.Flags().BoolVar(&configOptions.contextConfig, "contextConfig", false, "Defines if step context configuration should be loaded instead of step config")
|
||||
|
||||
cmd.MarkFlagRequired("stepMetadata")
|
||||
cmd.MarkFlagRequired("stepName")
|
||||
|
||||
}
|
||||
|
||||
func defaultsAndFilters(metadata *config.StepData) ([]io.ReadCloser, config.StepFilters, error) {
|
||||
if configOptions.contextConfig {
|
||||
defaults, err := metadata.GetContextDefaults(configOptions.stepName)
|
||||
if err != nil {
|
||||
return nil, config.StepFilters{}, errors.Wrap(err, "metadata: getting context defaults failed")
|
||||
}
|
||||
return []io.ReadCloser{defaults}, metadata.GetContextParameterFilters(), nil
|
||||
}
|
||||
//ToDo: retrieve default values from metadata
|
||||
return nil, metadata.GetParameterFilters(), nil
|
||||
}
|
90
cmd/getConfig_test.go
Normal file
90
cmd/getConfig_test.go
Normal file
@ -0,0 +1,90 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func configOpenFileMock(name string) (io.ReadCloser, error) {
|
||||
var r string
|
||||
switch name {
|
||||
case "TestAddCustomDefaults_default1":
|
||||
r = "default1"
|
||||
case "TestAddCustomDefaults_default2":
|
||||
r = "default3"
|
||||
default:
|
||||
r = ""
|
||||
}
|
||||
return ioutil.NopCloser(strings.NewReader(r)), nil
|
||||
}
|
||||
|
||||
func TestConfigCommand(t *testing.T) {
|
||||
cmd := ConfigCommand()
|
||||
|
||||
gotReq := []string{}
|
||||
gotOpt := []string{}
|
||||
|
||||
cmd.Flags().VisitAll(func(pflag *flag.Flag) {
|
||||
annotations, found := pflag.Annotations[cobra.BashCompOneRequiredFlag]
|
||||
if found && annotations[0] == "true" {
|
||||
gotReq = append(gotReq, pflag.Name)
|
||||
} else {
|
||||
gotOpt = append(gotOpt, pflag.Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Required flags", func(t *testing.T) {
|
||||
exp := []string{"stepMetadata", "stepName"}
|
||||
assert.Equal(t, exp, gotReq, "required flags incorrect")
|
||||
})
|
||||
|
||||
t.Run("Optional flags", func(t *testing.T) {
|
||||
exp := []string{"contextConfig", "output", "parametersJSON"}
|
||||
assert.Equal(t, exp, gotOpt, "optional flags incorrect")
|
||||
})
|
||||
|
||||
t.Run("Run", func(t *testing.T) {
|
||||
t.Run("Success case", func(t *testing.T) {
|
||||
configOptions.openFile = configOpenFileMock
|
||||
err := cmd.RunE(cmd, []string{})
|
||||
assert.NoError(t, err, "error occured but none expected")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDefaultsAndFilters(t *testing.T) {
|
||||
metadata := config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "paramOne", Scope: []string{"GENERAL", "STEPS", "STAGES", "PARAMETERS", "ENV"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Context config", func(t *testing.T) {
|
||||
configOptions.contextConfig = true
|
||||
defer func() { configOptions.contextConfig = false }()
|
||||
defaults, filters, err := defaultsAndFilters(&metadata)
|
||||
|
||||
assert.Equal(t, 1, len(defaults), "getting defaults failed")
|
||||
assert.Equal(t, 0, len(filters.All), "wrong number of filter values")
|
||||
assert.NoError(t, err, "error occured but none expected")
|
||||
})
|
||||
|
||||
t.Run("Step config", func(t *testing.T) {
|
||||
defaults, filters, err := defaultsAndFilters(&metadata)
|
||||
assert.Equal(t, 0, len(defaults), "getting defaults failed")
|
||||
assert.Equal(t, 1, len(filters.All), "wrong number of filter values")
|
||||
assert.NoError(t, err, "error occured but none expected")
|
||||
})
|
||||
|
||||
}
|
11
cmd/interfaces.go
Normal file
11
cmd/interfaces.go
Normal file
@ -0,0 +1,11 @@
|
||||
package cmd
|
||||
|
||||
type execRunner interface {
|
||||
RunExecutable(e string, p ...string) error
|
||||
Dir(d string)
|
||||
}
|
||||
|
||||
type shellRunner interface {
|
||||
RunShell(s string, c string) error
|
||||
Dir(d string)
|
||||
}
|
44
cmd/karmaExecuteTests.go
Normal file
44
cmd/karmaExecuteTests.go
Normal file
@ -0,0 +1,44 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/command"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
)
|
||||
|
||||
func karmaExecuteTests(myKarmaExecuteTestsOptions karmaExecuteTestsOptions) error {
|
||||
c := command.Command{}
|
||||
// reroute command output to loging framework
|
||||
// also log stdout as Karma reports into it
|
||||
c.Stdout = log.Entry().Writer()
|
||||
c.Stderr = log.Entry().Writer()
|
||||
runKarma(myKarmaExecuteTestsOptions, &c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runKarma(myKarmaExecuteTestsOptions karmaExecuteTestsOptions, command execRunner) {
|
||||
installCommandTokens := tokenize(myKarmaExecuteTestsOptions.InstallCommand)
|
||||
command.Dir(myKarmaExecuteTestsOptions.ModulePath)
|
||||
err := command.RunExecutable(installCommandTokens[0], installCommandTokens[1:]...)
|
||||
if err != nil {
|
||||
log.Entry().
|
||||
WithError(err).
|
||||
WithField("command", myKarmaExecuteTestsOptions.InstallCommand).
|
||||
Fatal("failed to execute install command")
|
||||
}
|
||||
|
||||
runCommandTokens := tokenize(myKarmaExecuteTestsOptions.RunCommand)
|
||||
command.Dir(myKarmaExecuteTestsOptions.ModulePath)
|
||||
err = command.RunExecutable(runCommandTokens[0], runCommandTokens[1:]...)
|
||||
if err != nil {
|
||||
log.Entry().
|
||||
WithError(err).
|
||||
WithField("command", myKarmaExecuteTestsOptions.RunCommand).
|
||||
Fatal("failed to execute run command")
|
||||
}
|
||||
}
|
||||
|
||||
func tokenize(command string) []string {
|
||||
return strings.Split(command, " ")
|
||||
}
|
90
cmd/karmaExecuteTests_generated.go
Normal file
90
cmd/karmaExecuteTests_generated.go
Normal file
@ -0,0 +1,90 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
//"os"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type karmaExecuteTestsOptions struct {
|
||||
InstallCommand string `json:"installCommand,omitempty"`
|
||||
ModulePath string `json:"modulePath,omitempty"`
|
||||
RunCommand string `json:"runCommand,omitempty"`
|
||||
}
|
||||
|
||||
var myKarmaExecuteTestsOptions karmaExecuteTestsOptions
|
||||
var karmaExecuteTestsStepConfigJSON string
|
||||
|
||||
// KarmaExecuteTestsCommand Executes the Karma test runner
|
||||
func KarmaExecuteTestsCommand() *cobra.Command {
|
||||
metadata := karmaExecuteTestsMetadata()
|
||||
var createKarmaExecuteTestsCmd = &cobra.Command{
|
||||
Use: "karmaExecuteTests",
|
||||
Short: "Executes the Karma test runner",
|
||||
Long: `In this step the ([Karma test runner](http://karma-runner.github.io)) is executed.
|
||||
|
||||
The step is using the ` + "`" + `seleniumExecuteTest` + "`" + ` step to spin up two containers in a Docker network:
|
||||
|
||||
* a Selenium/Chrome container (` + "`" + `selenium/standalone-chrome` + "`" + `)
|
||||
* a NodeJS container (` + "`" + `node:8-stretch` + "`" + `)
|
||||
|
||||
In the Docker network, the containers can be referenced by the values provided in ` + "`" + `dockerName` + "`" + ` and ` + "`" + `sidecarName` + "`" + `, the default values are ` + "`" + `karma` + "`" + ` and ` + "`" + `selenium` + "`" + `. These values must be used in the ` + "`" + `hostname` + "`" + ` properties of the test configuration ([Karma](https://karma-runner.github.io/1.0/config/configuration-file.html) and [WebDriver](https://github.com/karma-runner/karma-webdriver-launcher#usage)).
|
||||
|
||||
!!! note
|
||||
In a Kubernetes environment, the containers both need to be referenced with ` + "`" + `localhost` + "`" + `.`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
log.SetStepName("karmaExecuteTests")
|
||||
log.SetVerbose(generalConfig.verbose)
|
||||
return PrepareConfig(cmd, &metadata, "karmaExecuteTests", &myKarmaExecuteTestsOptions, openPiperFile)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return karmaExecuteTests(myKarmaExecuteTestsOptions)
|
||||
},
|
||||
}
|
||||
|
||||
addKarmaExecuteTestsFlags(createKarmaExecuteTestsCmd)
|
||||
return createKarmaExecuteTestsCmd
|
||||
}
|
||||
|
||||
func addKarmaExecuteTestsFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringVar(&myKarmaExecuteTestsOptions.InstallCommand, "installCommand", "npm install --quiet", "The command that is executed to install the test tool.")
|
||||
cmd.Flags().StringVar(&myKarmaExecuteTestsOptions.ModulePath, "modulePath", ".", "Define the path of the module to execute tests on.")
|
||||
cmd.Flags().StringVar(&myKarmaExecuteTestsOptions.RunCommand, "runCommand", "npm run karma", "The command that is executed to start the tests.")
|
||||
|
||||
cmd.MarkFlagRequired("installCommand")
|
||||
cmd.MarkFlagRequired("modulePath")
|
||||
cmd.MarkFlagRequired("runCommand")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func karmaExecuteTestsMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "installCommand",
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
{
|
||||
Name: "modulePath",
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
{
|
||||
Name: "runCommand",
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
16
cmd/karmaExecuteTests_generated_test.go
Normal file
16
cmd/karmaExecuteTests_generated_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKarmaExecuteTestsCommand(t *testing.T) {
|
||||
|
||||
testCmd := KarmaExecuteTestsCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "karmaExecuteTests", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
46
cmd/karmaExecuteTests_test.go
Normal file
46
cmd/karmaExecuteTests_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRunKarma(t *testing.T) {
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
opts := karmaExecuteTestsOptions{ModulePath: "./test", InstallCommand: "npm install test", RunCommand: "npm run test"}
|
||||
|
||||
e := execMockRunner{}
|
||||
runKarma(opts, &e)
|
||||
|
||||
assert.Equal(t, e.dir[0], "./test", "install command dir incorrect")
|
||||
assert.Equal(t, e.calls[0], execCall{exec: "npm", params: []string{"install", "test"}}, "install command/params incorrect")
|
||||
|
||||
assert.Equal(t, e.dir[1], "./test", "run command dir incorrect")
|
||||
assert.Equal(t, e.calls[1], execCall{exec: "npm", params: []string{"run", "test"}}, "run command/params incorrect")
|
||||
|
||||
})
|
||||
|
||||
t.Run("error case install command", func(t *testing.T) {
|
||||
var hasFailed bool
|
||||
log.Entry().Logger.ExitFunc = func(int) { hasFailed = true }
|
||||
|
||||
opts := karmaExecuteTestsOptions{ModulePath: "./test", InstallCommand: "fail install test", RunCommand: "npm run test"}
|
||||
|
||||
e := execMockRunner{}
|
||||
runKarma(opts, &e)
|
||||
assert.True(t, hasFailed, "expected command to exit with fatal")
|
||||
})
|
||||
|
||||
t.Run("error case run command", func(t *testing.T) {
|
||||
var hasFailed bool
|
||||
log.Entry().Logger.ExitFunc = func(int) { hasFailed = true }
|
||||
|
||||
opts := karmaExecuteTestsOptions{ModulePath: "./test", InstallCommand: "npm install test", RunCommand: "fail run test"}
|
||||
|
||||
e := execMockRunner{}
|
||||
runKarma(opts, &e)
|
||||
assert.True(t, hasFailed, "expected command to exit with fatal")
|
||||
})
|
||||
}
|
109
cmd/piper.go
Normal file
109
cmd/piper.go
Normal file
@ -0,0 +1,109 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type generalConfigOptions struct {
|
||||
customConfig string
|
||||
defaultConfig []string //ordered list of Piper default configurations. Can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
|
||||
parametersJSON string
|
||||
stageName string
|
||||
stepConfigJSON string
|
||||
stepMetadata string //metadata to be considered, can be filePath or ENV containing JSON in format 'ENV:MY_ENV_VAR'
|
||||
stepName string
|
||||
verbose bool
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "piper",
|
||||
Short: "Executes CI/CD steps from project 'Piper' ",
|
||||
Long: `
|
||||
This project 'Piper' binary provides a CI/CD step libary.
|
||||
It contains many steps which can be used within CI/CD systems as well as directly on e.g. a developer's machine.
|
||||
`,
|
||||
//ToDo: respect stageName to also come from parametersJSON -> first env.STAGE_NAME, second: parametersJSON, third: flag
|
||||
}
|
||||
|
||||
var generalConfig generalConfigOptions
|
||||
|
||||
// Execute is the starting point of the piper command line tool
|
||||
func Execute() {
|
||||
|
||||
rootCmd.AddCommand(ConfigCommand())
|
||||
rootCmd.AddCommand(VersionCommand())
|
||||
rootCmd.AddCommand(KarmaExecuteTestsCommand())
|
||||
|
||||
addRootFlags(rootCmd)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func addRootFlags(rootCmd *cobra.Command) {
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&generalConfig.customConfig, "customConfig", ".pipeline/config.yml", "Path to the pipeline configuration file")
|
||||
rootCmd.PersistentFlags().StringSliceVar(&generalConfig.defaultConfig, "defaultConfig", nil, "Default configurations, passed as path to yaml file")
|
||||
rootCmd.PersistentFlags().StringVar(&generalConfig.parametersJSON, "parametersJSON", os.Getenv("PIPER_parametersJSON"), "Parameters to be considered in JSON format")
|
||||
rootCmd.PersistentFlags().StringVar(&generalConfig.stageName, "stageName", os.Getenv("STAGE_NAME"), "Name of the stage for which configuration should be included")
|
||||
rootCmd.PersistentFlags().StringVar(&generalConfig.stepConfigJSON, "stepConfigJSON", os.Getenv("PIPER_stepConfigJSON"), "Step configuration in JSON format")
|
||||
rootCmd.PersistentFlags().BoolVarP(&generalConfig.verbose, "verbose", "v", false, "verbose output")
|
||||
|
||||
}
|
||||
|
||||
// PrepareConfig reads step configuration from various sources and merges it (defaults, config file, flags, ...)
|
||||
func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName string, options interface{}, openFile func(s string) (io.ReadCloser, error)) error {
|
||||
|
||||
filters := metadata.GetParameterFilters()
|
||||
|
||||
flagValues := config.AvailableFlagValues(cmd, &filters)
|
||||
|
||||
var myConfig config.Config
|
||||
var stepConfig config.StepConfig
|
||||
|
||||
if len(generalConfig.stepConfigJSON) != 0 {
|
||||
// ignore config & defaults in favor of passed stepConfigJSON
|
||||
stepConfig = config.GetStepConfigWithJSON(flagValues, generalConfig.stepConfigJSON, filters)
|
||||
} else {
|
||||
// use config & defaults
|
||||
|
||||
//accept that config file and defaults cannot be loaded since both are not mandatory here
|
||||
customConfig, _ := openFile(generalConfig.customConfig)
|
||||
var defaultConfig []io.ReadCloser
|
||||
for _, f := range generalConfig.defaultConfig {
|
||||
//ToDo: support also https as source
|
||||
fc, _ := openFile(f)
|
||||
defaultConfig = append(defaultConfig, fc)
|
||||
}
|
||||
|
||||
var err error
|
||||
stepConfig, err = myConfig.GetStepConfig(flagValues, generalConfig.parametersJSON, customConfig, defaultConfig, filters, metadata.Spec.Inputs.Parameters, generalConfig.stageName, stepName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "retrieving step configuration failed")
|
||||
}
|
||||
}
|
||||
|
||||
confJSON, _ := json.Marshal(stepConfig.Config)
|
||||
json.Unmarshal(confJSON, &options)
|
||||
|
||||
config.MarkFlagsWithValue(cmd, stepConfig)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func openPiperFile(name string) (io.ReadCloser, error) {
|
||||
//ToDo: support also https as source
|
||||
if !strings.HasPrefix(name, "http") {
|
||||
return os.Open(name)
|
||||
}
|
||||
return nil, fmt.Errorf("file location not yet supported for '%v'", name)
|
||||
}
|
148
cmd/piper_test.go
Normal file
148
cmd/piper_test.go
Normal file
@ -0,0 +1,148 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type execMockRunner struct {
|
||||
dir []string
|
||||
calls []execCall
|
||||
}
|
||||
|
||||
type execCall struct {
|
||||
exec string
|
||||
params []string
|
||||
}
|
||||
|
||||
type shellMockRunner struct {
|
||||
dir string
|
||||
calls []string
|
||||
}
|
||||
|
||||
func (m *execMockRunner) Dir(d string) {
|
||||
m.dir = append(m.dir, d)
|
||||
}
|
||||
|
||||
func (m *execMockRunner) RunExecutable(e string, p ...string) error {
|
||||
if e == "fail" {
|
||||
return fmt.Errorf("error case")
|
||||
}
|
||||
exec := execCall{exec: e, params: p}
|
||||
m.calls = append(m.calls, exec)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *shellMockRunner) Dir(d string) {
|
||||
m.dir = d
|
||||
}
|
||||
|
||||
func (m *shellMockRunner) RunShell(s string, c string) error {
|
||||
m.calls = append(m.calls, c)
|
||||
return nil
|
||||
}
|
||||
|
||||
type stepOptions struct {
|
||||
TestParam string `json:"testParam,omitempty"`
|
||||
}
|
||||
|
||||
func openFileMock(name string) (io.ReadCloser, error) {
|
||||
var r string
|
||||
switch name {
|
||||
case "testDefaults.yml":
|
||||
r = "general:\n testParam: testValue"
|
||||
case "testDefaultsInvalid.yml":
|
||||
r = "invalid yaml"
|
||||
default:
|
||||
r = ""
|
||||
}
|
||||
return ioutil.NopCloser(strings.NewReader(r)), nil
|
||||
}
|
||||
|
||||
func TestAddRootFlags(t *testing.T) {
|
||||
var testRootCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
|
||||
addRootFlags(testRootCmd)
|
||||
|
||||
assert.NotNil(t, testRootCmd.Flag("customConfig"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("defaultConfig"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("parametersJSON"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("stageName"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("stepConfigJSON"), "expected flag not available")
|
||||
assert.NotNil(t, testRootCmd.Flag("verbose"), "expected flag not available")
|
||||
|
||||
}
|
||||
|
||||
func TestPrepareConfig(t *testing.T) {
|
||||
defaultsBak := generalConfig.defaultConfig
|
||||
generalConfig.defaultConfig = []string{"testDefaults.yml"}
|
||||
defer func() { generalConfig.defaultConfig = defaultsBak }()
|
||||
|
||||
t.Run("using stepConfigJSON", func(t *testing.T) {
|
||||
stepConfigJSONBak := generalConfig.stepConfigJSON
|
||||
generalConfig.stepConfigJSON = `{"testParam": "testValueJSON"}`
|
||||
defer func() { generalConfig.stepConfigJSON = stepConfigJSONBak }()
|
||||
testOptions := stepOptions{}
|
||||
var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
|
||||
testCmd.Flags().StringVar(&testOptions.TestParam, "testParam", "", "test usage")
|
||||
metadata := config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "testParam", Scope: []string{"GENERAL"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
PrepareConfig(testCmd, &metadata, "testStep", &testOptions, openFileMock)
|
||||
assert.Equal(t, "testValueJSON", testOptions.TestParam, "wrong value retrieved from config")
|
||||
})
|
||||
|
||||
t.Run("using config files", func(t *testing.T) {
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
testOptions := stepOptions{}
|
||||
var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
|
||||
testCmd.Flags().StringVar(&testOptions.TestParam, "testParam", "", "test usage")
|
||||
metadata := config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "testParam", Scope: []string{"GENERAL"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := PrepareConfig(testCmd, &metadata, "testStep", &testOptions, openFileMock)
|
||||
assert.NoError(t, err, "no error expected but error occured")
|
||||
|
||||
//assert config
|
||||
assert.Equal(t, "testValue", testOptions.TestParam, "wrong value retrieved from config")
|
||||
|
||||
//assert that flag has been marked as changed
|
||||
testCmd.Flags().VisitAll(func(pflag *flag.Flag) {
|
||||
if pflag.Name == "testParam" {
|
||||
assert.True(t, pflag.Changed, "flag should be marked as changed")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("error case", func(t *testing.T) {
|
||||
generalConfig.defaultConfig = []string{"testDefaultsInvalid.yml"}
|
||||
testOptions := stepOptions{}
|
||||
var testCmd = &cobra.Command{Use: "test", Short: "This is just a test"}
|
||||
metadata := config.StepData{}
|
||||
|
||||
err := PrepareConfig(testCmd, &metadata, "testStep", &testOptions, openFileMock)
|
||||
assert.Error(t, err, "error expected but none occured")
|
||||
})
|
||||
})
|
||||
}
|
28
cmd/version.go
Normal file
28
cmd/version.go
Normal file
@ -0,0 +1,28 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GitCommit ...
|
||||
var GitCommit string
|
||||
|
||||
// GitTag ...
|
||||
var GitTag string
|
||||
|
||||
func version(myVersionOptions versionOptions) error {
|
||||
|
||||
gitCommit, gitTag := "<n/a>", "<n/a>"
|
||||
|
||||
if len(GitCommit) > 0 {
|
||||
gitCommit = GitCommit
|
||||
}
|
||||
|
||||
if len(GitTag) > 0 {
|
||||
gitTag = GitTag
|
||||
}
|
||||
|
||||
_, err := fmt.Printf("piper-version:\n commit: \"%s\"\n tag: \"%s\"\n", gitCommit, gitTag)
|
||||
|
||||
return err
|
||||
}
|
52
cmd/version_generated.go
Normal file
52
cmd/version_generated.go
Normal file
@ -0,0 +1,52 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
//"os"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type versionOptions struct {
|
||||
}
|
||||
|
||||
var myVersionOptions versionOptions
|
||||
var versionStepConfigJSON string
|
||||
|
||||
// VersionCommand Returns the version of the piper binary
|
||||
func VersionCommand() *cobra.Command {
|
||||
metadata := versionMetadata()
|
||||
var createVersionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Returns the version of the piper binary",
|
||||
Long: `Writes the commit hash and the tag (if any) to stdout and exits with 0.`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
log.SetStepName("version")
|
||||
log.SetVerbose(generalConfig.verbose)
|
||||
return PrepareConfig(cmd, &metadata, "version", &myVersionOptions, openPiperFile)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return version(myVersionOptions)
|
||||
},
|
||||
}
|
||||
|
||||
addVersionFlags(createVersionCmd)
|
||||
return createVersionCmd
|
||||
}
|
||||
|
||||
func addVersionFlags(cmd *cobra.Command) {
|
||||
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func versionMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
16
cmd/version_generated_test.go
Normal file
16
cmd/version_generated_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVersionCommand(t *testing.T) {
|
||||
|
||||
testCmd := VersionCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "version", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
64
cmd/version_test.go
Normal file
64
cmd/version_test.go
Normal file
@ -0,0 +1,64 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
|
||||
t.Run("versionAndTagInitialValues", func(t *testing.T) {
|
||||
|
||||
result := runVersionCommand(t, "", "")
|
||||
assert.Contains(t, result, "commit: \"<n/a>\"")
|
||||
assert.Contains(t, result, "tag: \"<n/a>\"")
|
||||
})
|
||||
|
||||
t.Run("versionAndTagSet", func(t *testing.T) {
|
||||
|
||||
result := runVersionCommand(t, "16bafe", "v1.2.3")
|
||||
assert.Contains(t, result, "commit: \"16bafe\"")
|
||||
assert.Contains(t, result, "tag: \"v1.2.3\"")
|
||||
})
|
||||
}
|
||||
|
||||
func runVersionCommand(t *testing.T, commitID, tag string) string {
|
||||
|
||||
orig := os.Stdout
|
||||
defer func() { os.Stdout = orig }()
|
||||
|
||||
r, w, e := os.Pipe()
|
||||
if e != nil {
|
||||
t.Error("Cannot setup pipes.")
|
||||
}
|
||||
|
||||
os.Stdout = w
|
||||
|
||||
//
|
||||
// needs to be set in the free wild by the build process:
|
||||
// go build -ldflags "-X github.com/SAP/jenkins-library/cmd.GitCommit=${GIT_COMMIT} -X github.com/SAP/jenkins-library/cmd.GitTag=${GIT_TAG}"
|
||||
if len(commitID) > 0 {
|
||||
GitCommit = commitID
|
||||
}
|
||||
if len(tag) > 0 {
|
||||
GitTag = tag
|
||||
}
|
||||
defer func() { GitCommit = ""; GitTag = "" }()
|
||||
//
|
||||
//
|
||||
|
||||
var myVersionOptions versionOptions
|
||||
e = version(myVersionOptions)
|
||||
if e != nil {
|
||||
t.Error("Version command failed.")
|
||||
}
|
||||
|
||||
w.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
return buf.String()
|
||||
}
|
@ -46,7 +46,7 @@ export ON_K8S=true"
|
||||
```
|
||||
|
||||
```groovy
|
||||
dockerExecuteOnKubernetes(script: script, containerMap: ['maven:3.5-jdk-8-alpine': 'maven', 's4sdk/docker-cf-cli': 'cfcli']){
|
||||
dockerExecuteOnKubernetes(script: script, containerMap: ['maven:3.5-jdk-8-alpine': 'maven', 'ppiper/cf-cli': 'cfcli']){
|
||||
container('maven'){
|
||||
sh "mvn clean install"
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* **SAP CP account** - the account to where the application is deployed.
|
||||
* **SAP CP account** - the account to where the application is deployed. To deploy MTA (`deployMode: mta`) an over existing _Java_ application, free _Java Quota_ of at least 1 is required, which means that this will not work on trial accounts.
|
||||
* **SAP CP user for deployment** - a user with deployment permissions in the given account.
|
||||
* **Jenkins credentials for deployment** - must be configured in Jenkins credentials with a dedicated Id.
|
||||
|
||||
|
@ -21,7 +21,7 @@ xsDeploy
|
||||
credentialsId: 'my-credentials-id',
|
||||
apiUrl: 'https://example.org/xs',
|
||||
space: 'mySpace',
|
||||
org:: 'myOrg'
|
||||
org: 'myOrg'
|
||||
```
|
||||
|
||||
Example configuration:
|
||||
@ -30,12 +30,11 @@ Example configuration:
|
||||
steps:
|
||||
<...>
|
||||
xsDeploy:
|
||||
script: this,
|
||||
mtaPath: path/to/archiveFile.mtar
|
||||
credentialsId: my-credentials-id
|
||||
apiUrl: https://example.org/xs
|
||||
space: mySpace
|
||||
org:: myOrg
|
||||
org: myOrg
|
||||
```
|
||||
|
||||
[dockerExecute]: ../dockerExecute
|
||||
|
@ -78,28 +78,6 @@ nav:
|
||||
- uiVeri5ExecuteTests: steps/uiVeri5ExecuteTests.md
|
||||
- whitesourceExecuteScan: steps/whitesourceExecuteScan.md
|
||||
- xsDeploy: steps/xsDeploy.md
|
||||
- 'Pipelines':
|
||||
- 'General purpose pipeline':
|
||||
- 'Introduction': stages/introduction.md
|
||||
- 'Examples': stages/examples.md
|
||||
- 'Stages':
|
||||
- 'Init Stage': stages/init.md
|
||||
- 'Pull-Request Voting Stage': stages/prvoting.md
|
||||
- 'Build Stage': stages/build.md
|
||||
- 'Additional Unit Test Stage': stages/additionalunittests.md
|
||||
- 'Integration Stage': stages/integration.md
|
||||
- 'Acceptance Stage': stages/acceptance.md
|
||||
- 'Security Stage': stages/security.md
|
||||
- 'Performance Stage': stages/performance.md
|
||||
- 'Compliance': stages/compliance.md
|
||||
- 'Confirm Stage': stages/confirm.md
|
||||
- 'Promote Stage': stages/promote.md
|
||||
- 'Release Stage': stages/release.md
|
||||
- 'Scenarios':
|
||||
- 'Build and Deploy Hybrid Applications with Jenkins and SAP Solution Manager': scenarios/changeManagement.md
|
||||
- 'Build and Deploy SAP UI5 or SAP Fiori Applications on SAP Cloud Platform with Jenkins': scenarios/ui5-sap-cp/Readme.md
|
||||
- 'Build and Deploy Applications with Jenkins and the SAP Cloud Application Programming Model': scenarios/CAP_Scenario.md
|
||||
- 'Build and Deploy SAP Fiori Applications for SAP HANA XS Advanced ': scenarios/xsa-deploy/Readme.md
|
||||
- Resources:
|
||||
- 'Required Plugins': jenkins/requiredPlugins.md
|
||||
|
||||
|
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.13
|
||||
require (
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.2.2
|
||||
|
6
go.sum
6
go.sum
@ -12,6 +12,7 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
@ -21,6 +22,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
@ -30,12 +33,15 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
|
9
main.go
Normal file
9
main.go
Normal file
@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/SAP/jenkins-library/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
134
pkg/command/command.go
Normal file
134
pkg/command/command.go
Normal file
@ -0,0 +1,134 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Command defines the information required for executing a call to any executable
|
||||
type Command struct {
|
||||
dir string
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
}
|
||||
|
||||
// Dir sets the working directory for the execution
|
||||
func (c *Command) Dir(d string) {
|
||||
c.dir = d
|
||||
}
|
||||
|
||||
// ExecCommand defines how to execute os commands
|
||||
var ExecCommand = exec.Command
|
||||
|
||||
// RunShell runs the specified command on the shell
|
||||
func (c *Command) RunShell(shell, script string) error {
|
||||
|
||||
_out, _err := prepareOut(c.Stdout, c.Stderr)
|
||||
|
||||
cmd := ExecCommand(shell)
|
||||
|
||||
cmd.Dir = c.dir
|
||||
in := bytes.Buffer{}
|
||||
in.Write([]byte(script))
|
||||
cmd.Stdin = &in
|
||||
|
||||
if err := runCmd(cmd, _out, _err); err != nil {
|
||||
return errors.Wrapf(err, "running shell script failed with %v", shell)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunExecutable runs the specified executable with parameters
|
||||
func (c *Command) RunExecutable(executable string, params ...string) error {
|
||||
|
||||
_out, _err := prepareOut(c.Stdout, c.Stderr)
|
||||
|
||||
cmd := ExecCommand(executable, params...)
|
||||
|
||||
if len(c.dir) > 0 {
|
||||
cmd.Dir = c.dir
|
||||
}
|
||||
|
||||
if err := runCmd(cmd, _out, _err); err != nil {
|
||||
return errors.Wrapf(err, "running command '%v' failed", executable)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCmd(cmd *exec.Cmd, _out, _err io.Writer) error {
|
||||
|
||||
stdout, stderr, err := cmdPipes(cmd)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting commmand pipes failed")
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "starting command failed")
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
var errStdout, errStderr error
|
||||
|
||||
go func() {
|
||||
_, errStdout = io.Copy(_out, stdout)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, errStderr = io.Copy(_err, stderr)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
err = cmd.Wait()
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cmd.Run() failed")
|
||||
}
|
||||
|
||||
if errStdout != nil || errStderr != nil {
|
||||
return fmt.Errorf("failed to capture stdout/stderr: '%v'/'%v'", errStdout, errStderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareOut(stdout, stderr io.Writer) (io.Writer, io.Writer) {
|
||||
|
||||
//ToDo: check use of multiwriter instead to always write into os.Stdout and os.Stdin?
|
||||
//stdout := io.MultiWriter(os.Stdout, &stdoutBuf)
|
||||
//stderr := io.MultiWriter(os.Stderr, &stderrBuf)
|
||||
|
||||
if stdout == nil {
|
||||
stdout = os.Stdout
|
||||
}
|
||||
if stderr == nil {
|
||||
stderr = os.Stderr
|
||||
}
|
||||
|
||||
return stdout, stderr
|
||||
}
|
||||
|
||||
func cmdPipes(cmd *exec.Cmd) (io.ReadCloser, io.ReadCloser, error) {
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "getting Stdout pipe failed")
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "getting Stderr pipe failed")
|
||||
}
|
||||
return stdout, stderr, nil
|
||||
}
|
181
pkg/command/command_test.go
Normal file
181
pkg/command/command_test.go
Normal file
@ -0,0 +1,181 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//based on https://golang.org/src/os/exec/exec_test.go
|
||||
func helperCommand(command string, s ...string) (cmd *exec.Cmd) {
|
||||
cs := []string{"-test.run=TestHelperProcess", "--", command}
|
||||
cs = append(cs, s...)
|
||||
cmd = exec.Command(os.Args[0], cs...)
|
||||
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func TestShellRun(t *testing.T) {
|
||||
|
||||
t.Run("test shell", func(t *testing.T) {
|
||||
ExecCommand = helperCommand
|
||||
defer func() { ExecCommand = exec.Command }()
|
||||
o := new(bytes.Buffer)
|
||||
e := new(bytes.Buffer)
|
||||
|
||||
s := Command{Stdout: o, Stderr: e}
|
||||
s.RunShell("/bin/bash", "myScript")
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
t.Run("stdin-stdout", func(t *testing.T) {
|
||||
expectedOut := "Stdout: command /bin/bash - Stdin: myScript\n"
|
||||
if oStr := o.String(); oStr != expectedOut {
|
||||
t.Errorf("expected: %v got: %v", expectedOut, oStr)
|
||||
}
|
||||
})
|
||||
t.Run("stderr", func(t *testing.T) {
|
||||
expectedErr := "Stderr: command /bin/bash\n"
|
||||
if eStr := e.String(); eStr != expectedErr {
|
||||
t.Errorf("expected: %v got: %v", expectedErr, eStr)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecutableRun(t *testing.T) {
|
||||
|
||||
t.Run("test shell", func(t *testing.T) {
|
||||
ExecCommand = helperCommand
|
||||
defer func() { ExecCommand = exec.Command }()
|
||||
o := new(bytes.Buffer)
|
||||
e := new(bytes.Buffer)
|
||||
|
||||
ex := Command{Stdout: o, Stderr: e}
|
||||
ex.RunExecutable("echo", []string{"foo bar", "baz"}...)
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
t.Run("stdin", func(t *testing.T) {
|
||||
expectedOut := "foo bar baz\n"
|
||||
if oStr := o.String(); oStr != expectedOut {
|
||||
t.Errorf("expected: %v got: %v", expectedOut, oStr)
|
||||
}
|
||||
})
|
||||
t.Run("stderr", func(t *testing.T) {
|
||||
expectedErr := "Stderr: command echo\n"
|
||||
if eStr := e.String(); eStr != expectedErr {
|
||||
t.Errorf("expected: %v got: %v", expectedErr, eStr)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrepareOut(t *testing.T) {
|
||||
|
||||
t.Run("os", func(t *testing.T) {
|
||||
s := Command{}
|
||||
_out, _err := prepareOut(s.Stdout, s.Stderr)
|
||||
|
||||
if _out != os.Stdout {
|
||||
t.Errorf("expected out to be os.Stdout")
|
||||
}
|
||||
|
||||
if _err != os.Stderr {
|
||||
t.Errorf("expected err to be os.Stderr")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("custom", func(t *testing.T) {
|
||||
o := bytes.NewBufferString("")
|
||||
e := bytes.NewBufferString("")
|
||||
s := Command{Stdout: o, Stderr: e}
|
||||
_out, _err := prepareOut(s.Stdout, s.Stderr)
|
||||
|
||||
expectOut := "Test out"
|
||||
expectErr := "Test err"
|
||||
_out.Write([]byte(expectOut))
|
||||
_err.Write([]byte(expectErr))
|
||||
|
||||
t.Run("out", func(t *testing.T) {
|
||||
if o.String() != expectOut {
|
||||
t.Errorf("expected: %v got: %v", expectOut, o.String())
|
||||
}
|
||||
})
|
||||
t.Run("err", func(t *testing.T) {
|
||||
if e.String() != expectErr {
|
||||
t.Errorf("expected: %v got: %v", expectErr, e.String())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCmdPipes(t *testing.T) {
|
||||
cmd := helperCommand("echo", "foo bar", "baz")
|
||||
defer func() { ExecCommand = exec.Command }()
|
||||
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
o, e, err := cmdPipes(cmd)
|
||||
t.Run("no error", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("error occured but no error expected")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("out pipe", func(t *testing.T) {
|
||||
if o == nil {
|
||||
t.Errorf("no pipe received")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("err pipe", func(t *testing.T) {
|
||||
if e == nil {
|
||||
t.Errorf("no pipe received")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//based on https://golang.org/src/os/exec/exec_test.go
|
||||
//this is not directly executed
|
||||
func TestHelperProcess(*testing.T) {
|
||||
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||
return
|
||||
}
|
||||
defer os.Exit(0)
|
||||
|
||||
args := os.Args
|
||||
for len(args) > 0 {
|
||||
if args[0] == "--" {
|
||||
args = args[1:]
|
||||
break
|
||||
}
|
||||
args = args[1:]
|
||||
}
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "No command\n")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
cmd, args := args[0], args[1:]
|
||||
switch cmd {
|
||||
case "/bin/bash":
|
||||
o, _ := ioutil.ReadAll(os.Stdin)
|
||||
fmt.Fprintf(os.Stdout, "Stdout: command %v - Stdin: %v\n", cmd, string(o))
|
||||
fmt.Fprintf(os.Stderr, "Stderr: command %v\n", cmd)
|
||||
case "echo":
|
||||
iargs := []interface{}{}
|
||||
for _, s := range args {
|
||||
iargs = append(iargs, s)
|
||||
}
|
||||
fmt.Println(iargs...)
|
||||
fmt.Fprintf(os.Stderr, "Stderr: command %v\n", cmd)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
|
||||
os.Exit(2)
|
||||
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/pkg/errors"
|
||||
@ -39,8 +40,44 @@ func (c *Config) ReadConfig(configuration io.ReadCloser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyAliasConfig adds configuration values available on aliases to primary configuration parameters
|
||||
func (c *Config) ApplyAliasConfig(parameters []StepParameters, filters StepFilters, stageName, stepName string) {
|
||||
for _, p := range parameters {
|
||||
c.General = setParamValueFromAlias(c.General, filters.General, p)
|
||||
if c.Stages[stageName] != nil {
|
||||
c.Stages[stageName] = setParamValueFromAlias(c.Stages[stageName], filters.Stages, p)
|
||||
}
|
||||
if c.Steps[stepName] != nil {
|
||||
c.Steps[stepName] = setParamValueFromAlias(c.Steps[stepName], filters.Steps, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setParamValueFromAlias(configMap map[string]interface{}, filter []string, p StepParameters) map[string]interface{} {
|
||||
if configMap[p.Name] == nil && sliceContains(filter, p.Name) {
|
||||
for _, a := range p.Aliases {
|
||||
configMap[p.Name] = getDeepAliasValue(configMap, a.Name)
|
||||
if configMap[p.Name] != nil {
|
||||
return configMap
|
||||
}
|
||||
}
|
||||
}
|
||||
return configMap
|
||||
}
|
||||
|
||||
func getDeepAliasValue(configMap map[string]interface{}, key string) interface{} {
|
||||
parts := strings.Split(key, "/")
|
||||
if len(parts) > 1 {
|
||||
if configMap[parts[0]] == nil {
|
||||
return nil
|
||||
}
|
||||
return getDeepAliasValue(configMap[parts[0]].(map[string]interface{}), strings.Join(parts[1:], "/"))
|
||||
}
|
||||
return configMap[key]
|
||||
}
|
||||
|
||||
// GetStepConfig provides merged step configuration using defaults, config, if available
|
||||
func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON string, configuration io.ReadCloser, defaults []io.ReadCloser, filters StepFilters, stageName, stepName string) (StepConfig, error) {
|
||||
func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON string, configuration io.ReadCloser, defaults []io.ReadCloser, filters StepFilters, parameters []StepParameters, stageName, stepName string) (StepConfig, error) {
|
||||
var stepConfig StepConfig
|
||||
var d PipelineDefaults
|
||||
|
||||
@ -52,6 +89,7 @@ func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON stri
|
||||
//ignoring unavailability of config file since considered optional
|
||||
}
|
||||
}
|
||||
c.ApplyAliasConfig(parameters, filters, stageName, stepName)
|
||||
|
||||
if err := d.ReadPipelineDefaults(defaults); err != nil {
|
||||
switch err.(type) {
|
||||
@ -64,6 +102,7 @@ func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON stri
|
||||
|
||||
// first: read defaults & merge general -> steps (-> general -> steps ...)
|
||||
for _, def := range d.Defaults {
|
||||
def.ApplyAliasConfig(parameters, filters, stageName, stepName)
|
||||
stepConfig.mixIn(def.General, filters.General)
|
||||
stepConfig.mixIn(def.Steps[stepName], filters.Steps)
|
||||
}
|
||||
@ -80,6 +119,12 @@ func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON stri
|
||||
if len(paramJSON) != 0 {
|
||||
var params map[string]interface{}
|
||||
json.Unmarshal([]byte(paramJSON), ¶ms)
|
||||
|
||||
//apply aliases
|
||||
for _, p := range parameters {
|
||||
params = setParamValueFromAlias(params, filters.Parameters, p)
|
||||
}
|
||||
|
||||
stepConfig.mixIn(params, filters.Parameters)
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ steps:
|
||||
defaults := []io.ReadCloser{ioutil.NopCloser(strings.NewReader(defaults1)), ioutil.NopCloser(strings.NewReader(defaults2))}
|
||||
|
||||
myConfig := ioutil.NopCloser(strings.NewReader(testConfig))
|
||||
stepConfig, err := c.GetStepConfig(flags, paramJSON, myConfig, defaults, filters, "stage1", "step1")
|
||||
stepConfig, err := c.GetStepConfig(flags, paramJSON, myConfig, defaults, filters, []StepParameters{}, "stage1", "step1")
|
||||
|
||||
assert.Equal(t, nil, err, "error occured but none expected")
|
||||
|
||||
@ -151,7 +151,7 @@ steps:
|
||||
t.Run("Failure case config", func(t *testing.T) {
|
||||
var c Config
|
||||
myConfig := ioutil.NopCloser(strings.NewReader("invalid config"))
|
||||
_, err := c.GetStepConfig(nil, "", myConfig, nil, StepFilters{}, "stage1", "step1")
|
||||
_, err := c.GetStepConfig(nil, "", myConfig, nil, StepFilters{}, []StepParameters{}, "stage1", "step1")
|
||||
assert.EqualError(t, err, "failed to parse custom pipeline configuration: error unmarshalling \"invalid config\": error unmarshaling JSON: json: cannot unmarshal string into Go value of type config.Config", "default error expected")
|
||||
})
|
||||
|
||||
@ -159,7 +159,7 @@ steps:
|
||||
var c Config
|
||||
myConfig := ioutil.NopCloser(strings.NewReader(""))
|
||||
myDefaults := []io.ReadCloser{ioutil.NopCloser(strings.NewReader("invalid defaults"))}
|
||||
_, err := c.GetStepConfig(nil, "", myConfig, myDefaults, StepFilters{}, "stage1", "step1")
|
||||
_, err := c.GetStepConfig(nil, "", myConfig, myDefaults, StepFilters{}, []StepParameters{}, "stage1", "step1")
|
||||
assert.EqualError(t, err, "failed to parse pipeline default configuration: error unmarshalling \"invalid defaults\": error unmarshaling JSON: json: cannot unmarshal string into Go value of type config.Config", "default error expected")
|
||||
})
|
||||
|
||||
@ -187,6 +187,130 @@ func TestGetStepConfigWithJSON(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestApplyAliasConfig(t *testing.T) {
|
||||
p := []StepParameters{
|
||||
{
|
||||
Name: "p0",
|
||||
Aliases: []Alias{
|
||||
{Name: "p0_notused"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "p1",
|
||||
Aliases: []Alias{
|
||||
{Name: "p1_alias"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "p2",
|
||||
Aliases: []Alias{
|
||||
{Name: "p2_alias/deep/test"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "p3",
|
||||
Aliases: []Alias{
|
||||
{Name: "p3_notused"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "p4",
|
||||
Aliases: []Alias{
|
||||
{Name: "p4_alias"},
|
||||
{Name: "p4_2nd_alias"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "p5",
|
||||
Aliases: []Alias{
|
||||
{Name: "p5_notused"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "p6",
|
||||
Aliases: []Alias{
|
||||
{Name: "p6_1st_alias"},
|
||||
{Name: "p6_alias"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
filters := StepFilters{
|
||||
General: []string{"p1", "p2"},
|
||||
Stages: []string{"p4"},
|
||||
Steps: []string{"p6"},
|
||||
}
|
||||
|
||||
c := Config{
|
||||
General: map[string]interface{}{
|
||||
"p0_notused": "p0_general",
|
||||
"p1_alias": "p1_general",
|
||||
"p2_alias": map[string]interface{}{
|
||||
"deep": map[string]interface{}{
|
||||
"test": "p2_general",
|
||||
},
|
||||
},
|
||||
},
|
||||
Stages: map[string]map[string]interface{}{
|
||||
"stage1": map[string]interface{}{
|
||||
"p3_notused": "p3_stage",
|
||||
"p4_alias": "p4_stage",
|
||||
},
|
||||
},
|
||||
Steps: map[string]map[string]interface{}{
|
||||
"step1": map[string]interface{}{
|
||||
"p5_notused": "p5_step",
|
||||
"p6_alias": "p6_step",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c.ApplyAliasConfig(p, filters, "stage1", "step1")
|
||||
|
||||
t.Run("Global", func(t *testing.T) {
|
||||
assert.Nil(t, c.General["p0"])
|
||||
assert.Equal(t, "p1_general", c.General["p1"])
|
||||
assert.Equal(t, "p2_general", c.General["p2"])
|
||||
})
|
||||
|
||||
t.Run("Stage", func(t *testing.T) {
|
||||
assert.Nil(t, c.General["p3"])
|
||||
assert.Equal(t, "p4_stage", c.Stages["stage1"]["p4"])
|
||||
})
|
||||
|
||||
t.Run("Stage", func(t *testing.T) {
|
||||
assert.Nil(t, c.General["p5"])
|
||||
assert.Equal(t, "p6_step", c.Steps["step1"]["p6"])
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestGetDeepAliasValue(t *testing.T) {
|
||||
c := map[string]interface{}{
|
||||
"p0": "p0_val",
|
||||
"p1": 11,
|
||||
"p2": map[string]interface{}{
|
||||
"p2_0": "p2_0_val",
|
||||
"p2_1": map[string]interface{}{
|
||||
"p2_1_0": "p2_1_0_val",
|
||||
},
|
||||
},
|
||||
}
|
||||
tt := []struct {
|
||||
key string
|
||||
expected interface{}
|
||||
}{
|
||||
{key: "p0", expected: "p0_val"},
|
||||
{key: "p1", expected: 11},
|
||||
{key: "p2/p2_0", expected: "p2_0_val"},
|
||||
{key: "p2/p2_1/p2_1_0", expected: "p2_1_0_val"},
|
||||
}
|
||||
|
||||
for k, v := range tt {
|
||||
assert.Equal(t, v.expected, getDeepAliasValue(c, v.key), fmt.Sprintf("wrong return value for run %v", k+1))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetJSON(t *testing.T) {
|
||||
|
||||
t.Run("Success case", func(t *testing.T) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
@ -25,8 +27,8 @@ type StepMetadata struct {
|
||||
type StepSpec struct {
|
||||
Inputs StepInputs `json:"inputs"`
|
||||
// Outputs string `json:"description,omitempty"`
|
||||
Containers []StepContainers `json:"containers,omitempty"`
|
||||
Sidecars []StepSidecars `json:"sidecars,omitempty"`
|
||||
Containers []Container `json:"containers,omitempty"`
|
||||
Sidecars []Container `json:"sidecars,omitempty"`
|
||||
}
|
||||
|
||||
// StepInputs defines the spec details for a step, like step inputs, containers, sidecars, ...
|
||||
@ -45,6 +47,13 @@ type StepParameters struct {
|
||||
Type string `json:"type"`
|
||||
Mandatory bool `json:"mandatory,omitempty"`
|
||||
Default interface{} `json:"default,omitempty"`
|
||||
Aliases []Alias `json:"aliases,omitempty"`
|
||||
}
|
||||
|
||||
// Alias defines a step input parameter alias
|
||||
type Alias struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Deprecated bool `json:"deprecated,omitempty"`
|
||||
}
|
||||
|
||||
// StepResources defines the resources to be provided by the step context, e.g. Jenkins pipeline
|
||||
@ -66,14 +75,23 @@ type StepSecrets struct {
|
||||
// Name string `json:"name"`
|
||||
//}
|
||||
|
||||
// StepContainers defines the containers required for a step
|
||||
type StepContainers struct {
|
||||
Containers map[string]interface{} `json:"containers"`
|
||||
// Container defines an execution container
|
||||
type Container struct {
|
||||
//ToDo: check dockerOptions, dockerVolumeBind, containerPortMappings, sidecarOptions, sidecarVolumeBind
|
||||
Command []string `json:"command"`
|
||||
EnvVars []EnvVar `json:"env"`
|
||||
Image string `json:"image"`
|
||||
ImagePullPolicy string `json:"imagePullPolicy"`
|
||||
Name string `json:"name"`
|
||||
ReadyCommand string `json:"readyCommand"`
|
||||
Shell string `json:"shell"`
|
||||
WorkingDir string `json:"workingDir"`
|
||||
}
|
||||
|
||||
// StepSidecars defines any sidears required for a step
|
||||
type StepSidecars struct {
|
||||
Sidecars map[string]interface{} `json:"sidecars"`
|
||||
// EnvVar defines an environment variable
|
||||
type EnvVar struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// StepFilters defines the filter parameters for the different sections
|
||||
@ -135,5 +153,97 @@ func (m *StepData) GetContextParameterFilters() StepFilters {
|
||||
filters.Parameters = append(filters.Parameters, secret.Name)
|
||||
filters.Env = append(filters.Env, secret.Name)
|
||||
}
|
||||
|
||||
containerFilters := []string{}
|
||||
if len(m.Spec.Containers) > 0 {
|
||||
containerFilters = append(containerFilters, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace"}...)
|
||||
}
|
||||
if len(m.Spec.Sidecars) > 0 {
|
||||
//ToDo: support fallback for "dockerName" configuration property -> via aliasing?
|
||||
containerFilters = append(containerFilters, []string{"containerName", "containerPortMappings", "dockerName", "sidecarEnvVars", "sidecarImage", "sidecarName", "sidecarOptions", "sidecarPullImage", "sidecarReadyCommand", "sidecarVolumeBind", "sidecarWorkspace"}...)
|
||||
}
|
||||
if len(containerFilters) > 0 {
|
||||
filters.All = append(filters.All, containerFilters...)
|
||||
filters.Steps = append(filters.Steps, containerFilters...)
|
||||
filters.Stages = append(filters.Stages, containerFilters...)
|
||||
filters.Parameters = append(filters.Parameters, containerFilters...)
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
// GetContextDefaults retrieves context defaults like container image, name, env vars, resources, ...
|
||||
// It only supports scenarios with one container and optionally one sidecar
|
||||
func (m *StepData) GetContextDefaults(stepName string) (io.ReadCloser, error) {
|
||||
|
||||
p := map[string]interface{}{}
|
||||
|
||||
//ToDo error handling empty Containers/Sidecars
|
||||
//ToDo handle empty Command
|
||||
|
||||
if len(m.Spec.Containers) > 0 {
|
||||
if len(m.Spec.Containers[0].Command) > 0 {
|
||||
p["containerCommand"] = m.Spec.Containers[0].Command[0]
|
||||
}
|
||||
p["containerName"] = m.Spec.Containers[0].Name
|
||||
p["containerShell"] = m.Spec.Containers[0].Shell
|
||||
p["dockerEnvVars"] = envVarsAsStringSlice(m.Spec.Containers[0].EnvVars)
|
||||
p["dockerImage"] = m.Spec.Containers[0].Image
|
||||
p["dockerName"] = m.Spec.Containers[0].Name
|
||||
p["dockerPullImage"] = m.Spec.Containers[0].ImagePullPolicy != "Never"
|
||||
p["dockerWorkspace"] = m.Spec.Containers[0].WorkingDir
|
||||
|
||||
// Ready command not relevant for main runtime container so far
|
||||
//p[] = m.Spec.Containers[0].ReadyCommand
|
||||
}
|
||||
|
||||
if len(m.Spec.Sidecars) > 0 {
|
||||
if len(m.Spec.Sidecars[0].Command) > 0 {
|
||||
p["sidecarCommand"] = m.Spec.Sidecars[0].Command[0]
|
||||
}
|
||||
p["sidecarEnvVars"] = envVarsAsStringSlice(m.Spec.Sidecars[0].EnvVars)
|
||||
p["sidecarImage"] = m.Spec.Sidecars[0].Image
|
||||
p["sidecarName"] = m.Spec.Sidecars[0].Name
|
||||
p["sidecarPullImage"] = m.Spec.Sidecars[0].ImagePullPolicy != "Never"
|
||||
p["sidecarReadyCommand"] = m.Spec.Sidecars[0].ReadyCommand
|
||||
p["sidecarWorkspace"] = m.Spec.Sidecars[0].WorkingDir
|
||||
}
|
||||
|
||||
// not filled for now since this is not relevant in Kubernetes case
|
||||
//p["dockerOptions"] = m.Spec.Containers[0].
|
||||
//p["dockerVolumeBind"] = m.Spec.Containers[0].
|
||||
//p["containerPortMappings"] = m.Spec.Sidecars[0].
|
||||
//p["sidecarOptions"] = m.Spec.Sidecars[0].
|
||||
//p["sidecarVolumeBind"] = m.Spec.Sidecars[0].
|
||||
|
||||
if len(m.Spec.Inputs.Resources) > 0 {
|
||||
var resources []string
|
||||
for _, resource := range m.Spec.Inputs.Resources {
|
||||
if resource.Type == "stash" {
|
||||
resources = append(resources, resource.Name)
|
||||
}
|
||||
}
|
||||
p["stashContent"] = resources
|
||||
}
|
||||
|
||||
c := Config{
|
||||
Steps: map[string]map[string]interface{}{
|
||||
stepName: p,
|
||||
},
|
||||
}
|
||||
|
||||
JSON, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create context defaults")
|
||||
}
|
||||
|
||||
r := ioutil.NopCloser(bytes.NewReader(JSON))
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func envVarsAsStringSlice(envVars []EnvVar) []string {
|
||||
e := []string{}
|
||||
for _, v := range envVars {
|
||||
e = append(e, fmt.Sprintf("%v=%v", v.Name, v.Value))
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
@ -2,9 +2,12 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReadPipelineStepData(t *testing.T) {
|
||||
@ -227,40 +230,164 @@ func TestGetContextParameterFilters(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
filters := metadata1.GetContextParameterFilters()
|
||||
metadata2 := StepData{
|
||||
Spec: StepSpec{
|
||||
Containers: []Container{
|
||||
{Name: "testcontainer"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
metadata3 := StepData{
|
||||
Spec: StepSpec{
|
||||
Sidecars: []Container{
|
||||
{Name: "testsidecar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Secrets", func(t *testing.T) {
|
||||
for _, s := range metadata1.Spec.Inputs.Secrets {
|
||||
t.Run("All", func(t *testing.T) {
|
||||
if !sliceContains(filters.All, s.Name) {
|
||||
t.Errorf("Creation of context filter failed, expected: %v to be contained", s.Name)
|
||||
}
|
||||
})
|
||||
t.Run("General", func(t *testing.T) {
|
||||
if !sliceContains(filters.General, s.Name) {
|
||||
t.Errorf("Creation of context filter failed, expected: %v to be contained", s.Name)
|
||||
}
|
||||
})
|
||||
t.Run("Step", func(t *testing.T) {
|
||||
if !sliceContains(filters.Steps, s.Name) {
|
||||
t.Errorf("Creation of context filter failed, expected: %v to be contained", s.Name)
|
||||
}
|
||||
})
|
||||
t.Run("Stages", func(t *testing.T) {
|
||||
if !sliceContains(filters.Steps, s.Name) {
|
||||
t.Errorf("Creation of context filter failed, expected: %v to be contained", s.Name)
|
||||
}
|
||||
})
|
||||
t.Run("Parameters", func(t *testing.T) {
|
||||
if !sliceContains(filters.Parameters, s.Name) {
|
||||
t.Errorf("Creation of context filter failed, expected: %v to be contained", s.Name)
|
||||
}
|
||||
})
|
||||
t.Run("Env", func(t *testing.T) {
|
||||
if !sliceContains(filters.Env, s.Name) {
|
||||
t.Errorf("Creation of context filter failed, expected: %v to be contained", s.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
filters := metadata1.GetContextParameterFilters()
|
||||
assert.Equal(t, []string{"testSecret1", "testSecret2"}, filters.All, "incorrect filter All")
|
||||
assert.Equal(t, []string{"testSecret1", "testSecret2"}, filters.General, "incorrect filter General")
|
||||
assert.Equal(t, []string{"testSecret1", "testSecret2"}, filters.Steps, "incorrect filter Steps")
|
||||
assert.Equal(t, []string{"testSecret1", "testSecret2"}, filters.Stages, "incorrect filter Stages")
|
||||
assert.Equal(t, []string{"testSecret1", "testSecret2"}, filters.Parameters, "incorrect filter Parameters")
|
||||
assert.Equal(t, []string{"testSecret1", "testSecret2"}, filters.Env, "incorrect filter Env")
|
||||
})
|
||||
|
||||
t.Run("Containers", func(t *testing.T) {
|
||||
filters := metadata2.GetContextParameterFilters()
|
||||
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace"}, filters.All, "incorrect filter All")
|
||||
assert.NotEqual(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace"}, filters.General, "incorrect filter General")
|
||||
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace"}, filters.Steps, "incorrect filter Steps")
|
||||
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace"}, filters.Stages, "incorrect filter Stages")
|
||||
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace"}, filters.Parameters, "incorrect filter Parameters")
|
||||
assert.NotEqual(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace"}, filters.Env, "incorrect filter Env")
|
||||
})
|
||||
|
||||
t.Run("Sidecars", func(t *testing.T) {
|
||||
filters := metadata3.GetContextParameterFilters()
|
||||
assert.Equal(t, []string{"containerName", "containerPortMappings", "dockerName", "sidecarEnvVars", "sidecarImage", "sidecarName", "sidecarOptions", "sidecarPullImage", "sidecarReadyCommand", "sidecarVolumeBind", "sidecarWorkspace"}, filters.All, "incorrect filter All")
|
||||
assert.NotEqual(t, []string{"containerName", "containerPortMappings", "dockerName", "sidecarEnvVars", "sidecarImage", "sidecarName", "sidecarOptions", "sidecarPullImage", "sidecarReadyCommand", "sidecarVolumeBind", "sidecarWorkspace"}, filters.General, "incorrect filter General")
|
||||
assert.Equal(t, []string{"containerName", "containerPortMappings", "dockerName", "sidecarEnvVars", "sidecarImage", "sidecarName", "sidecarOptions", "sidecarPullImage", "sidecarReadyCommand", "sidecarVolumeBind", "sidecarWorkspace"}, filters.Steps, "incorrect filter Steps")
|
||||
assert.Equal(t, []string{"containerName", "containerPortMappings", "dockerName", "sidecarEnvVars", "sidecarImage", "sidecarName", "sidecarOptions", "sidecarPullImage", "sidecarReadyCommand", "sidecarVolumeBind", "sidecarWorkspace"}, filters.Stages, "incorrect filter Stages")
|
||||
assert.Equal(t, []string{"containerName", "containerPortMappings", "dockerName", "sidecarEnvVars", "sidecarImage", "sidecarName", "sidecarOptions", "sidecarPullImage", "sidecarReadyCommand", "sidecarVolumeBind", "sidecarWorkspace"}, filters.Parameters, "incorrect filter Parameters")
|
||||
assert.NotEqual(t, []string{"containerName", "containerPortMappings", "dockerName", "sidecarEnvVars", "sidecarImage", "sidecarName", "sidecarOptions", "sidecarPullImage", "sidecarReadyCommand", "sidecarVolumeBind", "sidecarWorkspace"}, filters.Env, "incorrect filter Env")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetContextDefaults(t *testing.T) {
|
||||
|
||||
t.Run("Positive case", func(t *testing.T) {
|
||||
metadata := StepData{
|
||||
Spec: StepSpec{
|
||||
Inputs: StepInputs{
|
||||
Resources: []StepResources{
|
||||
{
|
||||
Name: "buildDescriptor",
|
||||
Type: "stash",
|
||||
},
|
||||
{
|
||||
Name: "source",
|
||||
Type: "stash",
|
||||
},
|
||||
{
|
||||
Name: "test",
|
||||
Type: "nonce",
|
||||
},
|
||||
},
|
||||
},
|
||||
Containers: []Container{
|
||||
{
|
||||
Command: []string{"test/command"},
|
||||
EnvVars: []EnvVar{
|
||||
{Name: "env1", Value: "val1"},
|
||||
{Name: "env2", Value: "val2"},
|
||||
},
|
||||
Name: "testcontainer",
|
||||
Image: "testImage:tag",
|
||||
Shell: "/bin/bash",
|
||||
WorkingDir: "/test/dir",
|
||||
},
|
||||
},
|
||||
Sidecars: []Container{
|
||||
{
|
||||
Command: []string{"/sidecar/command"},
|
||||
EnvVars: []EnvVar{
|
||||
{Name: "env3", Value: "val3"},
|
||||
{Name: "env4", Value: "val4"},
|
||||
},
|
||||
Name: "testsidecar",
|
||||
Image: "testSidecarImage:tag",
|
||||
ImagePullPolicy: "Never",
|
||||
ReadyCommand: "/sidecar/command",
|
||||
WorkingDir: "/sidecar/dir",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cd, err := metadata.GetContextDefaults("testStep")
|
||||
|
||||
t.Run("No error", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("No error expected but got error '%v'", err)
|
||||
}
|
||||
})
|
||||
|
||||
var d PipelineDefaults
|
||||
d.ReadPipelineDefaults([]io.ReadCloser{cd})
|
||||
|
||||
assert.Equal(t, []interface{}{"buildDescriptor", "source"}, d.Defaults[0].Steps["testStep"]["stashContent"], "stashContent default not available")
|
||||
assert.Equal(t, "test/command", d.Defaults[0].Steps["testStep"]["containerCommand"], "containerCommand default not available")
|
||||
assert.Equal(t, "testcontainer", d.Defaults[0].Steps["testStep"]["containerName"], "containerName default not available")
|
||||
assert.Equal(t, "/bin/bash", d.Defaults[0].Steps["testStep"]["containerShell"], "containerShell default not available")
|
||||
assert.Equal(t, []interface{}{"env1=val1", "env2=val2"}, d.Defaults[0].Steps["testStep"]["dockerEnvVars"], "dockerEnvVars default not available")
|
||||
assert.Equal(t, "testImage:tag", d.Defaults[0].Steps["testStep"]["dockerImage"], "dockerImage default not available")
|
||||
assert.Equal(t, "testcontainer", d.Defaults[0].Steps["testStep"]["dockerName"], "dockerName default not available")
|
||||
assert.Equal(t, true, d.Defaults[0].Steps["testStep"]["dockerPullImage"], "dockerPullImage default not available")
|
||||
assert.Equal(t, "/test/dir", d.Defaults[0].Steps["testStep"]["dockerWorkspace"], "dockerWorkspace default not available")
|
||||
|
||||
assert.Equal(t, "/sidecar/command", d.Defaults[0].Steps["testStep"]["sidecarCommand"], "sidecarCommand default not available")
|
||||
assert.Equal(t, []interface{}{"env3=val3", "env4=val4"}, d.Defaults[0].Steps["testStep"]["sidecarEnvVars"], "sidecarEnvVars default not available")
|
||||
assert.Equal(t, "testSidecarImage:tag", d.Defaults[0].Steps["testStep"]["sidecarImage"], "sidecarImage default not available")
|
||||
assert.Equal(t, "testsidecar", d.Defaults[0].Steps["testStep"]["sidecarName"], "sidecarName default not available")
|
||||
assert.Equal(t, false, d.Defaults[0].Steps["testStep"]["sidecarPullImage"], "sidecarPullImage default not available")
|
||||
assert.Equal(t, "/sidecar/command", d.Defaults[0].Steps["testStep"]["sidecarReadyCommand"], "sidecarReadyCommand default not available")
|
||||
assert.Equal(t, "/sidecar/dir", d.Defaults[0].Steps["testStep"]["sidecarWorkspace"], "sidecarWorkspace default not available")
|
||||
})
|
||||
|
||||
t.Run("Negative case", func(t *testing.T) {
|
||||
metadataErr := []StepData{
|
||||
StepData{},
|
||||
StepData{
|
||||
Spec: StepSpec{},
|
||||
},
|
||||
StepData{
|
||||
Spec: StepSpec{
|
||||
Containers: []Container{},
|
||||
Sidecars: []Container{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("No containers/sidecars", func(t *testing.T) {
|
||||
cd, _ := metadataErr[0].GetContextDefaults("testStep")
|
||||
|
||||
var d PipelineDefaults
|
||||
d.ReadPipelineDefaults([]io.ReadCloser{cd})
|
||||
|
||||
//no assert since we just want to make sure that no panic occurs
|
||||
})
|
||||
|
||||
t.Run("No command", func(t *testing.T) {
|
||||
cd, _ := metadataErr[1].GetContextDefaults("testStep")
|
||||
|
||||
var d PipelineDefaults
|
||||
d.ReadPipelineDefaults([]io.ReadCloser{cd})
|
||||
|
||||
//no assert since we just want to make sure that no panic occurs
|
||||
})
|
||||
})
|
||||
}
|
||||
|
315
pkg/generator/step-metadata.go
Normal file
315
pkg/generator/step-metadata.go
Normal file
@ -0,0 +1,315 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
)
|
||||
|
||||
type stepInfo struct {
|
||||
CobraCmdFuncName string
|
||||
CreateCmdVar string
|
||||
FlagsFunc string
|
||||
Long string
|
||||
Metadata []config.StepParameters
|
||||
OSImport bool
|
||||
Short string
|
||||
StepFunc string
|
||||
StepName string
|
||||
}
|
||||
|
||||
//StepGoTemplate ...
|
||||
const stepGoTemplate = `package cmd
|
||||
|
||||
import (
|
||||
//"os"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type {{ .StepName }}Options struct {
|
||||
{{- range $key, $value := .Metadata }}
|
||||
{{ $value.Name | golangName }} {{ $value.Type }} ` + "`json:\"{{$value.Name}},omitempty\"`" + `{{end}}
|
||||
}
|
||||
|
||||
var my{{ .StepName | title}}Options {{.StepName}}Options
|
||||
var {{ .StepName }}StepConfigJSON string
|
||||
|
||||
// {{.CobraCmdFuncName}} {{.Short}}
|
||||
func {{.CobraCmdFuncName}}() *cobra.Command {
|
||||
metadata := {{ .StepName }}Metadata()
|
||||
var {{.CreateCmdVar}} = &cobra.Command{
|
||||
Use: "{{.StepName}}",
|
||||
Short: "{{.Short}}",
|
||||
Long: {{ $tick := "` + "`" + `" }}{{ $tick }}{{.Long | longName }}{{ $tick }},
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
log.SetStepName("{{ .StepName }}")
|
||||
log.SetVerbose(generalConfig.verbose)
|
||||
return PrepareConfig(cmd, &metadata, "{{ .StepName }}", &my{{ .StepName | title}}Options, openPiperFile)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return {{.StepName}}(my{{ .StepName | title }}Options)
|
||||
},
|
||||
}
|
||||
|
||||
{{.FlagsFunc}}({{.CreateCmdVar}})
|
||||
return {{.CreateCmdVar}}
|
||||
}
|
||||
|
||||
func {{.FlagsFunc}}(cmd *cobra.Command) {
|
||||
{{- range $key, $value := .Metadata }}
|
||||
cmd.Flags().{{ $value.Type | flagType }}(&my{{ $.StepName | title }}Options.{{ $value.Name | golangName }}, "{{ $value.Name }}", {{ $value.Default }}, "{{ $value.Description }}"){{ end }}
|
||||
{{- printf "\n" }}
|
||||
{{- range $key, $value := .Metadata }}{{ if $value.Mandatory }}
|
||||
cmd.MarkFlagRequired("{{ $value.Name }}"){{ end }}{{ end }}
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func {{ .StepName }}Metadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{{- range $key, $value := .Metadata }}
|
||||
{
|
||||
Name: "{{ $value.Name }}",
|
||||
Scope: []string{{ "{" }}{{ range $notused, $scope := $value.Scope }}"{{ $scope }}",{{ end }}{{ "}" }},
|
||||
Type: "{{ $value.Type }}",
|
||||
Mandatory: {{ $value.Mandatory }},
|
||||
},{{ end }}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
||||
`
|
||||
|
||||
//StepTestGoTemplate ...
|
||||
const stepTestGoTemplate = `package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test{{.CobraCmdFuncName}}(t *testing.T) {
|
||||
|
||||
testCmd := {{.CobraCmdFuncName}}()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "{{.StepName}}", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
||||
`
|
||||
|
||||
func main() {
|
||||
|
||||
metadataPath := "./resources/metadata"
|
||||
|
||||
metadataFiles, err := metadataFiles(metadataPath)
|
||||
checkError(err)
|
||||
|
||||
err = processMetaFiles(metadataFiles, openMetaFile, fileWriter)
|
||||
checkError(err)
|
||||
|
||||
cmd := exec.Command("go", "fmt", "./cmd")
|
||||
err = cmd.Run()
|
||||
checkError(err)
|
||||
|
||||
}
|
||||
|
||||
func processMetaFiles(metadataFiles []string, openFile func(s string) (io.ReadCloser, error), writeFile func(filename string, data []byte, perm os.FileMode) error) error {
|
||||
for key := range metadataFiles {
|
||||
|
||||
var stepData config.StepData
|
||||
|
||||
configFilePath := metadataFiles[key]
|
||||
|
||||
metadataFile, err := openFile(configFilePath)
|
||||
checkError(err)
|
||||
defer metadataFile.Close()
|
||||
|
||||
fmt.Printf("Reading file %v\n", configFilePath)
|
||||
|
||||
err = stepData.ReadPipelineStepData(metadataFile)
|
||||
checkError(err)
|
||||
|
||||
fmt.Printf("Step name: %v\n", stepData.Metadata.Name)
|
||||
|
||||
err = setDefaultParameters(&stepData)
|
||||
checkError(err)
|
||||
|
||||
myStepInfo := getStepInfo(&stepData)
|
||||
|
||||
step := stepTemplate(myStepInfo)
|
||||
err = writeFile(fmt.Sprintf("cmd/%v_generated.go", stepData.Metadata.Name), step, 0644)
|
||||
checkError(err)
|
||||
|
||||
test := stepTestTemplate(myStepInfo)
|
||||
err = writeFile(fmt.Sprintf("cmd/%v_generated_test.go", stepData.Metadata.Name), test, 0644)
|
||||
checkError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func openMetaFile(name string) (io.ReadCloser, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
func fileWriter(filename string, data []byte, perm os.FileMode) error {
|
||||
return ioutil.WriteFile(filename, data, perm)
|
||||
}
|
||||
|
||||
func setDefaultParameters(stepData *config.StepData) error {
|
||||
//ToDo: custom function for default handling, support all relevant parameter types
|
||||
for k, param := range stepData.Spec.Inputs.Parameters {
|
||||
|
||||
if param.Default == nil {
|
||||
switch param.Type {
|
||||
case "string":
|
||||
param.Default = fmt.Sprintf("os.Getenv(\"PIPER_%v\")", param.Name)
|
||||
case "bool":
|
||||
// ToDo: Check if default should be read from env
|
||||
param.Default = "false"
|
||||
case "[]string":
|
||||
// ToDo: Check if default should be read from env
|
||||
param.Default = "[]string{}"
|
||||
default:
|
||||
return fmt.Errorf("Meta data type not set or not known: '%v'", param.Type)
|
||||
}
|
||||
} else {
|
||||
switch param.Type {
|
||||
case "string":
|
||||
param.Default = fmt.Sprintf("\"%v\"", param.Default)
|
||||
case "bool":
|
||||
boolVal := "false"
|
||||
if param.Default.(bool) == true {
|
||||
boolVal = "true"
|
||||
}
|
||||
param.Default = boolVal
|
||||
case "[]string":
|
||||
param.Default = fmt.Sprintf("[]string{\"%v\"}", strings.Join(param.Default.([]string), "\", \""))
|
||||
default:
|
||||
return fmt.Errorf("Meta data type not set or not known: '%v'", param.Type)
|
||||
}
|
||||
}
|
||||
|
||||
stepData.Spec.Inputs.Parameters[k] = param
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getStepInfo(stepData *config.StepData) stepInfo {
|
||||
return stepInfo{
|
||||
StepName: stepData.Metadata.Name,
|
||||
CobraCmdFuncName: fmt.Sprintf("%vCommand", strings.Title(stepData.Metadata.Name)),
|
||||
CreateCmdVar: fmt.Sprintf("create%vCmd", strings.Title(stepData.Metadata.Name)),
|
||||
Short: stepData.Metadata.Description,
|
||||
Long: stepData.Metadata.LongDescription,
|
||||
Metadata: stepData.Spec.Inputs.Parameters,
|
||||
FlagsFunc: fmt.Sprintf("add%vFlags", strings.Title(stepData.Metadata.Name)),
|
||||
}
|
||||
}
|
||||
|
||||
func checkError(err error) {
|
||||
if err != nil {
|
||||
fmt.Printf("Error occured: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func metadataFiles(sourceDirectory string) ([]string, error) {
|
||||
|
||||
var metadataFiles []string
|
||||
|
||||
err := filepath.Walk(sourceDirectory, func(path string, info os.FileInfo, err error) error {
|
||||
if filepath.Ext(path) == ".yaml" {
|
||||
metadataFiles = append(metadataFiles, path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return metadataFiles, nil
|
||||
}
|
||||
return metadataFiles, nil
|
||||
}
|
||||
|
||||
func stepTemplate(myStepInfo stepInfo) []byte {
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"flagType": flagType,
|
||||
"golangName": golangName,
|
||||
"title": strings.Title,
|
||||
"longName": longName,
|
||||
}
|
||||
|
||||
tmpl, err := template.New("step").Funcs(funcMap).Parse(stepGoTemplate)
|
||||
checkError(err)
|
||||
|
||||
var generatedCode bytes.Buffer
|
||||
err = tmpl.Execute(&generatedCode, myStepInfo)
|
||||
checkError(err)
|
||||
|
||||
return generatedCode.Bytes()
|
||||
}
|
||||
|
||||
func stepTestTemplate(myStepInfo stepInfo) []byte {
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"flagType": flagType,
|
||||
"golangName": golangName,
|
||||
"title": strings.Title,
|
||||
}
|
||||
|
||||
tmpl, err := template.New("stepTest").Funcs(funcMap).Parse(stepTestGoTemplate)
|
||||
checkError(err)
|
||||
|
||||
var generatedCode bytes.Buffer
|
||||
err = tmpl.Execute(&generatedCode, myStepInfo)
|
||||
checkError(err)
|
||||
|
||||
return generatedCode.Bytes()
|
||||
}
|
||||
|
||||
func longName(long string) string {
|
||||
l := strings.ReplaceAll(long, "`", "` + \"`\" + `")
|
||||
l = strings.TrimSpace(l)
|
||||
return l
|
||||
}
|
||||
|
||||
func golangName(name string) string {
|
||||
properName := strings.Replace(name, "Api", "API", -1)
|
||||
properName = strings.Replace(properName, "Url", "URL", -1)
|
||||
properName = strings.Replace(properName, "Id", "ID", -1)
|
||||
properName = strings.Replace(properName, "Json", "JSON", -1)
|
||||
properName = strings.Replace(properName, "json", "JSON", -1)
|
||||
return strings.Title(properName)
|
||||
}
|
||||
|
||||
func flagType(paramType string) string {
|
||||
var theFlagType string
|
||||
switch paramType {
|
||||
case "bool":
|
||||
theFlagType = "BoolVar"
|
||||
case "string":
|
||||
theFlagType = "StringVar"
|
||||
case "[]string":
|
||||
theFlagType = "StringSliceVar"
|
||||
default:
|
||||
fmt.Printf("Meta data type not set or not known: '%v'\n", paramType)
|
||||
os.Exit(1)
|
||||
}
|
||||
return theFlagType
|
||||
}
|
227
pkg/generator/step-metadata_test.go
Normal file
227
pkg/generator/step-metadata_test.go
Normal file
@ -0,0 +1,227 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
//"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func configOpenFileMock(name string) (io.ReadCloser, error) {
|
||||
meta1 := `metadata:
|
||||
name: testStep
|
||||
description: Test description
|
||||
longDescription: |
|
||||
Long Test description
|
||||
spec:
|
||||
inputs:
|
||||
params:
|
||||
- name: param0
|
||||
type: string
|
||||
description: param0 description
|
||||
default: val0
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
mandatory: true
|
||||
- name: param1
|
||||
type: string
|
||||
description: param1 description
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- name: param2
|
||||
type: string
|
||||
description: param1 description
|
||||
scope:
|
||||
- PARAMETERS
|
||||
mandatory: true
|
||||
`
|
||||
var r string
|
||||
switch name {
|
||||
case "test.yaml":
|
||||
r = meta1
|
||||
default:
|
||||
r = ""
|
||||
}
|
||||
return ioutil.NopCloser(strings.NewReader(r)), nil
|
||||
}
|
||||
|
||||
var files map[string][]byte
|
||||
|
||||
func writeFileMock(filename string, data []byte, perm os.FileMode) error {
|
||||
if files == nil {
|
||||
files = make(map[string][]byte)
|
||||
}
|
||||
files[filename] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestProcessMetaFiles(t *testing.T) {
|
||||
|
||||
processMetaFiles([]string{"test.yaml"}, configOpenFileMock, writeFileMock)
|
||||
|
||||
t.Run("step code", func(t *testing.T) {
|
||||
goldenFilePath := filepath.Join("testdata", t.Name()+"_generated.golden")
|
||||
expected, err := ioutil.ReadFile(goldenFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading %v", goldenFilePath)
|
||||
}
|
||||
assert.Equal(t, expected, files["cmd/testStep_generated.go"])
|
||||
})
|
||||
|
||||
t.Run("test code", func(t *testing.T) {
|
||||
goldenFilePath := filepath.Join("testdata", t.Name()+"_generated.golden")
|
||||
expected, err := ioutil.ReadFile(goldenFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading %v", goldenFilePath)
|
||||
}
|
||||
assert.Equal(t, expected, files["cmd/testStep_generated_test.go"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetDefaultParameters(t *testing.T) {
|
||||
t.Run("success case", func(t *testing.T) {
|
||||
stepData := config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "param0", Scope: []string{"GENERAL"}, Type: "string", Default: "val0"},
|
||||
{Name: "param1", Scope: []string{"STEPS"}, Type: "string"},
|
||||
{Name: "param2", Scope: []string{"STAGES"}, Type: "bool", Default: true},
|
||||
{Name: "param3", Scope: []string{"PARAMETERS"}, Type: "bool"},
|
||||
{Name: "param4", Scope: []string{"ENV"}, Type: "[]string", Default: []string{"val4_1", "val4_2"}},
|
||||
{Name: "param5", Scope: []string{"ENV"}, Type: "[]string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"\"val0\"",
|
||||
"os.Getenv(\"PIPER_param1\")",
|
||||
"true",
|
||||
"false",
|
||||
"[]string{\"val4_1\", \"val4_2\"}",
|
||||
"[]string{}",
|
||||
}
|
||||
|
||||
err := setDefaultParameters(&stepData)
|
||||
|
||||
assert.NoError(t, err, "error occured but none expected")
|
||||
|
||||
for k, v := range expected {
|
||||
assert.Equal(t, v, stepData.Spec.Inputs.Parameters[k].Default, fmt.Sprintf("default not correct for parameter %v", k))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error case", func(t *testing.T) {
|
||||
stepData := []config.StepData{
|
||||
{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "param0", Scope: []string{"GENERAL"}, Type: "int", Default: 10},
|
||||
{Name: "param1", Scope: []string{"GENERAL"}, Type: "int"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "param1", Scope: []string{"GENERAL"}, Type: "int"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range stepData {
|
||||
err := setDefaultParameters(&v)
|
||||
assert.Error(t, err, fmt.Sprintf("error expected but none occured for parameter %v", k))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetStepInfo(t *testing.T) {
|
||||
|
||||
stepData := config.StepData{
|
||||
Metadata: config.StepMetadata{
|
||||
Name: "testStep",
|
||||
Description: "Test description",
|
||||
LongDescription: "Long Test description",
|
||||
},
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{Name: "param0", Scope: []string{"GENERAL"}, Type: "string", Default: "test"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
myStepInfo := getStepInfo(&stepData)
|
||||
|
||||
assert.Equal(t, "testStep", myStepInfo.StepName, "StepName incorrect")
|
||||
assert.Equal(t, "TestStepCommand", myStepInfo.CobraCmdFuncName, "CobraCmdFuncName incorrect")
|
||||
assert.Equal(t, "createTestStepCmd", myStepInfo.CreateCmdVar, "CreateCmdVar incorrect")
|
||||
assert.Equal(t, "Test description", myStepInfo.Short, "Short incorrect")
|
||||
assert.Equal(t, "Long Test description", myStepInfo.Long, "Long incorrect")
|
||||
assert.Equal(t, stepData.Spec.Inputs.Parameters, myStepInfo.Metadata, "Metadata incorrect")
|
||||
assert.Equal(t, "addTestStepFlags", myStepInfo.FlagsFunc, "FlagsFunc incorrect")
|
||||
|
||||
}
|
||||
|
||||
func TestLongName(t *testing.T) {
|
||||
tt := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{input: "my long name with no ticks", expected: "my long name with no ticks"},
|
||||
{input: "my long name with `ticks`", expected: "my long name with ` + \"`\" + `ticks` + \"`\" + `"},
|
||||
}
|
||||
|
||||
for k, v := range tt {
|
||||
assert.Equal(t, v.expected, longName(v.input), fmt.Sprintf("wrong long name for run %v", k))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGolangName(t *testing.T) {
|
||||
tt := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{input: "testApi", expected: "TestAPI"},
|
||||
{input: "testUrl", expected: "TestURL"},
|
||||
{input: "testId", expected: "TestID"},
|
||||
{input: "testJson", expected: "TestJSON"},
|
||||
{input: "jsonTest", expected: "JSONTest"},
|
||||
}
|
||||
|
||||
for k, v := range tt {
|
||||
assert.Equal(t, v.expected, golangName(v.input), fmt.Sprintf("wrong golang name for run %v", k))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagType(t *testing.T) {
|
||||
tt := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{input: "bool", expected: "BoolVar"},
|
||||
{input: "string", expected: "StringVar"},
|
||||
{input: "[]string", expected: "StringSliceVar"},
|
||||
}
|
||||
|
||||
for k, v := range tt {
|
||||
assert.Equal(t, v.expected, flagType(v.input), fmt.Sprintf("wrong flag type for run %v", k))
|
||||
}
|
||||
}
|
79
pkg/generator/testdata/TestProcessMetaFiles/step_code_generated.golden
vendored
Normal file
79
pkg/generator/testdata/TestProcessMetaFiles/step_code_generated.golden
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
//"os"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type testStepOptions struct {
|
||||
Param0 string `json:"param0,omitempty"`
|
||||
Param1 string `json:"param1,omitempty"`
|
||||
Param2 string `json:"param2,omitempty"`
|
||||
}
|
||||
|
||||
var myTestStepOptions testStepOptions
|
||||
var testStepStepConfigJSON string
|
||||
|
||||
// TestStepCommand Test description
|
||||
func TestStepCommand() *cobra.Command {
|
||||
metadata := testStepMetadata()
|
||||
var createTestStepCmd = &cobra.Command{
|
||||
Use: "testStep",
|
||||
Short: "Test description",
|
||||
Long: `Long Test description`,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
log.SetStepName("testStep")
|
||||
log.SetVerbose(generalConfig.verbose)
|
||||
return PrepareConfig(cmd, &metadata, "testStep", &myTestStepOptions, openPiperFile)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return testStep(myTestStepOptions)
|
||||
},
|
||||
}
|
||||
|
||||
addTestStepFlags(createTestStepCmd)
|
||||
return createTestStepCmd
|
||||
}
|
||||
|
||||
func addTestStepFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().StringVar(&myTestStepOptions.Param0, "param0", "val0", "param0 description")
|
||||
cmd.Flags().StringVar(&myTestStepOptions.Param1, "param1", os.Getenv("PIPER_param1"), "param1 description")
|
||||
cmd.Flags().StringVar(&myTestStepOptions.Param2, "param2", os.Getenv("PIPER_param2"), "param1 description")
|
||||
|
||||
cmd.MarkFlagRequired("param0")
|
||||
cmd.MarkFlagRequired("param2")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func testStepMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "param0",
|
||||
Scope: []string{"GENERAL","PARAMETERS",},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
{
|
||||
Name: "param1",
|
||||
Scope: []string{"PARAMETERS",},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
},
|
||||
{
|
||||
Name: "param2",
|
||||
Scope: []string{"PARAMETERS",},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
16
pkg/generator/testdata/TestProcessMetaFiles/test_code_generated.golden
vendored
Normal file
16
pkg/generator/testdata/TestProcessMetaFiles/test_code_generated.golden
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTestStepCommand(t *testing.T) {
|
||||
|
||||
testCmd := TestStepCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procudure
|
||||
assert.Equal(t, "testStep", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
28
pkg/log/log.go
Normal file
28
pkg/log/log.go
Normal file
@ -0,0 +1,28 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var logger *logrus.Entry
|
||||
|
||||
// Entry returns the logger entry or creates one if none is present.
|
||||
func Entry() *logrus.Entry {
|
||||
if logger == nil {
|
||||
logger = logrus.WithField("library", "sap/jenkins-library")
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// SetVerbose sets the log level with respect to verbose flag.
|
||||
func SetVerbose(verbose bool) {
|
||||
if verbose {
|
||||
//Logger().Debugf("logging set to level: %s", level)
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// SetStepName sets the stepName field.
|
||||
func SetStepName(stepName string) {
|
||||
logger = Entry().WithField("stepName", stepName)
|
||||
}
|
7
pom.xml
7
pom.xml
@ -139,6 +139,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>fr.opensagres.js</groupId>
|
||||
<artifactId>minimatch.java</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
@ -167,10 +167,10 @@ steps:
|
||||
- 'deployDescriptor'
|
||||
- 'pipelineConfigAndTests'
|
||||
cf_native:
|
||||
dockerImage: 's4sdk/docker-cf-cli'
|
||||
dockerImage: 'ppiper/cf-cli'
|
||||
dockerWorkspace: '/home/piper'
|
||||
mtaDeployPlugin:
|
||||
dockerImage: 's4sdk/docker-cf-cli'
|
||||
dockerImage: 'ppiper/cf-cli'
|
||||
dockerWorkspace: '/home/piper'
|
||||
containerExecuteStructureTests:
|
||||
containerCommand: '/busybox/tail -f /dev/null'
|
||||
@ -328,7 +328,7 @@ steps:
|
||||
mtaJarLocation: '/opt/sap/mta/lib/mta.jar'
|
||||
dockerImage: 'ppiper/mta-archive-builder'
|
||||
neoDeploy:
|
||||
dockerImage: 's4sdk/docker-neo-cli'
|
||||
dockerImage: 'ppiper/neo-cli'
|
||||
deployMode: 'mta'
|
||||
warAction: 'deploy'
|
||||
extensions: []
|
||||
@ -440,14 +440,14 @@ steps:
|
||||
noDefaultExludes: []
|
||||
pipelineStashFilesBeforeBuild:
|
||||
stashIncludes:
|
||||
buildDescriptor: '**/pom.xml, **/.mvn/**, **/assembly.xml, **/.swagger-codegen-ignore, **/package.json, **/requirements.txt, **/setup.py, **/mta*.y*ml, **/.npmrc, Dockerfile, .hadolint.yaml, **/VERSION, **/version.txt, **/Gopkg.*, **/dub.json, **/dub.sdl, **/build.sbt, **/sbtDescriptor.json, **/project/*'
|
||||
buildDescriptor: '**/pom.xml, **/.mvn/**, **/assembly.xml, **/.swagger-codegen-ignore, **/package.json, **/requirements.txt, **/setup.py, **/mta*.y*ml, **/.npmrc, Dockerfile, .hadolint.yaml, **/VERSION, **/version.txt, **/Gopkg.*, **/dub.json, **/dub.sdl, **/build.sbt, **/sbtDescriptor.json, **/project/*, **/ui5.yaml, **/ui5.yml'
|
||||
deployDescriptor: '**/manifest*.y*ml, **/*.mtaext.y*ml, **/*.mtaext, **/xs-app.json, helm/**, *.y*ml'
|
||||
git: '.git/**'
|
||||
opa5: '**/*.*'
|
||||
opensourceConfiguration: '**/srcclr.yml, **/vulas-custom.properties, **/.nsprc, **/.retireignore, **/.retireignore.json, **/.snyk, **/wss-unified-agent.config, **/vendor/**/*'
|
||||
pipelineConfigAndTests: '.pipeline/**'
|
||||
securityDescriptor: '**/xs-security.json'
|
||||
tests: '**/pom.xml, **/*.json, **/*.xml, **/src/**, **/node_modules/**, **/specs/**, **/env/**, **/*.js, **/tests/**'
|
||||
tests: '**/pom.xml, **/*.json, **/*.xml, **/src/**, **/node_modules/**, **/specs/**, **/env/**, **/*.js, **/tests/**, **/*.html, **/*.css, **/*.properties'
|
||||
stashExcludes:
|
||||
buildDescriptor: '**/node_modules/**/package.json'
|
||||
deployDescriptor: ''
|
||||
@ -526,7 +526,7 @@ steps:
|
||||
archive: false
|
||||
active: false
|
||||
cobertura:
|
||||
pattern: '**/target/coverage/cobertura-coverage.xml'
|
||||
pattern: '**/target/coverage/**/cobertura-coverage.xml'
|
||||
onlyStableBuilds: true
|
||||
allowEmptyResults: true
|
||||
archive: false
|
||||
|
67
resources/metadata/karma.yaml
Normal file
67
resources/metadata/karma.yaml
Normal file
@ -0,0 +1,67 @@
|
||||
metadata:
|
||||
name: karmaExecuteTests
|
||||
description: Executes the Karma test runner
|
||||
longDescription: |
|
||||
In this step the ([Karma test runner](http://karma-runner.github.io)) is executed.
|
||||
|
||||
The step is using the `seleniumExecuteTest` step to spin up two containers in a Docker network:
|
||||
|
||||
* a Selenium/Chrome container (`selenium/standalone-chrome`)
|
||||
* a NodeJS container (`node:8-stretch`)
|
||||
|
||||
In the Docker network, the containers can be referenced by the values provided in `dockerName` and `sidecarName`, the default values are `karma` and `selenium`. These values must be used in the `hostname` properties of the test configuration ([Karma](https://karma-runner.github.io/1.0/config/configuration-file.html) and [WebDriver](https://github.com/karma-runner/karma-webdriver-launcher#usage)).
|
||||
|
||||
!!! note
|
||||
In a Kubernetes environment, the containers both need to be referenced with `localhost`.
|
||||
spec:
|
||||
inputs:
|
||||
resources:
|
||||
- name: buildDescriptor
|
||||
type: stash
|
||||
- name: tests
|
||||
type: stash
|
||||
params:
|
||||
- name: installCommand
|
||||
type: string
|
||||
description: The command that is executed to install the test tool.
|
||||
default: npm install --quiet
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: modulePath
|
||||
type: string
|
||||
description: Define the path of the module to execute tests on.
|
||||
default: '.'
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: runCommand
|
||||
type: string
|
||||
description: The command that is executed to start the tests.
|
||||
default: npm run karma
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
#outputs:
|
||||
containers:
|
||||
- name: maven
|
||||
image: maven:3.5-jdk-8
|
||||
volumeMounts:
|
||||
- mountPath: /dev/shm
|
||||
name: dev-shm
|
||||
sidecars:
|
||||
- image: selenium/standalone-chrome
|
||||
name: selenium
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- mountPath: /dev/shm
|
||||
name: dev-shm
|
5
resources/metadata/version.yaml
Normal file
5
resources/metadata/version.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
metadata:
|
||||
name: version
|
||||
description: Returns the version of the piper binary
|
||||
longDescription: |
|
||||
Writes the commit hash and the tag (if any) to stdout and exits with 0.
|
@ -152,7 +152,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
cfManifest: 'test.yml'
|
||||
])
|
||||
// asserts
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
assertThat(dockerExecuteRule.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}"))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -204,7 +204,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
]
|
||||
])
|
||||
// asserts
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
assertThat(dockerExecuteRule.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}"))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -278,7 +278,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
cfManifest: 'test.yml'
|
||||
])
|
||||
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -306,7 +306,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
cfManifest: 'test.yml'
|
||||
])
|
||||
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -335,7 +335,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
cfManifest: 'test.yml'
|
||||
])
|
||||
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -373,7 +373,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
cfManifest: 'test.yml'
|
||||
])
|
||||
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -469,7 +469,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
mtaPath: 'target/test.mtar'
|
||||
])
|
||||
// asserts
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf deploy target/test.mtar -f')))
|
||||
@ -545,7 +545,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
cfManifestVariablesFiles: ['vars.yml']
|
||||
])
|
||||
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
assertThat(dockerExecuteRule.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}"))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -558,7 +558,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
@Test
|
||||
void testCfPushDeploymentWithVariableSubstitutionFromNotExistingFilePrintsWarning() {
|
||||
readYamlRule.registerYaml('test.yml', "applications: [[name: '((appName))']]")
|
||||
fileExistsRule.registerExistingFile('test.yml')
|
||||
fileExistsRule.registerExistingFile('test.yml')
|
||||
|
||||
stepRule.step.cloudFoundryDeploy([
|
||||
script: nullScript,
|
||||
@ -574,15 +574,15 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
])
|
||||
|
||||
// asserts
|
||||
assertThat(shellRule.shell, hasItem(containsString("cf push testAppName -f 'test.yml'")))
|
||||
assertThat(shellRule.shell, hasItem(containsString("cf push testAppName -f 'test.yml'")))
|
||||
assertThat(loggingRule.log, containsString("[WARNING] We skip adding not-existing file 'vars.yml' as a vars-file to the cf create-service-push call"))
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCfPushDeploymentWithVariableSubstitutionFromVarsList() {
|
||||
readYamlRule.registerYaml('test.yml', "applications: [[name: '((appName))']]")
|
||||
List varsList = [["appName" : "testApplicationFromVarsList"]]
|
||||
|
||||
List varsList = [["appName" : "testApplicationFromVarsList"]]
|
||||
|
||||
stepRule.step.cloudFoundryDeploy([
|
||||
script: nullScript,
|
||||
juStabUtils: utils,
|
||||
@ -597,7 +597,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
])
|
||||
|
||||
// asserts
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
assertThat(dockerExecuteRule.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}"))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -609,8 +609,8 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
|
||||
@Test
|
||||
void testCfPushDeploymentWithVariableSubstitutionFromVarsListNotAList() {
|
||||
readYamlRule.registerYaml('test.yml', "applications: [[name: '((appName))']]")
|
||||
|
||||
readYamlRule.registerYaml('test.yml', "applications: [[name: '((appName))']]")
|
||||
|
||||
thrown.expect(hudson.AbortException)
|
||||
thrown.expectMessage('[cloudFoundryDeploy] ERROR: Parameter config.cloudFoundry.manifestVariables is not a List!')
|
||||
|
||||
@ -626,7 +626,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
cfManifest: 'test.yml',
|
||||
cfManifestVariables: 'notAList'
|
||||
])
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -650,7 +650,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
])
|
||||
|
||||
// asserts
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
assertThat(dockerExecuteRule.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}"))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -674,8 +674,8 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
cfManifest: 'test.yml'
|
||||
])
|
||||
|
||||
// asserts
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
// asserts
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
assertThat(dockerExecuteRule.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}"))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -720,7 +720,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
assertNotNull(testYamlData)
|
||||
assertThat(testYamlData.get("applications").get(0).get(0).get("name"), is("testApplication"))
|
||||
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
assertThat(dockerExecuteRule.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}"))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
@ -766,7 +766,7 @@ class CloudFoundryDeployTest extends BasePiperTest {
|
||||
assertNotNull(testYamlData)
|
||||
assertThat(testYamlData.get("applications").get(0).get(0).get("name"), is("testApplicationFromVarsList"))
|
||||
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 's4sdk/docker-cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerImage', 'ppiper/cf-cli'))
|
||||
assertThat(dockerExecuteRule.dockerParams, hasEntry('dockerWorkspace', '/home/piper'))
|
||||
assertThat(dockerExecuteRule.dockerParams.dockerEnvVars, hasEntry('STATUS_CODE', "${200}"))
|
||||
assertThat(shellRule.shell, hasItem(containsString('cf login -u "test_cf" -p \'********\' -a https://api.cf.eu10.hana.ondemand.com -o "testOrg" -s "testSpace"')))
|
||||
|
@ -92,7 +92,7 @@ class MulticloudDeployTest extends BasePiperTest {
|
||||
deployType: 'blue-green',
|
||||
keepOldInstance: true,
|
||||
cf_native: [
|
||||
dockerImage: 's4sdk/docker-cf-cli',
|
||||
dockerImage: 'ppiper/cf-cli',
|
||||
dockerWorkspace: '/home/piper'
|
||||
]
|
||||
]
|
||||
|
@ -12,6 +12,7 @@ import static org.junit.Assert.assertEquals
|
||||
import static org.junit.Assert.assertTrue
|
||||
|
||||
import util.Rules
|
||||
import minimatch.Minimatch
|
||||
|
||||
class TestsPublishResultsTest extends BasePiperTest {
|
||||
Map publisherStepOptions
|
||||
@ -90,14 +91,20 @@ class TestsPublishResultsTest extends BasePiperTest {
|
||||
stepRule.step.testsPublishResults(script: nullScript, jacoco: true, cobertura: true)
|
||||
|
||||
assertTrue('JaCoCo options are empty', publisherStepOptions.jacoco != null)
|
||||
assertTrue('Cobertura options are empty', publisherStepOptions.cobertura != null)
|
||||
assertEquals('JaCoCo default pattern not set correct',
|
||||
'**/target/*.exec', publisherStepOptions.jacoco.execPattern)
|
||||
assertEquals('Cobertura default pattern not set correct',
|
||||
'**/target/coverage/cobertura-coverage.xml', publisherStepOptions.cobertura.coberturaReportFile)
|
||||
// ensure nothing else is published
|
||||
assertTrue('JUnit options are not empty', publisherStepOptions.junit == null)
|
||||
assertTrue('JMeter options are not empty', publisherStepOptions.jmeter == null)
|
||||
|
||||
assertTrue('Cobertura options are empty', publisherStepOptions.cobertura != null)
|
||||
assertTrue('Cobertura default pattern is empty', publisherStepOptions.cobertura.coberturaReportFile != null)
|
||||
String sampleCoberturaPathForJava = 'my/workspace/my/project/target/coverage/cobertura-coverage.xml'
|
||||
assertTrue('Cobertura default pattern does not match files at target/coverage/cobertura-coverage.xml for Java projects',
|
||||
Minimatch.minimatch(sampleCoberturaPathForJava, publisherStepOptions.cobertura.coberturaReportFile))
|
||||
String sampleCoberturaPathForKarma = 'my/workspace/my/project/target/coverage/Chrome 78.0.3904 (Mac OS X 10.14.6)/cobertura-coverage.xml'
|
||||
assertTrue('Cobertura default pattern does not match files at target/coverage/<browser>/cobertura-coverage.xml for UI5 projects',
|
||||
Minimatch.minimatch(sampleCoberturaPathForKarma, publisherStepOptions.cobertura.coberturaReportFile))
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -145,7 +152,7 @@ class TestsPublishResultsTest extends BasePiperTest {
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
stepRule.step.testsPublishResults(script: nullScript)
|
||||
assertJobStatusSuccess()
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ import hudson.AbortException
|
||||
'containerEnvVars',
|
||||
/**
|
||||
* A map of docker image to the name of the container. The pod will be created with all the images from this map and they are labled based on the value field of each map entry.
|
||||
* Example: `['maven:3.5-jdk-8-alpine': 'mavenExecute', 'selenium/standalone-chrome': 'selenium', 'famiko/jmeter-base': 'checkJMeter', 's4sdk/docker-cf-cli': 'cloudfoundry']`
|
||||
* Example: `['maven:3.5-jdk-8-alpine': 'mavenExecute', 'selenium/standalone-chrome': 'selenium', 'famiko/jmeter-base': 'checkJMeter', 'ppiper/cf-cli': 'cloudfoundry']`
|
||||
*/
|
||||
'containerMap',
|
||||
/**
|
||||
|
@ -14,7 +14,7 @@ import java.nio.charset.StandardCharsets
|
||||
@Field Set GENERAL_CONFIG_KEYS = [
|
||||
/**
|
||||
* Pull-Request voting only:
|
||||
* The URL to the Github API. see https://docs.sonarqube.org/display/PLUG/GitHub+Plugin#GitHubPlugin-Usage
|
||||
* The URL to the Github API. see [GitHub plugin docs](https://docs.sonarqube.org/display/PLUG/GitHub+Plugin#GitHubPlugin-Usage)
|
||||
* deprecated: only supported in LTS / < 7.2
|
||||
*/
|
||||
'githubApiUrl',
|
||||
@ -38,7 +38,7 @@ import java.nio.charset.StandardCharsets
|
||||
*/
|
||||
'githubTokenCredentialsId',
|
||||
/**
|
||||
* The Jenkins credentialsId for a SonarQube token. It is needed for non-anonymous analysis runs. see https://sonarcloud.io/account/security
|
||||
* The Jenkins credentialsId for a SonarQube token. It is needed for non-anonymous analysis runs. see [SonarQube docs](https://docs.sonarqube.org/latest/user-guide/user-token/)
|
||||
* @possibleValues Jenkins credential id
|
||||
*/
|
||||
'sonarTokenCredentialsId',
|
||||
@ -62,7 +62,7 @@ import java.nio.charset.StandardCharsets
|
||||
'disableInlineComments',
|
||||
/**
|
||||
* Name of the docker image that should be used. If empty, Docker is not used and the command is executed directly on the Jenkins system.
|
||||
* see dockerExecute
|
||||
* see [dockerExecute](dockerExecute.md)
|
||||
*/
|
||||
'dockerImage',
|
||||
/**
|
||||
@ -120,21 +120,22 @@ void call(Map parameters = [:]) {
|
||||
configuration.options = [].plus(configuration.options)
|
||||
|
||||
def worker = { config ->
|
||||
withSonarQubeEnv(config.instance) {
|
||||
try{
|
||||
loadSonarScanner(config)
|
||||
try {
|
||||
withSonarQubeEnv(config.instance) {
|
||||
|
||||
loadCertificates(config)
|
||||
loadSonarScanner(config)
|
||||
|
||||
if(config.organization) config.options.add("sonar.organization=${config.organization}")
|
||||
if(config.projectVersion) config.options.add("sonar.projectVersion=${config.projectVersion}")
|
||||
// prefix options
|
||||
config.options = config.options.collect { it.startsWith('-D') ? it : "-D${it}" }
|
||||
loadCertificates(config)
|
||||
|
||||
sh "PATH=\$PATH:${env.WORKSPACE}/.sonar-scanner/bin sonar-scanner ${config.options.join(' ')}"
|
||||
}finally{
|
||||
sh 'rm -rf .sonar-scanner .certificates .scannerwork'
|
||||
if(config.organization) config.options.add("sonar.organization=${config.organization}")
|
||||
if(config.projectVersion) config.options.add("sonar.projectVersion=${config.projectVersion}")
|
||||
// prefix options
|
||||
config.options = config.options.collect { it.startsWith('-D') ? it : "-D${it}" }
|
||||
|
||||
sh "PATH=\$PATH:${env.WORKSPACE}/.sonar-scanner/bin sonar-scanner ${config.options.join(' ')}"
|
||||
}
|
||||
} finally {
|
||||
sh 'rm -rf .sonar-scanner .certificates .scannerwork'
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user