github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/cmd/helm/dependency_update_test.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package main
    17  
    18  import (
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/stefanmcshane/helm/internal/test/ensure"
    26  	"github.com/stefanmcshane/helm/pkg/chart"
    27  	"github.com/stefanmcshane/helm/pkg/chartutil"
    28  	"github.com/stefanmcshane/helm/pkg/helmpath"
    29  	"github.com/stefanmcshane/helm/pkg/provenance"
    30  	"github.com/stefanmcshane/helm/pkg/repo"
    31  	"github.com/stefanmcshane/helm/pkg/repo/repotest"
    32  )
    33  
    34  func TestDependencyUpdateCmd(t *testing.T) {
    35  	srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz")
    36  	if err != nil {
    37  		t.Fatal(err)
    38  	}
    39  	defer srv.Stop()
    40  	t.Logf("Listening on directory %s", srv.Root())
    41  
    42  	ociSrv, err := repotest.NewOCIServer(t, srv.Root())
    43  	if err != nil {
    44  		t.Fatal(err)
    45  	}
    46  
    47  	ociChartName := "oci-depending-chart"
    48  	c := createTestingMetadataForOCI(ociChartName, ociSrv.RegistryURL)
    49  	if _, err := chartutil.Save(c, ociSrv.Dir); err != nil {
    50  		t.Fatal(err)
    51  	}
    52  	ociSrv.Run(t, repotest.WithDependingChart(c))
    53  
    54  	if err := srv.LinkIndices(); err != nil {
    55  		t.Fatal(err)
    56  	}
    57  
    58  	dir := func(p ...string) string {
    59  		return filepath.Join(append([]string{srv.Root()}, p...)...)
    60  	}
    61  
    62  	chartname := "depup"
    63  	ch := createTestingMetadata(chartname, srv.URL())
    64  	md := ch.Metadata
    65  	if err := chartutil.SaveDir(ch, dir()); err != nil {
    66  		t.Fatal(err)
    67  	}
    68  
    69  	_, out, err := executeActionCommand(
    70  		fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()),
    71  	)
    72  	if err != nil {
    73  		t.Logf("Output: %s", out)
    74  		t.Fatal(err)
    75  	}
    76  
    77  	// This is written directly to stdout, so we have to capture as is.
    78  	if !strings.Contains(out, `update from the "test" chart repository`) {
    79  		t.Errorf("Repo did not get updated\n%s", out)
    80  	}
    81  
    82  	// Make sure the actual file got downloaded.
    83  	expect := dir(chartname, "charts/reqtest-0.1.0.tgz")
    84  	if _, err := os.Stat(expect); err != nil {
    85  		t.Fatal(err)
    86  	}
    87  
    88  	hash, err := provenance.DigestFile(expect)
    89  	if err != nil {
    90  		t.Fatal(err)
    91  	}
    92  
    93  	i, err := repo.LoadIndexFile(dir(helmpath.CacheIndexFile("test")))
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  
    98  	reqver := i.Entries["reqtest"][0]
    99  	if h := reqver.Digest; h != hash {
   100  		t.Errorf("Failed hash match: expected %s, got %s", hash, h)
   101  	}
   102  
   103  	// Now change the dependencies and update. This verifies that on update,
   104  	// old dependencies are cleansed and new dependencies are added.
   105  	md.Dependencies = []*chart.Dependency{
   106  		{Name: "reqtest", Version: "0.1.0", Repository: srv.URL()},
   107  		{Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()},
   108  	}
   109  	if err := chartutil.SaveChartfile(dir(chartname, "Chart.yaml"), md); err != nil {
   110  		t.Fatal(err)
   111  	}
   112  
   113  	_, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()))
   114  	if err != nil {
   115  		t.Logf("Output: %s", out)
   116  		t.Fatal(err)
   117  	}
   118  
   119  	// In this second run, we should see compressedchart-0.3.0.tgz, and not
   120  	// the 0.1.0 version.
   121  	expect = dir(chartname, "charts/compressedchart-0.3.0.tgz")
   122  	if _, err := os.Stat(expect); err != nil {
   123  		t.Fatalf("Expected %q: %s", expect, err)
   124  	}
   125  	unexpected := dir(chartname, "charts/compressedchart-0.1.0.tgz")
   126  	if _, err := os.Stat(unexpected); err == nil {
   127  		t.Fatalf("Unexpected %q", unexpected)
   128  	}
   129  
   130  	// test for OCI charts
   131  	if err := chartutil.SaveDir(c, dir()); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json",
   135  		dir(ociChartName),
   136  		dir("repositories.yaml"),
   137  		dir(),
   138  		dir())
   139  	_, out, err = executeActionCommand(cmd)
   140  	if err != nil {
   141  		t.Logf("Output: %s", out)
   142  		t.Fatal(err)
   143  	}
   144  	expect = dir(ociChartName, "charts/oci-dependent-chart-0.1.0.tgz")
   145  	if _, err := os.Stat(expect); err != nil {
   146  		t.Fatal(err)
   147  	}
   148  }
   149  
   150  func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
   151  	defer resetEnv()()
   152  	defer ensure.HelmHome(t)()
   153  
   154  	srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz")
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	defer srv.Stop()
   159  	t.Logf("Listening on directory %s", srv.Root())
   160  
   161  	if err := srv.LinkIndices(); err != nil {
   162  		t.Fatal(err)
   163  	}
   164  
   165  	chartname := "depupdelete"
   166  
   167  	dir := func(p ...string) string {
   168  		return filepath.Join(append([]string{srv.Root()}, p...)...)
   169  	}
   170  	createTestingChart(t, dir(), chartname, srv.URL())
   171  
   172  	_, output, err := executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()))
   173  	if err != nil {
   174  		t.Logf("Output: %s", output)
   175  		t.Fatal(err)
   176  	}
   177  
   178  	// Chart repo is down
   179  	srv.Stop()
   180  
   181  	_, output, err = executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()))
   182  	if err == nil {
   183  		t.Logf("Output: %s", output)
   184  		t.Fatal("Expected error, got nil")
   185  	}
   186  
   187  	// Make sure charts dir still has dependencies
   188  	files, err := os.ReadDir(filepath.Join(dir(chartname), "charts"))
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  	dependencies := []string{"compressedchart-0.1.0.tgz", "reqtest-0.1.0.tgz"}
   193  
   194  	if len(dependencies) != len(files) {
   195  		t.Fatalf("Expected %d chart dependencies, got %d", len(dependencies), len(files))
   196  	}
   197  	for index, file := range files {
   198  		if dependencies[index] != file.Name() {
   199  			t.Fatalf("Chart dependency %s not matching %s", dependencies[index], file.Name())
   200  		}
   201  	}
   202  
   203  	// Make sure tmpcharts is deleted
   204  	if _, err := os.Stat(filepath.Join(dir(chartname), "tmpcharts")); !os.IsNotExist(err) {
   205  		t.Fatalf("tmpcharts dir still exists")
   206  	}
   207  }
   208  
   209  // createTestingMetadata creates a basic chart that depends on reqtest-0.1.0
   210  //
   211  // The baseURL can be used to point to a particular repository server.
   212  func createTestingMetadata(name, baseURL string) *chart.Chart {
   213  	return &chart.Chart{
   214  		Metadata: &chart.Metadata{
   215  			APIVersion: chart.APIVersionV2,
   216  			Name:       name,
   217  			Version:    "1.2.3",
   218  			Dependencies: []*chart.Dependency{
   219  				{Name: "reqtest", Version: "0.1.0", Repository: baseURL},
   220  				{Name: "compressedchart", Version: "0.1.0", Repository: baseURL},
   221  			},
   222  		},
   223  	}
   224  }
   225  
   226  func createTestingMetadataForOCI(name, registryURL string) *chart.Chart {
   227  	return &chart.Chart{
   228  		Metadata: &chart.Metadata{
   229  			APIVersion: chart.APIVersionV2,
   230  			Name:       name,
   231  			Version:    "1.2.3",
   232  			Dependencies: []*chart.Dependency{
   233  				{Name: "oci-dependent-chart", Version: "0.1.0", Repository: fmt.Sprintf("oci://%s/u/ocitestuser", registryURL)},
   234  			},
   235  		},
   236  	}
   237  }
   238  
   239  // createTestingChart creates a basic chart that depends on reqtest-0.1.0
   240  //
   241  // The baseURL can be used to point to a particular repository server.
   242  func createTestingChart(t *testing.T, dest, name, baseURL string) {
   243  	t.Helper()
   244  	cfile := createTestingMetadata(name, baseURL)
   245  	if err := chartutil.SaveDir(cfile, dest); err != nil {
   246  		t.Fatal(err)
   247  	}
   248  }