2016-09-13 15:29:58 -04:00
|
|
|
|
/*
|
2018-08-24 15:03:55 -04:00
|
|
|
|
Copyright The Helm Authors.
|
2016-09-13 15:29:58 -04:00
|
|
|
|
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.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2025-02-24 10:11:54 -05:00
|
|
|
|
package cmd
|
2016-09-13 15:29:58 -04:00
|
|
|
|
|
|
|
|
|
|
import (
|
2024-11-18 12:54:09 -05:00
|
|
|
|
"errors"
|
2018-05-09 11:37:20 -04:00
|
|
|
|
"fmt"
|
2024-11-18 12:54:09 -05:00
|
|
|
|
"io/fs"
|
2016-09-13 15:29:58 -04:00
|
|
|
|
"os"
|
|
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
2024-12-26 16:33:51 -05:00
|
|
|
|
"helm.sh/helm/v4/internal/test/ensure"
|
2025-02-25 15:20:44 -05:00
|
|
|
|
chart "helm.sh/helm/v4/pkg/chart/v2"
|
|
|
|
|
|
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
|
2024-12-26 16:33:51 -05:00
|
|
|
|
"helm.sh/helm/v4/pkg/helmpath"
|
|
|
|
|
|
"helm.sh/helm/v4/pkg/provenance"
|
2025-08-31 09:04:48 -04:00
|
|
|
|
"helm.sh/helm/v4/pkg/repo/v1"
|
|
|
|
|
|
"helm.sh/helm/v4/pkg/repo/v1/repotest"
|
2016-09-13 15:29:58 -04:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func TestDependencyUpdateCmd(t *testing.T) {
|
2024-12-01 02:46:18 -05:00
|
|
|
|
srv := repotest.NewTempServer(
|
|
|
|
|
|
t,
|
|
|
|
|
|
repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
|
|
|
|
|
|
)
|
2019-08-23 02:31:50 -04:00
|
|
|
|
defer srv.Stop()
|
2016-09-17 00:19:23 -04:00
|
|
|
|
t.Logf("Listening on directory %s", srv.Root())
|
2016-09-13 15:29:58 -04:00
|
|
|
|
|
2020-10-01 17:37:44 -04:00
|
|
|
|
ociSrv, err := repotest.NewOCIServer(t, srv.Root())
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
2025-08-21 14:25:55 -04:00
|
|
|
|
contentCache := t.TempDir()
|
2020-10-01 17:37:44 -04:00
|
|
|
|
|
|
|
|
|
|
ociChartName := "oci-depending-chart"
|
|
|
|
|
|
c := createTestingMetadataForOCI(ociChartName, ociSrv.RegistryURL)
|
2021-06-28 10:50:09 -04:00
|
|
|
|
if _, err := chartutil.Save(c, ociSrv.Dir); err != nil {
|
2020-10-01 17:37:44 -04:00
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
ociSrv.Run(t, repotest.WithDependingChart(c))
|
|
|
|
|
|
|
2019-08-23 02:31:50 -04:00
|
|
|
|
if err := srv.LinkIndices(); err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-26 13:21:52 -04:00
|
|
|
|
dir := func(p ...string) string {
|
|
|
|
|
|
return filepath.Join(append([]string{srv.Root()}, p...)...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-13 15:29:58 -04:00
|
|
|
|
chartname := "depup"
|
2019-04-15 14:17:19 -04:00
|
|
|
|
ch := createTestingMetadata(chartname, srv.URL())
|
|
|
|
|
|
md := ch.Metadata
|
2019-08-26 13:21:52 -04:00
|
|
|
|
if err := chartutil.SaveDir(ch, dir()); err != nil {
|
2016-09-13 15:29:58 -04:00
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-23 02:31:50 -04:00
|
|
|
|
_, out, err := executeActionCommand(
|
2025-08-21 14:25:55 -04:00
|
|
|
|
fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --content-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir(), contentCache),
|
2019-08-23 02:31:50 -04:00
|
|
|
|
)
|
2018-05-09 11:37:20 -04:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Logf("Output: %s", out)
|
2016-09-13 15:29:58 -04:00
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This is written directly to stdout, so we have to capture as is.
|
2018-05-09 11:37:20 -04:00
|
|
|
|
if !strings.Contains(out, `update from the "test" chart repository`) {
|
|
|
|
|
|
t.Errorf("Repo did not get updated\n%s", out)
|
2016-09-13 15:29:58 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Make sure the actual file got downloaded.
|
2019-08-26 13:21:52 -04:00
|
|
|
|
expect := dir(chartname, "charts/reqtest-0.1.0.tgz")
|
2016-09-13 15:29:58 -04:00
|
|
|
|
if _, err := os.Stat(expect); err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
hash, err := provenance.DigestFile(expect)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-26 13:21:52 -04:00
|
|
|
|
i, err := repo.LoadIndexFile(dir(helmpath.CacheIndexFile("test")))
|
2016-09-13 15:29:58 -04:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-09-28 20:51:12 -04:00
|
|
|
|
reqver := i.Entries["reqtest"][0]
|
|
|
|
|
|
if h := reqver.Digest; h != hash {
|
2016-09-13 15:29:58 -04:00
|
|
|
|
t.Errorf("Failed hash match: expected %s, got %s", hash, h)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-02-03 17:52:16 -05:00
|
|
|
|
// Now change the dependencies and update. This verifies that on update,
|
|
|
|
|
|
// old dependencies are cleansed and new dependencies are added.
|
2018-11-28 13:20:33 -05:00
|
|
|
|
md.Dependencies = []*chart.Dependency{
|
2018-08-29 17:05:37 -04:00
|
|
|
|
{Name: "reqtest", Version: "0.1.0", Repository: srv.URL()},
|
|
|
|
|
|
{Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()},
|
2017-02-03 17:52:16 -05:00
|
|
|
|
}
|
2019-08-26 13:21:52 -04:00
|
|
|
|
if err := chartutil.SaveChartfile(dir(chartname, "Chart.yaml"), md); err != nil {
|
2017-02-03 17:52:16 -05:00
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
2018-05-09 11:37:20 -04:00
|
|
|
|
|
2025-08-21 14:25:55 -04:00
|
|
|
|
_, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --content-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir(), contentCache))
|
2018-05-09 11:37:20 -04:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Logf("Output: %s", out)
|
2017-02-03 17:52:16 -05:00
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// In this second run, we should see compressedchart-0.3.0.tgz, and not
|
|
|
|
|
|
// the 0.1.0 version.
|
2019-08-26 13:21:52 -04:00
|
|
|
|
expect = dir(chartname, "charts/compressedchart-0.3.0.tgz")
|
2017-02-03 17:52:16 -05:00
|
|
|
|
if _, err := os.Stat(expect); err != nil {
|
|
|
|
|
|
t.Fatalf("Expected %q: %s", expect, err)
|
|
|
|
|
|
}
|
2019-12-18 07:04:08 -05:00
|
|
|
|
unexpected := dir(chartname, "charts/compressedchart-0.1.0.tgz")
|
|
|
|
|
|
if _, err := os.Stat(unexpected); err == nil {
|
|
|
|
|
|
t.Fatalf("Unexpected %q", unexpected)
|
2017-02-03 17:52:16 -05:00
|
|
|
|
}
|
2020-10-01 17:37:44 -04:00
|
|
|
|
|
|
|
|
|
|
// test for OCI charts
|
2021-06-28 10:50:09 -04:00
|
|
|
|
if err := chartutil.SaveDir(c, dir()); err != nil {
|
2021-06-08 01:45:40 -04:00
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
2025-08-21 14:25:55 -04:00
|
|
|
|
cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json --content-cache %s --plain-http",
|
2020-10-01 17:37:44 -04:00
|
|
|
|
dir(ociChartName),
|
|
|
|
|
|
dir("repositories.yaml"),
|
|
|
|
|
|
dir(),
|
2025-08-21 14:25:55 -04:00
|
|
|
|
dir(),
|
|
|
|
|
|
contentCache)
|
2020-10-01 17:37:44 -04:00
|
|
|
|
_, out, err = executeActionCommand(cmd)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Logf("Output: %s", out)
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
2020-12-18 17:29:15 -05:00
|
|
|
|
expect = dir(ociChartName, "charts/oci-dependent-chart-0.1.0.tgz")
|
2020-10-01 17:37:44 -04:00
|
|
|
|
if _, err := os.Stat(expect); err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
2016-09-13 15:29:58 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-03-15 21:11:57 -04:00
|
|
|
|
func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
|
2018-05-14 12:23:21 -04:00
|
|
|
|
defer resetEnv()()
|
2023-05-06 06:02:32 -04:00
|
|
|
|
ensure.HelmHome(t)
|
2017-08-10 11:11:10 -04:00
|
|
|
|
|
2024-12-01 02:46:18 -05:00
|
|
|
|
srv := repotest.NewTempServer(
|
|
|
|
|
|
t,
|
|
|
|
|
|
repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
|
|
|
|
|
|
)
|
2019-08-26 13:21:52 -04:00
|
|
|
|
defer srv.Stop()
|
2017-08-10 11:11:10 -04:00
|
|
|
|
t.Logf("Listening on directory %s", srv.Root())
|
|
|
|
|
|
|
2019-08-26 13:21:52 -04:00
|
|
|
|
if err := srv.LinkIndices(); err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-10 11:11:10 -04:00
|
|
|
|
chartname := "depupdelete"
|
|
|
|
|
|
|
2019-08-26 13:21:52 -04:00
|
|
|
|
dir := func(p ...string) string {
|
|
|
|
|
|
return filepath.Join(append([]string{srv.Root()}, p...)...)
|
|
|
|
|
|
}
|
|
|
|
|
|
createTestingChart(t, dir(), chartname, srv.URL())
|
|
|
|
|
|
|
2025-01-02 06:59:55 -05:00
|
|
|
|
_, output, err := executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()))
|
2019-02-08 19:02:57 -05:00
|
|
|
|
if err != nil {
|
2017-08-10 11:11:10 -04:00
|
|
|
|
t.Logf("Output: %s", output)
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Chart repo is down
|
|
|
|
|
|
srv.Stop()
|
2025-08-21 14:25:55 -04:00
|
|
|
|
contentCache := t.TempDir()
|
2017-08-10 11:11:10 -04:00
|
|
|
|
|
2025-08-21 14:25:55 -04:00
|
|
|
|
_, output, err = executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s --content-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir(), contentCache))
|
2019-02-08 19:02:57 -05:00
|
|
|
|
if err == nil {
|
2017-08-10 11:11:10 -04:00
|
|
|
|
t.Logf("Output: %s", output)
|
|
|
|
|
|
t.Fatal("Expected error, got nil")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Make sure charts dir still has dependencies
|
2021-07-08 09:14:06 -04:00
|
|
|
|
files, err := os.ReadDir(filepath.Join(dir(chartname), "charts"))
|
2017-08-10 11:11:10 -04:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
dependencies := []string{"compressedchart-0.1.0.tgz", "reqtest-0.1.0.tgz"}
|
|
|
|
|
|
|
|
|
|
|
|
if len(dependencies) != len(files) {
|
|
|
|
|
|
t.Fatalf("Expected %d chart dependencies, got %d", len(dependencies), len(files))
|
|
|
|
|
|
}
|
|
|
|
|
|
for index, file := range files {
|
|
|
|
|
|
if dependencies[index] != file.Name() {
|
|
|
|
|
|
t.Fatalf("Chart dependency %s not matching %s", dependencies[index], file.Name())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-18 00:11:45 -04:00
|
|
|
|
// Make sure tmpcharts-x is deleted
|
|
|
|
|
|
tmpPath := filepath.Join(dir(chartname), fmt.Sprintf("tmpcharts-%d", os.Getpid()))
|
2024-11-18 12:54:09 -05:00
|
|
|
|
if _, err := os.Stat(tmpPath); !errors.Is(err, fs.ErrNotExist) {
|
2017-08-10 11:11:10 -04:00
|
|
|
|
t.Fatalf("tmpcharts dir still exists")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-09-22 12:13:35 -04:00
|
|
|
|
func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) {
|
|
|
|
|
|
srv := setupMockRepoServer(t)
|
|
|
|
|
|
srvForUnmanagedRepo := setupMockRepoServer(t)
|
|
|
|
|
|
defer srv.Stop()
|
|
|
|
|
|
defer srvForUnmanagedRepo.Stop()
|
|
|
|
|
|
|
|
|
|
|
|
dir := func(p ...string) string {
|
|
|
|
|
|
return filepath.Join(append([]string{srv.Root()}, p...)...)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
chartname := "depup"
|
|
|
|
|
|
ch := createTestingMetadata(chartname, srv.URL())
|
|
|
|
|
|
chartDependency := &chart.Dependency{
|
|
|
|
|
|
Name: "signtest",
|
|
|
|
|
|
Version: "0.1.0",
|
|
|
|
|
|
Repository: srvForUnmanagedRepo.URL(),
|
|
|
|
|
|
}
|
|
|
|
|
|
ch.Metadata.Dependencies = append(ch.Metadata.Dependencies, chartDependency)
|
|
|
|
|
|
|
|
|
|
|
|
if err := chartutil.SaveDir(ch, dir()); err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-21 14:25:55 -04:00
|
|
|
|
contentCache := t.TempDir()
|
|
|
|
|
|
|
2022-09-22 12:13:35 -04:00
|
|
|
|
_, out, err := executeActionCommand(
|
2025-08-21 14:25:55 -04:00
|
|
|
|
fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --content-cache %s", dir(chartname),
|
|
|
|
|
|
dir("repositories.yaml"), dir(), contentCache),
|
2022-09-22 12:13:35 -04:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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, `Getting updates for unmanaged Helm repositories...`) {
|
|
|
|
|
|
t.Errorf("No ‘unmanaged’ Helm repo used in test chartdependency or it doesn’t cause the creation "+
|
|
|
|
|
|
"of an ‘ad hoc’ repo index cache file\n%s", out)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func setupMockRepoServer(t *testing.T) *repotest.Server {
|
2025-05-16 03:55:50 -04:00
|
|
|
|
t.Helper()
|
2024-12-01 02:46:18 -05:00
|
|
|
|
srv := repotest.NewTempServer(
|
|
|
|
|
|
t,
|
|
|
|
|
|
repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
|
|
|
|
|
|
)
|
2022-09-22 12:13:35 -04:00
|
|
|
|
|
|
|
|
|
|
t.Logf("Listening on directory %s", srv.Root())
|
|
|
|
|
|
|
|
|
|
|
|
if err := srv.LinkIndices(); err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return srv
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-08-29 17:05:37 -04:00
|
|
|
|
// createTestingMetadata creates a basic chart that depends on reqtest-0.1.0
|
2016-09-13 15:29:58 -04:00
|
|
|
|
//
|
|
|
|
|
|
// The baseURL can be used to point to a particular repository server.
|
2019-04-15 14:17:19 -04:00
|
|
|
|
func createTestingMetadata(name, baseURL string) *chart.Chart {
|
|
|
|
|
|
return &chart.Chart{
|
|
|
|
|
|
Metadata: &chart.Metadata{
|
2019-12-11 12:07:05 -05:00
|
|
|
|
APIVersion: chart.APIVersionV2,
|
2019-04-15 14:17:19 -04:00
|
|
|
|
Name: name,
|
|
|
|
|
|
Version: "1.2.3",
|
|
|
|
|
|
Dependencies: []*chart.Dependency{
|
|
|
|
|
|
{Name: "reqtest", Version: "0.1.0", Repository: baseURL},
|
|
|
|
|
|
{Name: "compressedchart", Version: "0.1.0", Repository: baseURL},
|
|
|
|
|
|
},
|
2016-09-13 15:29:58 -04:00
|
|
|
|
},
|
|
|
|
|
|
}
|
2017-02-03 17:52:16 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-10-01 17:37:44 -04:00
|
|
|
|
func createTestingMetadataForOCI(name, registryURL string) *chart.Chart {
|
|
|
|
|
|
return &chart.Chart{
|
|
|
|
|
|
Metadata: &chart.Metadata{
|
|
|
|
|
|
APIVersion: chart.APIVersionV2,
|
|
|
|
|
|
Name: name,
|
|
|
|
|
|
Version: "1.2.3",
|
|
|
|
|
|
Dependencies: []*chart.Dependency{
|
|
|
|
|
|
{Name: "oci-dependent-chart", Version: "0.1.0", Repository: fmt.Sprintf("oci://%s/u/ocitestuser", registryURL)},
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-08-29 17:05:37 -04:00
|
|
|
|
// createTestingChart creates a basic chart that depends on reqtest-0.1.0
|
|
|
|
|
|
//
|
|
|
|
|
|
// The baseURL can be used to point to a particular repository server.
|
2019-08-23 02:31:50 -04:00
|
|
|
|
func createTestingChart(t *testing.T, dest, name, baseURL string) {
|
|
|
|
|
|
t.Helper()
|
2018-08-29 17:05:37 -04:00
|
|
|
|
cfile := createTestingMetadata(name, baseURL)
|
2019-08-23 02:31:50 -04:00
|
|
|
|
if err := chartutil.SaveDir(cfile, dest); err != nil {
|
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
|
}
|
2016-09-13 15:29:58 -04:00
|
|
|
|
}
|