diff --git a/cmd/helm/library_update_test.go b/cmd/helm/library_update_test.go new file mode 100644 index 000000000..b95820c3d --- /dev/null +++ b/cmd/helm/library_update_test.go @@ -0,0 +1,232 @@ +/* +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 main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "k8s.io/helm/pkg/chart" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/provenance" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/repo/repotest" +) + +func TestLibraryUpdateCmd(t *testing.T) { + defer resetEnv()() + + hh := testHelmHome(t) + settings.Home = hh + + srv := repotest.NewServer(hh.String()) + defer srv.Stop() + copied, err := srv.CopyCharts("testdata/testcharts/lib-charts/*.tgz") + if err != nil { + t.Fatal(err) + } + t.Logf("Copied charts:\n%s", strings.Join(copied, "\n")) + t.Logf("Listening on directory %s", srv.Root()) + + chartname := "libup" + md := createTestingMetadataLibRef(chartname, srv.URL()) + if _, err := chartutil.Create(md, hh.String()); err != nil { + t.Fatal(err) + } + + out, err := executeCommand(nil, fmt.Sprintf("--home='%s' library update '%s'", hh, hh.Path(chartname))) + if err != nil { + t.Logf("Output: %s", out) + t.Fatal(err) + } + + // This is written directly to stdout, so we have to capture as is. + if !strings.Contains(out, `update from the "test" chart repository`) { + t.Errorf("Repo did not get updated\n%s", out) + } + + // Make sure the actual file got downloaded. + expect := hh.Path(chartname, "library/common-0.0.5.tgz") + if _, err := os.Stat(expect); err != nil { + t.Fatal(err) + } + + hash, err := provenance.DigestFile(expect) + if err != nil { + t.Fatal(err) + } + + i, err := repo.LoadIndexFile(hh.CacheIndex("test")) + if err != nil { + t.Fatal(err) + } + + reqver := i.Entries["common"][0] + if h := reqver.Digest; h != hash { + t.Errorf("Failed hash match: expected %s, got %s", hash, h) + } + + // Now change the libraries and update. This verifies that on update, + // old libraries are cleansed and new libraries are added. + md.Libraries = []*chart.Dependency{ + {Name: "common", Version: "0.0.5", Repository: srv.URL()}, + {Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()}, + } + dir := hh.Path(chartname, "Chart.yaml") + if err := chartutil.SaveChartfile(dir, md); err != nil { + t.Fatal(err) + } + + out, err = executeCommand(nil, fmt.Sprintf("--home='%s' library update '%s'", hh, hh.Path(chartname))) + if err != nil { + t.Logf("Output: %s", out) + t.Fatal(err) + } + + // In this second run, we should see compressedchart-0.3.0.tgz, and not + // the 0.1.0 version. + expect = hh.Path(chartname, "library/compressedchart-0.3.0.tgz") + if _, err := os.Stat(expect); err != nil { + t.Fatalf("Expected %q: %s", expect, err) + } + dontExpect := hh.Path(chartname, "library/compressedchart-0.1.0.tgz") + if _, err := os.Stat(dontExpect); err == nil { + t.Fatalf("Unexpected %q", dontExpect) + } +} + +func TestLibraryUpdateCmd_SkipRefresh(t *testing.T) { + defer resetEnv()() + + hh := testHelmHome(t) + settings.Home = hh + + srv := repotest.NewServer(hh.String()) + defer srv.Stop() + copied, err := srv.CopyCharts("testdata/testcharts/lib-charts/*.tgz") + if err != nil { + t.Fatal(err) + } + t.Logf("Copied charts:\n%s", strings.Join(copied, "\n")) + t.Logf("Listening on directory %s", srv.Root()) + + chartname := "libup" + if err := createTestingChartLibRef(hh.String(), chartname, srv.URL()); err != nil { + t.Fatal(err) + } + + out, err := executeCommand(nil, fmt.Sprintf("--home='%s' library update --skip-refresh '%s'", hh, hh.Path(chartname))) + if err == nil { + t.Fatal("Expected failure to find the repo with skipRefresh") + } + + // This is written directly to stdout, so we have to capture as is. + if strings.Contains(out, `update from the "test" chart repository`) { + t.Errorf("Repo was unexpectedly updated\n%s", out) + } +} + +func TestLibraryUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { + defer resetEnv()() + + hh := testHelmHome(t) + settings.Home = hh + + srv := repotest.NewServer(hh.String()) + defer srv.Stop() + copied, err := srv.CopyCharts("testdata/testcharts/lib-charts/*.tgz") + if err != nil { + t.Fatal(err) + } + t.Logf("Copied charts:\n%s", strings.Join(copied, "\n")) + t.Logf("Listening on directory %s", srv.Root()) + + chartname := "libupdelete" + if err := createTestingChartLibRef(hh.String(), chartname, srv.URL()); err != nil { + t.Fatal(err) + } + + out := bytes.NewBuffer(nil) + o := &refUpdateOptions{} + o.helmhome = hh + o.chartpath = hh.Path(chartname) + + if err := o.run(out, true); err != nil { + output := out.String() + t.Logf("Output: %s", output) + t.Fatal(err) + } + + // Chart repo is down + srv.Stop() + + if err := o.run(out, true); err == nil { + output := out.String() + t.Logf("Output: %s", output) + t.Fatal("Expected error, got nil") + } + + // Make sure charts dir still has libraries + files, err := ioutil.ReadDir(filepath.Join(o.chartpath, "library")) + if err != nil { + t.Fatal(err) + } + libraries := []string{"common-0.0.5.tgz", "compressedchart-0.1.0.tgz"} + + if len(libraries) != len(files) { + t.Fatalf("Expected %d chart library, got %d", len(libraries), len(files)) + } + for index, file := range files { + if libraries[index] != file.Name() { + t.Fatalf("Chart library %s not matching %s", libraries[index], file.Name()) + } + } + + // Make sure tmpcharts is deleted + if _, err := os.Stat(filepath.Join(o.chartpath, "tmpcharts")); !os.IsNotExist(err) { + t.Fatalf("tmpcharts dir still exists") + } +} + +// createTestingMetadataLibRef creates a basic chart that depends on lib chart +// common-0.0.5 +// +// The baseURL can be used to point to a particular repository server. +func createTestingMetadataLibRef(name, baseURL string) *chart.Metadata { + return &chart.Metadata{ + Name: name, + Version: "1.2.3", + Libraries: []*chart.Dependency{ + {Name: "common", Version: "0.0.5", Repository: baseURL}, + {Name: "compressedchart", Version: "0.1.0", Repository: baseURL}, + }, + } +} + +// createTestingChartLibRef creates a basic chart that depends on +// lib chart common-0.0.5 +// +// The baseURL can be used to point to a particular repository server. +func createTestingChartLibRef(dest, name, baseURL string) error { + cfile := createTestingMetadataLibRef(name, baseURL) + _, err := chartutil.Create(cfile, dest) + return err +} diff --git a/cmd/helm/package_test.go b/cmd/helm/package_test.go index f6a35ac77..032e0bc30 100644 --- a/cmd/helm/package_test.go +++ b/cmd/helm/package_test.go @@ -140,6 +140,12 @@ func TestPackage(t *testing.T) { hasfile: "chart-missing-deps-0.1.0.tgz", err: true, }, + { + name: "package testdata/testcharts/chart-missing-libs", + args: []string{"testdata/testcharts/chart-missing-libs"}, + hasfile: "chart-missing-libs-0.1.0.tgz", + err: true, + }, { name: "package --values does-not-exist", args: []string{"testdata/testcharts/alpine"}, diff --git a/cmd/helm/testdata/testcharts/chart-missing-libs/.helmignore b/cmd/helm/testdata/testcharts/chart-missing-libs/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-libs/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/cmd/helm/testdata/testcharts/chart-missing-libs/Chart.yaml b/cmd/helm/testdata/testcharts/chart-missing-libs/Chart.yaml new file mode 100644 index 000000000..c22793502 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-libs/Chart.yaml @@ -0,0 +1,10 @@ +description: A Helm chart for Kubernetes +name: chart-missing-libs +version: 0.1.0 +libraries: + - name: reqsubchart + version: 0.1.0 + repository: "https://example.com/charts" + - name: reqsubchart2 + version: 0.2.0 + repository: "https://example.com/charts" diff --git a/cmd/helm/testdata/testcharts/chart-missing-libs/charts/reqsubchart/.helmignore b/cmd/helm/testdata/testcharts/chart-missing-libs/charts/reqsubchart/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-libs/charts/reqsubchart/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/cmd/helm/testdata/testcharts/chart-missing-libs/charts/reqsubchart/Chart.yaml b/cmd/helm/testdata/testcharts/chart-missing-libs/charts/reqsubchart/Chart.yaml new file mode 100644 index 000000000..c3813bc8c --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-libs/charts/reqsubchart/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: reqsubchart +version: 0.1.0 diff --git a/cmd/helm/testdata/testcharts/chart-missing-libs/charts/reqsubchart/values.yaml b/cmd/helm/testdata/testcharts/chart-missing-libs/charts/reqsubchart/values.yaml new file mode 100644 index 000000000..0f0b63f2a --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-libs/charts/reqsubchart/values.yaml @@ -0,0 +1,4 @@ +# Default values for reqsubchart. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/cmd/helm/testdata/testcharts/chart-missing-libs/values.yaml b/cmd/helm/testdata/testcharts/chart-missing-libs/values.yaml new file mode 100644 index 000000000..d57f76b07 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-missing-libs/values.yaml @@ -0,0 +1,4 @@ +# Default values for reqtest. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/cmd/helm/testdata/testcharts/lib-charts/common-0.0.5.tgz b/cmd/helm/testdata/testcharts/lib-charts/common-0.0.5.tgz new file mode 100644 index 000000000..ca0a64ae3 Binary files /dev/null and b/cmd/helm/testdata/testcharts/lib-charts/common-0.0.5.tgz differ diff --git a/cmd/helm/testdata/testcharts/lib-charts/compressedchart-0.1.0.tgz b/cmd/helm/testdata/testcharts/lib-charts/compressedchart-0.1.0.tgz new file mode 100644 index 000000000..575b27128 Binary files /dev/null and b/cmd/helm/testdata/testcharts/lib-charts/compressedchart-0.1.0.tgz differ diff --git a/cmd/helm/testdata/testcharts/lib-charts/compressedchart-0.3.0.tgz b/cmd/helm/testdata/testcharts/lib-charts/compressedchart-0.3.0.tgz new file mode 100644 index 000000000..89776bfa8 Binary files /dev/null and b/cmd/helm/testdata/testcharts/lib-charts/compressedchart-0.3.0.tgz differ diff --git a/pkg/resolver/resolver_test.go b/pkg/resolver/resolver_test.go index 8a4c0239a..d4e2440e8 100644 --- a/pkg/resolver/resolver_test.go +++ b/pkg/resolver/resolver_test.go @@ -21,7 +21,65 @@ import ( "k8s.io/helm/pkg/chart" ) -func TestResolve(t *testing.T) { +func TestDependencyResolve(t *testing.T) { + resolve(t, false) +} + +func TestLibraryResolve(t *testing.T) { + resolve(t, true) +} + +func TestHashReq(t *testing.T) { + expect := "sha256:d661820b01ed7bcf26eed8f01cf16380e0a76326ba33058d3150f919d9b15bc0" + req := []*chart.Dependency{ + {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"}, + } + h, err := HashReq(req) + if err != nil { + t.Fatal(err) + } + if expect != h { + t.Errorf("Expected %q, got %q", expect, h) + } + + req = []*chart.Dependency{} + h, err = HashReq(req) + if err != nil { + t.Fatal(err) + } + if expect == h { + t.Errorf("Expected %q != %q", expect, h) + } +} + +func resolve(t *testing.T, isLib bool) { + var lock1 *chart.Lock + var lock2 *chart.Lock + if isLib { + lock1 = &chart.Lock{ + Libraries: []*chart.Dependency{ + {Name: "alpine", Repository: "http://example.com", Version: "0.2.0"}, + }, + } + lock2 = &chart.Lock{ + Libraries: []*chart.Dependency{ + {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, + }, + } + + } else { + lock1 = &chart.Lock{ + Dependencies: []*chart.Dependency{ + {Name: "alpine", Repository: "http://example.com", Version: "0.2.0"}, + }, + } + lock2 = &chart.Lock{ + Dependencies: []*chart.Dependency{ + {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, + }, + } + } + tests := []struct { name string req []*chart.Dependency @@ -61,22 +119,14 @@ func TestResolve(t *testing.T) { req: []*chart.Dependency{ {Name: "alpine", Repository: "http://example.com", Version: ">=0.1.0"}, }, - expect: &chart.Lock{ - Dependencies: []*chart.Dependency{ - {Name: "alpine", Repository: "http://example.com", Version: "0.2.0"}, - }, - }, + expect: lock1, }, { name: "repo from valid local path", req: []*chart.Dependency{ {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, }, - expect: &chart.Lock{ - Dependencies: []*chart.Dependency{ - {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, - }, - }, + expect: lock2, }, { name: "repo from invalid local path", @@ -95,12 +145,18 @@ func TestResolve(t *testing.T) { t.Fatal(err) } - l, err := r.Resolve(tt.req, repoNames, hash, false) - if err != nil { + var l *chart.Lock + var errRes error + if isLib { + l, errRes = r.Resolve(tt.req, repoNames, hash, true) + } else { + l, errRes = r.Resolve(tt.req, repoNames, hash, false) + } + if errRes != nil { if tt.err { continue } - t.Fatal(err) + t.Fatal(errRes) } if tt.err { @@ -114,11 +170,20 @@ func TestResolve(t *testing.T) { } // Check fields. - if len(l.Dependencies) != len(tt.req) { + var reqs []*chart.Dependency + var ttReqs []*chart.Dependency + if isLib { + reqs = l.Libraries + ttReqs = tt.expect.Libraries + } else { + reqs = l.Dependencies + ttReqs = tt.expect.Dependencies + } + if len(reqs) != len(tt.req) { t.Errorf("%s: wrong number of dependencies in lock", tt.name) } - d0 := l.Dependencies[0] - e0 := tt.expect.Dependencies[0] + d0 := reqs[0] + e0 := ttReqs[0] if d0.Name != e0.Name { t.Errorf("%s: expected name %s, got %s", tt.name, e0.Name, d0.Name) } @@ -130,26 +195,3 @@ func TestResolve(t *testing.T) { } } } - -func TestHashReq(t *testing.T) { - expect := "sha256:d661820b01ed7bcf26eed8f01cf16380e0a76326ba33058d3150f919d9b15bc0" - req := []*chart.Dependency{ - {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"}, - } - h, err := HashReq(req) - if err != nil { - t.Fatal(err) - } - if expect != h { - t.Errorf("Expected %q, got %q", expect, h) - } - - req = []*chart.Dependency{} - h, err = HashReq(req) - if err != nil { - t.Fatal(err) - } - if expect == h { - t.Errorf("Expected %q != %q", expect, h) - } -}