...

Source file src/helm.sh/helm/v3/pkg/repo/chartrepo_test.go

Documentation: helm.sh/helm/v3/pkg/repo

     1  /*
     2  Copyright The Helm Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package repo
    18  
    19  import (
    20  	"bytes"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"os"
    24  	"path/filepath"
    25  	"reflect"
    26  	"runtime"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"sigs.k8s.io/yaml"
    32  
    33  	"helm.sh/helm/v3/pkg/chart"
    34  	"helm.sh/helm/v3/pkg/cli"
    35  	"helm.sh/helm/v3/pkg/getter"
    36  )
    37  
    38  const (
    39  	testRepository = "testdata/repository"
    40  	testURL        = "http://example-charts.com"
    41  )
    42  
    43  func TestLoadChartRepository(t *testing.T) {
    44  	r, err := NewChartRepository(&Entry{
    45  		Name: testRepository,
    46  		URL:  testURL,
    47  	}, getter.All(&cli.EnvSettings{}))
    48  	if err != nil {
    49  		t.Errorf("Problem creating chart repository from %s: %v", testRepository, err)
    50  	}
    51  
    52  	if err := r.Load(); err != nil {
    53  		t.Errorf("Problem loading chart repository from %s: %v", testRepository, err)
    54  	}
    55  
    56  	paths := []string{
    57  		filepath.Join(testRepository, "frobnitz-1.2.3.tgz"),
    58  		filepath.Join(testRepository, "sprocket-1.1.0.tgz"),
    59  		filepath.Join(testRepository, "sprocket-1.2.0.tgz"),
    60  		filepath.Join(testRepository, "universe/zarthal-1.0.0.tgz"),
    61  	}
    62  
    63  	if r.Config.Name != testRepository {
    64  		t.Errorf("Expected %s as Name but got %s", testRepository, r.Config.Name)
    65  	}
    66  
    67  	if !reflect.DeepEqual(r.ChartPaths, paths) {
    68  		t.Errorf("Expected %#v but got %#v\n", paths, r.ChartPaths)
    69  	}
    70  
    71  	if r.Config.URL != testURL {
    72  		t.Errorf("Expected url for chart repository to be %s but got %s", testURL, r.Config.URL)
    73  	}
    74  }
    75  
    76  func TestIndex(t *testing.T) {
    77  	r, err := NewChartRepository(&Entry{
    78  		Name: testRepository,
    79  		URL:  testURL,
    80  	}, getter.All(&cli.EnvSettings{}))
    81  	if err != nil {
    82  		t.Errorf("Problem creating chart repository from %s: %v", testRepository, err)
    83  	}
    84  
    85  	if err := r.Load(); err != nil {
    86  		t.Errorf("Problem loading chart repository from %s: %v", testRepository, err)
    87  	}
    88  
    89  	err = r.Index()
    90  	if err != nil {
    91  		t.Errorf("Error performing index: %v\n", err)
    92  	}
    93  
    94  	tempIndexPath := filepath.Join(testRepository, indexPath)
    95  	actual, err := LoadIndexFile(tempIndexPath)
    96  	defer os.Remove(tempIndexPath) // clean up
    97  	if err != nil {
    98  		t.Errorf("Error loading index file %v", err)
    99  	}
   100  	verifyIndex(t, actual)
   101  
   102  	// Re-index and test again.
   103  	err = r.Index()
   104  	if err != nil {
   105  		t.Errorf("Error performing re-index: %s\n", err)
   106  	}
   107  	second, err := LoadIndexFile(tempIndexPath)
   108  	if err != nil {
   109  		t.Errorf("Error re-loading index file %v", err)
   110  	}
   111  	verifyIndex(t, second)
   112  }
   113  
   114  type CustomGetter struct {
   115  	repoUrls []string
   116  }
   117  
   118  func (g *CustomGetter) Get(href string, _ ...getter.Option) (*bytes.Buffer, error) {
   119  	index := &IndexFile{
   120  		APIVersion: "v1",
   121  		Generated:  time.Now(),
   122  	}
   123  	indexBytes, err := yaml.Marshal(index)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	g.repoUrls = append(g.repoUrls, href)
   128  	return bytes.NewBuffer(indexBytes), nil
   129  }
   130  
   131  func TestIndexCustomSchemeDownload(t *testing.T) {
   132  	repoName := "gcs-repo"
   133  	repoURL := "gs://some-gcs-bucket"
   134  	myCustomGetter := &CustomGetter{}
   135  	customGetterConstructor := func(_ ...getter.Option) (getter.Getter, error) {
   136  		return myCustomGetter, nil
   137  	}
   138  	providers := getter.Providers{{
   139  		Schemes: []string{"gs"},
   140  		New:     customGetterConstructor,
   141  	}}
   142  	repo, err := NewChartRepository(&Entry{
   143  		Name: repoName,
   144  		URL:  repoURL,
   145  	}, providers)
   146  	if err != nil {
   147  		t.Fatalf("Problem loading chart repository from %s: %v", repoURL, err)
   148  	}
   149  	repo.CachePath = t.TempDir()
   150  
   151  	tempIndexFile, err := os.CreateTemp("", "test-repo")
   152  	if err != nil {
   153  		t.Fatalf("Failed to create temp index file: %v", err)
   154  	}
   155  	defer os.Remove(tempIndexFile.Name())
   156  
   157  	idx, err := repo.DownloadIndexFile()
   158  	if err != nil {
   159  		t.Fatalf("Failed to download index file to %s: %v", idx, err)
   160  	}
   161  
   162  	if len(myCustomGetter.repoUrls) != 1 {
   163  		t.Fatalf("Custom Getter.Get should be called once")
   164  	}
   165  
   166  	expectedRepoIndexURL := repoURL + "/index.yaml"
   167  	if myCustomGetter.repoUrls[0] != expectedRepoIndexURL {
   168  		t.Fatalf("Custom Getter.Get should be called with %s", expectedRepoIndexURL)
   169  	}
   170  }
   171  
   172  func verifyIndex(t *testing.T, actual *IndexFile) {
   173  	var empty time.Time
   174  	if actual.Generated.Equal(empty) {
   175  		t.Errorf("Generated should be greater than 0: %s", actual.Generated)
   176  	}
   177  
   178  	if actual.APIVersion != APIVersionV1 {
   179  		t.Error("Expected v1 API")
   180  	}
   181  
   182  	entries := actual.Entries
   183  	if numEntries := len(entries); numEntries != 3 {
   184  		t.Errorf("Expected 3 charts to be listed in index file but got %v", numEntries)
   185  	}
   186  
   187  	expects := map[string]ChartVersions{
   188  		"frobnitz": {
   189  			{
   190  				Metadata: &chart.Metadata{
   191  					Name:    "frobnitz",
   192  					Version: "1.2.3",
   193  				},
   194  			},
   195  		},
   196  		"sprocket": {
   197  			{
   198  				Metadata: &chart.Metadata{
   199  					Name:    "sprocket",
   200  					Version: "1.2.0",
   201  				},
   202  			},
   203  			{
   204  				Metadata: &chart.Metadata{
   205  					Name:    "sprocket",
   206  					Version: "1.1.0",
   207  				},
   208  			},
   209  		},
   210  		"zarthal": {
   211  			{
   212  				Metadata: &chart.Metadata{
   213  					Name:    "zarthal",
   214  					Version: "1.0.0",
   215  				},
   216  			},
   217  		},
   218  	}
   219  
   220  	for name, versions := range expects {
   221  		got, ok := entries[name]
   222  		if !ok {
   223  			t.Errorf("Could not find %q entry", name)
   224  			continue
   225  		}
   226  		if len(versions) != len(got) {
   227  			t.Errorf("Expected %d versions, got %d", len(versions), len(got))
   228  			continue
   229  		}
   230  		for i, e := range versions {
   231  			g := got[i]
   232  			if e.Name != g.Name {
   233  				t.Errorf("Expected %q, got %q", e.Name, g.Name)
   234  			}
   235  			if e.Version != g.Version {
   236  				t.Errorf("Expected %q, got %q", e.Version, g.Version)
   237  			}
   238  			if len(g.Keywords) != 3 {
   239  				t.Error("Expected 3 keywords.")
   240  			}
   241  			if len(g.Maintainers) != 2 {
   242  				t.Error("Expected 2 maintainers.")
   243  			}
   244  			if g.Created.Equal(empty) {
   245  				t.Error("Expected created to be non-empty")
   246  			}
   247  			if g.Description == "" {
   248  				t.Error("Expected description to be non-empty")
   249  			}
   250  			if g.Home == "" {
   251  				t.Error("Expected home to be non-empty")
   252  			}
   253  			if g.Digest == "" {
   254  				t.Error("Expected digest to be non-empty")
   255  			}
   256  			if len(g.URLs) != 1 {
   257  				t.Error("Expected exactly 1 URL")
   258  			}
   259  		}
   260  	}
   261  }
   262  
   263  // startLocalServerForTests Start the local helm server
   264  func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) {
   265  	if handler == nil {
   266  		fileBytes, err := os.ReadFile("testdata/local-index.yaml")
   267  		if err != nil {
   268  			return nil, err
   269  		}
   270  		handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
   271  			w.Write(fileBytes)
   272  		})
   273  	}
   274  
   275  	return httptest.NewServer(handler), nil
   276  }
   277  
   278  // startLocalTLSServerForTests Start the local helm server with TLS
   279  func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) {
   280  	if handler == nil {
   281  		fileBytes, err := os.ReadFile("testdata/local-index.yaml")
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  		handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
   286  			w.Write(fileBytes)
   287  		})
   288  	}
   289  
   290  	return httptest.NewTLSServer(handler), nil
   291  }
   292  
   293  func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) {
   294  	srv, err := startLocalTLSServerForTests(nil)
   295  	if err != nil {
   296  		t.Fatal(err)
   297  	}
   298  	defer srv.Close()
   299  
   300  	chartURL, err := FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, false, getter.All(&cli.EnvSettings{}))
   301  	if err != nil {
   302  		t.Fatalf("%v", err)
   303  	}
   304  	if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" {
   305  		t.Errorf("%s is not the valid URL", chartURL)
   306  	}
   307  
   308  	// If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority".
   309  	_, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{}))
   310  	// Go communicates with the platform and different platforms return different messages. Go itself tests darwin
   311  	// differently for its message. On newer versions of Darwin the message includes the "Acme Co" portion while older
   312  	// versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test
   313  	// for both messages.
   314  	if runtime.GOOS == "darwin" {
   315  		if !strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") && !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
   316  			t.Errorf("Expected TLS error for function  FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err)
   317  		}
   318  	} else if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
   319  		t.Errorf("Expected TLS error for function  FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err)
   320  	}
   321  }
   322  
   323  func TestFindChartInRepoURL(t *testing.T) {
   324  	srv, err := startLocalServerForTests(nil)
   325  	if err != nil {
   326  		t.Fatal(err)
   327  	}
   328  	defer srv.Close()
   329  
   330  	chartURL, err := FindChartInRepoURL(srv.URL, "nginx", "", "", "", "", getter.All(&cli.EnvSettings{}))
   331  	if err != nil {
   332  		t.Fatalf("%v", err)
   333  	}
   334  	if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" {
   335  		t.Errorf("%s is not the valid URL", chartURL)
   336  	}
   337  
   338  	chartURL, err = FindChartInRepoURL(srv.URL, "nginx", "0.1.0", "", "", "", getter.All(&cli.EnvSettings{}))
   339  	if err != nil {
   340  		t.Errorf("%s", err)
   341  	}
   342  	if chartURL != "https://charts.helm.sh/stable/nginx-0.1.0.tgz" {
   343  		t.Errorf("%s is not the valid URL", chartURL)
   344  	}
   345  }
   346  
   347  func TestErrorFindChartInRepoURL(t *testing.T) {
   348  
   349  	g := getter.All(&cli.EnvSettings{
   350  		RepositoryCache: t.TempDir(),
   351  	})
   352  
   353  	if _, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", g); err == nil {
   354  		t.Errorf("Expected error for bad chart URL, but did not get any errors")
   355  	} else if !strings.Contains(err.Error(), `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`) {
   356  		t.Errorf("Expected error for bad chart URL, but got a different error (%v)", err)
   357  	}
   358  
   359  	srv, err := startLocalServerForTests(nil)
   360  	if err != nil {
   361  		t.Fatal(err)
   362  	}
   363  	defer srv.Close()
   364  
   365  	if _, err = FindChartInRepoURL(srv.URL, "nginx1", "", "", "", "", g); err == nil {
   366  		t.Errorf("Expected error for chart not found, but did not get any errors")
   367  	} else if err.Error() != `chart "nginx1" not found in `+srv.URL+` repository` {
   368  		t.Errorf("Expected error for chart not found, but got a different error (%v)", err)
   369  	}
   370  
   371  	if _, err = FindChartInRepoURL(srv.URL, "nginx1", "0.1.0", "", "", "", g); err == nil {
   372  		t.Errorf("Expected error for chart not found, but did not get any errors")
   373  	} else if err.Error() != `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository` {
   374  		t.Errorf("Expected error for chart not found, but got a different error (%v)", err)
   375  	}
   376  
   377  	if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", "", "", "", "", g); err == nil {
   378  		t.Errorf("Expected error for no chart URLs available, but did not get any errors")
   379  	} else if err.Error() != `chart "chartWithNoURL" has no downloadable URLs` {
   380  		t.Errorf("Expected error for chart not found, but got a different error (%v)", err)
   381  	}
   382  }
   383  
   384  func TestResolveReferenceURL(t *testing.T) {
   385  	for _, tt := range []struct {
   386  		baseURL, refURL, chartURL string
   387  	}{
   388  		{"http://localhost:8123/charts/", "nginx-0.2.0.tgz", "http://localhost:8123/charts/nginx-0.2.0.tgz"},
   389  		{"http://localhost:8123/charts-with-no-trailing-slash", "nginx-0.2.0.tgz", "http://localhost:8123/charts-with-no-trailing-slash/nginx-0.2.0.tgz"},
   390  		{"http://localhost:8123", "https://charts.helm.sh/stable/nginx-0.2.0.tgz", "https://charts.helm.sh/stable/nginx-0.2.0.tgz"},
   391  		{"http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz", "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz"},
   392  		{"http://localhost:8123/charts?with=queryparameter", "nginx-0.2.0.tgz", "http://localhost:8123/charts/nginx-0.2.0.tgz?with=queryparameter"},
   393  	} {
   394  		chartURL, err := ResolveReferenceURL(tt.baseURL, tt.refURL)
   395  		if err != nil {
   396  			t.Errorf("unexpected error in ResolveReferenceURL(%q, %q): %s", tt.baseURL, tt.refURL, err)
   397  		}
   398  		if chartURL != tt.chartURL {
   399  			t.Errorf("expected ResolveReferenceURL(%q, %q) to equal %q, got %q", tt.baseURL, tt.refURL, tt.chartURL, chartURL)
   400  		}
   401  	}
   402  }
   403  

View as plain text