Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(appset): Support various merge strategies for merge generator #16080

Open
wants to merge 37 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
02af8e4
Support duplicate merge key-value pairs in base generator
sonamkshenoy Oct 19, 2023
1513524
Minor miss
sonamkshenoy Oct 19, 2023
29a1959
Comment fix
sonamkshenoy Oct 19, 2023
dd862fe
Documentation for new mode
sonamkshenoy Oct 19, 2023
249bfb6
Typo fix
sonamkshenoy Oct 19, 2023
88708d0
commit check
Oct 23, 2023
ef44654
codegen results
Oct 23, 2023
6178f97
Correction
Oct 23, 2023
869e6da
Codegen after correction
Oct 23, 2023
18e5b0a
Final logic changes
Oct 23, 2023
ee363f0
Remove spaces
Oct 23, 2023
693598b
Remove spaces
Oct 23, 2023
df4351a
Remove spaces
Oct 23, 2023
abec9f2
Remove spaces
Oct 23, 2023
1545618
Merge branch 'master' into mergen1leftjoin
sonamkshenoy Oct 23, 2023
acdc15c
Support for various merge modes
Nov 6, 2023
627fbdc
Merge remote-tracking branch 'origin/mergen1leftjoin' into mergen1lef…
Nov 6, 2023
6f47f64
Revert changes to test yaml
Nov 6, 2023
7972b2b
Revert test changes
Nov 6, 2023
8819353
Merge branch 'master' of https://github.com/argoproj/argo-cd into mer…
Dec 20, 2023
4ae4652
Codegen
Dec 20, 2023
1024c3d
Codegen changes
Dec 21, 2023
5a2e6e9
Fix unit tests and handle default merge mode
Dec 21, 2023
bd423f8
Unit tests for non-unique merge key feature
Dec 21, 2023
181ecd6
Changed merge mode type
Dec 21, 2023
5f0edeb
Added unit tests for all merge-modes
Dec 21, 2023
c35ac69
Minor changes
Dec 21, 2023
c32ce97
Addressed lint error - simplified
Dec 22, 2023
740fbf4
Todos
Dec 22, 2023
5b4a971
Handle edge cases and unit tests
Jan 4, 2024
288bc5b
Fixes and documentation changes
Jan 4, 2024
a01d015
codegen changes
Jan 4, 2024
587afaf
Merged with master
Jan 4, 2024
856d54c
Minor changes
Jan 4, 2024
b757373
Minor fix
Jan 4, 2024
b5908f1
Apply suggestions from code review
sonamkshenoy Mar 12, 2024
1548125
Update applicationset/generators/merge.go
sonamkshenoy Mar 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 118 additions & 23 deletions applicationset/generators/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package generators
import (
"encoding/json"
"fmt"
"strings"
"time"

"github.com/imdario/mergo"
Expand All @@ -19,8 +20,20 @@ var (
ErrLessThanTwoGeneratorsInMerge = fmt.Errorf("found less than two generators, Merge requires two or more")
ErrNoMergeKeys = fmt.Errorf("no merge keys were specified, Merge requires at least one")
ErrNonUniqueParamSets = fmt.Errorf("the parameters from a generator were not unique by the given mergeKeys, Merge requires all param sets to be unique")
ErrNoCommonMergeKeys = fmt.Errorf("no merge key was common amongst param sets produced by all generators")
)

const (
LeftJoinUniq argoprojiov1alpha1.MergeMode = "left-join-uniq"
LeftJoin argoprojiov1alpha1.MergeMode = "left-join"
InnerJoinUniq argoprojiov1alpha1.MergeMode = "inner-join-uniq"
InnerJoin argoprojiov1alpha1.MergeMode = "inner-join"
FullJoinUniq argoprojiov1alpha1.MergeMode = "full-join-uniq"
FullJoin argoprojiov1alpha1.MergeMode = "full-join"
)

const UniqJoinSuffix = "-uniq"

type MergeGenerator struct {
// The inner generators supported by the merge generator (cluster, git, list...)
supportedGenerators map[string]Generator
Expand Down Expand Up @@ -59,55 +72,135 @@ func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appl
return nil, ErrLessThanTwoGeneratorsInMerge
}

var joinType, err = getJoinType(appSetGenerator.Merge.Mode)
if err != nil {
return nil, err
}

paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet)
if err != nil {
return nil, fmt.Errorf("error getting param sets from generators: %w", err)
}

baseParamSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSetsFromGenerators[0])
isUniqueJoin := strings.HasSuffix(string(joinType), UniqJoinSuffix)
baseParamSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSetsFromGenerators[0], !isUniqueJoin)
if err != nil {
return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
}

for _, paramSets := range paramSetsFromGenerators[1:] {
paramSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSets)
paramSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSets, false)
if err != nil {
return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
}

for mergeKeyValue, baseParamSet := range baseParamSetsByMergeKey {
baseParamSetsByMergeKey, err = combineParamSetsByJoinType(
joinType,
baseParamSetsByMergeKey,
paramSetsByMergeKey,
appSet)

if err != nil {
return nil, err
}
}

var mergedParamSets []map[string]interface{}
for _, mergedParamSetList := range baseParamSetsByMergeKey {
mergedParamSets = append(mergedParamSets, mergedParamSetList...)
}

return mergedParamSets, nil
}

