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 { | ||||||
|  | 		for _, d := range ds { | ||||||
| 			if d.VersionConstraint == "" { | 			if d.VersionConstraint == "" { | ||||||
| 				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) |  | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	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 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) | ||||||
|  | @ -1469,6 +1472,11 @@ generated: 2019-05-14T11:29:35.144399+09:00 | ||||||
| 			}, | 			}, | ||||||
| 			{ | 			{ | ||||||
| 				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