fix: two releases using the same chart in different version fails (#1685)
* fix: two releases using the same chart in different version fails Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
parent
65f4e6122a
commit
18529ab7c5
|
|
@ -19,6 +19,10 @@ type ChartMeta struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unresolvedChartDependency represents a dependency that is to be resolved.
|
||||||
|
//
|
||||||
|
// Helmfile generates Helm Chart.yaml containing unresolved dependencies, and runs `helm dependency update` to produce Helm Chart.lock
|
||||||
|
// which becomes helmfile.lock, which is then used to resolve the dependencies.
|
||||||
type unresolvedChartDependency struct {
|
type unresolvedChartDependency struct {
|
||||||
// ChartName identifies the dependant chart. In Helmfile, ChartName for `chart: stable/envoy` would be just `envoy`.
|
// ChartName identifies the dependant chart. In Helmfile, ChartName for `chart: stable/envoy` would be just `envoy`.
|
||||||
// It can't be collided with other charts referenced in the same helmfile spec.
|
// It can't be collided with other charts referenced in the same helmfile spec.
|
||||||
|
|
@ -28,6 +32,10 @@ type unresolvedChartDependency struct {
|
||||||
Repository string `yaml:"repository"`
|
Repository string `yaml:"repository"`
|
||||||
// VersionConstraint is the version constraint of the dependent chart. "*" means the latest version.
|
// VersionConstraint is the version constraint of the dependent chart. "*" means the latest version.
|
||||||
VersionConstraint string `yaml:"version"`
|
VersionConstraint string `yaml:"version"`
|
||||||
|
// Alias differentiates multiple dependencies with the same ChartName.
|
||||||
|
// Despite its name, and its optional in Helm's Chart.yaml, we use this as a unique identifier for the dependency.
|
||||||
|
// So, every dependency have an alias, even if it's not explicitly set in the helmfile.
|
||||||
|
Alias string `yaml:"alias"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResolvedChartDependency struct {
|
type ResolvedChartDependency struct {
|
||||||
|
|
@ -57,43 +65,13 @@ type ChartLockedRequirements struct {
|
||||||
Generated string `yaml:"generated"`
|
Generated string `yaml:"generated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *UnresolvedDependencies) Add(chart, url, versionConstraint string) error {
|
func (d *UnresolvedDependencies) Add(chart, url, versionConstraint, alias string) {
|
||||||
dep := unresolvedChartDependency{
|
d.deps[chart] = append(d.deps[chart], unresolvedChartDependency{
|
||||||
ChartName: chart,
|
ChartName: chart,
|
||||||
Repository: url,
|
Repository: url,
|
||||||
VersionConstraint: versionConstraint,
|
VersionConstraint: versionConstraint,
|
||||||
}
|
Alias: alias,
|
||||||
if !d.contains(dep) {
|
})
|
||||||
return d.add(dep)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *UnresolvedDependencies) add(dep unresolvedChartDependency) error {
|
|
||||||
deps := d.deps[dep.ChartName]
|
|
||||||
if deps == nil {
|
|
||||||
deps = []unresolvedChartDependency{dep}
|
|
||||||
} else {
|
|
||||||
deps = append(deps, dep)
|
|
||||||
}
|
|
||||||
d.deps[dep.ChartName] = deps
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// contains checks if the UnresolvedDependencies contains the specified unresolvedChartDependency.
|
|
||||||
// It returns true if the dependency is found, otherwise it returns false.
|
|
||||||
// fix 'more than one dependency with name or alias "raw"' error since helm v3.14.0
|
|
||||||
func (d *UnresolvedDependencies) contains(dep unresolvedChartDependency) bool {
|
|
||||||
deps := d.deps[dep.ChartName]
|
|
||||||
if deps == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, existDep := range deps {
|
|
||||||
if existDep.ChartName == dep.ChartName {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *UnresolvedDependencies) ToChartRequirements() *ChartRequirements {
|
func (d *UnresolvedDependencies) ToChartRequirements() *ChartRequirements {
|
||||||
|
|
@ -152,10 +130,7 @@ func (d *ResolvedDependencies) Get(chart, versionConstraint string) (string, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *HelmState) mergeLockedDependencies() (*HelmState, error) {
|
func (st *HelmState) mergeLockedDependencies() (*HelmState, error) {
|
||||||
filename, unresolved, err := getUnresolvedDependenciess(st)
|
filename, unresolved := getUnresolvedDependenciess(st)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(unresolved.deps) == 0 {
|
if len(unresolved.deps) == 0 {
|
||||||
return st, nil
|
return st, nil
|
||||||
|
|
@ -211,10 +186,7 @@ func resolveDependencies(st *HelmState, depMan *chartDependencyManager, unresolv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *HelmState) updateDependenciesInTempDir(shell helmexec.DependencyUpdater, tempDir func(string, string) (string, error)) (*HelmState, error) {
|
func (st *HelmState) updateDependenciesInTempDir(shell helmexec.DependencyUpdater, tempDir func(string, string) (string, error)) (*HelmState, error) {
|
||||||
filename, unresolved, err := getUnresolvedDependenciess(st)
|
filename, unresolved := getUnresolvedDependenciess(st)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(unresolved.deps) == 0 {
|
if len(unresolved.deps) == 0 {
|
||||||
st.logger.Warnf("There are no repositories defined in your helmfile.yaml.\nThis means helmfile cannot update your dependencies or create a lock file.\nSee https://github.com/roboll/helmfile/issues/878 for more information.")
|
st.logger.Warnf("There are no repositories defined in your helmfile.yaml.\nThis means helmfile cannot update your dependencies or create a lock file.\nSee https://github.com/roboll/helmfile/issues/878 for more information.")
|
||||||
|
|
@ -232,7 +204,12 @@ func (st *HelmState) updateDependenciesInTempDir(shell helmexec.DependencyUpdate
|
||||||
return updateDependencies(st, shell, unresolved, filename, d)
|
return updateDependencies(st, shell, unresolved, filename, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUnresolvedDependenciess(st *HelmState) (string, *UnresolvedDependencies, error) {
|
// aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$") from helm code
|
||||||
|
func chartDependenciesAlias(namespace, releaseName string) string {
|
||||||
|
return fmt.Sprintf("%s-%s", namespace, releaseName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUnresolvedDependenciess(st *HelmState) (string, *UnresolvedDependencies) {
|
||||||
repoToURL := map[string]RepositorySpec{}
|
repoToURL := map[string]RepositorySpec{}
|
||||||
|
|
||||||
for _, r := range st.Repositories {
|
for _, r := range st.Repositories {
|
||||||
|
|
@ -260,9 +237,7 @@ func getUnresolvedDependenciess(st *HelmState) (string, *UnresolvedDependencies,
|
||||||
url = fmt.Sprintf("oci://%s", url)
|
url = fmt.Sprintf("oci://%s", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := unresolved.Add(chart, url, r.Version); err != nil {
|
unresolved.Add(chart, url, r.Version, chartDependenciesAlias(r.Namespace, r.Name))
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := filepath.Base(st.FilePath)
|
filename := filepath.Base(st.FilePath)
|
||||||
|
|
@ -270,7 +245,7 @@ func getUnresolvedDependenciess(st *HelmState) (string, *UnresolvedDependencies,
|
||||||
filename = strings.TrimSuffix(filename, ".yaml")
|
filename = strings.TrimSuffix(filename, ".yaml")
|
||||||
filename = strings.TrimSuffix(filename, ".yml")
|
filename = strings.TrimSuffix(filename, ".yml")
|
||||||
|
|
||||||
return filename, unresolved, nil
|
return filename, unresolved
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateDependencies(st *HelmState, shell helmexec.DependencyUpdater, unresolved *UnresolvedDependencies, filename, wd string) (*HelmState, error) {
|
func updateDependencies(st *HelmState, shell helmexec.DependencyUpdater, unresolved *UnresolvedDependencies, filename, wd string) (*HelmState, error) {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ func TestGetUnresolvedDependenciess(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
helmState *HelmState
|
helmState *HelmState
|
||||||
wantErr bool
|
|
||||||
expectfile string
|
expectfile string
|
||||||
expectDeps *UnresolvedDependencies
|
expectDeps *UnresolvedDependencies
|
||||||
}{
|
}{
|
||||||
|
|
@ -21,21 +20,34 @@ func TestGetUnresolvedDependenciess(t *testing.T) {
|
||||||
ReleaseSetSpec: ReleaseSetSpec{
|
ReleaseSetSpec: ReleaseSetSpec{
|
||||||
Releases: []ReleaseSpec{
|
Releases: []ReleaseSpec{
|
||||||
{
|
{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Chart: "charts/abc",
|
Chart: "chartsa/abc",
|
||||||
Version: "0.1.0",
|
Version: "0.1.0",
|
||||||
|
Namespace: "ns1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "empty",
|
||||||
|
Chart: "chartsb/empty",
|
||||||
|
Namespace: "ns2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "empty",
|
||||||
|
Chart: "chartsb/empty",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Repositories: []RepositorySpec{
|
Repositories: []RepositorySpec{
|
||||||
{
|
{
|
||||||
Name: "charts",
|
Name: "chartsa",
|
||||||
URL: "localhost:5000/aaa",
|
URL: "localhost:5000/aaa",
|
||||||
OCI: true,
|
OCI: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "chartsb",
|
||||||
|
URL: "localhost:5000/bbb",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
|
||||||
expectfile: "helmfile",
|
expectfile: "helmfile",
|
||||||
expectDeps: &UnresolvedDependencies{
|
expectDeps: &UnresolvedDependencies{
|
||||||
deps: map[string][]unresolvedChartDependency{
|
deps: map[string][]unresolvedChartDependency{
|
||||||
|
|
@ -44,6 +56,77 @@ func TestGetUnresolvedDependenciess(t *testing.T) {
|
||||||
ChartName: "abc",
|
ChartName: "abc",
|
||||||
Repository: "oci://localhost:5000/aaa",
|
Repository: "oci://localhost:5000/aaa",
|
||||||
VersionConstraint: "0.1.0",
|
VersionConstraint: "0.1.0",
|
||||||
|
Alias: "ns1-foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
{
|
||||||
|
ChartName: "empty",
|
||||||
|
Repository: "localhost:5000/bbb",
|
||||||
|
Alias: "ns2-empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ChartName: "empty",
|
||||||
|
Repository: "localhost:5000/bbb",
|
||||||
|
Alias: "-empty",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate charts are differentiated by alias",
|
||||||
|
helmState: &HelmState{
|
||||||
|
FilePath: "helmfile.yaml",
|
||||||
|
ReleaseSetSpec: ReleaseSetSpec{
|
||||||
|
Releases: []ReleaseSpec{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Chart: "myrepo/abc",
|
||||||
|
Version: "> 0.2.0",
|
||||||
|
Namespace: "ns1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Chart: "myrepo/abc",
|
||||||
|
Version: "0.1.0",
|
||||||
|
Namespace: "ns2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "baz",
|
||||||
|
Chart: "myrepo/abc",
|
||||||
|
Version: "0.3.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Repositories: []RepositorySpec{
|
||||||
|
{
|
||||||
|
Name: "myrepo",
|
||||||
|
URL: "localhost:5000/aaa",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectfile: "helmfile",
|
||||||
|
expectDeps: &UnresolvedDependencies{
|
||||||
|
deps: map[string][]unresolvedChartDependency{
|
||||||
|
"abc": {
|
||||||
|
{
|
||||||
|
ChartName: "abc",
|
||||||
|
Repository: "localhost:5000/aaa",
|
||||||
|
VersionConstraint: "> 0.2.0",
|
||||||
|
Alias: "ns1-foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ChartName: "abc",
|
||||||
|
Repository: "localhost:5000/aaa",
|
||||||
|
VersionConstraint: "0.1.0",
|
||||||
|
Alias: "ns2-bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ChartName: "abc",
|
||||||
|
Repository: "localhost:5000/aaa",
|
||||||
|
VersionConstraint: "0.3.0",
|
||||||
|
Alias: "-baz",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -53,82 +136,30 @@ func TestGetUnresolvedDependenciess(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
f, ds, err := getUnresolvedDependenciess(tt.helmState)
|
f, ds := getUnresolvedDependenciess(tt.helmState)
|
||||||
if tt.wantErr {
|
|
||||||
require.Error(t, err, "getUnresolvedDependenciess() error = nil, wantErr")
|
|
||||||
} else {
|
|
||||||
require.NoErrorf(t, err, "getUnresolvedDependenciess() want no error, got %v", err)
|
|
||||||
}
|
|
||||||
require.Equalf(t, tt.expectfile, f, "getUnresolvedDependenciess() expect file %s, got %s", tt.expectfile, f)
|
require.Equalf(t, tt.expectfile, f, "getUnresolvedDependenciess() expect file %s, got %s", tt.expectfile, f)
|
||||||
require.Equalf(t, tt.expectDeps, ds, "getUnresolvedDependenciess() expect deps %v, got %v", tt.expectDeps, ds)
|
require.Equalf(t, tt.expectDeps, ds, "getUnresolvedDependenciess() expect deps %v, got %v", tt.expectDeps, ds)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContains(t *testing.T) {
|
func TestChartDependenciesAlias(t *testing.T) {
|
||||||
tests := []struct {
|
type testCase struct {
|
||||||
name string
|
releaseName string
|
||||||
dep unresolvedChartDependency
|
namespace string
|
||||||
deps map[string][]unresolvedChartDependency
|
expected string
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "existing dependency with right item",
|
|
||||||
dep: unresolvedChartDependency{
|
|
||||||
ChartName: "abc",
|
|
||||||
Repository: "oci://localhost:5000/aaa",
|
|
||||||
VersionConstraint: "0.1.0",
|
|
||||||
},
|
|
||||||
deps: map[string][]unresolvedChartDependency{
|
|
||||||
"abc": {
|
|
||||||
{
|
|
||||||
ChartName: "abc",
|
|
||||||
Repository: "oci://localhost:5000/aaa",
|
|
||||||
VersionConstraint: "0.1.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "existing dependency with empty item",
|
|
||||||
dep: unresolvedChartDependency{
|
|
||||||
ChartName: "ghi",
|
|
||||||
Repository: "oci://localhost:5000/aaa",
|
|
||||||
VersionConstraint: "0.1.0",
|
|
||||||
},
|
|
||||||
deps: map[string][]unresolvedChartDependency{
|
|
||||||
"ghi": {},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "non-existing dependency",
|
|
||||||
dep: unresolvedChartDependency{
|
|
||||||
ChartName: "def",
|
|
||||||
Repository: "oci://localhost:5000/bbb",
|
|
||||||
VersionConstraint: "0.2.0",
|
|
||||||
},
|
|
||||||
deps: map[string][]unresolvedChartDependency{
|
|
||||||
"abc": {
|
|
||||||
{
|
|
||||||
ChartName: "abc",
|
|
||||||
Repository: "oci://localhost:5000/aaa",
|
|
||||||
VersionConstraint: "0.1.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
testCases := []testCase{
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
{"release1", "n1", "n1-release1"},
|
||||||
d := &UnresolvedDependencies{
|
{"release2", "n2", "n2-release2"},
|
||||||
deps: tt.deps,
|
{"empty", "", "-empty"},
|
||||||
}
|
}
|
||||||
actual := d.contains(tt.dep)
|
|
||||||
require.Equal(t, tt.expected, actual)
|
for _, tc := range testCases {
|
||||||
})
|
result := chartDependenciesAlias(tc.namespace, tc.releaseName)
|
||||||
|
if result != tc.expected {
|
||||||
|
t.Errorf("Expected %s, but got %s", tc.expected, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,4 +34,14 @@ if [[ ${HELMFILE_V1MODE} = true ]]; then
|
||||||
fi
|
fi
|
||||||
(${helmfile} -f ${regression_case_input_dir}/${config_file} template 1>/dev/null) || fail "\"helmfile template\" shouldn't fail"
|
(${helmfile} -f ${regression_case_input_dir}/${config_file} template 1>/dev/null) || fail "\"helmfile template\" shouldn't fail"
|
||||||
|
|
||||||
|
info "https://github.com/helmfile/helmfile/issues/1682"
|
||||||
|
config_file="issue.1682.yaml"
|
||||||
|
if [[ ${HELMFILE_V1MODE} = true ]]; then
|
||||||
|
pushd "${regression_case_input_dir}"
|
||||||
|
mv "${config_file}" "${config_file}.gotmpl"
|
||||||
|
config_file="${config_file}.gotmpl"
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
(${helmfile} -f ${regression_case_input_dir}/${config_file} deps 1>/dev/null) || fail "\"helmfile deps\" shouldn't fail"
|
||||||
|
|
||||||
test_pass "regression tests"
|
test_pass "regression tests"
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
repositories:
|
||||||
|
- name: elastic
|
||||||
|
url: https://helm.elastic.co/
|
||||||
|
|
||||||
|
releases:
|
||||||
|
- name: elasticsearch7
|
||||||
|
chart: elastic/elasticsearch
|
||||||
|
version: 7.17.3
|
||||||
|
- name: elasticsearch8
|
||||||
|
chart: elastic/elasticsearch
|
||||||
|
version: 8.5.1
|
||||||
|
- name: elasticsearchempty
|
||||||
|
chart: elastic/elasticsearch
|
||||||
Loading…
Reference in New Issue