1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-18 05:18:24 +02:00

Go configuration - add aliasing functionality (#925)

* Go configuration - add aliasing functionality

This assists in backward compatibility cases.
It is now possible to define per parameter aliases.
This commit is contained in:
Oliver Nocon 2019-10-29 10:58:24 +01:00 committed by GitHub
parent 0f3436d1a5
commit e01b3327fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 215 additions and 7 deletions

View File

@ -73,7 +73,12 @@ func generateConfig() error {
var flags map[string]interface{}
stepConfig, err = myConfig.GetStepConfig(flags, generalConfig.parametersJSON, customConfig, defaultConfig, paramFilter, generalConfig.stageName, configOptions.stepName)
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")
}

View File

@ -84,7 +84,7 @@ func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName strin
}
var err error
stepConfig, err = myConfig.GetStepConfig(flagValues, generalConfig.parametersJSON, customConfig, defaultConfig, filters, generalConfig.stageName, stepName)
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")
}

View File

@ -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), &params)
//apply aliases
for _, p := range parameters {
params = setParamValueFromAlias(params, filters.Parameters, p)
}
stepConfig.mixIn(params, filters.Parameters)
}

View File

@ -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) {

View File

@ -47,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
@ -164,7 +171,7 @@ func (m *StepData) GetContextParameterFilters() StepFilters {
return filters
}
// GetContextDefaults retrieves context defaults like container image, name, env vars, ...
// 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) {
@ -208,6 +215,16 @@ func (m *StepData) GetContextDefaults(stepName string) (io.ReadCloser, error) {
//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,

View File

@ -282,6 +282,22 @@ 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"},
@ -323,6 +339,7 @@ func TestGetContextDefaults(t *testing.T) {
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")