mirror of https://github.com/helm/helm
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
601 lines
15 KiB
601 lines
15 KiB
/*
|
|
Copyright The Helm Authors.
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package downloader
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"helm.sh/helm/v3/pkg/chart"
|
|
"helm.sh/helm/v3/pkg/chart/loader"
|
|
"helm.sh/helm/v3/pkg/chartutil"
|
|
"helm.sh/helm/v3/pkg/getter"
|
|
"helm.sh/helm/v3/pkg/repo/repotest"
|
|
)
|
|
|
|
func TestVersionEquals(t *testing.T) {
|
|
tests := []struct {
|
|
name, v1, v2 string
|
|
expect bool
|
|
}{
|
|
{name: "semver match", v1: "1.2.3-beta.11", v2: "1.2.3-beta.11", expect: true},
|
|
{name: "semver match, build info", v1: "1.2.3-beta.11+a", v2: "1.2.3-beta.11+b", expect: true},
|
|
{name: "string match", v1: "abcdef123", v2: "abcdef123", expect: true},
|
|
{name: "semver mismatch", v1: "1.2.3-beta.11", v2: "1.2.3-beta.22", expect: false},
|
|
{name: "semver mismatch, invalid semver", v1: "1.2.3-beta.11", v2: "stinkycheese", expect: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
if versionEquals(tt.v1, tt.v2) != tt.expect {
|
|
t.Errorf("%s: failed comparison of %q and %q (expect equal: %t)", tt.name, tt.v1, tt.v2, tt.expect)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNormalizeURL(t *testing.T) {
|
|
tests := []struct {
|
|
name, base, path, expect string
|
|
}{
|
|
{name: "basic URL", base: "https://example.com", path: "http://helm.sh/foo", expect: "http://helm.sh/foo"},
|
|
{name: "relative path", base: "https://helm.sh/charts", path: "foo", expect: "https://helm.sh/charts/foo"},
|
|
{name: "Encoded path", base: "https://helm.sh/a%2Fb/charts", path: "foo", expect: "https://helm.sh/a%2Fb/charts/foo"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got, err := normalizeURL(tt.base, tt.path)
|
|
if err != nil {
|
|
t.Errorf("%s: error %s", tt.name, err)
|
|
continue
|
|
} else if got != tt.expect {
|
|
t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindChartURL(t *testing.T) {
|
|
var b bytes.Buffer
|
|
m := &Manager{
|
|
Out: &b,
|
|
RepositoryConfig: repoConfig,
|
|
RepositoryCache: repoCache,
|
|
}
|
|
repos, err := m.loadChartRepositories()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
name := "alpine"
|
|
version := "0.1.0"
|
|
repoURL := "http://example.com/charts"
|
|
|
|
churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err := m.findChartURL(name, version, repoURL, repos, make(map[string]string))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if churl != "https://charts.helm.sh/stable/alpine-0.1.0.tgz" {
|
|
t.Errorf("Unexpected URL %q", churl)
|
|
}
|
|
if username != "" {
|
|
t.Errorf("Unexpected username %q", username)
|
|
}
|
|
if password != "" {
|
|
t.Errorf("Unexpected password %q", password)
|
|
}
|
|
if passcredentialsall != false {
|
|
t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
|
|
}
|
|
if insecureSkipTLSVerify {
|
|
t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
|
|
}
|
|
|
|
name = "tlsfoo"
|
|
version = "1.2.3"
|
|
repoURL = "https://example-https-insecureskiptlsverify.com"
|
|
|
|
churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos, make(map[string]string))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !insecureSkipTLSVerify {
|
|
t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
|
|
}
|
|
if churl != "https://example.com/tlsfoo-1.2.3.tgz" {
|
|
t.Errorf("Unexpected URL %q", churl)
|
|
}
|
|
if username != "" {
|
|
t.Errorf("Unexpected username %q", username)
|
|
}
|
|
if password != "" {
|
|
t.Errorf("Unexpected password %q", password)
|
|
}
|
|
if passcredentialsall != false {
|
|
t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
|
|
}
|
|
}
|
|
|
|
func TestGetRepoNames(t *testing.T) {
|
|
b := bytes.NewBuffer(nil)
|
|
m := &Manager{
|
|
Out: b,
|
|
RepositoryConfig: repoConfig,
|
|
RepositoryCache: repoCache,
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
req []*chart.Dependency
|
|
expect map[string]string
|
|
err bool
|
|
}{
|
|
{
|
|
name: "no repo definition, but references a url",
|
|
req: []*chart.Dependency{
|
|
{Name: "oedipus-rex", Repository: "http://example.com/test"},
|
|
},
|
|
expect: map[string]string{"http://example.com/test": "http://example.com/test"},
|
|
},
|
|
{
|
|
name: "no repo definition failure -- stable repo",
|
|
req: []*chart.Dependency{
|
|
{Name: "oedipus-rex", Repository: "stable"},
|
|
},
|
|
err: true,
|
|
},
|
|
{
|
|
name: "no repo definition failure",
|
|
req: []*chart.Dependency{
|
|
{Name: "oedipus-rex", Repository: "http://example.com"},
|
|
},
|
|
expect: map[string]string{"oedipus-rex": "testing"},
|
|
},
|
|
{
|
|
name: "repo from local path",
|
|
req: []*chart.Dependency{
|
|
{Name: "local-dep", Repository: "file://./testdata/signtest"},
|
|
},
|
|
expect: map[string]string{"local-dep": "file://./testdata/signtest"},
|
|
},
|
|
{
|
|
name: "repo alias (alias:)",
|
|
req: []*chart.Dependency{
|
|
{Name: "oedipus-rex", Repository: "alias:testing"},
|
|
},
|
|
expect: map[string]string{"oedipus-rex": "testing"},
|
|
},
|
|
{
|
|
name: "repo alias (@)",
|
|
req: []*chart.Dependency{
|
|
{Name: "oedipus-rex", Repository: "@testing"},
|
|
},
|
|
expect: map[string]string{"oedipus-rex": "testing"},
|
|
},
|
|
{
|
|
name: "repo from local chart under charts path",
|
|
req: []*chart.Dependency{
|
|
{Name: "local-subchart", Repository: ""},
|
|
},
|
|
expect: map[string]string{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
l, err := m.resolveRepoNames(tt.req)
|
|
if err != nil {
|
|
if tt.err {
|
|
continue
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if tt.err {
|
|
t.Fatalf("Expected error in test %q", tt.name)
|
|
}
|
|
|
|
// m1 and m2 are the maps we want to compare
|
|
eq := reflect.DeepEqual(l, tt.expect)
|
|
if !eq {
|
|
t.Errorf("%s: expected map %v, got %v", tt.name, l, tt.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDownloadAll(t *testing.T) {
|
|
chartPath := t.TempDir()
|
|
m := &Manager{
|
|
Out: new(bytes.Buffer),
|
|
RepositoryConfig: repoConfig,
|
|
RepositoryCache: repoCache,
|
|
ChartPath: chartPath,
|
|
}
|
|
signtest, err := loader.LoadDir(filepath.Join("testdata", "signtest"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := chartutil.SaveDir(signtest, filepath.Join(chartPath, "testdata")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
local, err := loader.LoadDir(filepath.Join("testdata", "local-subchart"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := chartutil.SaveDir(local, filepath.Join(chartPath, "charts")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
signDep := &chart.Dependency{
|
|
Name: signtest.Name(),
|
|
Repository: "file://./testdata/signtest",
|
|
Version: signtest.Metadata.Version,
|
|
}
|
|
localDep := &chart.Dependency{
|
|
Name: local.Name(),
|
|
Repository: "",
|
|
Version: local.Metadata.Version,
|
|
}
|
|
|
|
// create a 'tmpcharts' directory to test #5567
|
|
if err := os.MkdirAll(filepath.Join(chartPath, "tmpcharts"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := m.downloadAll([]*chart.Dependency{signDep, localDep}, make(map[string]string)); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); os.IsNotExist(err) {
|
|
t.Error(err)
|
|
}
|
|
|
|
// A chart with a bad name like this cannot be loaded and saved. Handling in
|
|
// the loading and saving will return an error about the invalid name. In
|
|
// this case, the chart needs to be created directly.
|
|
badchartyaml := `apiVersion: v2
|
|
description: A Helm chart for Kubernetes
|
|
name: ../bad-local-subchart
|
|
version: 0.1.0`
|
|
if err := os.MkdirAll(filepath.Join(chartPath, "testdata", "bad-local-subchart"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = os.WriteFile(filepath.Join(chartPath, "testdata", "bad-local-subchart", "Chart.yaml"), []byte(badchartyaml), 0644)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
badLocalDep := &chart.Dependency{
|
|
Name: "../bad-local-subchart",
|
|
Repository: "file://./testdata/bad-local-subchart",
|
|
Version: "0.1.0",
|
|
}
|
|
|
|
err = m.downloadAll([]*chart.Dependency{badLocalDep}, make(map[string]string))
|
|
if err == nil {
|
|
t.Fatal("Expected error for bad dependency name")
|
|
}
|
|
}
|
|
|
|
func TestUpdateBeforeBuild(t *testing.T) {
|
|
// Set up a fake repo
|
|
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer srv.Stop()
|
|
if err := srv.LinkIndices(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := func(p ...string) string {
|
|
return filepath.Join(append([]string{srv.Root()}, p...)...)
|
|
}
|
|
|
|
// Save dep
|
|
d := &chart.Chart{
|
|
Metadata: &chart.Metadata{
|
|
Name: "dep-chart",
|
|
Version: "0.1.0",
|
|
APIVersion: "v1",
|
|
},
|
|
}
|
|
if err := chartutil.SaveDir(d, dir()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Save a chart
|
|
c := &chart.Chart{
|
|
Metadata: &chart.Metadata{
|
|
Name: "with-dependency",
|
|
Version: "0.1.0",
|
|
APIVersion: "v2",
|
|
Dependencies: []*chart.Dependency{{
|
|
Name: d.Metadata.Name,
|
|
Version: ">=0.1.0",
|
|
Repository: "file://../dep-chart",
|
|
}},
|
|
},
|
|
}
|
|
if err := chartutil.SaveDir(c, dir()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set-up a manager
|
|
b := bytes.NewBuffer(nil)
|
|
g := getter.Providers{getter.Provider{
|
|
Schemes: []string{"http", "https"},
|
|
New: getter.NewHTTPGetter,
|
|
}}
|
|
m := &Manager{
|
|
ChartPath: dir(c.Metadata.Name),
|
|
Out: b,
|
|
Getters: g,
|
|
RepositoryConfig: dir("repositories.yaml"),
|
|
RepositoryCache: dir(),
|
|
}
|
|
|
|
// Update before Build. see issue: https://github.com/helm/helm/issues/7101
|
|
err = m.Update()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = m.Build()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// TestUpdateWithNoRepo is for the case of a dependency that has no repo listed.
|
|
// This happens when the dependency is in the charts directory and does not need
|
|
// to be fetched.
|
|
func TestUpdateWithNoRepo(t *testing.T) {
|
|
// Set up a fake repo
|
|
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer srv.Stop()
|
|
if err := srv.LinkIndices(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := func(p ...string) string {
|
|
return filepath.Join(append([]string{srv.Root()}, p...)...)
|
|
}
|
|
|
|
// Setup the dependent chart
|
|
d := &chart.Chart{
|
|
Metadata: &chart.Metadata{
|
|
Name: "dep-chart",
|
|
Version: "0.1.0",
|
|
APIVersion: "v1",
|
|
},
|
|
}
|
|
|
|
// Save a chart with the dependency
|
|
c := &chart.Chart{
|
|
Metadata: &chart.Metadata{
|
|
Name: "with-dependency",
|
|
Version: "0.1.0",
|
|
APIVersion: "v2",
|
|
Dependencies: []*chart.Dependency{{
|
|
Name: d.Metadata.Name,
|
|
Version: "0.1.0",
|
|
}},
|
|
},
|
|
}
|
|
if err := chartutil.SaveDir(c, dir()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Save dependent chart into the parents charts directory. If the chart is
|
|
// not in the charts directory Helm will return an error that it is not
|
|
// found.
|
|
if err := chartutil.SaveDir(d, dir(c.Metadata.Name, "charts")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set-up a manager
|
|
b := bytes.NewBuffer(nil)
|
|
g := getter.Providers{getter.Provider{
|
|
Schemes: []string{"http", "https"},
|
|
New: getter.NewHTTPGetter,
|
|
}}
|
|
m := &Manager{
|
|
ChartPath: dir(c.Metadata.Name),
|
|
Out: b,
|
|
Getters: g,
|
|
RepositoryConfig: dir("repositories.yaml"),
|
|
RepositoryCache: dir(),
|
|
}
|
|
|
|
// Test the update
|
|
err = m.Update()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// This function is the skeleton test code of failing tests for #6416 and #6871 and bugs due to #5874.
|
|
//
|
|
// This function is used by below tests that ensures success of build operation
|
|
// with optional fields, alias, condition, tags, and even with ranged version.
|
|
// Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default.
|
|
// If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used.
|
|
func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) {
|
|
// Set up a fake repo
|
|
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer srv.Stop()
|
|
if err := srv.LinkIndices(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := func(p ...string) string {
|
|
return filepath.Join(append([]string{srv.Root()}, p...)...)
|
|
}
|
|
|
|
// Set main fields if not exist
|
|
if dep.Name == "" {
|
|
dep.Name = "local-subchart"
|
|
}
|
|
if dep.Version == "" {
|
|
dep.Version = "0.1.0"
|
|
}
|
|
if dep.Repository == "" {
|
|
dep.Repository = srv.URL()
|
|
}
|
|
|
|
// Save a chart
|
|
c := &chart.Chart{
|
|
Metadata: &chart.Metadata{
|
|
Name: chartName,
|
|
Version: "0.1.0",
|
|
APIVersion: "v2",
|
|
Dependencies: []*chart.Dependency{&dep},
|
|
},
|
|
}
|
|
if err := chartutil.SaveDir(c, dir()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Set-up a manager
|
|
b := bytes.NewBuffer(nil)
|
|
g := getter.Providers{getter.Provider{
|
|
Schemes: []string{"http", "https"},
|
|
New: getter.NewHTTPGetter,
|
|
}}
|
|
m := &Manager{
|
|
ChartPath: dir(chartName),
|
|
Out: b,
|
|
Getters: g,
|
|
RepositoryConfig: dir("repositories.yaml"),
|
|
RepositoryCache: dir(),
|
|
}
|
|
|
|
// First build will update dependencies and create Chart.lock file.
|
|
err = m.Build()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Second build should be passed. See PR #6655.
|
|
err = m.Build()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestBuild_WithoutOptionalFields(t *testing.T) {
|
|
// Dependency has main fields only (name/version/repository)
|
|
checkBuildWithOptionalFields(t, "without-optional-fields", chart.Dependency{})
|
|
}
|
|
|
|
func TestBuild_WithSemVerRange(t *testing.T) {
|
|
// Dependency version is the form of SemVer range
|
|
checkBuildWithOptionalFields(t, "with-semver-range", chart.Dependency{
|
|
Version: ">=0.1.0",
|
|
})
|
|
}
|
|
|
|
func TestBuild_WithAlias(t *testing.T) {
|
|
// Dependency has an alias
|
|
checkBuildWithOptionalFields(t, "with-alias", chart.Dependency{
|
|
Alias: "local-subchart-alias",
|
|
})
|
|
}
|
|
|
|
func TestBuild_WithCondition(t *testing.T) {
|
|
// Dependency has a condition
|
|
checkBuildWithOptionalFields(t, "with-condition", chart.Dependency{
|
|
Condition: "some.condition",
|
|
})
|
|
}
|
|
|
|
func TestBuild_WithTags(t *testing.T) {
|
|
// Dependency has several tags
|
|
checkBuildWithOptionalFields(t, "with-tags", chart.Dependency{
|
|
Tags: []string{"tag1", "tag2"},
|
|
})
|
|
}
|
|
|
|
// Failing test for #6871
|
|
func TestBuild_WithRepositoryAlias(t *testing.T) {
|
|
// Dependency repository is aliased in Chart.yaml
|
|
checkBuildWithOptionalFields(t, "with-repository-alias", chart.Dependency{
|
|
Repository: "@test",
|
|
})
|
|
}
|
|
|
|
func TestErrRepoNotFound_Error(t *testing.T) {
|
|
type fields struct {
|
|
Repos []string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
want string
|
|
}{
|
|
{
|
|
name: "OK",
|
|
fields: fields{
|
|
Repos: []string{"https://charts1.example.com", "https://charts2.example.com"},
|
|
},
|
|
want: "no repository definition for https://charts1.example.com, https://charts2.example.com",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
e := ErrRepoNotFound{
|
|
Repos: tt.fields.Repos,
|
|
}
|
|
if got := e.Error(); got != tt.want {
|
|
t.Errorf("Error() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKey(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
expect string
|
|
}{
|
|
{
|
|
name: "file:////tmp",
|
|
expect: "afeed3459e92a874f6373aca264ce1459bfa91f9c1d6612f10ae3dc2ee955df3",
|
|
},
|
|
{
|
|
name: "https://example.com/charts",
|
|
expect: "7065c57c94b2411ad774638d76823c7ccb56415441f5ab2f5ece2f3845728e5d",
|
|
},
|
|
{
|
|
name: "foo/bar/baz",
|
|
expect: "15c46a4f8a189ae22f36f201048881d6c090c93583bedcf71f5443fdef224c82",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
o, err := key(tt.name)
|
|
if err != nil {
|
|
t.Fatalf("unable to generate key for %q with error: %s", tt.name, err)
|
|
}
|
|
if o != tt.expect {
|
|
t.Errorf("wrong key name generated for %q, expected %q but got %q", tt.name, tt.expect, o)
|
|
}
|
|
}
|
|
}
|