Skip to content

Commit 8c863e4

Browse files
GooglomGulom Alimovjliempt
authored
sapCumulusUpload step deactivation if its the only active step in stage (#4476)
* implement deactivation logic * add step condition field * add unit test and fix evaluateConditions * add unit test for v1 and fix evaluateConditionsV1 * rollback old evaluator * rollback v1 evaluator * move into notActiveCondition and fix unit tests * add a comment about sapCumulusUpload step * optimize evaluateConditionsV1 parameters and map memory allocation * refactor unit tests and add more test cases * evaluateConditionsV1 refactored --------- Co-authored-by: Gulom Alimov <[email protected]> Co-authored-by: Jordi van Liempt <[email protected]>
1 parent 97edad0 commit 8c863e4

File tree

5 files changed

+343
-240
lines changed

5 files changed

+343
-240
lines changed

cmd/checkIfStepActive.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func checkIfStepActive(utils piperutils.FileUtils) error {
8585
if checkStepActiveOptions.v1Active {
8686
runConfig := config.RunConfig{StageConfigFile: stageConfigFile}
8787
runConfigV1 := &config.RunConfigV1{RunConfig: runConfig}
88-
err = runConfigV1.InitRunConfigV1(projectConfig, nil, nil, nil, nil, utils, GeneralConfig.EnvRootPath)
88+
err = runConfigV1.InitRunConfigV1(projectConfig, utils, GeneralConfig.EnvRootPath)
8989
if err != nil {
9090
return err
9191
}

pkg/config/evaluation.go

+88-47
Original file line numberDiff line numberDiff line change
@@ -21,89 +21,110 @@ const (
2121
npmScriptsCondition = "npmScripts"
2222
)
2323

24-
// EvaluateConditionsV1 validates stage conditions and updates runSteps in runConfig according to V1 schema
25-
func (r *RunConfigV1) evaluateConditionsV1(config *Config, filters map[string]StepFilters, parameters map[string][]StepParameters,
26-
secrets map[string][]StepSecrets, stepAliases map[string][]Alias, utils piperutils.FileUtils, envRootPath string) error {
27-
28-
// initialize in case not initialized
29-
if r.RunConfig.RunSteps == nil {
30-
r.RunConfig.RunSteps = map[string]map[string]bool{}
24+
// evaluateConditionsV1 validates stage conditions and updates runSteps in runConfig according to V1 schema.
25+
// Priority of step activation/deactivation is follow:
26+
// - stepNotActiveCondition (highest, if any)
27+
// - explicit activation/deactivation (medium, if any)
28+
// - stepActiveConditions (lowest, step is active by default if no conditions are configured)
29+
func (r *RunConfigV1) evaluateConditionsV1(config *Config, utils piperutils.FileUtils, envRootPath string) error {
30+
if r.RunSteps == nil {
31+
r.RunSteps = make(map[string]map[string]bool, len(r.PipelineConfig.Spec.Stages))
3132
}
32-
if r.RunConfig.RunStages == nil {
33-
r.RunConfig.RunStages = map[string]bool{}
33+
if r.RunStages == nil {
34+
r.RunStages = make(map[string]bool, len(r.PipelineConfig.Spec.Stages))
3435
}
3536

37+
currentOrchestrator := orchestrator.DetectOrchestrator().String()
3638
for _, stage := range r.PipelineConfig.Spec.Stages {
37-
runStep := map[string]bool{}
38-
stageActive := false
39-
40-
// currently displayName is used, may need to consider to use technical name as well
39+
// Currently, the displayName is being used, but it may be necessary
40+
// to also consider using the technical name.
4141
stageName := stage.DisplayName
4242

43+
// Check #1: Apply explicit activation/deactivation from config file (if any)
44+
// and then evaluate stepActive conditions
45+
runStep := make(map[string]bool, len(stage.Steps))
46+
stepConfigCache := make(map[string]StepConfig, len(stage.Steps))
4347
for _, step := range stage.Steps {
44-
// Only consider orchestrator-specific steps in case orchestrator limitation is set
45-
currentOrchestrator := orchestrator.DetectOrchestrator().String()
48+
// Consider only orchestrator-specific steps if the orchestrator limitation is set.
4649
if len(step.Orchestrators) > 0 && !piperutils.ContainsString(step.Orchestrators, currentOrchestrator) {
4750
continue
4851
}
4952

50-
stepActive := false
51-
stepNotActive := false
52-
53-
stepConfig, err := r.getStepConfig(config, stageName, step.Name, filters, parameters, secrets, stepAliases)
53+
stepConfig, err := r.getStepConfig(config, stageName, step.Name, nil, nil, nil, nil)
5454
if err != nil {
5555
return err
5656
}
57+
stepConfigCache[step.Name] = stepConfig
5758

59+
// Respect explicit activation/deactivation if available.
60+
// Note that this has higher priority than step conditions
5861
if active, ok := stepConfig.Config[step.Name].(bool); ok {
59-
// respect explicit activation/de-activation if available
60-
stepActive = active
61-
} else {
62-
if step.Conditions == nil || len(step.Conditions) == 0 {
63-
// if no condition is available, step will be active by default
64-
stepActive = true
65-
} else {
66-
for _, condition := range step.Conditions {
67-
stepActive, err = condition.evaluateV1(stepConfig, utils, step.Name, envRootPath)
68-
if err != nil {
69-
return fmt.Errorf("failed to evaluate stage conditions: %w", err)
70-
}
71-
if stepActive {
72-
// first condition which matches will be considered to activate the step
73-
break
74-
}
75-
}
62+
runStep[step.Name] = active
63+
continue
64+
}
65+
66+
// If no condition is available, the step will be active by default.
67+
stepActive := true
68+
for _, condition := range step.Conditions {
69+
stepActive, err = condition.evaluateV1(stepConfig, utils, step.Name, envRootPath, runStep)
70+
if err != nil {
71+
return fmt.Errorf("failed to evaluate step conditions: %w", err)
72+
}
73+
if stepActive {
74+
// The first condition that matches will be considered to activate the step.
75+
break
7676
}
7777
}
7878

79-
// TODO: PART 1 : if explicit activation/de-activation is available should notActiveConditions be checked ?
80-
// Fortify has no anchor, so if we explicitly set it to true then it may run even during commit pipelines, if we implement TODO PART 1??
79+
runStep[step.Name] = stepActive
80+
}
81+
82+
// Check #2: Evaluate stepNotActive conditions (if any) and deactivate the step if the condition is met.
83+
//
84+
// TODO: PART 1 : if explicit activation/de-activation is available should notActiveConditions be checked ?
85+
// Fortify has no anchor, so if we explicitly set it to true then it may run even during commit pipelines, if we implement TODO PART 1??
86+
for _, step := range stage.Steps {
87+
stepConfig, found := stepConfigCache[step.Name]
88+
if !found {
89+
// If no stepConfig exists here, it means that this step was skipped in previous checks.
90+
continue
91+
}
92+
8193
for _, condition := range step.NotActiveConditions {
82-
stepNotActive, err = condition.evaluateV1(stepConfig, utils, step.Name, envRootPath)
94+
stepNotActive, err := condition.evaluateV1(stepConfig, utils, step.Name, envRootPath, runStep)
8395
if err != nil {
84-
return fmt.Errorf("failed to evaluate not active stage conditions: %w", err)
96+
return fmt.Errorf("failed to evaluate not active step conditions: %w", err)
8597
}
98+
99+
// Deactivate the step if the notActive condition is met.
86100
if stepNotActive {
87-
// first condition which matches will be considered to not activate the step
101+
runStep[step.Name] = false
88102
break
89103
}
90104
}
105+
}
91106

92-
// final decision is when step is activated and negate when not active is true
93-
stepActive = stepActive && !stepNotActive
107+
r.RunSteps[stageName] = runStep
94108

95-
if stepActive {
109+
stageActive := false
110+
for _, anyStepIsActive := range r.RunSteps[stageName] {
111+
if anyStepIsActive {
96112
stageActive = true
97113
}
98-
runStep[step.Name] = stepActive
99-
r.RunSteps[stageName] = runStep
100114
}
101115
r.RunStages[stageName] = stageActive
102116
}
117+
103118
return nil
104119
}
105120

106-
func (s *StepCondition) evaluateV1(config StepConfig, utils piperutils.FileUtils, stepName string, envRootPath string) (bool, error) {
121+
func (s *StepCondition) evaluateV1(
122+
config StepConfig,
123+
utils piperutils.FileUtils,
124+
stepName string,
125+
envRootPath string,
126+
runSteps map[string]bool,
127+
) (bool, error) {
107128

108129
// only the first condition will be evaluated.
109130
// if multiple conditions should be checked they need to provided via the Conditions list
@@ -189,6 +210,14 @@ func (s *StepCondition) evaluateV1(config StepConfig, utils piperutils.FileUtils
189210
return false, nil
190211
}
191212

213+
if s.OnlyActiveStepInStage {
214+
// Used only in NotActiveConditions.
215+
// Returns true if all other steps are inactive, so step will be deactivated
216+
// if it's the only active step in stage.
217+
// For example, sapCumulusUpload step must be deactivated in a stage where others steps are inactive.
218+
return !anyOtherStepIsActive(stepName, runSteps), nil
219+
}
220+
192221
// needs to be checked last:
193222
// if none of the other conditions matches, step will be active unless set to inactive
194223
if s.Inactive == true {
@@ -491,3 +520,15 @@ func checkForNpmScriptsInPackagesV1(npmScript string, config StepConfig, utils p
491520
}
492521
return false, nil
493522
}
523+
524+
// anyOtherStepIsActive loops through previous steps active states and returns true
525+
// if at least one of them is active, otherwise result is false. Ignores the step that is being checked.
526+
func anyOtherStepIsActive(targetStep string, runSteps map[string]bool) bool {
527+
for step, isActive := range runSteps {
528+
if isActive && step != targetStep {
529+
return true
530+
}
531+
}
532+
533+
return false
534+
}

0 commit comments

Comments
 (0)