diff --git a/cmd/helm-schema/main.go b/cmd/helm-schema/main.go index 81a1777..cc71c4a 100644 --- a/cmd/helm-schema/main.go +++ b/cmd/helm-schema/main.go @@ -6,13 +6,13 @@ import ( "os" "path/filepath" "runtime" - "sort" "strings" "sync" "github.com/dadav/helm-schema/pkg/chart" "github.com/dadav/helm-schema/pkg/schema" "github.com/dadav/helm-schema/pkg/util" + mapset "github.com/deckarep/golang-set/v2" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -47,6 +47,53 @@ type Result struct { Errors []error } +// sortResults sorts the given Results via topology algorithm +// see: https://dnaeon.github.io/dependency-graph-resolution-algorithm-in-go/ +func sortResults(results []*Result) ([]*Result, error) { + depNamesToResults := make(map[string]*Result) + depNamesToNames := make(map[string]mapset.Set[string]) + + for _, result := range results { + depNamesToResults[result.Chart.Name] = result + dependencySet := mapset.NewSet[string]() + for _, dep := range result.Chart.Dependencies { + dependencySet.Add(dep.Name) + } + depNamesToNames[result.Chart.Name] = dependencySet + } + + var sorted []*Result + + for len(depNamesToNames) != 0 { + readySet := mapset.NewSet[string]() + for name, deps := range depNamesToNames { + if deps.Cardinality() == 0 { + readySet.Add(name) + } + } + + if readySet.Cardinality() == 0 { + var g []*Result + for name := range depNamesToNames { + g = append(g, depNamesToResults[name]) + } + + return g, errors.New("Circular dependency found") + } + + for name := range readySet.Iter() { + delete(depNamesToNames, name) + sorted = append(sorted, depNamesToResults[name]) + } + + for name, deps := range depNamesToNames { + diff := deps.Difference(readySet) + depNamesToNames[name] = diff + } + } + return sorted, nil +} + func worker( dryRun, keepFullComment bool, valueFileNames []string, @@ -141,7 +188,7 @@ func exec(cmd *cobra.Command, _ []string) error { // 1. Start a producer that searches Chart.yaml and values.yaml files queue := make(chan string) resultsChan := make(chan Result) - results := []Result{} + results := []*Result{} errs := make(chan error) done := make(chan struct{}) @@ -176,39 +223,26 @@ loop: case err := <-errs: log.Error(err) case res := <-resultsChan: - results = append(results, res) + results = append(results, &res) case <-done: break loop } } + // sort results with topology sort + results, err := sortResults(results) + if err != nil { + log.Errorf("Error while sorting results: %s", err) + return err + } + conditionsToPatch := make(map[string][]string) // Sort results if dependencies should be processed // Need to resolve the dependencies from deepest level to highest - if !noDeps { - sort.Slice(results, func(i, j int) bool { - first := results[i] - second := results[j] - // No dependencies - if len(first.Chart.Dependencies) == 0 { - return true - } - // First is dependency of second - for _, dep := range second.Chart.Dependencies { - if dep.Name != "" { - if dep.Name == first.Chart.Name { - return true - } - } - } - - // first comes after second - return false - }) - - // Iterate over deps to find conditions we need to patch + if !noDeps { + // Iterate over deps to find conditions we need to patch (dependencies that have a condition) for _, result := range results { if len(result.Errors) > 0 { continue @@ -222,7 +256,7 @@ loop: } } - chartNameToResult := make(map[string]Result) + chartNameToResult := make(map[string]*Result) foundErrors := false // process results diff --git a/go.mod b/go.mod index 1f9f25b..ee53700 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/dadav/helm-schema go 1.21.0 require ( + github.com/deckarep/golang-set/v2 v2.3.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 diff --git a/go.sum b/go.sum index 04f9244..b8499b9 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A= +github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=