feat: support for locking the same chart for two or more versions (#600)
Resolves #598
This commit is contained in:
parent
a769d1a82a
commit
0104c91fce
2
go.mod
2
go.mod
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue