github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/downloader/manager_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 downloader
    17  
    18  import (
    19  	"bytes"
    20  	"os"
    21  	"path/filepath"
    22  	"reflect"
    23  	"testing"
    24  
    25  	"github.com/stefanmcshane/helm/pkg/chart"
    26  	"github.com/stefanmcshane/helm/pkg/chart/loader"
    27  	"github.com/stefanmcshane/helm/pkg/chartutil"
    28  	"github.com/stefanmcshane/helm/pkg/getter"
    29  	"github.com/stefanmcshane/helm/pkg/repo/repotest"
    30  )
    31  
    32  func TestVersionEquals(t *testing.T) {
    33  	tests := []struct {
    34  		name, v1, v2 string
    35  		expect       bool
    36  	}{
    37  		{name: "semver match", v1: "1.2.3-beta.11", v2: "1.2.3-beta.11", expect: true},
    38  		{name: "semver match, build info", v1: "1.2.3-beta.11+a", v2: "1.2.3-beta.11+b", expect: true},
    39  		{name: "string match", v1: "abcdef123", v2: "abcdef123", expect: true},
    40  		{name: "semver mismatch", v1: "1.2.3-beta.11", v2: "1.2.3-beta.22", expect: false},
    41  		{name: "semver mismatch, invalid semver", v1: "1.2.3-beta.11", v2: "stinkycheese", expect: false},
    42  	}
    43  
    44  	for _, tt := range tests {
    45  		if versionEquals(tt.v1, tt.v2) != tt.expect {
    46  			t.Errorf("%s: failed comparison of %q and %q (expect equal: %t)", tt.name, tt.v1, tt.v2, tt.expect)
    47  		}
    48  	}
    49  }
    50  
    51  func TestNormalizeURL(t *testing.T) {
    52  	tests := []struct {
    53  		name, base, path, expect string
    54  	}{
    55  		{name: "basic URL", base: "https://example.com", path: "http://helm.sh/foo", expect: "http://helm.sh/foo"},
    56  		{name: "relative path", base: "https://helm.sh/charts", path: "foo", expect: "https://helm.sh/charts/foo"},
    57  		{name: "Encoded path", base: "https://helm.sh/a%2Fb/charts", path: "foo", expect: "https://helm.sh/a%2Fb/charts/foo"},
    58  	}
    59  
    60  	for _, tt := range tests {
    61  		got, err := normalizeURL(tt.base, tt.path)
    62  		if err != nil {
    63  			t.Errorf("%s: error %s", tt.name, err)
    64  			continue
    65  		} else if got != tt.expect {
    66  			t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
    67  		}
    68  	}
    69  }
    70  
    71  func TestFindChartURL(t *testing.T) {
    72  	var b bytes.Buffer
    73  	m := &Manager{
    74  		Out:              &b,
    75  		RepositoryConfig: repoConfig,
    76  		RepositoryCache:  repoCache,
    77  	}
    78  	repos, err := m.loadChartRepositories()
    79  	if err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	name := "alpine"
    84  	version := "0.1.0"
    85  	repoURL := "http://example.com/charts"
    86  
    87  	churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err := m.findChartURL(name, version, repoURL, repos)
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  
    92  	if churl != "https://charts.helm.sh/stable/alpine-0.1.0.tgz" {
    93  		t.Errorf("Unexpected URL %q", churl)
    94  	}
    95  	if username != "" {
    96  		t.Errorf("Unexpected username %q", username)
    97  	}
    98  	if password != "" {
    99  		t.Errorf("Unexpected password %q", password)
   100  	}
   101  	if passcredentialsall != false {
   102  		t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
   103  	}
   104  	if insecureSkipTLSVerify {
   105  		t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
   106  	}
   107  
   108  	name = "tlsfoo"
   109  	version = "1.2.3"
   110  	repoURL = "https://example-https-insecureskiptlsverify.com"
   111  
   112  	churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos)
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  
   117  	if !insecureSkipTLSVerify {
   118  		t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify)
   119  	}
   120  	if churl != "https://example.com/tlsfoo-1.2.3.tgz" {
   121  		t.Errorf("Unexpected URL %q", churl)
   122  	}
   123  	if username != "" {
   124  		t.Errorf("Unexpected username %q", username)
   125  	}
   126  	if password != "" {
   127  		t.Errorf("Unexpected password %q", password)
   128  	}
   129  	if passcredentialsall != false {
   130  		t.Errorf("Unexpected passcredentialsall %t", passcredentialsall)
   131  	}
   132  }
   133  
   134  func TestGetRepoNames(t *testing.T) {
   135  	b := bytes.NewBuffer(nil)
   136  	m := &Manager{
   137  		Out:              b,
   138  		RepositoryConfig: repoConfig,
   139  		RepositoryCache:  repoCache,
   140  	}
   141  	tests := []struct {
   142  		name   string
   143  		req    []*chart.Dependency
   144  		expect map[string]string
   145  		err    bool
   146  	}{
   147  		{
   148  			name: "no repo definition, but references a url",
   149  			req: []*chart.Dependency{
   150  				{Name: "oedipus-rex", Repository: "http://example.com/test"},
   151  			},
   152  			expect: map[string]string{"http://example.com/test": "http://example.com/test"},
   153  		},
   154  		{
   155  			name: "no repo definition failure -- stable repo",
   156  			req: []*chart.Dependency{
   157  				{Name: "oedipus-rex", Repository: "stable"},
   158  			},
   159  			err: true,
   160  		},
   161  		{
   162  			name: "no repo definition failure",
   163  			req: []*chart.Dependency{
   164  				{Name: "oedipus-rex", Repository: "http://example.com"},
   165  			},
   166  			expect: map[string]string{"oedipus-rex": "testing"},
   167  		},
   168  		{
   169  			name: "repo from local path",
   170  			req: []*chart.Dependency{
   171  				{Name: "local-dep", Repository: "file://./testdata/signtest"},
   172  			},
   173  			expect: map[string]string{"local-dep": "file://./testdata/signtest"},
   174  		},
   175  		{
   176  			name: "repo alias (alias:)",
   177  			req: []*chart.Dependency{
   178  				{Name: "oedipus-rex", Repository: "alias:testing"},
   179  			},
   180  			expect: map[string]string{"oedipus-rex": "testing"},
   181  		},
   182  		{
   183  			name: "repo alias (@)",
   184  			req: []*chart.Dependency{
   185  				{Name: "oedipus-rex", Repository: "@testing"},
   186  			},
   187  			expect: map[string]string{"oedipus-rex": "testing"},
   188  		},
   189  		{
   190  			name: "repo from local chart under charts path",
   191  			req: []*chart.Dependency{
   192  				{Name: "local-subchart", Repository: ""},
   193  			},
   194  			expect: map[string]string{},
   195  		},
   196  	}
   197  
   198  	for _, tt := range tests {
   199  		l, err := m.resolveRepoNames(tt.req)
   200  		if err != nil {
   201  			if tt.err {
   202  				continue
   203  			}
   204  			t.Fatal(err)
   205  		}
   206  
   207  		if tt.err {
   208  			t.Fatalf("Expected error in test %q", tt.name)
   209  		}
   210  
   211  		// m1 and m2 are the maps we want to compare
   212  		eq := reflect.DeepEqual(l, tt.expect)
   213  		if !eq {
   214  			t.Errorf("%s: expected map %v, got %v", tt.name, l, tt.name)
   215  		}
   216  	}
   217  }
   218  
   219  func TestDownloadAll(t *testing.T) {
   220  	chartPath := t.TempDir()
   221  	m := &Manager{
   222  		Out:              new(bytes.Buffer),
   223  		RepositoryConfig: repoConfig,
   224  		RepositoryCache:  repoCache,
   225  		ChartPath:        chartPath,
   226  	}
   227  	signtest, err := loader.LoadDir(filepath.Join("testdata", "signtest"))
   228  	if err != nil {
   229  		t.Fatal(err)
   230  	}
   231  	if err := chartutil.SaveDir(signtest, filepath.Join(chartPath, "testdata")); err != nil {
   232  		t.Fatal(err)
   233  	}
   234  
   235  	local, err := loader.LoadDir(filepath.Join("testdata", "local-subchart"))
   236  	if err != nil {
   237  		t.Fatal(err)
   238  	}
   239  	if err := chartutil.SaveDir(local, filepath.Join(chartPath, "charts")); err != nil {
   240  		t.Fatal(err)
   241  	}
   242  
   243  	signDep := &chart.Dependency{
   244  		Name:       signtest.Name(),
   245  		Repository: "file://./testdata/signtest",
   246  		Version:    signtest.Metadata.Version,
   247  	}
   248  	localDep := &chart.Dependency{
   249  		Name:       local.Name(),
   250  		Repository: "",
   251  		Version:    local.Metadata.Version,
   252  	}
   253  
   254  	// create a 'tmpcharts' directory to test #5567
   255  	if err := os.MkdirAll(filepath.Join(chartPath, "tmpcharts"), 0755); err != nil {
   256  		t.Fatal(err)
   257  	}
   258  	if err := m.downloadAll([]*chart.Dependency{signDep, localDep}); err != nil {
   259  		t.Error(err)
   260  	}
   261  
   262  	if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); os.IsNotExist(err) {
   263  		t.Error(err)
   264  	}
   265  }
   266  
   267  func TestUpdateBeforeBuild(t *testing.T) {
   268  	// Set up a fake repo
   269  	srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
   270  	if err != nil {
   271  		t.Fatal(err)
   272  	}
   273  	defer srv.Stop()
   274  	if err := srv.LinkIndices(); err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	dir := func(p ...string) string {
   278  		return filepath.Join(append([]string{srv.Root()}, p...)...)
   279  	}
   280  
   281  	// Save dep
   282  	d := &chart.Chart{
   283  		Metadata: &chart.Metadata{
   284  			Name:       "dep-chart",
   285  			Version:    "0.1.0",
   286  			APIVersion: "v1",
   287  		},
   288  	}
   289  	if err := chartutil.SaveDir(d, dir()); err != nil {
   290  		t.Fatal(err)
   291  	}
   292  	// Save a chart
   293  	c := &chart.Chart{
   294  		Metadata: &chart.Metadata{
   295  			Name:       "with-dependency",
   296  			Version:    "0.1.0",
   297  			APIVersion: "v2",
   298  			Dependencies: []*chart.Dependency{{
   299  				Name:       d.Metadata.Name,
   300  				Version:    ">=0.1.0",
   301  				Repository: "file://../dep-chart",
   302  			}},
   303  		},
   304  	}
   305  	if err := chartutil.SaveDir(c, dir()); err != nil {
   306  		t.Fatal(err)
   307  	}
   308  
   309  	// Set-up a manager
   310  	b := bytes.NewBuffer(nil)
   311  	g := getter.Providers{getter.Provider{
   312  		Schemes: []string{"http", "https"},
   313  		New:     getter.NewHTTPGetter,
   314  	}}
   315  	m := &Manager{
   316  		ChartPath:        dir(c.Metadata.Name),
   317  		Out:              b,
   318  		Getters:          g,
   319  		RepositoryConfig: dir("repositories.yaml"),
   320  		RepositoryCache:  dir(),
   321  	}
   322  
   323  	// Update before Build. see issue: https://github.com/helm/helm/issues/7101
   324  	err = m.Update()
   325  	if err != nil {
   326  		t.Fatal(err)
   327  	}
   328  
   329  	err = m.Build()
   330  	if err != nil {
   331  		t.Fatal(err)
   332  	}
   333  }
   334  
   335  // TestUpdateWithNoRepo is for the case of a dependency that has no repo listed.
   336  // This happens when the dependency is in the charts directory and does not need
   337  // to be fetched.
   338  func TestUpdateWithNoRepo(t *testing.T) {
   339  	// Set up a fake repo
   340  	srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
   341  	if err != nil {
   342  		t.Fatal(err)
   343  	}
   344  	defer srv.Stop()
   345  	if err := srv.LinkIndices(); err != nil {
   346  		t.Fatal(err)
   347  	}
   348  	dir := func(p ...string) string {
   349  		return filepath.Join(append([]string{srv.Root()}, p...)...)
   350  	}
   351  
   352  	// Setup the dependent chart
   353  	d := &chart.Chart{
   354  		Metadata: &chart.Metadata{
   355  			Name:       "dep-chart",
   356  			Version:    "0.1.0",
   357  			APIVersion: "v1",
   358  		},
   359  	}
   360  
   361  	// Save a chart with the dependency
   362  	c := &chart.Chart{
   363  		Metadata: &chart.Metadata{
   364  			Name:       "with-dependency",
   365  			Version:    "0.1.0",
   366  			APIVersion: "v2",
   367  			Dependencies: []*chart.Dependency{{
   368  				Name:    d.Metadata.Name,
   369  				Version: "0.1.0",
   370  			}},
   371  		},
   372  	}
   373  	if err := chartutil.SaveDir(c, dir()); err != nil {
   374  		t.Fatal(err)
   375  	}
   376  
   377  	// Save dependent chart into the parents charts directory. If the chart is
   378  	// not in the charts directory Helm will return an error that it is not
   379  	// found.
   380  	if err := chartutil.SaveDir(d, dir(c.Metadata.Name, "charts")); err != nil {
   381  		t.Fatal(err)
   382  	}
   383  
   384  	// Set-up a manager
   385  	b := bytes.NewBuffer(nil)
   386  	g := getter.Providers{getter.Provider{
   387  		Schemes: []string{"http", "https"},
   388  		New:     getter.NewHTTPGetter,
   389  	}}
   390  	m := &Manager{
   391  		ChartPath:        dir(c.Metadata.Name),
   392  		Out:              b,
   393  		Getters:          g,
   394  		RepositoryConfig: dir("repositories.yaml"),
   395  		RepositoryCache:  dir(),
   396  	}
   397  
   398  	// Test the update
   399  	err = m.Update()
   400  	if err != nil {
   401  		t.Fatal(err)
   402  	}
   403  }
   404  
   405  // This function is the skeleton test code of failing tests for #6416 and #6871 and bugs due to #5874.
   406  //
   407  // This function is used by below tests that ensures success of build operation
   408  // with optional fields, alias, condition, tags, and even with ranged version.
   409  // Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default.
   410  // If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used.
   411  func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) {
   412  	// Set up a fake repo
   413  	srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*")
   414  	if err != nil {
   415  		t.Fatal(err)
   416  	}
   417  	defer srv.Stop()
   418  	if err := srv.LinkIndices(); err != nil {
   419  		t.Fatal(err)
   420  	}
   421  	dir := func(p ...string) string {
   422  		return filepath.Join(append([]string{srv.Root()}, p...)...)
   423  	}
   424  
   425  	// Set main fields if not exist
   426  	if dep.Name == "" {
   427  		dep.Name = "local-subchart"
   428  	}
   429  	if dep.Version == "" {
   430  		dep.Version = "0.1.0"
   431  	}
   432  	if dep.Repository == "" {
   433  		dep.Repository = srv.URL()
   434  	}
   435  
   436  	// Save a chart
   437  	c := &chart.Chart{
   438  		Metadata: &chart.Metadata{
   439  			Name:         chartName,
   440  			Version:      "0.1.0",
   441  			APIVersion:   "v2",
   442  			Dependencies: []*chart.Dependency{&dep},
   443  		},
   444  	}
   445  	if err := chartutil.SaveDir(c, dir()); err != nil {
   446  		t.Fatal(err)
   447  	}
   448  
   449  	// Set-up a manager
   450  	b := bytes.NewBuffer(nil)
   451  	g := getter.Providers{getter.Provider{
   452  		Schemes: []string{"http", "https"},
   453  		New:     getter.NewHTTPGetter,
   454  	}}
   455  	m := &Manager{
   456  		ChartPath:        dir(chartName),
   457  		Out:              b,
   458  		Getters:          g,
   459  		RepositoryConfig: dir("repositories.yaml"),
   460  		RepositoryCache:  dir(),
   461  	}
   462  
   463  	// First build will update dependencies and create Chart.lock file.
   464  	err = m.Build()
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  
   469  	// Second build should be passed. See PR #6655.
   470  	err = m.Build()
   471  	if err != nil {
   472  		t.Fatal(err)
   473  	}
   474  }
   475  
   476  func TestBuild_WithoutOptionalFields(t *testing.T) {
   477  	// Dependency has main fields only (name/version/repository)
   478  	checkBuildWithOptionalFields(t, "without-optional-fields", chart.Dependency{})
   479  }
   480  
   481  func TestBuild_WithSemVerRange(t *testing.T) {
   482  	// Dependency version is the form of SemVer range
   483  	checkBuildWithOptionalFields(t, "with-semver-range", chart.Dependency{
   484  		Version: ">=0.1.0",
   485  	})
   486  }
   487  
   488  func TestBuild_WithAlias(t *testing.T) {
   489  	// Dependency has an alias
   490  	checkBuildWithOptionalFields(t, "with-alias", chart.Dependency{
   491  		Alias: "local-subchart-alias",
   492  	})
   493  }
   494  
   495  func TestBuild_WithCondition(t *testing.T) {
   496  	// Dependency has a condition
   497  	checkBuildWithOptionalFields(t, "with-condition", chart.Dependency{
   498  		Condition: "some.condition",
   499  	})
   500  }
   501  
   502  func TestBuild_WithTags(t *testing.T) {
   503  	// Dependency has several tags
   504  	checkBuildWithOptionalFields(t, "with-tags", chart.Dependency{
   505  		Tags: []string{"tag1", "tag2"},
   506  	})
   507  }
   508  
   509  // Failing test for #6871
   510  func TestBuild_WithRepositoryAlias(t *testing.T) {
   511  	// Dependency repository is aliased in Chart.yaml
   512  	checkBuildWithOptionalFields(t, "with-repository-alias", chart.Dependency{
   513  		Repository: "@test",
   514  	})
   515  }
   516  
   517  func TestErrRepoNotFound_Error(t *testing.T) {
   518  	type fields struct {
   519  		Repos []string
   520  	}
   521  	tests := []struct {
   522  		name   string
   523  		fields fields
   524  		want   string
   525  	}{
   526  		{
   527  			name: "OK",
   528  			fields: fields{
   529  				Repos: []string{"https://charts1.example.com", "https://charts2.example.com"},
   530  			},
   531  			want: "no repository definition for https://charts1.example.com, https://charts2.example.com",
   532  		},
   533  	}
   534  	for _, tt := range tests {
   535  		t.Run(tt.name, func(t *testing.T) {
   536  			e := ErrRepoNotFound{
   537  				Repos: tt.fields.Repos,
   538  			}
   539  			if got := e.Error(); got != tt.want {
   540  				t.Errorf("Error() = %v, want %v", got, tt.want)
   541  			}
   542  		})
   543  	}
   544  }
   545  
   546  func TestKey(t *testing.T) {
   547  	tests := []struct {
   548  		name   string
   549  		expect string
   550  	}{
   551  		{
   552  			name:   "file:////tmp",
   553  			expect: "afeed3459e92a874f6373aca264ce1459bfa91f9c1d6612f10ae3dc2ee955df3",
   554  		},
   555  		{
   556  			name:   "https://example.com/charts",
   557  			expect: "7065c57c94b2411ad774638d76823c7ccb56415441f5ab2f5ece2f3845728e5d",
   558  		},
   559  		{
   560  			name:   "foo/bar/baz",
   561  			expect: "15c46a4f8a189ae22f36f201048881d6c090c93583bedcf71f5443fdef224c82",
   562  		},
   563  	}
   564  
   565  	for _, tt := range tests {
   566  		o, err := key(tt.name)
   567  		if err != nil {
   568  			t.Fatalf("unable to generate key for %q with error: %s", tt.name, err)
   569  		}
   570  		if o != tt.expect {
   571  			t.Errorf("wrong key name generated for %q, expected %q but got %q", tt.name, tt.expect, o)
   572  		}
   573  	}
   574  }