Clean-up checkIfStepActive (#4814)

* Clean-up checkIfStepActive

* Mark --useV1 deprecated

* Clean up tests

* Update test

* Add warning message

* Update warning msg
This commit is contained in:
Vyacheslav Starostin
2024-02-08 15:43:26 +06:00
committed by GitHub
parent c2343b19b8
commit 4e3fa38dd3
7 changed files with 27 additions and 1094 deletions
+14 -27
View File
@@ -6,12 +6,12 @@ import (
"io"
"os"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/bmatcuk/doublestar"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type checkStepActiveCommandOptions struct {
@@ -63,6 +63,9 @@ func checkIfStepActive(utils piperutils.FileUtils) error {
if checkStepActiveOptions.stageName == "" {
return errors.New("stage name must not be empty")
}
if checkStepActiveOptions.v1Active {
log.Entry().Warning("Please do not use --useV1 flag since it is deprecated and will be removed in future releases")
}
var pConfig config.Config
// load project config and defaults
@@ -78,31 +81,15 @@ func checkIfStepActive(utils piperutils.FileUtils) error {
}
defer stageConfigFile.Close()
var runSteps map[string]map[string]bool
var runStages map[string]bool
// load and evaluate step conditions
if checkStepActiveOptions.v1Active {
runConfig := config.RunConfig{StageConfigFile: stageConfigFile}
runConfigV1 := &config.RunConfigV1{RunConfig: runConfig}
err = runConfigV1.InitRunConfigV1(projectConfig, utils, GeneralConfig.EnvRootPath)
if err != nil {
return err
}
runSteps = runConfigV1.RunSteps
runStages = runConfigV1.RunStages
} else {
log.Entry().Warning("This step is using deprecated format of stage conditions which will be removed in Jan 2024. " +
"To avoid pipeline breakage, please call checkIfStepActive command with --useV1 flag.",
)
runConfig := &config.RunConfig{StageConfigFile: stageConfigFile}
err = runConfig.InitRunConfig(projectConfig, nil, nil, nil, nil, doublestar.Glob, checkStepActiveOptions.openFile)
if err != nil {
return err
}
runSteps = runConfig.RunSteps
runStages = runConfig.RunStages
runConfig := config.RunConfig{StageConfigFile: stageConfigFile}
runConfigV1 := &config.RunConfigV1{RunConfig: runConfig}
err = runConfigV1.InitRunConfigV1(projectConfig, utils, GeneralConfig.EnvRootPath)
if err != nil {
return err
}
runSteps := runConfigV1.RunSteps
runStages := runConfigV1.RunStages
log.Entry().Debugf("RunSteps: %v", runSteps)
log.Entry().Debugf("RunStages: %v", runStages)
@@ -149,7 +136,7 @@ func addCheckStepActiveFlags(cmd *cobra.Command) {
"Default config of piper pipeline stages")
cmd.Flags().StringVar(&checkStepActiveOptions.stepName, "step", "", "Name of the step being checked")
cmd.Flags().StringVar(&checkStepActiveOptions.stageName, "stage", "", "Name of the stage in which contains the step being checked")
cmd.Flags().BoolVar(&checkStepActiveOptions.v1Active, "useV1", false, "Use new CRD-style stage configuration")
cmd.Flags().BoolVar(&checkStepActiveOptions.v1Active, "useV1", false, "Use new CRD-style stage configuration (deprecated)")
cmd.Flags().StringVar(&checkStepActiveOptions.stageOutputFile, "stageOutputFile", "", "Defines a file path. If set, the stage output will be written to the defined file")
cmd.Flags().StringVar(&checkStepActiveOptions.stepOutputFile, "stepOutputFile", "", "Defines a file path. If set, the step output will be written to the defined file")
_ = cmd.MarkFlagRequired("step")
+8 -5
View File
@@ -25,11 +25,14 @@ stages:
steps:`
case "stage-config.yml":
fileContent = `
stages:
testStage:
stepConditions:
testStep:
config: testConfig`
spec:
stages:
- name: testStage
displayName: testStage
steps:
- name: testStep
conditions:
- configKey: testConfig`
case ".pipeline/config.yml":
fileContent = `
steps:
+3 -5
View File
@@ -6,11 +6,12 @@ import (
"io"
"os"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/SAP/jenkins-library/pkg/config"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type defaultsCommandOptions struct {
@@ -81,9 +82,6 @@ func getDefaults() ([]map[string]string, error) {
var yamlContent string
if !defaultsOptions.useV1 {
log.Entry().Warning("This step is using deprecated format of stage conditions which will be removed in Jan 2024. " +
"To avoid pipeline breakage, please call getDefaults command with --useV1 flag.",
)
var c config.Config
c.ReadConfig(fc)
+2 -229
View File
@@ -3,14 +3,13 @@ package config
import (
"encoding/json"
"fmt"
"io"
"path"
"strings"
"github.com/pkg/errors"
"github.com/SAP/jenkins-library/pkg/orchestrator"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/pkg/errors"
)
const (
@@ -254,232 +253,6 @@ func checkConfigKeyV1(config map[string]interface{}, configKey []string) (bool,
return checkConfigKeyV1(castedValue, configKey[1:])
}
// EvaluateConditions validates stage conditions and updates runSteps in runConfig
func (r *RunConfig) evaluateConditions(config *Config, filters map[string]StepFilters, parameters map[string][]StepParameters,
secrets map[string][]StepSecrets, stepAliases map[string][]Alias, glob func(pattern string) (matches []string, err error)) error {
for stageName, stepConditions := range r.StageConfig.Stages {
runStep := map[string]bool{}
for stepName, stepCondition := range stepConditions.Conditions {
stepActive := false
stepConfig, err := r.getStepConfig(config, stageName, stepName, filters, parameters, secrets, stepAliases)
if err != nil {
return err
}
if active, ok := stepConfig.Config[stepName].(bool); ok {
// respect explicit activation/de-activation if available
stepActive = active
} else {
for conditionName, condition := range stepCondition {
var err error
switch conditionName {
case configCondition:
if stepActive, err = checkConfig(condition, stepConfig, stepName); err != nil {
return errors.Wrapf(err, "error: check config condition failed")
}
case configKeysCondition:
if stepActive, err = checkConfigKeys(condition, stepConfig, stepName); err != nil {
return errors.Wrapf(err, "error: check configKeys condition failed")
}
case filePatternFromConfigCondition:
if stepActive, err = checkForFilesWithPatternFromConfig(condition, stepConfig, stepName, glob); err != nil {
return errors.Wrapf(err, "error: check filePatternFromConfig condition failed")
}
case filePatternCondition:
if stepActive, err = checkForFilesWithPattern(condition, stepConfig, stepName, glob); err != nil {
return errors.Wrapf(err, "error: check filePattern condition failed")
}
case npmScriptsCondition:
if stepActive, err = checkForNpmScriptsInPackages(condition, stepConfig, stepName, glob, r.OpenFile); err != nil {
return errors.Wrapf(err, "error: check npmScripts condition failed")
}
default:
return errors.Errorf("unknown condition %s", conditionName)
}
if stepActive {
break
}
}
}
runStep[stepName] = stepActive
r.RunSteps[stageName] = runStep
}
}
return nil
}
func checkConfig(condition interface{}, config StepConfig, stepName string) (bool, error) {
switch condition := condition.(type) {
case string:
if configValue := stepConfigLookup(config.Config, stepName, condition); configValue != nil {
return true, nil
}
case map[string]interface{}:
for conditionConfigKey, conditionConfigValue := range condition {
configValue := stepConfigLookup(config.Config, stepName, conditionConfigKey)
if configValue == nil {
return false, nil
}
configValueStr, ok := configValue.(string)
if !ok {
return false, errors.Errorf("error: config value of %v to compare with is not a string", configValue)
}
condConfigValueArr, ok := conditionConfigValue.([]interface{})
if !ok {
return false, errors.Errorf("error: type assertion to []interface{} failed: %T", conditionConfigValue)
}
for _, item := range condConfigValueArr {
itemStr, ok := item.(string)
if !ok {
return false, errors.Errorf("error: type assertion to string failed: %T", conditionConfigValue)
}
if configValueStr == itemStr {
return true, nil
}
}
}
default:
return false, errors.Errorf("error: condidiion type invalid: %T, possible types: string, map[string]interface{}", condition)
}
return false, nil
}
func checkConfigKey(configKey string, config StepConfig, stepName string) (bool, error) {
if configValue := stepConfigLookup(config.Config, stepName, configKey); configValue != nil {
return true, nil
}
return false, nil
}
func checkConfigKeys(condition interface{}, config StepConfig, stepName string) (bool, error) {
arrCondition, ok := condition.([]interface{})
if !ok {
return false, errors.Errorf("error: type assertion to []interface{} failed: %T", condition)
}
for _, configKey := range arrCondition {
if configValue := stepConfigLookup(config.Config, stepName, configKey.(string)); configValue != nil {
return true, nil
}
}
return false, nil
}
func checkForFilesWithPatternFromConfig(condition interface{}, config StepConfig, stepName string,
glob func(pattern string) (matches []string, err error)) (bool, error) {
filePatternConfig, ok := condition.(string)
if !ok {
return false, errors.Errorf("error: type assertion to string failed: %T", condition)
}
filePatternFromConfig := stepConfigLookup(config.Config, stepName, filePatternConfig)
if filePatternFromConfig == nil {
return false, nil
}
filePattern, ok := filePatternFromConfig.(string)
if !ok {
return false, errors.Errorf("error: type assertion to string failed: %T", filePatternFromConfig)
}
matches, err := glob(filePattern)
if err != nil {
return false, errors.Wrap(err, "error: failed to check if file-exists")
}
if len(matches) > 0 {
return true, nil
}
return false, nil
}
func checkForFilesWithPattern(condition interface{}, config StepConfig, stepName string,
glob func(pattern string) (matches []string, err error)) (bool, error) {
switch condition := condition.(type) {
case string:
filePattern := condition
matches, err := glob(filePattern)
if err != nil {
return false, errors.Wrap(err, "error: failed to check if file-exists")
}
if len(matches) > 0 {
return true, nil
}
case []interface{}:
filePatterns := condition
for _, filePattern := range filePatterns {
filePatternStr, ok := filePattern.(string)
if !ok {
return false, errors.Errorf("error: type assertion to string failed: %T", filePatternStr)
}
matches, err := glob(filePatternStr)
if err != nil {
return false, errors.Wrap(err, "error: failed to check if file-exists")
}
if len(matches) > 0 {
return true, nil
}
}
default:
return false, errors.Errorf("error: condidiion type invalid: %T, possible types: string, []interface{}", condition)
}
return false, nil
}
func checkForNpmScriptsInPackages(condition interface{}, config StepConfig, stepName string,
glob func(pattern string) (matches []string, err error), openFile func(s string, t map[string]string) (io.ReadCloser, error)) (bool, error) {
packages, err := glob("**/package.json")
if err != nil {
return false, errors.Wrap(err, "error: failed to check if file-exists")
}
for _, pack := range packages {
packDirs := strings.Split(path.Dir(pack), "/")
isNodeModules := false
for _, dir := range packDirs {
if dir == "node_modules" {
isNodeModules = true
break
}
}
if isNodeModules {
continue
}
jsonFile, err := openFile(pack, nil)
if err != nil {
return false, errors.Errorf("error: failed to open file %s: %v", pack, err)
}
defer jsonFile.Close()
packageJSON := map[string]interface{}{}
if err := json.NewDecoder(jsonFile).Decode(&packageJSON); err != nil {
return false, errors.Errorf("error: failed to unmarshal json file %s: %v", pack, err)
}
npmScripts, ok := packageJSON["scripts"]
if !ok {
continue
}
scriptsMap, ok := npmScripts.(map[string]interface{})
if !ok {
return false, errors.Errorf("error: type assertion to map[string]interface{} failed: %T", npmScripts)
}
switch condition := condition.(type) {
case string:
if _, ok := scriptsMap[condition]; ok {
return true, nil
}
case []interface{}:
for _, conditionNpmScript := range condition {
conditionNpmScriptStr, ok := conditionNpmScript.(string)
if !ok {
return false, errors.Errorf("error: type assertion to string failed: %T", conditionNpmScript)
}
if _, ok := scriptsMap[conditionNpmScriptStr]; ok {
return true, nil
}
}
default:
return false, errors.Errorf("error: condidiion type invalid: %T, possible types: string, []interface{}", condition)
}
}
return false, nil
}
func checkForNpmScriptsInPackagesV1(npmScript string, config StepConfig, utils piperutils.FileUtils) (bool, error) {
packages, err := utils.Glob("**/package.json")
if err != nil {
-507
View File
@@ -4,7 +4,6 @@
package config
import (
"errors"
"fmt"
"io"
"os"
@@ -445,512 +444,6 @@ func TestEvaluateV1(t *testing.T) {
}
}
func TestEvaluateConditions(t *testing.T) {
tests := []struct {
name string
customConfig *Config
stageConfig io.ReadCloser
runStepsExpected map[string]map[string]bool
globFunc func(pattern string) ([]string, error)
wantErr bool
}{
{
name: "test config condition - success",
customConfig: &Config{
General: map[string]interface{}{
"testGeneral": "myVal1",
},
Stages: map[string]map[string]interface{}{
"testStage2": {
"testStage": "myVal2",
},
},
Steps: map[string]map[string]interface{}{
"thirdStep": {
"testStep1": "myVal3",
},
},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
config: testGeneral
testStage2:
stepConditions:
secondStep:
config: testStage
testStage3:
stepConditions:
thirdStep:
config: testStep1
forthStep:
config: testStep2
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {"firstStep": true},
"testStage2": {"secondStep": true},
"testStage3": {
"thirdStep": true,
"forthStep": false,
},
},
wantErr: false,
},
{
name: "test config condition - wrong usage with list",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
config:
- testGeneral
`)),
runStepsExpected: map[string]map[string]bool{},
wantErr: true,
},
{
name: "test config value condition - success",
customConfig: &Config{
General: map[string]interface{}{
"testGeneral": "myVal1",
},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"thirdStep": {
"testStep": "myVal3",
},
},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
config:
testGeneral:
- myValx
- myVal1
testStage2:
stepConditions:
secondStep:
config:
testStage:
- maValXyz
testStage3:
stepConditions:
thirdStep:
config:
testStep:
- myVal3
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {"firstStep": true},
"testStage2": {"secondStep": false},
"testStage3": {"thirdStep": true},
},
wantErr: false,
},
{
name: "test configKey condition - success",
customConfig: &Config{
General: map[string]interface{}{
"myKey1_1": "myVal1_1",
},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"thirdStep": {
"myKey3_1": "myVal3_1",
},
},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
configKeys:
- myKey1_1
- myKey1_2
testStage2:
stepConditions:
secondStep:
configKeys:
- myKey2_1
testStage3:
stepConditions:
thirdStep:
configKeys:
- myKey3_1
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {"firstStep": true},
"testStage2": {"secondStep": false},
"testStage3": {"thirdStep": true},
},
wantErr: false,
},
{
name: "test configKey condition - not list",
customConfig: &Config{
General: map[string]interface{}{
"myKey1_1": "myVal1_1",
},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
configKeys: myKey1_1
`)),
wantErr: true,
},
{
name: "test configKey condition - success",
customConfig: &Config{
General: map[string]interface{}{
"myKey1_1": "myVal1_1",
},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"thirdStep": {
"myKey3_1": "myVal3_1",
},
},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
configKeys:
- myKey1_1
- myKey1_2
testStage2:
stepConditions:
secondStep:
configKeys:
- myKey2_1
testStage3:
stepConditions:
thirdStep:
configKeys:
- myKey3_1
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {"firstStep": true},
"testStage2": {"secondStep": false},
"testStage3": {"thirdStep": true},
},
wantErr: false,
},
{
name: "test filePattern condition - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePattern: '**/conf.js'
secondStep:
filePattern: '**/conf.jsx'
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
wantErr: false,
},
{
name: "test filePattern condition - error while searching files by pattern",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePattern: '**/conf.js'
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return nil, errors.New("failed to check if file exists")
},
wantErr: true,
},
{
name: "test filePattern condition with list - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePattern:
- '**/conf.js'
- 'myCollection.json'
secondStep:
filePattern:
- '**/conf.jsx'
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
wantErr: false,
},
{
name: "test filePattern condition with list - error while searching files by pattern",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePattern:
- '**/conf.js'
- 'myCollection.json'
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return nil, errors.New("failed to check if file exists")
},
wantErr: true,
},
{
name: "test filePatternFromConfig condition - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"firstStep": {
"myVal1": "**/conf.js",
},
"thirdStep": {
"myVal3": "**/conf.jsx",
},
},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePatternFromConfig: myVal1
secondStep:
filePatternFromConfig: myVal2
thirdStep:
filePatternFromConfig: myVal3
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
"thirdStep": false,
},
},
wantErr: false,
},
{
name: "test filePatternFromConfig condition - error while searching files by pattern",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{
"firstStep": {
"myVal1": "**/conf.js",
},
},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePatternFromConfig: myVal1
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return nil, errors.New("failed to check if file exists")
},
wantErr: true,
},
{
name: "test npmScripts condition - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
npmScripts: 'npmScript'
secondStep:
npmScripts: 'npmScript1'
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
wantErr: false,
},
{
name: "test npmScripts condition with list - success",
globFunc: evaluateConditionsGlobMock,
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
npmScripts:
- 'npmScript'
- 'npmScript2'
secondStep:
npmScripts:
- 'npmScript3'
- 'npmScript4'
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
wantErr: false,
},
{
name: "test npmScripts condition - json with wrong format",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
npmScripts:
- 'npmScript3'
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return []string{"_package.json"}, nil
},
wantErr: true,
},
{
name: "test npmScripts condition - error while searching package.json",
customConfig: &Config{
General: map[string]interface{}{},
Stages: map[string]map[string]interface{}{},
Steps: map[string]map[string]interface{}{},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
npmScripts:
- 'npmScript3'
`)),
runStepsExpected: map[string]map[string]bool{},
globFunc: func(pattern string) ([]string, error) {
return nil, errors.New("failed to check if file exists")
},
wantErr: true,
},
{
name: "test explicit activation / de-activation of step",
customConfig: &Config{
Stages: map[string]map[string]interface{}{
"testStage1": {
"firstStep": true,
"fisecondStep": false,
},
},
},
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
config: testGeneral
secondStep:
config: testStage
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
"secondStep": false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runConfig := RunConfig{
StageConfigFile: tt.stageConfig,
RunSteps: map[string]map[string]bool{},
OpenFile: evaluateConditionsOpenFileMock,
}
err := runConfig.loadConditions()
assert.NoError(t, err)
err = runConfig.evaluateConditions(tt.customConfig, nil, nil, nil, nil, tt.globFunc)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.runStepsExpected, runConfig.RunSteps)
}
})
}
}
func TestAnyOtherStepIsActive(t *testing.T) {
targetStep := "step3"
-76
View File
@@ -94,27 +94,6 @@ func (r *RunConfigV1) InitRunConfigV1(config *Config, utils piperutils.FileUtils
return nil
}
// InitRunConfig ...
func (r *RunConfig) InitRunConfig(config *Config, filters map[string]StepFilters, parameters map[string][]StepParameters,
secrets map[string][]StepSecrets, stepAliases map[string][]Alias, glob func(pattern string) (matches []string, err error),
openFile func(s string, t map[string]string) (io.ReadCloser, error)) error {
r.OpenFile = openFile
r.RunSteps = map[string]map[string]bool{}
if len(r.StageConfig.Stages) == 0 {
if err := r.loadConditions(); err != nil {
return errors.Wrap(err, "failed to load pipeline run conditions")
}
}
err := r.evaluateConditions(config, filters, parameters, secrets, stepAliases, glob)
if err != nil {
return errors.Wrap(err, "failed to evaluate step conditions: %v")
}
return nil
}
// ToDo: optimize parameter handling
func (r *RunConfig) getStepConfig(config *Config, stageName, stepName string, filters map[string]StepFilters,
parameters map[string][]StepParameters, secrets map[string][]StepSecrets, stepAliases map[string][]Alias) (StepConfig, error) {
@@ -139,20 +118,6 @@ func (r *RunConfig) getStepConfig(config *Config, stageName, stepName string, fi
return config.GetStepConfig(flagValues, paramJSON, nil, nil, false, filters[stepName], stepMeta, envParameters, stageName, stepName)
}
func (r *RunConfig) loadConditions() error {
defer r.StageConfigFile.Close()
content, err := io.ReadAll(r.StageConfigFile)
if err != nil {
return errors.Wrapf(err, "error: failed to read the stageConfig file")
}
err = yaml.Unmarshal(content, &r.StageConfig)
if err != nil {
return errors.Errorf("format of configuration is invalid %q: %v", content, err)
}
return nil
}
// LoadConditionsV1 loads stage conditions (in CRD-style) into PipelineConfig
func (r *RunConfigV1) LoadConditionsV1() error {
defer r.StageConfigFile.Close()
@@ -167,44 +132,3 @@ func (r *RunConfigV1) LoadConditionsV1() error {
}
return nil
}
func stepConfigLookup(m map[string]interface{}, stepName, key string) interface{} {
// flat map: key is on top level
if m[key] != nil {
return m[key]
}
// lookup for step config with following format
// general:
// <key>: <value>
// stages:
// <stepName>:
// <key>: <value>
// steps:
// <stepName>:
// <key>: <value>
if m["general"] != nil {
general := m["general"].(map[string]interface{})
if general[key] != nil {
return general[key]
}
}
if m["stages"] != nil {
stages := m["stages"].(map[string]interface{})
if stages[stepName] != nil {
stageStepConfig := stages[stepName].(map[string]interface{})
if stageStepConfig[key] != nil {
return stageStepConfig[key]
}
}
}
if m["steps"] != nil {
steps := m["steps"].(map[string]interface{})
if steps[stepName] != nil {
stepConfig := steps[stepName].(map[string]interface{})
if stepConfig[key] != nil {
return stepConfig[key]
}
}
}
return nil
}
-245
View File
@@ -6,7 +6,6 @@ package config
import (
"fmt"
"io"
"reflect"
"strings"
"testing"
@@ -71,247 +70,3 @@ func TestInitRunConfigV1(t *testing.T) {
}
}
func TestInitRunConfig(t *testing.T) {
tests := []struct {
name string
customConfig io.ReadCloser
stageConfig io.ReadCloser
runStepsExpected map[string]map[string]bool
wantErr bool
}{
{
name: "init run config with config condition - success",
customConfig: io.NopCloser(strings.NewReader(`
general:
testGeneral: 'myVal1'
stages:
testStage2:
testStage: 'myVal2'
steps:
thirdStep:
testStep: 'myVal3'
`)),
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
config: testGeneral
testStage2:
stepConditions:
secondStep:
config: testStage
testStage3:
stepConditions:
thirdStep:
config: testStep
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
},
"testStage2": {
"secondStep": true,
},
"testStage3": {
"thirdStep": true,
},
},
wantErr: false,
},
{
name: "init run config with filePattern condition - success",
customConfig: io.NopCloser(strings.NewReader(`
general:
testGeneral: 'myVal1'
stages:
testStage2:
testStage: 'myVal2'
steps:
thirdStep:
testStep: 'myVal3'
`)),
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage1:
stepConditions:
firstStep:
filePattern: "**/file1"
testStage2:
stepConditions:
secondStep:
filePattern: "directory/file2"
testStage3:
stepConditions:
thirdStep:
filePattern: "file3"
`)),
runStepsExpected: map[string]map[string]bool{
"testStage1": {
"firstStep": true,
},
"testStage2": {
"secondStep": true,
},
"testStage3": {
"thirdStep": false,
},
},
wantErr: false,
},
{
name: "init run config - unknown condition in stage config",
customConfig: io.NopCloser(strings.NewReader(`
steps:
testStep:
testConfig: 'testVal'
`)),
stageConfig: io.NopCloser(strings.NewReader(`
stages:
testStage:
stepConditions:
testStep:
wrongCondition: "condVal"
`)),
runStepsExpected: map[string]map[string]bool{},
wantErr: true,
},
{
name: "init run config - load conditions with invalid format",
stageConfig: io.NopCloser(strings.NewReader("wrong stage config format")),
runStepsExpected: map[string]map[string]bool{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runConfig := RunConfig{StageConfigFile: tt.stageConfig}
filter := StepFilters{All: []string{}, General: []string{}, Stages: []string{}, Steps: []string{}, Env: []string{}}
projectConfig := Config{}
_, err := projectConfig.GetStepConfig(map[string]interface{}{}, "", tt.customConfig,
[]io.ReadCloser{}, false, filter, StepData{}, nil, "", "")
assert.NoError(t, err)
err = runConfig.InitRunConfig(&projectConfig, nil, nil, nil, nil, initRunConfigGlobMock, nil)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.runStepsExpected, runConfig.RunSteps)
}
})
}
}
func TestRunConfigLoadConditions(t *testing.T) {
stageConfigContent := `stages:
'testStage1':
stepConditions:
firstStep:
filePattern: '**/my.file'
`
t.Run("load conditions - file of invalid format", func(t *testing.T) {
runConfig := &RunConfig{StageConfigFile: io.NopCloser(strings.NewReader("-- {{ \\ wrong } file format }"))}
err := runConfig.loadConditions()
assert.Error(t, err, "format of configuration is invalid")
})
t.Run("load conditions - success", func(t *testing.T) {
runConfig := &RunConfig{StageConfigFile: io.NopCloser(strings.NewReader(stageConfigContent))}
err := runConfig.loadConditions()
assert.NoError(t, err)
condition := map[string]interface{}{
"filePattern": "**/my.file",
}
assert.Equal(t, 1, len(runConfig.StageConfig.Stages))
assert.Equal(t, 1, len(runConfig.StageConfig.Stages["testStage1"].Conditions))
assert.Equal(t, condition, runConfig.StageConfig.Stages["testStage1"].Conditions["firstStep"])
})
}
func Test_stepConfigLookup(t *testing.T) {
testConfig := map[string]interface{}{
"general": map[string]interface{}{
"generalKey": "generalValue",
},
"stages": map[string]interface{}{
"testStep": map[string]interface{}{
"stagesKey": "stagesValue",
},
},
"steps": map[string]interface{}{
"testStep": map[string]interface{}{
"stepKey": "stepValue",
"stepKeyStringSlice": []string{"val1", "val2"},
},
},
"configKey": "configValue",
}
type args struct {
m map[string]interface{}
stepName string
key string
}
tests := []struct {
name string
args args
want interface{}
}{
{
name: "empty map",
args: args{nil, "", ""},
want: nil,
},
{
name: "key not in map, invalid stepName",
args: args{testConfig, "some step", "some key"},
want: nil,
},
{
name: "key not in map, valid stepName",
args: args{testConfig, "testStep", "some key"},
want: nil,
},
{
name: "key in map under general",
args: args{testConfig, "some step", "generalKey"},
want: "generalValue",
},
{
name: "key in map under stages",
args: args{testConfig, "testStep", "stagesKey"},
want: "stagesValue",
},
{
name: "key in map under general",
args: args{testConfig, "testStep", "stepKey"},
want: "stepValue",
},
{
name: "key in map under general",
args: args{testConfig, "testStep", "stepKeyStringSlice"},
want: []string{"val1", "val2"},
},
{
name: "key in map on top level string",
args: args{testConfig, "", "configKey"},
want: "configValue",
},
{
name: "key in map on top level map",
args: args{testConfig, "", "general"},
want: map[string]interface{}{"generalKey": "generalValue"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := stepConfigLookup(tt.args.m, tt.args.stepName, tt.args.key); !reflect.DeepEqual(got, tt.want) {
t.Errorf("stepConfigLookup() = %v, want %v", got, tt.want)
}
})
}
}