diff --git a/pkg/template/depgraph.go b/pkg/template/depgraph.go index 1d6bf55168..fe43ab147f 100644 --- a/pkg/template/depgraph.go +++ b/pkg/template/depgraph.go @@ -12,6 +12,8 @@ import ( type depGraph struct { Dependencies map[string]map[string]struct{} + CertItems map[string]map[string]struct{} // items that use the TLSCert template function. map of certnames to configoptions that provide the certname + KeyItems map[string]map[string]struct{} // items that use the TLSKey template function. map of configoptions to the certnames they use } // these config functions are used to add their dependencies to the depGraph @@ -21,12 +23,24 @@ func (d *depGraph) funcMap(parent string) template.FuncMap { return dep } + addCertFunc := func(certName string, _ ...string) string { + d.AddCert(parent, certName) + return certName + } + + addKeyFunc := func(certName string, _ ...string) string { + d.AddKey(parent, certName) + return certName + } + return template.FuncMap{ "ConfigOption": addDepFunc, "ConfigOptionIndex": addDepFunc, "ConfigOptionData": addDepFunc, "ConfigOptionEquals": addDepFunc, "ConfigOptionNotEquals": addDepFunc, + "TLSCert": addCertFunc, + "TLSKey": addKeyFunc, } } @@ -46,6 +60,40 @@ func (d *depGraph) AddDep(source, newDependency string) { d.Dependencies[source][newDependency] = struct{}{} } +func (d *depGraph) AddCert(source, certName string) { + if d.CertItems == nil { + d.CertItems = make(map[string]map[string]struct{}) + } + if _, ok := d.CertItems[certName]; !ok { + d.CertItems[certName] = make(map[string]struct{}) + } + + d.CertItems[certName][source] = struct{}{} +} + +func (d *depGraph) AddKey(source, certName string) { + if d.KeyItems == nil { + d.KeyItems = make(map[string]map[string]struct{}) + } + if _, ok := d.KeyItems[source]; !ok { + d.KeyItems[source] = make(map[string]struct{}) + } + + d.KeyItems[source][certName] = struct{}{} +} + +func (d *depGraph) resolveCertKeys() { + for source, certNameMap := range d.KeyItems { + for certName := range certNameMap { + for certProvider := range d.CertItems[certName] { + if certProvider != source { + d.AddDep(source, certProvider) + } + } + } + } +} + func (d *depGraph) ResolveDep(resolvedDependency string) { for _, depMap := range d.Dependencies { delete(depMap, resolvedDependency) @@ -66,7 +114,7 @@ func (d *depGraph) GetHeadNodes() ([]string, error) { waitList := []string{} for k, v := range d.Dependencies { depsList := []string{} - for dep, _ := range v { + for dep := range v { depsList = append(depsList, fmt.Sprintf("%q", dep)) } waitItem := fmt.Sprintf(`%q depends on %s`, k, strings.Join(depsList, `, `)) @@ -87,31 +135,28 @@ func (d *depGraph) Copy() (depGraph, error) { var buf bytes.Buffer enc := json.NewEncoder(&buf) dec := json.NewDecoder(&buf) - err := enc.Encode(d.Dependencies) + err := enc.Encode(d) if err != nil { return depGraph{}, err } - var depCopy map[string]map[string]struct{} + depCopy := depGraph{} err = dec.Decode(&depCopy) if err != nil { return depGraph{}, err } - return depGraph{ - Dependencies: depCopy, - }, nil + return depCopy, nil } func (d *depGraph) ParseConfigGroup(configGroups []kotsv1beta1.ConfigGroup) error { - staticCtx := &StaticCtx{} for _, configGroup := range configGroups { for _, configItem := range configGroup.Items { // add this to the dependency graph d.AddNode(configItem.Name) depBuilder := Builder{ - Ctx: []Ctx{staticCtx}, + Ctx: []Ctx{}, Functs: d.funcMap(configItem.Name), } @@ -122,5 +167,7 @@ func (d *depGraph) ParseConfigGroup(configGroups []kotsv1beta1.ConfigGroup) erro } } + d.resolveCertKeys() + return nil } diff --git a/pkg/template/depgraph_test.go b/pkg/template/depgraph_test.go index 13ea204a82..7e66771b8d 100644 --- a/pkg/template/depgraph_test.go +++ b/pkg/template/depgraph_test.go @@ -12,6 +12,8 @@ import ( type depGraphTestCase struct { dependencies map[string][]string + testCerts map[string][]string + testKeys map[string][]string resolveOrder []string expectError bool //expect an error fetching head nodes expectNotFound string //expect this dependency not to be part of the head nodes @@ -113,6 +115,41 @@ func TestDepGraph(t *testing.T) { expectError: true, name: "does_not_exist", }, + { + dependencies: map[string][]string{ + "alpha": {}, + "bravo": {"alpha"}, + "charlie": {"bravo"}, + "delta": {"alpha", "charlie"}, + "echo": {}, + }, + testCerts: map[string][]string{ + "echo": {"certA"}, + }, + testKeys: map[string][]string{ + "delta": {"certA"}, + }, + resolveOrder: []string{"alpha", "bravo", "charlie", "echo", "delta"}, + name: "basic_certs", + }, + { + dependencies: map[string][]string{ + "alpha": {}, + "bravo": {"alpha"}, + "charlie": {"bravo"}, + "delta": {"alpha", "charlie"}, + "echo": {}, + }, + testCerts: map[string][]string{ + "echo": {"certA"}, + }, + testKeys: map[string][]string{ + "delta": {"certA"}, + }, + resolveOrder: []string{"alpha", "bravo", "charlie", "delta", "echo"}, + name: "basic_certs_original_order", + expectNotFound: "delta", + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -126,6 +163,18 @@ func TestDepGraph(t *testing.T) { graph.AddDep(source, dep) } } + for source, certNames := range test.testCerts { + for _, certName := range certNames { + graph.AddCert(source, certName) + } + } + for source, keys := range test.testKeys { + for _, key := range keys { + graph.AddKey(source, key) + } + } + graph.resolveCertKeys() + runGraphTests(t, test, graph) }) @@ -135,7 +184,7 @@ func TestDepGraph(t *testing.T) { graph := depGraph{} - groups := buildTestConfigGroups(test.dependencies, "templateStringStart", "templateStringEnd", true) + groups := buildTestConfigGroups(test.dependencies, test.testCerts, test.testKeys, "templateStringStart", "templateStringEnd", true) err := graph.ParseConfigGroup(groups) require.NoError(t, err) @@ -146,7 +195,7 @@ func TestDepGraph(t *testing.T) { } // this makes sure that we test with each of the configOption types, in both Value and Default -func buildTestConfigGroups(dependencies map[string][]string, prefix string, suffix string, rotate bool) []kotsv1beta1.ConfigGroup { +func buildTestConfigGroups(dependencies, certs, keys map[string][]string, prefix string, suffix string, rotate bool) []kotsv1beta1.ConfigGroup { group := kotsv1beta1.ConfigGroup{} group.Items = make([]kotsv1beta1.ConfigItem, 0) counter := 0 @@ -156,7 +205,7 @@ func buildTestConfigGroups(dependencies map[string][]string, prefix string, suff "{{repl ConfigOptionIndex \"%s\" }}", "{{repl ConfigOptionData \"%s\" }}", "repl{{ ConfigOptionEquals \"%s\" \"abc\" }}", - "{{repl ConfigOptionNotEquals \"%s\" \"xyz\" }}{{repl DoesNotExistFunc }}", + "{{repl ConfigOptionNotEquals \"%s\" \"xyz\" }}", } if !rotate { @@ -166,12 +215,28 @@ func buildTestConfigGroups(dependencies map[string][]string, prefix string, suff } } + totalDepItems := 0 + for source, deps := range dependencies { newItem := kotsv1beta1.ConfigItem{Type: "text", Name: source} depString := prefix - for i, dep := range deps { - depString += fmt.Sprintf(templateFuncs[i%len(templateFuncs)], dep) + for _, dep := range deps { + depString += fmt.Sprintf(templateFuncs[totalDepItems%len(templateFuncs)], dep) + totalDepItems++ + } + + if certNames, ok := certs[source]; ok { + for _, certName := range certNames { + depString += fmt.Sprintf("{{repl TLSCert \"%s\" }}", certName) + } } + + if certNames, ok := keys[source]; ok { + for _, certName := range certNames { + depString += fmt.Sprintf("{{repl TLSKey \"%s\" }}", certName) + } + } + depString += suffix if counter%2 == 0 {