diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 000000000..d35be3f4b --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,29 @@ +name: build-pr +on: + pull_request: + branches: + - main +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v2 + with: + go-version: '1.16' + - name: Install golangci-lint + run: | + curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz + shasum -a 256 golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz | grep "^$GOLANGCI_LINT_SHA256 " > /dev/null + tar -xf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz + sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint + rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64* + env: + GOLANGCI_LINT_VERSION: '1.36.0' + GOLANGCI_LINT_SHA256: '9b8856b3a1c9bfbcf3a06b78e94611763b79abd9751c245246787cd3bf0e78a5' + - name: Test style + run: make test-style + - name: Run unit tests + run: make test-coverage diff --git a/cmd/helm/dependency_update_test.go b/cmd/helm/dependency_update_test.go index 9ba085c27..3f708dd42 100644 --- a/cmd/helm/dependency_update_test.go +++ b/cmd/helm/dependency_update_test.go @@ -17,7 +17,6 @@ package main import ( "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -191,7 +190,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) { } // Make sure charts dir still has dependencies - files, err := ioutil.ReadDir(filepath.Join(dir(chartname), "charts")) + files, err := os.ReadDir(filepath.Join(dir(chartname), "charts")) if err != nil { t.Fatal(err) } diff --git a/cmd/helm/repo_update.go b/cmd/helm/repo_update.go index 23dca194a..43036847d 100644 --- a/cmd/helm/repo_update.go +++ b/cmd/helm/repo_update.go @@ -32,6 +32,10 @@ import ( const updateDesc = ` Update gets the latest information about charts from the respective chart repositories. Information is cached locally, where it is used by commands like 'helm search'. + +You can optionally specify a list of repositories you want to update. + $ helm repo update ... +To update all the repositories, use 'helm repo update'. ` var errNoRepositories = errors.New("no repositories found. You must add one before updating") @@ -40,21 +44,25 @@ type repoUpdateOptions struct { update func([]*repo.ChartRepository, io.Writer) repoFile string repoCache string + names []string } func newRepoUpdateCmd(out io.Writer) *cobra.Command { o := &repoUpdateOptions{update: updateCharts} cmd := &cobra.Command{ - Use: "update", - Aliases: []string{"up"}, - Short: "update information of available charts locally from chart repositories", - Long: updateDesc, - Args: require.NoArgs, - ValidArgsFunction: noCompletions, + Use: "update [REPO1 [REPO2 ...]]", + Aliases: []string{"up"}, + Short: "update information of available charts locally from chart repositories", + Long: updateDesc, + Args: require.MinimumNArgs(0), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp + }, RunE: func(cmd *cobra.Command, args []string) error { o.repoFile = settings.RepositoryConfig o.repoCache = settings.RepositoryCache + o.names = args return o.run(out) }, } @@ -73,15 +81,26 @@ func (o *repoUpdateOptions) run(out io.Writer) error { } var repos []*repo.ChartRepository - for _, cfg := range f.Repositories { - r, err := repo.NewChartRepository(cfg, getter.All(settings)) - if err != nil { + updateAllRepos := len(o.names) == 0 + + if !updateAllRepos { + // Fail early if the user specified an invalid repo to update + if err := checkRequestedRepos(o.names, f.Repositories); err != nil { return err } - if o.repoCache != "" { - r.CachePath = o.repoCache + } + + for _, cfg := range f.Repositories { + if updateAllRepos || isRepoRequested(cfg.Name, o.names) { + r, err := repo.NewChartRepository(cfg, getter.All(settings)) + if err != nil { + return err + } + if o.repoCache != "" { + r.CachePath = o.repoCache + } + repos = append(repos, r) } - repos = append(repos, r) } o.update(repos, out) @@ -105,3 +124,28 @@ func updateCharts(repos []*repo.ChartRepository, out io.Writer) { wg.Wait() fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈") } + +func checkRequestedRepos(requestedRepos []string, validRepos []*repo.Entry) error { + for _, requestedRepo := range requestedRepos { + found := false + for _, repo := range validRepos { + if requestedRepo == repo.Name { + found = true + break + } + } + if !found { + return errors.Errorf("no repositories found matching '%s'. Nothing will be updated", requestedRepo) + } + } + return nil +} + +func isRepoRequested(repoName string, requestedRepos []string) bool { + for _, requestedRepo := range requestedRepos { + if repoName == requestedRepo { + return true + } + } + return false +} diff --git a/cmd/helm/repo_update_test.go b/cmd/helm/repo_update_test.go index 83ef24349..d769c8aa7 100644 --- a/cmd/helm/repo_update_test.go +++ b/cmd/helm/repo_update_test.go @@ -48,8 +48,54 @@ func TestUpdateCmd(t *testing.T) { t.Fatal(err) } - if got := out.String(); !strings.Contains(got, "charts") { - t.Errorf("Expected 'charts' got %q", got) + if got := out.String(); !strings.Contains(got, "charts") || + !strings.Contains(got, "firstexample") || + !strings.Contains(got, "secondexample") { + t.Errorf("Expected 'charts', 'firstexample' and 'secondexample' but got %q", got) + } +} + +func TestUpdateCmdMultiple(t *testing.T) { + var out bytes.Buffer + // Instead of using the HTTP updater, we provide our own for this test. + // The TestUpdateCharts test verifies the HTTP behavior independently. + updater := func(repos []*repo.ChartRepository, out io.Writer) { + for _, re := range repos { + fmt.Fprintln(out, re.Config.Name) + } + } + o := &repoUpdateOptions{ + update: updater, + repoFile: "testdata/repositories.yaml", + names: []string{"firstexample", "charts"}, + } + if err := o.run(&out); err != nil { + t.Fatal(err) + } + + if got := out.String(); !strings.Contains(got, "charts") || + !strings.Contains(got, "firstexample") || + strings.Contains(got, "secondexample") { + t.Errorf("Expected 'charts' and 'firstexample' but not 'secondexample' but got %q", got) + } +} + +func TestUpdateCmdInvalid(t *testing.T) { + var out bytes.Buffer + // Instead of using the HTTP updater, we provide our own for this test. + // The TestUpdateCharts test verifies the HTTP behavior independently. + updater := func(repos []*repo.ChartRepository, out io.Writer) { + for _, re := range repos { + fmt.Fprintln(out, re.Config.Name) + } + } + o := &repoUpdateOptions{ + update: updater, + repoFile: "testdata/repositories.yaml", + names: []string{"firstexample", "invalid"}, + } + if err := o.run(&out); err == nil { + t.Fatal("expected error but did not get one") } } @@ -111,4 +157,5 @@ func TestUpdateCharts(t *testing.T) { func TestRepoUpdateFileCompletion(t *testing.T) { checkFileCompletion(t, "repo update", false) + checkFileCompletion(t, "repo update repo1", false) } diff --git a/cmd/helm/search_repo.go b/cmd/helm/search_repo.go index ba692a2e7..3a5a23848 100644 --- a/cmd/helm/search_repo.go +++ b/cmd/helm/search_repo.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "io/ioutil" + "os" "path/filepath" "strings" @@ -340,7 +341,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell // listing the entire content of the current directory which will // be too many choices for the user to find the real repos) if includeFiles && len(completions) > 0 && len(toComplete) > 0 { - if files, err := ioutil.ReadDir("."); err == nil { + if files, err := os.ReadDir("."); err == nil { for _, file := range files { if strings.HasPrefix(file.Name(), toComplete) { // We are completing a file prefix diff --git a/cmd/helm/testdata/repositories.yaml b/cmd/helm/testdata/repositories.yaml index 423b9f195..6be26b771 100644 --- a/cmd/helm/testdata/repositories.yaml +++ b/cmd/helm/testdata/repositories.yaml @@ -2,3 +2,8 @@ apiVersion: v1 repositories: - name: charts url: "https://charts.helm.sh/stable" + - name: firstexample + url: "http://firstexample.com" + - name: secondexample + url: "http://secondexample.com" + diff --git a/internal/third_party/dep/fs/fs.go b/internal/third_party/dep/fs/fs.go index 832592197..4e4eacc60 100644 --- a/internal/third_party/dep/fs/fs.go +++ b/internal/third_party/dep/fs/fs.go @@ -33,7 +33,6 @@ package fs import ( "io" - "io/ioutil" "os" "path/filepath" "runtime" @@ -119,7 +118,7 @@ func CopyDir(src, dst string) error { return errors.Wrapf(err, "cannot mkdir %s", dst) } - entries, err := ioutil.ReadDir(src) + entries, err := os.ReadDir(src) if err != nil { return errors.Wrapf(err, "cannot read directory %s", dst) } diff --git a/pkg/action/lint.go b/pkg/action/lint.go index 2292c14bf..bdb93dcc2 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -96,7 +96,7 @@ func lintChart(path string, vals map[string]interface{}, namespace string, stric return linter, errors.Wrap(err, "unable to extract tarball") } - files, err := ioutil.ReadDir(tempDir) + files, err := os.ReadDir(tempDir) if err != nil { return linter, errors.Wrapf(err, "unable to read temporary output directory %s", tempDir) } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index b01f046f0..b11c8db50 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -878,7 +878,7 @@ func tarFromLocalDir(chartpath, name, repo, version string) (string, error) { // move files from tmppath to destpath func move(tmpPath, destPath string) error { - files, _ := ioutil.ReadDir(tmpPath) + files, _ := os.ReadDir(tmpPath) for _, file := range files { filename := file.Name() tmpfile := filepath.Join(tmpPath, filename) diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 67ede93fd..956997cc9 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -286,7 +286,8 @@ func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, // ResolveReferenceURL resolves refURL relative to baseURL. // If refURL is absolute, it simply returns refURL. func ResolveReferenceURL(baseURL, refURL string) (string, error) { - parsedBaseURL, err := url.Parse(baseURL) + // We need a trailing slash for ResolveReference to work, but make sure there isn't already one + parsedBaseURL, err := url.Parse(strings.TrimSuffix(baseURL, "/") + "/") if err != nil { return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL) } @@ -296,8 +297,6 @@ func ResolveReferenceURL(baseURL, refURL string) (string, error) { return "", errors.Wrapf(err, "failed to parse %s as URL", refURL) } - // We need a trailing slash for ResolveReference to work, but make sure there isn't already one - parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/" return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil } diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index 85401284e..0f317b2fd 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -400,4 +400,12 @@ func TestResolveReferenceURL(t *testing.T) { if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { t.Errorf("%s", chartURL) } + + chartURL, err = ResolveReferenceURL("http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz") + if err != nil { + t.Errorf("%s", err) + } + if chartURL != "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz" { + t.Errorf("%s", chartURL) + } }