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:
parent
0f3436d1a5
commit
e01b3327fd
@ -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")
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user