func combineParamSetsByJoinType(mergeMode argoprojiov1alpha1.MergeMode,
baseParamSetsByMergeKey map[string][]map[string]interface{},
paramSetsByMergeKey map[string][]map[string]interface{},
appSet *argoprojiov1alpha1.ApplicationSet) (map[string][]map[string]interface{}, error) {

var err error

switch mergeMode {
case LeftJoin, LeftJoinUniq:
for mergeKeyValue, baseParamSetList := range baseParamSetsByMergeKey {
if overrideParamSet, exists := paramSetsByMergeKey[mergeKeyValue]; exists {
for i, baseParamSet := range baseParamSetList {
baseParamSetsByMergeKey, err = overrideParamSets(i, baseParamSet, overrideParamSet[0], mergeKeyValue, baseParamSetsByMergeKey, appSet)
if err != nil {
return nil, err
}
}
}
}

if appSet.Spec.GoTemplate {
if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("error merging base param set with override param set: %w", err)
case InnerJoin, InnerJoinUniq:
for mergeKeyValue, baseParamSetList := range baseParamSetsByMergeKey {
if overrideParamSet, exists := paramSetsByMergeKey[mergeKeyValue]; exists {
for i, baseParamSet := range baseParamSetList {
baseParamSetsByMergeKey, err = overrideParamSets(i, baseParamSet, overrideParamSet[0], mergeKeyValue, baseParamSetsByMergeKey, appSet)
if err != nil {
return nil, err
}
baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
} else {
overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
}
} else {
delete(baseParamSetsByMergeKey, mergeKeyValue)
}
}
if len(baseParamSetsByMergeKey) == 0 {
return nil, ErrNoCommonMergeKeys
}

case FullJoin, FullJoinUniq:
for mergeKeyValue, paramSet := range paramSetsByMergeKey {
if baseParamSetList, exists := baseParamSetsByMergeKey[mergeKeyValue]; exists {
for i, baseParamSet := range baseParamSetList {
baseParamSetsByMergeKey, err = overrideParamSets(i, baseParamSet, paramSet[0], mergeKeyValue, baseParamSetsByMergeKey, appSet)
if err != nil {
return nil, fmt.Errorf("error combining string maps: %w", err)
return nil, err
}
baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet)
}
} else {
baseParamSetsByMergeKey[mergeKeyValue] = paramSet
}
}
}
return baseParamSetsByMergeKey, nil
}

mergedParamSets := make([]map[string]interface{}, len(baseParamSetsByMergeKey))
var i = 0
for _, mergedParamSet := range baseParamSetsByMergeKey {
mergedParamSets[i] = mergedParamSet
i += 1
func overrideParamSets(i int, baseParamSet map[string]interface{}, overrideParamSet map[string]interface{},
mergeKeyValue string, baseParamSetsByMergeKey map[string][]map[string]interface{},
appSet *argoprojiov1alpha1.ApplicationSet) (map[string][]map[string]interface{}, error) {
if appSet.Spec.GoTemplate {
if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil {
return nil, fmt.Errorf("error merging base param set with override param set: %w", err)
}
baseParamSetsByMergeKey[mergeKeyValue][i] = baseParamSet
} else {
overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
if err != nil {
return nil, fmt.Errorf("error combining string maps: %w", err)
}
baseParamSetsByMergeKey[mergeKeyValue][i] = utils.ConvertToMapStringInterface(overriddenParamSet)
}
return baseParamSetsByMergeKey, nil
}

return mergedParamSets, nil
func getJoinType(joinType string) (argoprojiov1alpha1.MergeMode, error) {
switch argoprojiov1alpha1.MergeMode(joinType) {
case LeftJoin, LeftJoinUniq, InnerJoin, InnerJoinUniq, FullJoin, FullJoinUniq:
return argoprojiov1alpha1.MergeMode(joinType), nil
case "":
// No merge mode passed. Using default left-join-uniq
return LeftJoinUniq, nil
default:
return "", fmt.Errorf("incorrect merge mode passed. %s merge mode is not supported", joinType)
}
}

// getParamSetsByMergeKey converts the given list of parameter sets to a map of parameter sets where the key is the
// unique key of the parameter set as determined by the given mergeKeys. If any two parameter sets share the same merge
// key, getParamSetsByMergeKey will throw NonUniqueParamSets.
func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface{}) (map[string]map[string]interface{}, error) {
// key, getParamSetsByMergeKey will throw NonUniqueParamSets, if allowDuplicateMergeKey is false
func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface{}, allowDuplicateMergeKey bool) (map[string][]map[string]interface{}, error) {
if len(mergeKeys) < 1 {
return nil, ErrNoMergeKeys
}
Expand All @@ -117,7 +210,7 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface
deDuplicatedMergeKeys[mergeKey] = false
}

paramSetsByMergeKey := make(map[string]map[string]interface{}, len(paramSets))
paramSetsByMergeKey := make(map[string][]map[string]interface{}, len(paramSets))
for _, paramSet := range paramSets {
paramSetKey := make(map[string]interface{})
for mergeKey := range deDuplicatedMergeKeys {
Expand All @@ -128,10 +221,12 @@ func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface
return nil, fmt.Errorf("error marshalling param set key json: %w", err)
}
paramSetKeyString := string(paramSetKeyJson)
if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
return nil, fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, paramSetKeyString)
if !allowDuplicateMergeKey {
if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
return nil, fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, paramSetKeyString)
}
}
paramSetsByMergeKey[paramSetKeyString] = paramSet
paramSetsByMergeKey[paramSetKeyString] = append(paramSetsByMergeKey[paramSetKeyString], paramSet)
}

return paramSetsByMergeKey, nil
Expand Down
Loading
Loading