Skip to content

Commit

Permalink
Merge pull request #35 from dadav/fix_sorting
Browse files Browse the repository at this point in the history
feat: Support semver in dependencies
  • Loading branch information
dadav authored Jul 21, 2024
2 parents 446a61c + 6158417 commit 6d07aeb
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 198 deletions.
146 changes: 11 additions & 135 deletions cmd/helm-schema/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"bytes"
"errors"
"fmt"
"os"
Expand All @@ -13,11 +12,8 @@ import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
yaml "gopkg.in/yaml.v3"

"github.com/dadav/helm-schema/pkg/chart"
"github.com/dadav/helm-schema/pkg/schema"
"github.com/dadav/helm-schema/pkg/util"
)

func searchFiles(startPath, fileName string, queue chan<- string, errs chan<- error) {
Expand All @@ -39,119 +35,6 @@ func searchFiles(startPath, fileName string, queue chan<- string, errs chan<- er
}
}

type Result struct {
ChartPath string
ValuesPath string
Chart *chart.ChartFile
Schema schema.Schema
Errors []error
}

func worker(
dryRun, uncomment, addSchemaReference, keepFullComment, dontRemoveHelmDocsPrefix bool,
valueFileNames []string,
skipAutoGenerationConfig *schema.SkipAutoGenerationConfig,
outFile string,
queue <-chan string,
results chan<- Result,
) {
for chartPath := range queue {
result := Result{ChartPath: chartPath}

chartBasePath := filepath.Dir(chartPath)
file, err := os.Open(chartPath)
if err != nil {
result.Errors = append(result.Errors, err)
results <- result
continue
}

chart, err := chart.ReadChart(file)
if err != nil {
result.Errors = append(result.Errors, err)
results <- result
continue
}
result.Chart = &chart

var valuesPath string
var valuesFound bool
errorsWeMaybeCanIgnore := []error{}

for _, possibleValueFileName := range valueFileNames {
valuesPath = filepath.Join(chartBasePath, possibleValueFileName)
_, err := os.Stat(valuesPath)
if err != nil {
if !os.IsNotExist(err) {
errorsWeMaybeCanIgnore = append(errorsWeMaybeCanIgnore, err)
}
continue
}
valuesFound = true
break
}

if !valuesFound {
for _, err := range errorsWeMaybeCanIgnore {
result.Errors = append(result.Errors, err)
}
result.Errors = append(result.Errors, errors.New("No values file found."))
results <- result
continue
}
result.ValuesPath = valuesPath

valuesFile, err := os.Open(valuesPath)
if err != nil {
result.Errors = append(result.Errors, err)
results <- result
continue
}
content, err := util.ReadFileAndFixNewline(valuesFile)
if err != nil {
result.Errors = append(result.Errors, err)
results <- result
continue
}

// Check if we need to add a schema reference
if addSchemaReference {
schemaRef := `# yaml-language-server: $schema=values.schema.json`
if !strings.Contains(string(content), schemaRef) {
err = util.InsertLineToFile(schemaRef, valuesPath)
if err != nil {
result.Errors = append(result.Errors, err)
results <- result
continue
}
}
}

// Optional preprocessing
if uncomment {
// Remove comments from valid yaml
content, err = util.RemoveCommentsFromYaml(bytes.NewReader(content))
if err != nil {
result.Errors = append(result.Errors, err)
results <- result
continue
}
}

var values yaml.Node
err = yaml.Unmarshal(content, &values)
if err != nil {
result.Errors = append(result.Errors, err)
results <- result
continue
}

result.Schema = schema.YamlToSchema(&values, keepFullComment, dontRemoveHelmDocsPrefix, skipAutoGenerationConfig, nil)

results <- result
}
}

