mirror of
https://github.com/SAP/jenkins-library.git
synced 2026-06-19 22:58:55 +02:00
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:
+14
-27
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user