feat: support for locking the same chart for two or more versions (#600)

Resolves #598
This commit is contained in:
KUOKA Yusuke 2019-05-16 21:19:30 +09:00 committed by GitHub
parent a769d1a82a
commit 0104c91fce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 38 deletions

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/roboll/helmfile
go 1.12 go 1.12
require ( require (
github.com/Masterminds/semver v1.4.1 // indirect github.com/Masterminds/semver v1.4.1
github.com/Masterminds/sprig v2.15.0+incompatible github.com/Masterminds/sprig v2.15.0+incompatible
github.com/aokoli/goutils v1.0.1 // indirect github.com/aokoli/goutils v1.0.1 // indirect
github.com/google/go-cmp v0.3.0 // indirect github.com/google/go-cmp v0.3.0 // indirect

View File

@ -2,6 +2,7 @@ package state
import ( import (
"fmt" "fmt"
"github.com/Masterminds/semver"
"github.com/roboll/helmfile/helmexec" "github.com/roboll/helmfile/helmexec"
"go.uber.org/zap" "go.uber.org/zap"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -38,19 +39,8 @@ type ResolvedChartDependency struct {
Version string `yaml:"version"` Version string `yaml:"version"`
} }
// StatePackage is for packaging your helmfile state file along with its dependencies.
// The only type of dependency currently supported is `chart`.
// It is transient and generated on demand while resolving dependencies, and automatically removed afterwards.
type StatePackage struct {
// name is the name of the package.
// Usually this is the "basename" of the helmfile state file, e.g. `helmfile.2` when the state file is named `helmfile.2.yaml`, `helmfille.2.gotmpl`, or `helmfile.2.yaml.gotmpl`.
name string
chartDependencies map[string]unresolvedChartDependency
}
type UnresolvedDependencies struct { type UnresolvedDependencies struct {
deps map[string]unresolvedChartDependency deps map[string][]unresolvedChartDependency
} }
type ChartRequirements struct { type ChartRequirements struct {
@ -71,46 +61,68 @@ func (d *UnresolvedDependencies) Add(chart, url, versionConstraint string) error
} }
func (d *UnresolvedDependencies) add(dep unresolvedChartDependency) error { func (d *UnresolvedDependencies) add(dep unresolvedChartDependency) error {
existing, exists := d.deps[dep.ChartName] deps := d.deps[dep.ChartName]
if exists && (existing.Repository != dep.Repository || existing.VersionConstraint != dep.VersionConstraint) { if deps == nil {
return fmt.Errorf("duplicate chart dependency \"%s\". you can't have two or more charts with the same name but with different urls or versions: existing=%v, new=%v", dep.ChartName, existing, dep) deps = []unresolvedChartDependency{dep}
} else {
deps = append(deps, dep)
} }
d.deps[dep.ChartName] = dep d.deps[dep.ChartName] = deps
return nil return nil
} }
func (d *UnresolvedDependencies) ToChartRequirements() *ChartRequirements { func (d *UnresolvedDependencies) ToChartRequirements() *ChartRequirements {
deps := []unresolvedChartDependency{} deps := []unresolvedChartDependency{}
for _, d := range d.deps { for _, ds := range d.deps {
if d.VersionConstraint == "" { for _, d := range ds {
d.VersionConstraint = "*" if d.VersionConstraint == "" {
d.VersionConstraint = "*"
}
deps = append(deps, d)
} }
deps = append(deps, d)
} }
return &ChartRequirements{UnresolvedDependencies: deps} return &ChartRequirements{UnresolvedDependencies: deps}
} }
type ResolvedDependencies struct { type ResolvedDependencies struct {
deps map[string]ResolvedChartDependency deps map[string][]ResolvedChartDependency
} }
func (d *ResolvedDependencies) add(dep ResolvedChartDependency) error { func (d *ResolvedDependencies) add(dep ResolvedChartDependency) error {
_, exists := d.deps[dep.ChartName] deps := d.deps[dep.ChartName]
if exists { if deps == nil {
return fmt.Errorf("duplicate chart dependency \"%s\"", dep.ChartName) deps = []ResolvedChartDependency{dep}
} else {
deps = append(deps, dep)
} }
d.deps[dep.ChartName] = dep d.deps[dep.ChartName] = deps
return nil return nil
} }
func (d *ResolvedDependencies) Get(chart string) (string, error) { func (d *ResolvedDependencies) Get(chart, versionConstraint string) (string, error) {
dep, exists := d.deps[chart] if versionConstraint == "" {
if !exists { versionConstraint = "*"
return "", fmt.Errorf("no resolved dependency found for \"%s\"", chart)
} }
return dep.Version, nil
deps, exists := d.deps[chart]
if exists {
for _, dep := range deps {
constraint, err := semver.NewConstraint(versionConstraint)
if err != nil {
return "", err
}
version, err := semver.NewVersion(dep.Version)
if err != nil {
return "", err
}
if constraint.Check(version) {
return dep.Version, nil
}
}
}
return "", fmt.Errorf("no resolved dependency found for \"%s\"", chart)
} }
func resolveRemoteChart(repoAndChart string) (string, string, bool) { func resolveRemoteChart(repoAndChart string) (string, string, bool) {
@ -176,7 +188,7 @@ func resolveDependencies(st *HelmState, depMan *chartDependencyManager, unresolv
continue continue
} }
ver, err := resolved.Get(chart) ver, err := resolved.Get(chart, r.Version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -213,7 +225,7 @@ func getUnresolvedDependenciess(st *HelmState) (string, *UnresolvedDependencies,
repoToURL[r.Name] = r.URL repoToURL[r.Name] = r.URL
} }
unresolved := &UnresolvedDependencies{deps: map[string]unresolvedChartDependency{}} unresolved := &UnresolvedDependencies{deps: map[string][]unresolvedChartDependency{}}
//if err := unresolved.Add("stable/envoy", "https://kubernetes-charts.storage.googleapis.com", ""); err != nil { //if err := unresolved.Add("stable/envoy", "https://kubernetes-charts.storage.googleapis.com", ""); err != nil {
// panic(err) // panic(err)
//} //}
@ -340,7 +352,7 @@ func (m *chartDependencyManager) Resolve(unresolved *UnresolvedDependencies) (*R
return nil, false, err return nil, false, err
} }
resolved := &ResolvedDependencies{deps: map[string]ResolvedChartDependency{}} resolved := &ResolvedDependencies{deps: map[string][]ResolvedChartDependency{}}
for _, d := range lockedReqs.ResolvedDependencies { for _, d := range lockedReqs.ResolvedDependencies {
if err := resolved.add(d); err != nil { if err := resolved.add(d); err != nil {
return nil, false, err return nil, false, err

View File

@ -1437,8 +1437,11 @@ func TestHelmState_UpdateDeps(t *testing.T) {
- name: envoy - name: envoy
repository: https://kubernetes-charts.storage.googleapis.com repository: https://kubernetes-charts.storage.googleapis.com
version: 1.5.0 version: 1.5.0
digest: sha256:e43b05c8528ea8ef1560f4980a519719ad2a634658abde0a98daefdb83a104e9 - name: envoy
generated: 2019-05-14T11:29:35.144399+09:00 repository: https://kubernetes-charts.storage.googleapis.com
version: 1.4.0
digest: sha256:8194b597c85bb3d1fee8476d4a486e952681d5c65f185ad5809f2118bc4079b5
generated: 2019-05-16T15:42:45.50486+09:00
`) `)
filename := filepath.Join(generatedDir, "requirements.lock") filename := filepath.Join(generatedDir, "requirements.lock")
logger.Debugf("test: writing %s: %s", filename, content) logger.Debugf("test: writing %s: %s", filename, content)
@ -1468,7 +1471,12 @@ generated: 2019-05-14T11:29:35.144399+09:00
Chart: "published/deeper", Chart: "published/deeper",
}, },
{ {
Chart: "stable/envoy", Chart: "stable/envoy",
Version: "1.5.0",
},
{
Chart: "stable/envoy",
Version: "1.4.0",
}, },
}, },
Repositories: []RepositorySpec{ Repositories: []RepositorySpec{
@ -1498,9 +1506,12 @@ generated: 2019-05-14T11:29:35.144399+09:00
if resolved.Releases[5].Version != "1.5.0" { if resolved.Releases[5].Version != "1.5.0" {
t.Errorf("unexpected version number: expected=1.5.0, got=%s", resolved.Releases[5].Version) t.Errorf("unexpected version number: expected=1.5.0, got=%s", resolved.Releases[5].Version)
} }
if resolved.Releases[6].Version != "1.4.0" {
t.Errorf("unexpected version number: expected=1.4.0, got=%s", resolved.Releases[6].Version)
}
} }
func TestHelmState_ResolveDeps(t *testing.T) { func TestHelmState_ResolveDeps_NoLockFile(t *testing.T) {
logger := helmexec.NewLogger(os.Stderr, "debug") logger := helmexec.NewLogger(os.Stderr, "debug")
state := &HelmState{ state := &HelmState{
basePath: "/src", basePath: "/src",