func exec(cmd *cobra.Command, _ []string) error {
configureLogging()

Expand Down Expand Up @@ -180,8 +63,8 @@ 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{}
resultsChan := make(chan schema.Result)
results := []*schema.Result{}
errs := make(chan error)
done := make(chan struct{})

Expand All @@ -199,7 +82,7 @@ func exec(cmd *cobra.Command, _ []string) error {

go func() {
defer wg.Done()
worker(
schema.Worker(
dryRun,
uncomment,
addSchemaReference,
Expand Down Expand Up @@ -230,20 +113,9 @@ loop:
// sort results with topology sort (only if we're checking the dependencies)
if !noDeps {
// sort results with topology sort
results, err = util.TopSort[*Result, string](results, func(i *Result) string {
// the chart is identified by its name and version
return fmt.Sprintf("%s-%s", i.Chart.Name, i.Chart.Version)
},
func(d *Result) []string {
deps := []string{}
for _, dep := range d.Chart.Dependencies {
// the dependencies must be identified the same way as above
deps = append(deps, fmt.Sprintf("%s-%s", dep.Name, dep.Version))
}
return deps
})
results, err = schema.TopoSort(results)
if err != nil {
if _, ok := err.(*util.CircularError); !ok {
if _, ok := err.(*schema.CircularError); !ok {
log.Errorf("Error while sorting results: %s", err)
return err
} else {
Expand Down Expand Up @@ -271,7 +143,7 @@ loop:
}
}

chartNameToResult := make(map[string]*Result)
chartNameToResult := make(map[string]*schema.Result)
foundErrors := false

// process results
Expand Down Expand Up @@ -342,7 +214,11 @@ loop:
// so every required check will be disabled
depSchema.DisableRequiredProperties()

result.Schema.Properties[dep.Name] = &depSchema
if dep.Alias != "" {
result.Schema.Properties[dep.Alias] = &depSchema
} else {
result.Schema.Properties[dep.Name] = &depSchema
}

} else {
log.Warnf("Dependency (%s->%s) specified but no schema found. If you want to create jsonschemas for external dependencies, you need to run helm dependency build & untar the charts.", result.Chart.Name, dep.Name)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
)

require (
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
Expand Down
10 changes: 7 additions & 3 deletions pkg/chart/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ import (
)

type Dependency struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
Condition string `yaml:"condition"`
Name string `yaml:"name"`
Version string `yaml:"version"`
Condition string `yaml:"condition,omitempty"`
Repository string `yaml:"repository,omitempty"`
Alias string `yaml:"alias,omitempty"`
// Tags []string `yaml:"tags,omitempty"`
// ImportValues []string `yaml:"import-values,omitempty"`
}

// Maintainer describes a Chart maintainer.
Expand Down
2 changes: 1 addition & 1 deletion pkg/util/err.go → pkg/schema/err.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package util
package schema

type CircularError struct {
msg string
Expand Down
86 changes: 86 additions & 0 deletions pkg/schema/toposort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package schema

import (
"fmt"
"strings"

"github.com/Masterminds/semver/v3"
"github.com/dadav/helm-schema/pkg/chart"
mapset "github.com/deckarep/golang-set/v2"
)

// TopoSort uses topological sorting to sort the results
func TopoSort(results []*Result) ([]*Result, error) {
// Map result identifier to result
lookup := make(map[string]*Result)

// Map result identifier to dependencies identifiers
todo := make(map[string]mapset.Set[chart.Dependency])

// Create the work queue
for _, result := range results {
dependencies := mapset.NewSet[chart.Dependency]()
for _, dep := range result.Chart.Dependencies {
dependencies.Add(*dep)
}
resultId := fmt.Sprintf("%s|%s", result.Chart.Name, result.Chart.Version)
if _, ok := lookup[resultId]; ok {
return nil, fmt.Errorf("duplicate chart found: %s - consider changing the name or version, so that helm-schema can distinguish them", result.Chart.Name)
}
lookup[resultId] = result
todo[resultId] = dependencies
}

var sorted []*Result

// if we have work left
for len(todo) != 0 {
ready := mapset.NewSet[string]()
for name, deps := range todo {
// if no further deps found (end of the dep tree), this chart is ready
if deps.Cardinality() == 0 {
ready.Add(name)
}
}

// if no items are ready, we are stuck
if ready.Cardinality() == 0 {
// append unsorted to sorted items and return them
for name := range todo {
sorted = append(sorted, lookup[name])
}

return sorted, &CircularError{fmt.Sprintf("circular or missing dependency found: %v - Please build and untar all your helm dependencies: helm dep build && ls charts/*.tgz |xargs -n1 tar -C charts/ -xzf", todo)}
}

// remove ready items from todo list and add to sorted list
for name := range ready.Iter() {
delete(todo, name)
sorted = append(sorted, lookup[name])

// remove ready items from deps list too
for todoName, deps := range todo {
newDeps := deps.Clone()
for dep := range deps.Iter() {
c, err := semver.NewConstraint(dep.Version)
if err != nil {
return sorted, err
}

nameVersion := strings.Split(name, "|")
sem, err := semver.NewVersion(nameVersion[1])
if err != nil {
return sorted, err
}

if c.Check(sem) {
newDeps.Remove(dep)
}
}
todo[todoName] = newDeps
}
}

}
return sorted, nil
}
Loading

0 comments on commit 6d07aeb

Please sign in to comment.