github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/repo/chartrepo_test.go (about)

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