diff --git a/.github/workflows/asset-transparency.yaml b/.github/workflows/asset-transparency.yaml deleted file mode 100644 index ff58d1a5f..000000000 --- a/.github/workflows/asset-transparency.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: Publish Release Assets to Asset Transparency Log - -on: - release: - types: [published, created, edited, released] - -jobs: - github_release_asset_transparency_log_publish_job: - runs-on: ubuntu-latest - name: Publish GitHub release asset digests to https://beta-asset.transparencylog.net - steps: - - name: Gather URLs from GitHub release and publish - id: asset-transparency - uses: transparencylog/github-releases-asset-transparency-verify-action@c77874b4514ae4003994ece9582675195fe012e2 # v11 - - name: List verified and published URLs - run: echo "Verified URLs ${{ steps.asset-transparency.outputs.verified }}" - - name: List failed URLs - run: echo "Failed URLs ${{ steps.asset-transparency.outputs.failed }}" diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 7696b4e21..3199b15b3 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -13,23 +13,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1 - name: Setup Go - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # pin@5.0.0 with: - go-version: '1.20' - - 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.51.2' - GOLANGCI_LINT_SHA256: '4de479eb9d9bc29da51aec1834e7c255b333723d38dbd56781c68e5dddc6a90b' - - name: Test style - run: make test-style + go-version: '1.21' + - name: Test source headers are present + run: make test-source-headers - name: Run unit tests run: make test-coverage - name: Test build diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b219b2c5d..1be802ccc 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@6a28655e3dcb49cb0840ea372fd6d17733edd8a4 # pinv2.21.8 + uses: github/codeql-action/init@47b3d888fe66b639e431abf22ebca059152f1eea # pinv3.24.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@6a28655e3dcb49cb0840ea372fd6d17733edd8a4 # pinv2.21.8 + uses: github/codeql-action/autobuild@47b3d888fe66b639e431abf22ebca059152f1eea # pinv3.24.5 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@6a28655e3dcb49cb0840ea372fd6d17733edd8a4 # pinv2.21.8 + uses: github/codeql-action/analyze@47b3d888fe66b639e431abf22ebca059152f1eea # pinv3.24.5 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 000000000..dcc982dc9 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,22 @@ +name: golangci-lint + +on: + push: + pull_request: + +jobs: + golangci: + name: golangci-lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1 + + - name: Setup Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0 + with: + go-version: "1.21" + - name: golangci-lint + uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc #pin@3.7.0 + with: + version: v1.55 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 05b4cfa45..9a9e96b8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,25 +18,36 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0 - fetch-depth: 0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1 + with: + fetch-depth: 0 - name: Setup Go - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # pin@5.0.0 with: - go-version: '1.20' - + go-version: '1.21' + - name: Run unit tests run: make test-coverage - name: Build Helm Binaries run: | + set -eu -o pipefail + make build-cross make dist checksum VERSION="${{ github.ref_name }}" - + - name: Set latest version + run: | + set -eu -o pipefail + + mkdir -p _dist_versions + # Push the latest semver tag, excluding prerelease tags - git tag | sort -r --version-sort | grep '^v[0-9]' | grep -v '-' | head -n1 > _dist/helm-latest-version + LATEST_VERSION="$(git tag | sort -r --version-sort | grep '^v[0-9]' | grep -v '-' | head -n1)" + echo "LATEST_VERSION=${LATEST_VERSION}" + echo "${LATEST_VERSION}" > _dist_versions/helm-latest-version + echo "${LATEST_VERSION}" > _dist_versions/helm3-latest-version - name: Upload Binaries uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0 @@ -49,17 +60,28 @@ jobs: connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }} extra_args: '--pattern helm-*' + - name: Upload Version tag files + uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0 + env: + AZURE_STORAGE_CONNECTION_STRING: "${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}" + AZURE_STORAGE_CONTAINER_NAME: "${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}" + with: + overwrite: 'true' + source_dir: _dist_versions + container_name: ${{ secrets.AZURE_STORAGE_CONTAINER_NAME }} + connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }} + canary-release: runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Checkout source code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1 - name: Setup Go - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # pin@5.0.0 with: - go-version: '1.20' + go-version: '1.21' - name: Run unit tests run: make test-coverage diff --git a/.gitignore b/.gitignore index d1af995a3..cdb68ceb5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,9 @@ .idea/ .vimrc .vscode/ +.devcontainer/ _dist/ +_dist_versions/ bin/ vendor/ # Ignores charts pulled for dependency build tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 37627e716..5bbe2ec63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ chance to try to fix the issue before it is exploited in the wild. ## Sign Your Work -The sign-off is a simple line at the end of the explanation for a commit. All commits needs to be +The sign-off is a simple line at the end of the explanation for a commit. All commits need to be signed. Your signature certifies that you wrote the patch or otherwise have the right to contribute the material. The rules are pretty simple, if you can certify the below (from [developercertificate.org](https://developercertificate.org/)): @@ -195,7 +195,7 @@ below. See [Proposing an Idea](#proposing-an-idea). Smaller quality-of-life enhancements are exempt. - Issues that are labeled as `feature` or `bug` should be connected to the PR that resolves it. - Whoever is working on a `feature` or `bug` issue (whether a maintainer or someone from the - community), should either assign the issue to themself or make a comment in the issue saying + community), should either assign the issue to themselves or make a comment in the issue saying that they are taking it. - `proposal` and `support/question` issues should stay open until resolved or if they have not been active for more than 30 days. This will help keep the issue queue to a manageable size diff --git a/Makefile b/Makefile index d61ac1507..c8ced67a8 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ BINDIR := $(CURDIR)/bin INSTALL_PATH ?= /usr/local/bin DIST_DIRS := find * -type d -exec -TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64 -TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum darwin-arm64.tar.gz darwin-arm64.tar.gz.sha256 darwin-arm64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum +TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x linux/riscv64 windows/amd64 +TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum darwin-arm64.tar.gz darwin-arm64.tar.gz.sha256 darwin-arm64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum linux-riscv64.tar.gz linux-riscv64.tar.gz.sha256 linux-riscv64.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum BINNAME ?= helm GOBIN = $(shell go env GOBIN) @@ -11,7 +11,7 @@ GOBIN = $(shell go env GOPATH)/bin endif GOX = $(GOBIN)/gox GOIMPORTS = $(GOBIN)/goimports -ARCH = $(shell uname -p) +ARCH = $(shell go env GOARCH) ACCEPTANCE_DIR:=../acceptance-testing # To specify the subset of acceptance tests to run. '.' means all tests @@ -114,7 +114,11 @@ test-coverage: .PHONY: test-style test-style: - GO111MODULE=on golangci-lint run + golangci-lint run ./... + @scripts/validate-license.sh + +.PHONY: test-source-headers +test-source-headers: @scripts/validate-license.sh .PHONY: test-acceptance @@ -155,7 +159,7 @@ gen-test-golden: test-unit # without a go.mod file when downloading the following dependencies $(GOX): - (cd /; GO111MODULE=on go install github.com/mitchellh/gox@latest) + (cd /; GO111MODULE=on go install github.com/mitchellh/gox@v1.0.2-0.20220701044238-9f712387e2d2) $(GOIMPORTS): (cd /; GO111MODULE=on go install golang.org/x/tools/cmd/goimports@latest) diff --git a/cmd/helm/completion.go b/cmd/helm/completion.go index 310c915b8..93b9e8eab 100644 --- a/cmd/helm/completion.go +++ b/cmd/helm/completion.go @@ -210,6 +210,6 @@ func runCompletionPowershell(out io.Writer, cmd *cobra.Command) error { } // Function to disable file completion -func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func noCompletions(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/helm/docs.go b/cmd/helm/docs.go index 523a96022..6e9788f26 100644 --- a/cmd/helm/docs.go +++ b/cmd/helm/docs.go @@ -77,7 +77,7 @@ func newDocsCmd(out io.Writer) *cobra.Command { return cmd } -func (o *docsOptions) run(out io.Writer) error { +func (o *docsOptions) run(_ io.Writer) error { switch o.docTypeString { case "markdown", "mdown", "md": if o.generateHeaders { diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go index a8f25cb35..4fcd8a0e4 100644 --- a/cmd/helm/flags.go +++ b/cmd/helm/flags.go @@ -195,7 +195,7 @@ func (p *postRendererArgsSlice) GetSlice() []string { return p.options.args } -func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellCompDirective) { +func compVersionFlag(chartRef string, _ string) ([]string, cobra.ShellCompDirective) { chartInfo := strings.Split(chartRef, "/") if len(chartInfo) != 2 { return nil, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/helm/history.go b/cmd/helm/history.go index ee6f391e4..de8b13a98 100644 --- a/cmd/helm/history.go +++ b/cmd/helm/history.go @@ -184,7 +184,7 @@ func min(x, y int) int { return y } -func compListRevisions(toComplete string, cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) { +func compListRevisions(_ string, cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) { client := action.NewHistory(cfg) var revisions []string diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index 73a37b6fe..46f3c0efd 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -27,6 +27,7 @@ import ( "github.com/spf13/cobra" "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/lint/support" @@ -44,6 +45,7 @@ or recommendation, it will emit [WARNING] messages. func newLintCmd(out io.Writer) *cobra.Command { client := action.NewLint() valueOpts := &values.Options{} + var kubeVersion string cmd := &cobra.Command{ Use: "lint PATH", @@ -54,6 +56,15 @@ func newLintCmd(out io.Writer) *cobra.Command { if len(args) > 0 { paths = args } + + if kubeVersion != "" { + parsedKubeVersion, err := chartutil.ParseKubeVersion(kubeVersion) + if err != nil { + return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err) + } + client.KubeVersion = parsedKubeVersion + } + if client.WithSubcharts { for _, p := range paths { filepath.Walk(filepath.Join(p, "charts"), func(path string, info os.FileInfo, err error) error { @@ -137,6 +148,7 @@ func newLintCmd(out io.Writer) *cobra.Command { f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts") f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors") + f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for capabilities and deprecation checks") addValueOptionsFlags(f, valueOpts) return cmd diff --git a/cmd/helm/lint_test.go b/cmd/helm/lint_test.go index 314b54c35..166b69ba0 100644 --- a/cmd/helm/lint_test.go +++ b/cmd/helm/lint_test.go @@ -63,6 +63,34 @@ func TestLintCmdWithQuietFlag(t *testing.T) { } +func TestLintCmdWithKubeVersionFlag(t *testing.T) { + testChart := "testdata/testcharts/chart-with-deprecated-api" + tests := []cmdTestCase{{ + name: "lint chart with deprecated api version using kube version flag", + cmd: fmt.Sprintf("lint --kube-version 1.22.0 %s", testChart), + golden: "output/lint-chart-with-deprecated-api.txt", + wantError: false, + }, { + name: "lint chart with deprecated api version using kube version and strict flag", + cmd: fmt.Sprintf("lint --kube-version 1.22.0 --strict %s", testChart), + golden: "output/lint-chart-with-deprecated-api-strict.txt", + wantError: true, + }, { + // the test builds will use the default k8sVersionMinor const in deprecations.go and capabilities.go + // which is "20" + name: "lint chart with deprecated api version without kube version", + cmd: fmt.Sprintf("lint %s", testChart), + golden: "output/lint-chart-with-deprecated-api-old-k8s.txt", + wantError: false, + }, { + name: "lint chart with deprecated api version with older kube version", + cmd: fmt.Sprintf("lint --kube-version 1.21.0 --strict %s", testChart), + golden: "output/lint-chart-with-deprecated-api-old-k8s.txt", + wantError: false, + }} + runTestCmd(t, tests) +} + func TestLintFileCompletion(t *testing.T) { checkFileCompletion(t, "lint", true) checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given diff --git a/cmd/helm/plugin_list.go b/cmd/helm/plugin_list.go index ddf01f6f2..fcd415191 100644 --- a/cmd/helm/plugin_list.go +++ b/cmd/helm/plugin_list.go @@ -75,7 +75,7 @@ func filterPlugins(plugins []*plugin.Plugin, ignoredPluginNames []string) []*plu } // Provide dynamic auto-completion for plugin names -func compListPlugins(toComplete string, ignoredPluginNames []string) []string { +func compListPlugins(_ string, ignoredPluginNames []string) []string { var pNames []string plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err == nil && len(plugins) > 0 { diff --git a/cmd/helm/repo_index.go b/cmd/helm/repo_index.go index 3960380d1..d947aca60 100644 --- a/cmd/helm/repo_index.go +++ b/cmd/helm/repo_index.go @@ -76,7 +76,7 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command { return cmd } -func (i *repoIndexOptions) run(out io.Writer) error { +func (i *repoIndexOptions) run(_ io.Writer) error { path, err := filepath.Abs(i.dir) if err != nil { return err diff --git a/cmd/helm/repo_list.go b/cmd/helm/repo_list.go index c9b952fee..334f859a1 100644 --- a/cmd/helm/repo_list.go +++ b/cmd/helm/repo_list.go @@ -123,7 +123,7 @@ func filterRepos(repos []*repo.Entry, ignoredRepoNames []string) []*repo.Entry { } // Provide dynamic auto-completion for repo names -func compListRepos(prefix string, ignoredRepoNames []string) []string { +func compListRepos(_ string, ignoredRepoNames []string) []string { var rNames []string f, err := repo.LoadFile(settings.RepositoryConfig) diff --git a/cmd/helm/rollback_test.go b/cmd/helm/rollback_test.go index 6d38e16eb..b58e4c162 100644 --- a/cmd/helm/rollback_test.go +++ b/cmd/helm/rollback_test.go @@ -17,6 +17,8 @@ limitations under the License. package main import ( + "fmt" + "reflect" "testing" "helm.sh/helm/v3/pkg/chart" @@ -121,3 +123,44 @@ func TestRollbackFileCompletion(t *testing.T) { checkFileCompletion(t, "rollback myrelease", false) checkFileCompletion(t, "rollback myrelease 1", false) } + +func TestRollbackWithLabels(t *testing.T) { + labels1 := map[string]string{"operation": "install", "firstLabel": "firstValue"} + labels2 := map[string]string{"operation": "upgrade", "secondLabel": "secondValue"} + + releaseName := "funny-bunny-labels" + rels := []*release.Release{ + { + Name: releaseName, + Info: &release.Info{Status: release.StatusSuperseded}, + Chart: &chart.Chart{}, + Version: 1, + Labels: labels1, + }, + { + Name: releaseName, + Info: &release.Info{Status: release.StatusDeployed}, + Chart: &chart.Chart{}, + Version: 2, + Labels: labels2, + }, + } + storage := storageFixture() + for _, rel := range rels { + if err := storage.Create(rel); err != nil { + t.Fatal(err) + } + } + _, _, err := executeActionCommandC(storage, fmt.Sprintf("rollback %s 1", releaseName)) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + updatedRel, err := storage.Get(releaseName, 3) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + if !reflect.DeepEqual(updatedRel.Labels, labels1) { + t.Errorf("Expected {%v}, got {%v}", labels1, updatedRel.Labels) + } +} diff --git a/cmd/helm/root.go b/cmd/helm/root.go index dd95b1df2..55b7e8892 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -45,31 +45,32 @@ Common actions for Helm: Environment variables: -| Name | Description | -|------------------------------------|---------------------------------------------------------------------------------------------------| -| $HELM_CACHE_HOME | set an alternative location for storing cached files. | -| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. | -| $HELM_DATA_HOME | set an alternative location for storing Helm data. | -| $HELM_DEBUG | indicate whether or not Helm is running in Debug mode | -| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, sql. | -| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. | -| $HELM_MAX_HISTORY | set the maximum number of helm release history. | -| $HELM_NAMESPACE | set the namespace used for the helm operations. | -| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | -| $HELM_PLUGINS | set the path to the plugins directory | -| $HELM_REGISTRY_CONFIG | set the path to the registry config file. | -| $HELM_REPOSITORY_CACHE | set the path to the repository cache directory | -| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. | -| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | -| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication | -| $HELM_KUBECAFILE | set the Kubernetes certificate authority file. | -| $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. | -| $HELM_KUBEASUSER | set the Username to impersonate for the operation. | -| $HELM_KUBECONTEXT | set the name of the kubeconfig context. | -| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. | -| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure) | -| $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate | -| $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable)| +| Name | Description | +|------------------------------------|------------------------------------------------------------------------------------------------------------| +| $HELM_CACHE_HOME | set an alternative location for storing cached files. | +| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. | +| $HELM_DATA_HOME | set an alternative location for storing Helm data. | +| $HELM_DEBUG | indicate whether or not Helm is running in Debug mode | +| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, sql. | +| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. | +| $HELM_MAX_HISTORY | set the maximum number of helm release history. | +| $HELM_NAMESPACE | set the namespace used for the helm operations. | +| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. | +| $HELM_PLUGINS | set the path to the plugins directory | +| $HELM_REGISTRY_CONFIG | set the path to the registry config file. | +| $HELM_REPOSITORY_CACHE | set the path to the repository cache directory | +| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. | +| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | +| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication | +| $HELM_KUBECAFILE | set the Kubernetes certificate authority file. | +| $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. | +| $HELM_KUBEASUSER | set the Username to impersonate for the operation. | +| $HELM_KUBECONTEXT | set the name of the kubeconfig context. | +| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. | +| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure) | +| $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate | +| $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable) | +| $HELM_QPS | set the Queries Per Second in cases where a high number of calls exceed the option for higher burst values | Helm stores cache, configuration, and data based on the following configuration order: diff --git a/cmd/helm/search/search_test.go b/cmd/helm/search/search_test.go index dc82ca3d9..415c085b4 100644 --- a/cmd/helm/search/search_test.go +++ b/cmd/helm/search/search_test.go @@ -101,7 +101,7 @@ var indexfileEntries = map[string]repo.ChartVersions{ }, } -func loadTestIndex(t *testing.T, all bool) *Index { +func loadTestIndex(_ *testing.T, all bool) *Index { i := NewIndex() i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all) i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{ diff --git a/cmd/helm/template_test.go b/cmd/helm/template_test.go index 123a4c9bc..e5b939879 100644 --- a/cmd/helm/template_test.go +++ b/cmd/helm/template_test.go @@ -142,7 +142,7 @@ func TestTemplateCmd(t *testing.T) { golden: "output/issue-9027.txt", }, { - // Ensure that imported values take precedence over parent chart values + // Ensure that parent chart values take precedence over imported values name: "template with imported subchart values ensuring import", cmd: fmt.Sprintf("template '%s' --set configmap.enabled=true --set subchartb.enabled=true", chartPath), golden: "output/template-subchart-cm.txt", diff --git a/cmd/helm/testdata/output/env-comp.txt b/cmd/helm/testdata/output/env-comp.txt index b7d93c12e..8f9c53fc7 100644 --- a/cmd/helm/testdata/output/env-comp.txt +++ b/cmd/helm/testdata/output/env-comp.txt @@ -15,6 +15,7 @@ HELM_KUBETOKEN HELM_MAX_HISTORY HELM_NAMESPACE HELM_PLUGINS +HELM_QPS HELM_REGISTRY_CONFIG HELM_REPOSITORY_CACHE HELM_REPOSITORY_CONFIG diff --git a/cmd/helm/testdata/output/lint-chart-with-deprecated-api-old-k8s.txt b/cmd/helm/testdata/output/lint-chart-with-deprecated-api-old-k8s.txt new file mode 100644 index 000000000..bd0d70000 --- /dev/null +++ b/cmd/helm/testdata/output/lint-chart-with-deprecated-api-old-k8s.txt @@ -0,0 +1,4 @@ +==> Linting testdata/testcharts/chart-with-deprecated-api +[INFO] Chart.yaml: icon is recommended + +1 chart(s) linted, 0 chart(s) failed diff --git a/cmd/helm/testdata/output/lint-chart-with-deprecated-api-strict.txt b/cmd/helm/testdata/output/lint-chart-with-deprecated-api-strict.txt new file mode 100644 index 000000000..a1ec4394e --- /dev/null +++ b/cmd/helm/testdata/output/lint-chart-with-deprecated-api-strict.txt @@ -0,0 +1,5 @@ +==> Linting testdata/testcharts/chart-with-deprecated-api +[INFO] Chart.yaml: icon is recommended +[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler + +Error: 1 chart(s) linted, 1 chart(s) failed diff --git a/cmd/helm/testdata/output/lint-chart-with-deprecated-api.txt b/cmd/helm/testdata/output/lint-chart-with-deprecated-api.txt new file mode 100644 index 000000000..dac54620c --- /dev/null +++ b/cmd/helm/testdata/output/lint-chart-with-deprecated-api.txt @@ -0,0 +1,5 @@ +==> Linting testdata/testcharts/chart-with-deprecated-api +[INFO] Chart.yaml: icon is recommended +[WARNING] templates/horizontalpodautoscaler.yaml: autoscaling/v2beta1 HorizontalPodAutoscaler is deprecated in v1.22+, unavailable in v1.25+; use autoscaling/v2 HorizontalPodAutoscaler + +1 chart(s) linted, 0 chart(s) failed diff --git a/cmd/helm/testdata/output/template-subchart-cm.txt b/cmd/helm/testdata/output/template-subchart-cm.txt index f7e7b3d37..9cc9e2296 100644 --- a/cmd/helm/testdata/output/template-subchart-cm.txt +++ b/cmd/helm/testdata/output/template-subchart-cm.txt @@ -11,7 +11,7 @@ kind: ConfigMap metadata: name: subchart-cm data: - value: bar + value: foo --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 diff --git a/cmd/helm/testdata/testcharts/chart-with-deprecated-api/Chart.yaml b/cmd/helm/testdata/testcharts/chart-with-deprecated-api/Chart.yaml new file mode 100644 index 000000000..3a6e99952 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-deprecated-api/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +appVersion: "1.0.0" +description: A Helm chart for Kubernetes +name: chart-with-deprecated-api +type: application +version: 1.0.0 diff --git a/cmd/helm/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml b/cmd/helm/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml new file mode 100644 index 000000000..b77a4beeb --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-deprecated-api/templates/horizontalpodautoscaler.yaml @@ -0,0 +1,9 @@ +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: deprecated +spec: + scaleTargetRef: + kind: Pod + name: pod + maxReplicas: 3 \ No newline at end of file diff --git a/internal/ignore/testdata/.joonix b/cmd/helm/testdata/testcharts/chart-with-deprecated-api/values.yaml similarity index 100% rename from internal/ignore/testdata/.joonix rename to cmd/helm/testdata/testcharts/chart-with-deprecated-api/values.yaml diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 4ba54df2c..5101cc72b 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -253,6 +253,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.DurationVar(&client.HookTimeout, "hook-timeout", 300*time.Second, "time to wait for any individual hook. Defaults to --timeout if unsets") f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") + f.BoolVar(&client.ResetThenReuseValues, "reset-then-reuse-values", false, "when upgrading, reset the values to the ones built into the chart, apply the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' or '--reuse-values' is specified, this is ignored") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") diff --git a/go.mod b/go.mod index 1a5a6d8fe..e200d4fcb 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,19 @@ module helm.sh/helm/v3 -go 1.19 +go 1.21 require ( github.com/BurntSushi/toml v1.3.2 - github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/sprig/v3 v3.2.3 github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/vcs v1.13.3 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 - github.com/containerd/containerd v1.7.6 + github.com/containerd/containerd v1.7.12 github.com/cyphar/filepath-securejoin v0.2.4 github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 - github.com/evanphx/json-patch v5.6.0+incompatible + github.com/evanphx/json-patch v5.7.0+incompatible github.com/foxcpp/go-mockdns v1.0.0 github.com/gobwas/glob v0.2.3 github.com/gofrs/flock v0.8.1 @@ -24,27 +24,27 @@ require ( github.com/mattn/go-shellwords v1.0.12 github.com/mitchellh/copystructure v1.2.0 github.com/moby/term v0.5.0 - github.com/opencontainers/image-spec v1.1.0-rc4 + github.com/opencontainers/image-spec v1.1.0-rc5 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/pkg/errors v0.9.1 github.com/rubenv/sql-migrate v1.5.2 github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/crypto v0.11.0 - golang.org/x/term v0.11.0 - golang.org/x/text v0.13.0 - k8s.io/api v0.28.2 - k8s.io/apiextensions-apiserver v0.28.2 - k8s.io/apimachinery v0.28.2 - k8s.io/apiserver v0.28.2 - k8s.io/cli-runtime v0.28.2 - k8s.io/client-go v0.28.2 - k8s.io/klog/v2 v2.100.1 - k8s.io/kubectl v0.28.2 - oras.land/oras-go v1.2.3 + golang.org/x/crypto v0.17.0 + golang.org/x/term v0.15.0 + golang.org/x/text v0.14.0 + k8s.io/api v0.29.0 + k8s.io/apiextensions-apiserver v0.29.0 + k8s.io/apimachinery v0.29.0 + k8s.io/apiserver v0.29.0 + k8s.io/cli-runtime v0.29.0 + k8s.io/client-go v0.29.0 + k8s.io/klog/v2 v2.110.1 + k8s.io/kubectl v0.29.0 + oras.land/oras-go v1.2.4 sigs.k8s.io/yaml v1.3.0 ) @@ -53,7 +53,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Microsoft/hcsshim v0.11.0 // indirect + github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect @@ -62,25 +62,26 @@ require ( github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v23.0.3+incompatible // indirect + github.com/docker/cli v24.0.6+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v23.0.3+incompatible // indirect + github.com/docker/docker v24.0.7+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/fatih/color v1.13.0 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -90,12 +91,13 @@ require ( github.com/gomodule/redigo v1.8.2 // indirect github.com/google/btree v1.0.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect @@ -123,6 +125,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -139,26 +142,28 @@ require ( github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect - go.opentelemetry.io/otel v1.14.0 // indirect - go.opentelemetry.io/otel/trace v1.14.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect + go.opentelemetry.io/otel v1.19.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/trace v1.19.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - golang.org/x/net v0.13.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.11.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.10.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.15.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect - google.golang.org/grpc v1.54.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.28.2 // indirect - k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + k8s.io/component-base v0.29.0 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/go.sum b/go.sum index 7d946b76d..2799262df 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg6 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -22,13 +22,15 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE= github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/hcsshim v0.11.0 h1:7EFNIY4igHEXUdj1zXgAyU3fLc7QfOKHbkldRVTBdiM= -github.com/Microsoft/hcsshim v0.11.0/go.mod h1:OEthFdQv/AD2RAdzR6Mm1N1KPCztGKDurW1Z8b8VGMM= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= +github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -36,6 +38,7 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= @@ -54,13 +57,18 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/containerd v1.7.6 h1:oNAVsnhPoy4BTPQivLgTzI9Oleml9l/+eYIDYXRCYo8= -github.com/containerd/containerd v1.7.6/go.mod h1:SY6lrkkuJT40BVNO37tlYTSnKJnP5AXBc0fhx0q+TJ4= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= +github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -68,12 +76,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= -github.com/docker/cli v23.0.3+incompatible h1:Zcse1DuDqBdgI7OQDV8Go7b83xLgfhW1eza4HfEdxpY= -github.com/docker/cli v23.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWTcc7GAneOY= +github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho= -github.com/docker/docker v23.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -86,12 +94,12 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= +github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= @@ -102,6 +110,7 @@ github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -111,10 +120,9 @@ github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpj github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -127,9 +135,13 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= +github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= +github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= +github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= @@ -139,6 +151,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -165,12 +178,14 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -181,6 +196,8 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -210,14 +227,17 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -234,8 +254,11 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -250,6 +273,7 @@ github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebG github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -261,6 +285,7 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/51gbeLHfKGNfgEQQIWrlbdaOsidbQ= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -269,6 +294,7 @@ github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQ github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -285,12 +311,16 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE= -github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= -github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -301,6 +331,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= +github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= @@ -321,11 +352,13 @@ github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -335,8 +368,8 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -372,10 +405,15 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPS github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM= -go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU= -go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M= -go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q= +go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -385,8 +423,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -394,7 +432,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -410,11 +449,11 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -422,8 +461,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -444,21 +483,21 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -471,7 +510,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -483,13 +523,13 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -500,8 +540,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -519,39 +559,40 @@ gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw= -k8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg= -k8s.io/apiextensions-apiserver v0.28.2 h1:J6/QRWIKV2/HwBhHRVITMLYoypCoPY1ftigDM0Kn+QU= -k8s.io/apiextensions-apiserver v0.28.2/go.mod h1:5tnkxLGa9nefefYzWuAlWZ7RZYuN/765Au8cWLA6SRg= -k8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ= -k8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU= -k8s.io/apiserver v0.28.2 h1:rBeYkLvF94Nku9XfXyUIirsVzCzJBs6jMn3NWeHieyI= -k8s.io/apiserver v0.28.2/go.mod h1:f7D5e8wH8MWcKD7azq6Csw9UN+CjdtXIVQUyUhrtb+E= -k8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk= -k8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA= -k8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY= -k8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY= -k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E= -k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/kubectl v0.28.2 h1:fOWOtU6S0smdNjG1PB9WFbqEIMlkzU5ahyHkc7ESHgM= -k8s.io/kubectl v0.28.2/go.mod h1:6EQWTPySF1fn7yKoQZHYf9TPwIl2AygHEcJoxFekr64= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.3 h1:v8PJl+gEAntI1pJ/LCrDgsuk+1PKVavVEPsYIHFE5uY= -oras.land/oras-go v1.2.3/go.mod h1:M/uaPdYklze0Vf3AakfarnpoEckvw0ESbRdN8Z1vdJg= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= +k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= +k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= +k8s.io/cli-runtime v0.29.0 h1:q2kC3cex4rOBLfPOnMSzV2BIrrQlx97gxHJs21KxKS4= +k8s.io/cli-runtime v0.29.0/go.mod h1:VKudXp3X7wR45L+nER85YUzOQIru28HQpXr0mTdeCrk= +k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= +k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= +k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kubectl v0.29.0 h1:Oqi48gXjikDhrBF67AYuZRTcJV4lg2l42GmvsP7FmYI= +k8s.io/kubectl v0.29.0/go.mod h1:0jMjGWIcMIQzmUaMgAzhSELv5WtHo2a8pq67DtviAJs= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= +oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index 40c1ffdb6..0af625dff 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -22,6 +22,7 @@ import ( "github.com/pkg/errors" + "helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/release" helmtime "helm.sh/helm/v3/pkg/time" ) @@ -51,7 +52,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation} } - if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation, timeout); err != nil { return err } @@ -88,7 +89,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, h.LastRun.Phase = release.HookPhaseFailed // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted // under failed condition. If so, then clear the corresponding resource object in the hook - if err := cfg.deleteHookByPolicy(h, release.HookFailed); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookFailed, timeout); err != nil { return err } return err @@ -99,7 +100,7 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted // under succeeded condition. If so, then clear the corresponding resource object in each hook for _, h := range executingHooks { - if err := cfg.deleteHookByPolicy(h, release.HookSucceeded); err != nil { + if err := cfg.deleteHookByPolicy(h, release.HookSucceeded, timeout); err != nil { return err } } @@ -120,7 +121,7 @@ func (x hookByWeight) Less(i, j int) bool { } // deleteHookByPolicy deletes a hook if the hook policy instructs it to -func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy) error { +func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, timeout time.Duration) error { // Never delete CustomResourceDefinitions; this could cause lots of // cascading garbage collection. if h.Kind == "CustomResourceDefinition" { @@ -135,6 +136,13 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo if len(errs) > 0 { return errors.New(joinErrors(errs)) } + + //wait for resources until they are deleted to avoid conflicts + if kubeClient, ok := cfg.KubeClient.(kube.InterfaceExt); ok { + if err := kubeClient.WaitForDelete(resources, timeout); err != nil { + return err + } + } } return nil } diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 0fca8a456..2a0ee2074 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -502,6 +502,9 @@ func TestInstallRelease_Atomic(t *testing.T) { failer.WaitError = fmt.Errorf("I timed out") instAction.cfg.KubeClient = failer instAction.Atomic = true + // disabling hooks to avoid an early fail when the + // the WaitForDelete is called on the pre-delete hook execution + instAction.DisableHooks = true vals := map[string]interface{}{} res, err := instAction.Run(buildChart(), vals) diff --git a/pkg/action/lint.go b/pkg/action/lint.go index e71cfe733..ca497f2b8 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -36,6 +36,7 @@ type Lint struct { Namespace string WithSubcharts bool Quiet bool + KubeVersion *chartutil.KubeVersion } // LintResult is the result of Lint @@ -58,7 +59,7 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult { } result := &LintResult{} for _, path := range paths { - linter, err := lintChart(path, vals, l.Namespace, l.Strict) + linter, err := lintChart(path, vals, l.Namespace, l.KubeVersion) if err != nil { result.Errors = append(result.Errors, err) continue @@ -85,7 +86,7 @@ func HasWarningsOrErrors(result *LintResult) bool { return len(result.Errors) > 0 } -func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) { +func lintChart(path string, vals map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) (support.Linter, error) { var chartPath string linter := support.Linter{} @@ -124,5 +125,5 @@ func lintChart(path string, vals map[string]interface{}, namespace string, stric return linter, errors.Wrap(err, "unable to check Chart.yaml file in chart") } - return lint.All(chartPath, vals, namespace, strict), nil + return lint.AllWithKubeVersion(chartPath, vals, namespace, kubeVersion), nil } diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go index ff69407ca..80bf4ce7e 100644 --- a/pkg/action/lint_test.go +++ b/pkg/action/lint_test.go @@ -23,7 +23,6 @@ import ( var ( values = make(map[string]interface{}) namespace = "testNamespace" - strict = false chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1" chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2" corruptedTgzChart = "testdata/charts/corrupted-compressed-chart.tgz" @@ -78,7 +77,7 @@ func TestLintChart(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, strict) + _, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, nil) switch { case err != nil && !tt.err: t.Errorf("%s", err) diff --git a/pkg/action/package.go b/pkg/action/package.go index 698169032..b79fcb54f 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -54,7 +54,7 @@ func NewPackage() *Package { } // Run executes 'helm package' against the given chart and returns the path to the packaged chart. -func (p *Package) Run(path string, vals map[string]interface{}) (string, error) { +func (p *Package) Run(path string, _ map[string]interface{}) (string, error) { ch, err := loader.LoadDir(path) if err != nil { return "", err diff --git a/pkg/action/registry_login.go b/pkg/action/registry_login.go index a55f2de58..cd144e1e7 100644 --- a/pkg/action/registry_login.go +++ b/pkg/action/registry_login.go @@ -73,7 +73,7 @@ func NewRegistryLogin(cfg *Configuration) *RegistryLogin { } // Run executes the registry login operation -func (a *RegistryLogin) Run(out io.Writer, hostname string, username string, password string, opts ...RegistryLoginOpt) error { +func (a *RegistryLogin) Run(_ io.Writer, hostname string, username string, password string, opts ...RegistryLoginOpt) error { for _, opt := range opts { if err := opt(a); err != nil { return err diff --git a/pkg/action/registry_logout.go b/pkg/action/registry_logout.go index 69add4163..7ce92defc 100644 --- a/pkg/action/registry_logout.go +++ b/pkg/action/registry_logout.go @@ -33,6 +33,6 @@ func NewRegistryLogout(cfg *Configuration) *RegistryLogout { } // Run executes the registry logout operation -func (a *RegistryLogout) Run(out io.Writer, hostname string) error { +func (a *RegistryLogout) Run(_ io.Writer, hostname string) error { return a.cfg.RegistryClient.Logout(hostname) } diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 6b8bbc1fd..6bebb0511 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -152,6 +152,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele Description: fmt.Sprintf("Rollback to %d", previousVersion), }, Version: currentRelease.Version + 1, + Labels: previousRelease.Labels, Manifest: previousRelease.Manifest, Hooks: previousRelease.Hooks, } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 04df82ba6..fb9b29fdf 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -84,6 +84,8 @@ type Upgrade struct { ResetValues bool // ReuseValues will re-use the user's last supplied values. ReuseValues bool + // ResetThenReuseValues will reset the values to the chart's built-ins then merge with user's last supplied values. + ResetThenReuseValues bool // Recreate will (if true) recreate pods after a rollback. Recreate bool // MaxHistory limits the maximum number of revisions saved per release @@ -264,7 +266,6 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, err } - fmt.Println(driver.ContainsSystemLabels(u.Labels)) if driver.ContainsSystemLabels(u.Labels) { return nil, nil, fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()) } @@ -558,6 +559,15 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV return newVals, nil } + // If the ResetThenReuseValues flag is set, we use the new chart's values, but we copy the old config's values over the new config's values. + if u.ResetThenReuseValues { + u.cfg.Log("merging values from old release to new values") + + newVals = chartutil.CoalesceTables(newVals, current.Config) + + return newVals, nil + } + if len(newVals) == 0 && len(current.Config) > 0 { u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) newVals = current.Config diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 694530ea7..0667c8578 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -332,6 +332,59 @@ func TestUpgradeRelease_ReuseValues(t *testing.T) { }) } +func TestUpgradeRelease_ResetThenReuseValues(t *testing.T) { + is := assert.New(t) + + t.Run("reset then reuse values should work with values", func(t *testing.T) { + upAction := upgradeAction(t) + + existingValues := map[string]interface{}{ + "name": "value", + "maxHeapSize": "128m", + "replicas": 2, + } + newValues := map[string]interface{}{ + "name": "newValue", + "maxHeapSize": "512m", + "cpu": "12m", + } + newChartValues := map[string]interface{}{ + "memory": "256m", + } + expectedValues := map[string]interface{}{ + "name": "newValue", + "maxHeapSize": "512m", + "cpu": "12m", + "replicas": 2, + } + + rel := releaseStub() + rel.Name = "nuketown" + rel.Info.Status = release.StatusDeployed + rel.Config = existingValues + + err := upAction.cfg.Releases.Create(rel) + is.NoError(err) + + upAction.ResetThenReuseValues = true + // setting newValues and upgrading + res, err := upAction.Run(rel.Name, buildChart(withValues(newChartValues)), newValues) + is.NoError(err) + + // Now make sure it is actually upgraded + updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2) + is.NoError(err) + + if updatedRes == nil { + is.Fail("Updated Release is nil") + return + } + is.Equal(release.StatusDeployed, updatedRes.Info.Status) + is.Equal(expectedValues, updatedRes.Config) + is.Equal(newChartValues, updatedRes.Chart.Values) + }) +} + func TestUpgradeRelease_Pending(t *testing.T) { req := require.New(t) diff --git a/pkg/chart/loader/directory.go b/pkg/chart/loader/directory.go index 489eea93c..9bcbee60c 100644 --- a/pkg/chart/loader/directory.go +++ b/pkg/chart/loader/directory.go @@ -25,9 +25,9 @@ import ( "github.com/pkg/errors" - "helm.sh/helm/v3/internal/ignore" "helm.sh/helm/v3/internal/sympath" "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/ignore" ) var utf8bom = []byte{0xEF, 0xBB, 0xBF} diff --git a/pkg/chart/metadata.go b/pkg/chart/metadata.go index ae572abb7..a08a97cd1 100644 --- a/pkg/chart/metadata.go +++ b/pkg/chart/metadata.go @@ -16,6 +16,7 @@ limitations under the License. package chart import ( + "path/filepath" "strings" "unicode" @@ -110,6 +111,11 @@ func (md *Metadata) Validate() error { if md.Name == "" { return ValidationError("chart.metadata.name is required") } + + if md.Name != filepath.Base(md.Name) { + return ValidationErrorf("chart.metadata.name %q is invalid", md.Name) + } + if md.Version == "" { return ValidationError("chart.metadata.version is required") } @@ -128,10 +134,19 @@ func (md *Metadata) Validate() error { // Aliases need to be validated here to make sure that the alias name does // not contain any illegal characters. + dependencies := map[string]*Dependency{} for _, dependency := range md.Dependencies { if err := dependency.Validate(); err != nil { return err } + key := dependency.Name + if dependency.Alias != "" { + key = dependency.Alias + } + if dependencies[key] != nil { + return ValidationErrorf("more than one dependency with name or alias %q", key) + } + dependencies[key] = dependency } return nil } diff --git a/pkg/chart/metadata_test.go b/pkg/chart/metadata_test.go index cc04f095b..62aea7261 100644 --- a/pkg/chart/metadata_test.go +++ b/pkg/chart/metadata_test.go @@ -21,34 +21,47 @@ import ( func TestValidate(t *testing.T) { tests := []struct { - md *Metadata - err error + name string + md *Metadata + err error }{ { + "chart without metadata", nil, ValidationError("chart.metadata is required"), }, { + "chart without apiVersion", &Metadata{Name: "test", Version: "1.0"}, ValidationError("chart.metadata.apiVersion is required"), }, { + "chart without name", &Metadata{APIVersion: "v2", Version: "1.0"}, ValidationError("chart.metadata.name is required"), }, { + "chart without name", + &Metadata{Name: "../../test", APIVersion: "v2", Version: "1.0"}, + ValidationError("chart.metadata.name \"../../test\" is invalid"), + }, + { + "chart without version", &Metadata{Name: "test", APIVersion: "v2"}, ValidationError("chart.metadata.version is required"), }, { + "chart with bad type", &Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "test"}, ValidationError("chart.metadata.type must be application or library"), }, { + "chart without dependency", &Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "application"}, nil, }, { + "dependency with valid alias", &Metadata{ Name: "test", APIVersion: "v2", @@ -61,6 +74,7 @@ func TestValidate(t *testing.T) { nil, }, { + "dependency with bad characters in alias", &Metadata{ Name: "test", APIVersion: "v2", @@ -73,6 +87,67 @@ func TestValidate(t *testing.T) { ValidationError("dependency \"bad\" has disallowed characters in the alias"), }, { + "same dependency twice", + &Metadata{ + Name: "test", + APIVersion: "v2", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "foo", Alias: ""}, + {Name: "foo", Alias: ""}, + }, + }, + ValidationError("more than one dependency with name or alias \"foo\""), + }, + { + "two dependencies with alias from second dependency shadowing first one", + &Metadata{ + Name: "test", + APIVersion: "v2", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "foo", Alias: ""}, + {Name: "bar", Alias: "foo"}, + }, + }, + ValidationError("more than one dependency with name or alias \"foo\""), + }, + { + // this case would make sense and could work in future versions of Helm, currently template rendering would + // result in undefined behaviour + "same dependency twice with different version", + &Metadata{ + Name: "test", + APIVersion: "v2", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "foo", Alias: "", Version: "1.2.3"}, + {Name: "foo", Alias: "", Version: "1.0.0"}, + }, + }, + ValidationError("more than one dependency with name or alias \"foo\""), + }, + { + // this case would make sense and could work in future versions of Helm, currently template rendering would + // result in undefined behaviour + "two dependencies with same name but different repos", + &Metadata{ + Name: "test", + APIVersion: "v2", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "foo", Repository: "repo-0"}, + {Name: "foo", Repository: "repo-1"}, + }, + }, + ValidationError("more than one dependency with name or alias \"foo\""), + }, + { + "dependencies has nil", &Metadata{ Name: "test", APIVersion: "v2", @@ -85,6 +160,7 @@ func TestValidate(t *testing.T) { ValidationError("dependencies must not contain empty or null nodes"), }, { + "maintainer not empty", &Metadata{ Name: "test", APIVersion: "v2", @@ -97,6 +173,7 @@ func TestValidate(t *testing.T) { ValidationError("maintainers must not contain empty or null nodes"), }, { + "version invalid", &Metadata{APIVersion: "v2", Name: "test", Version: "1.2.3.4"}, ValidationError("chart.metadata.version \"1.2.3.4\" is invalid"), }, @@ -105,7 +182,7 @@ func TestValidate(t *testing.T) { for _, tt := range tests { result := tt.md.Validate() if result != tt.err { - t.Errorf("expected '%s', got '%s'", tt.err, result) + t.Errorf("expected %q, got %q in test %q", tt.err, result, tt.name) } } } diff --git a/pkg/chartutil/coalesce.go b/pkg/chartutil/coalesce.go index 6cf23a122..f0272fd6a 100644 --- a/pkg/chartutil/coalesce.go +++ b/pkg/chartutil/coalesce.go @@ -129,7 +129,7 @@ func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{} // coalesceGlobals copies the globals out of src and merges them into dest. // // For convenience, returns dest. -func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string, merge bool) { +func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string, _ bool) { var dg, sg map[string]interface{} if destglob, ok := dest[GlobalKey]; !ok { diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index e43aaf479..50212f9d5 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -175,6 +175,15 @@ resources: {} # cpu: 100m # memory: 128Mi +livenessProbe: + httpGet: + path: / + port: http +readinessProbe: + httpGet: + path: / + port: http + autoscaling: enabled: false minReplicas: 1 @@ -311,7 +320,7 @@ spec: {{- end }} labels: {{- include ".labels" . | nindent 8 }} - {{- with .Values.podLabels }} + {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} spec: @@ -333,13 +342,9 @@ spec: containerPort: {{ .Values.service.port }} protocol: TCP livenessProbe: - httpGet: - path: / - port: http + {{- toYaml .Values.livenessProbe | nindent 12 }} readinessProbe: - httpGet: - path: / - port: http + {{- toYaml .Values.readinessProbe | nindent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.volumeMounts }} @@ -392,7 +397,7 @@ metadata: annotations: {{- toYaml . | nindent 4 }} {{- end }} - automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} {{- end }} ` @@ -443,7 +448,7 @@ const defaultNotes = `1. Get the application URL by running these commands: echo http://$NODE_IP:$NODE_PORT {{- else if contains "LoadBalancer" .Values.service.type }} NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include ".fullname" . }}' + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include ".fullname" . }}' export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include ".fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") echo http://$SERVICE_IP:{{ .Values.service.port }} {{- else if contains "ClusterIP" .Values.service.type }} diff --git a/pkg/chartutil/dependencies.go b/pkg/chartutil/dependencies.go index c38a8b6c4..205d99e09 100644 --- a/pkg/chartutil/dependencies.go +++ b/pkg/chartutil/dependencies.go @@ -59,9 +59,8 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath s if bv, ok := vv.(bool); ok { r.Enabled = bv break - } else { - log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) } + log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) } else if _, ok := err.(ErrNoValue); !ok { // this is a real error log.Printf("Warning: PathValue returned error %v", err) @@ -297,20 +296,22 @@ func processImportValues(c *chart.Chart, merge bool) error { r.ImportValues = outiv } - // Imported values from a child to a parent chart have a higher priority than - // values specified in the parent chart. + // Imported values from a child to a parent chart have a lower priority than + // the parents values. This enables parent charts to import a large section + // from a child and then override select parts. This is why b is merged into + // cvals in the code below and not the other way around. if merge { // deep copying the cvals as there are cases where pointers can end // up in the cvals when they are copied onto b in ways that break things. cvals = deepCopyMap(cvals) - c.Values = MergeTables(b, cvals) + c.Values = MergeTables(cvals, b) } else { // Trimming the nil values from cvals is needed for backwards compatibility. // Previously, the b value had been populated with cvals along with some // overrides. This caused the coalescing functionality to remove the // nil/null values. This trimming is for backwards compat. cvals = trimNilValues(cvals) - c.Values = CoalesceTables(b, cvals) + c.Values = CoalesceTables(cvals, b) } return nil @@ -332,11 +333,9 @@ func trimNilValues(vals map[string]interface{}) map[string]interface{} { valsCopyMap := valsCopy.(map[string]interface{}) for key, val := range valsCopyMap { if val == nil { - log.Printf("trim deleting %q", key) // Iterate over the values and remove nil keys delete(valsCopyMap, key) } else if istable(val) { - log.Printf("trim copying %q", key) // Recursively call into ourselves to remove keys from inner tables valsCopyMap[key] = trimNilValues(val.(map[string]interface{})) } diff --git a/pkg/chartutil/dependencies_test.go b/pkg/chartutil/dependencies_test.go index 34ae12f95..7e035be5f 100644 --- a/pkg/chartutil/dependencies_test.go +++ b/pkg/chartutil/dependencies_test.go @@ -181,13 +181,13 @@ func TestProcessDependencyImportValues(t *testing.T) { e["imported-chartA-B.SPextra5"] = "k8s" e["imported-chartA-B.SC1extra5"] = "tiller" - // These values are imported from the child chart to the parent. Imported - // values take precedence over those in the parent so these should be the - // values from the child chart. - e["overridden-chart1.SC1bool"] = "true" - e["overridden-chart1.SC1float"] = "3.14" - e["overridden-chart1.SC1int"] = "100" - e["overridden-chart1.SC1string"] = "dollywood" + // These values are imported from the child chart to the parent. Parent + // values take precedence over imported values. This enables importing a + // large section from a child chart and overriding a selection from it. + e["overridden-chart1.SC1bool"] = "false" + e["overridden-chart1.SC1float"] = "3.141592" + e["overridden-chart1.SC1int"] = "99" + e["overridden-chart1.SC1string"] = "pollywog" e["overridden-chart1.SPextra2"] = "42" e["overridden-chartA.SCAbool"] = "true" @@ -196,17 +196,17 @@ func TestProcessDependencyImportValues(t *testing.T) { e["overridden-chartA.SCAstring"] = "jabberwocky" e["overridden-chartA.SPextra4"] = "true" - // These values are imported from the child chart to the parent. Imported - // values take precedence over those in the parent so these should be the - // values from the child chart. + // These values are imported from the child chart to the parent. Parent + // values take precedence over imported values. This enables importing a + // large section from a child chart and overriding a selection from it. e["overridden-chartA-B.SCAbool"] = "true" - e["overridden-chartA-B.SCAfloat"] = "3.33" - e["overridden-chartA-B.SCAint"] = "555" - e["overridden-chartA-B.SCAstring"] = "wormwood" - e["overridden-chartA-B.SCBbool"] = "true" - e["overridden-chartA-B.SCBfloat"] = "0.25" - e["overridden-chartA-B.SCBint"] = "98" - e["overridden-chartA-B.SCBstring"] = "murkwood" + e["overridden-chartA-B.SCAfloat"] = "41.3" + e["overridden-chartA-B.SCAint"] = "808" + e["overridden-chartA-B.SCAstring"] = "jabberwocky" + e["overridden-chartA-B.SCBbool"] = "false" + e["overridden-chartA-B.SCBfloat"] = "1.99" + e["overridden-chartA-B.SCBint"] = "77" + e["overridden-chartA-B.SCBstring"] = "jango" e["overridden-chartA-B.SPextra6"] = "111" e["overridden-chartA-B.SCAextra1"] = "23" e["overridden-chartA-B.SCBextra1"] = "13" @@ -278,20 +278,20 @@ func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) { // The order of precedence should be: // 1. User specified values (e.g CLI) - // 2. Imported values - // 3. Parent chart values + // 2. Parent chart values + // 3. Imported values // 4. Sub-chart values // The 4 app charts here deal with things differently: // - app1 has a port value set in the umbrella chart. It does not import any // values so the value from the umbrella chart should be used. // - app2 has a value in the app chart and imports from the library. The - // library chart value should take precedence. + // app chart value should take precedence. // - app3 has no value in the app chart and imports the value from the library // chart. The library chart value should be used. // - app4 has a value in the app chart and does not import the value from the // library chart. The app charts value should be used. e["app1.service.port"] = "3456" - e["app2.service.port"] = "9090" + e["app2.service.port"] = "8080" e["app3.service.port"] = "9090" e["app4.service.port"] = "1234" if err := processDependencyImportValues(c, true); err != nil { diff --git a/pkg/chartutil/errors.go b/pkg/chartutil/errors.go index fcdcc27ea..0a4046d2e 100644 --- a/pkg/chartutil/errors.go +++ b/pkg/chartutil/errors.go @@ -33,3 +33,11 @@ type ErrNoValue struct { } func (e ErrNoValue) Error() string { return fmt.Sprintf("%q is not a value", e.Key) } + +type ErrInvalidChartName struct { + Name string +} + +func (e ErrInvalidChartName) Error() string { + return fmt.Sprintf("%q is not a valid chart name", e.Name) +} diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go index 2ce4eddaf..4ee90709c 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chartutil/save.go @@ -39,6 +39,10 @@ var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // directory, writing the chart's contents to that subdirectory. func SaveDir(c *chart.Chart, dest string) error { // Create the chart directory + err := validateName(c.Name()) + if err != nil { + return err + } outdir := filepath.Join(dest, c.Name()) if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() { return errors.Errorf("file %s already exists and is not a directory", outdir) @@ -149,6 +153,10 @@ func Save(c *chart.Chart, outDir string) (string, error) { } func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { + err := validateName(c.Name()) + if err != nil { + return err + } base := filepath.Join(prefix, c.Name()) // Pull out the dependencies of a v1 Chart, since there's no way @@ -242,3 +250,15 @@ func writeToTar(out *tar.Writer, name string, body []byte) error { _, err := out.Write(body) return err } + +// If the name has directory name has characters which would change the location +// they need to be removed. +func validateName(name string) error { + nname := filepath.Base(name) + + if nname != name { + return ErrInvalidChartName{name} + } + + return nil +} diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go index db485c7cb..98b4e641b 100644 --- a/pkg/chartutil/save_test.go +++ b/pkg/chartutil/save_test.go @@ -106,6 +106,24 @@ func TestSave(t *testing.T) { } }) } + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV1, + Name: "../ahab", + Version: "1.2.3", + }, + Lock: &chart.Lock{ + Digest: "testdigest", + }, + Files: []*chart.File{ + {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, + }, + } + _, err := Save(c, tmp) + if err == nil { + t.Fatal("Expected error saving chart with invalid name") + } } // Creates a copy with a different schema; does not modify anything. @@ -232,4 +250,15 @@ func TestSaveDir(t *testing.T) { if len(c2.Files) != 1 || c2.Files[0].Name != c.Files[0].Name { t.Fatal("Files data did not match") } + + tmp2 := t.TempDir() + c.Metadata.Name = "../ahab" + pth := filepath.Join(tmp2, "tmpcharts") + if err := os.MkdirAll(filepath.Join(pth), 0755); err != nil { + t.Fatal(err) + } + + if err := SaveDir(c, pth); err.Error() != "\"../ahab\" is not a valid chart name" { + t.Fatalf("Did not get expected error for chart named %q", c.Name()) + } } diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/README.md b/pkg/chartutil/testdata/three-level-dependent-chart/README.md index e6f586a5c..536bb9792 100644 --- a/pkg/chartutil/testdata/three-level-dependent-chart/README.md +++ b/pkg/chartutil/testdata/three-level-dependent-chart/README.md @@ -5,7 +5,7 @@ This chart is for testing the processing of multi-level dependencies. Consists of the following charts: - Library Chart -- App Chart (Uses Library Chart as dependecy, 2x: app1/app2) +- App Chart (Uses Library Chart as dependency, 2x: app1/app2) - Umbrella Chart (Has all the app charts as dependencies) The precedence is as follows: `library < app < umbrella` diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index dac2a4bc1..4f74f2642 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -44,6 +44,9 @@ const defaultMaxHistory = 10 // defaultBurstLimit sets the default client-side throttling limit const defaultBurstLimit = 100 +// defaultQPS sets the default QPS value to 0 to to use library defaults unless specified +const defaultQPS = float32(0) + // EnvSettings describes all of the environment settings. type EnvSettings struct { namespace string @@ -83,6 +86,8 @@ type EnvSettings struct { MaxHistory int // BurstLimit is the default client-side throttling limit. BurstLimit int + // QPS is queries per second which may be used to avoid throttling. + QPS float32 } func New() *EnvSettings { @@ -102,6 +107,7 @@ func New() *EnvSettings { RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), + QPS: envFloat32Or("HELM_QPS", defaultQPS), } env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) @@ -119,6 +125,7 @@ func New() *EnvSettings { ImpersonateGroup: &env.KubeAsGroups, WrapConfigFn: func(config *rest.Config) *rest.Config { config.Burst = env.BurstLimit + config.QPS = env.QPS config.Wrap(func(rt http.RoundTripper) http.RoundTripper { return &retryingRoundTripper{wrapped: rt} }) @@ -146,6 +153,7 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs") fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the file containing cached repository indexes") fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit") + fs.Float32Var(&s.QPS, "qps", s.QPS, "queries per second used when communicating with the Kubernetes API, not including bursting") } func envOr(name, def string) string { @@ -179,6 +187,18 @@ func envIntOr(name string, def int) int { return ret } +func envFloat32Or(name string, def float32) float32 { + if name == "" { + return def + } + envVal := envOr(name, strconv.FormatFloat(float64(def), 'f', 2, 32)) + ret, err := strconv.ParseFloat(envVal, 32) + if err != nil { + return def + } + return float32(ret) +} + func envCSV(name string) (ls []string) { trimmed := strings.Trim(os.Getenv(name), ", ") if trimmed != "" { @@ -201,6 +221,7 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_NAMESPACE": s.Namespace(), "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), "HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit), + "HELM_QPS": strconv.FormatFloat(float64(s.QPS), 'f', 2, 32), // broken, these are populated from helm flags and not kubeconfig. "HELM_KUBECONTEXT": s.KubeContext, diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 3de6fab4c..1692a89d5 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -59,20 +59,23 @@ func TestEnvSettings(t *testing.T) { kubeInsecure bool kubeTLSServer string burstLimit int + qps float32 }{ { name: "defaults", ns: "default", maxhistory: defaultMaxHistory, burstLimit: defaultBurstLimit, + qps: defaultQPS, }, { name: "with flags set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", + args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --qps 50.12 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", ns: "myns", debug: true, maxhistory: defaultMaxHistory, burstLimit: 100, + qps: 50.12, kubeAsUser: "poro", kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, kubeCaFile: "/tmp/ca.crt", @@ -81,10 +84,11 @@ func TestEnvSettings(t *testing.T) { }, { name: "with envvars set", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"}, + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "60.34"}, ns: "yourns", maxhistory: 5, burstLimit: 150, + qps: 60.34, debug: true, kubeAsUser: "pikachu", kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"}, @@ -94,12 +98,13 @@ func TestEnvSettings(t *testing.T) { }, { name: "with flags and envvars set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"}, + args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --qps 70 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "40"}, ns: "myns", debug: true, maxhistory: 5, burstLimit: 175, + qps: 70, kubeAsUser: "poro", kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, kubeCaFile: "/my/ca.crt", diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index a5b0af080..68c9c6e00 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -714,15 +714,21 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* var entry repo.ChartVersions entry, err = findEntryByName(name, cr) if err != nil { + // TODO: Where linting is skipped in this function we should + // refactor to remove naked returns while ensuring the same + // behavior + //nolint:nakedret return } var ve *repo.ChartVersion ve, err = findVersionedEntry(version, entry) if err != nil { + //nolint:nakedret return } url, err = normalizeURL(repoURL, ve.URLs[0]) if err != nil { + //nolint:nakedret return } username = cr.Config.Username @@ -732,6 +738,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]* caFile = cr.Config.CAFile certFile = cr.Config.CertFile keyFile = cr.Config.KeyFile + //nolint:nakedret return } } diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index f7ab1a568..db2487d16 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -262,6 +262,32 @@ func TestDownloadAll(t *testing.T) { if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); os.IsNotExist(err) { t.Error(err) } + + // A chart with a bad name like this cannot be loaded and saved. Handling in + // the loading and saving will return an error about the invalid name. In + // this case, the chart needs to be created directly. + badchartyaml := `apiVersion: v2 +description: A Helm chart for Kubernetes +name: ../bad-local-subchart +version: 0.1.0` + if err := os.MkdirAll(filepath.Join(chartPath, "testdata", "bad-local-subchart"), 0755); err != nil { + t.Fatal(err) + } + err = os.WriteFile(filepath.Join(chartPath, "testdata", "bad-local-subchart", "Chart.yaml"), []byte(badchartyaml), 0644) + if err != nil { + t.Fatal(err) + } + + badLocalDep := &chart.Dependency{ + Name: "../bad-local-subchart", + Repository: "file://./testdata/bad-local-subchart", + Version: "0.1.0", + } + + err = m.downloadAll([]*chart.Dependency{badLocalDep}) + if err == nil { + t.Fatal("Expected error for bad dependency name") + } } func TestUpdateBeforeBuild(t *testing.T) { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 150be16b7..61c0782fc 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -40,16 +40,17 @@ type Engine struct { Strict bool // In LintMode, some 'required' template values may be missing, so don't fail LintMode bool - // the rest config to connect to the kubernetes api - config *rest.Config + // optional provider of clients to talk to the Kubernetes API + clientProvider *ClientProvider // EnableDNS tells the engine to allow DNS lookups when rendering templates EnableDNS bool } // New creates a new instance of Engine using the passed in rest config. func New(config *rest.Config) Engine { + var clientProvider ClientProvider = clientProviderFromConfig{config} return Engine{ - config: config, + clientProvider: &clientProvider, } } @@ -85,10 +86,21 @@ func Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, erro // RenderWithClient takes a chart, optional values, and value overrides, and attempts to // render the Go templates using the default options. This engine is client aware and so can have template -// functions that interact with the client +// functions that interact with the client. func RenderWithClient(chrt *chart.Chart, values chartutil.Values, config *rest.Config) (map[string]string, error) { + var clientProvider ClientProvider = clientProviderFromConfig{config} return Engine{ - config: config, + clientProvider: &clientProvider, + }.Render(chrt, values) +} + +// RenderWithClientProvider takes a chart, optional values, and value overrides, and attempts to +// render the Go templates using the default options. This engine is client aware and so can have template +// functions that interact with the client. +// This function differs from RenderWithClient in that it lets you customize the way a dynamic client is constructed. +func RenderWithClientProvider(chrt *chart.Chart, values chartutil.Values, clientProvider ClientProvider) (map[string]string, error) { + return Engine{ + clientProvider: &clientProvider, }.Render(chrt, values) } @@ -112,13 +124,10 @@ func warnWrap(warn string) string { return warnStartDelim + warn + warnEndDelim } -// initFunMap creates the Engine's FuncMap and adds context-specific functions. -func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) { - funcMap := funcMap() - includedNames := make(map[string]int) - - // Add the 'include' function here so we can close over t. - funcMap["include"] = func(name string, data interface{}) (string, error) { +// 'include' needs to be defined in the scope of a 'tpl' template as +// well as regular file-loaded templates. +func includeFun(t *template.Template, includedNames map[string]int) func(string, interface{}) (string, error) { + return func(name string, data interface{}) (string, error) { var buf strings.Builder if v, ok := includedNames[name]; ok { if v > recursionMaxNums { @@ -132,33 +141,62 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render includedNames[name]-- return buf.String(), err } +} - // Add the 'tpl' function here - funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) { - basePath, err := vals.PathValue("Template.BasePath") +// As does 'tpl', so that nested calls to 'tpl' see the templates +// defined by their enclosing contexts. +func tplFun(parent *template.Template, includedNames map[string]int, strict bool) func(string, interface{}) (string, error) { + return func(tpl string, vals interface{}) (string, error) { + t, err := parent.Clone() if err != nil { - return "", errors.Wrapf(err, "cannot retrieve Template.Basepath from values inside tpl function: %s", tpl) + return "", errors.Wrapf(err, "cannot clone template") } - templateName, err := vals.PathValue("Template.Name") - if err != nil { - return "", errors.Wrapf(err, "cannot retrieve Template.Name from values inside tpl function: %s", tpl) + // Re-inject the missingkey option, see text/template issue https://github.com/golang/go/issues/43022 + // We have to go by strict from our engine configuration, as the option fields are private in Template. + // TODO: Remove workaround (and the strict parameter) once we build only with golang versions with a fix. + if strict { + t.Option("missingkey=error") + } else { + t.Option("missingkey=zero") } - templates := map[string]renderable{ - templateName.(string): { - tpl: tpl, - vals: vals, - basePath: basePath.(string), - }, + // Re-inject 'include' so that it can close over our clone of t; + // this lets any 'define's inside tpl be 'include'd. + t.Funcs(template.FuncMap{ + "include": includeFun(t, includedNames), + "tpl": tplFun(t, includedNames, strict), + }) + + // We need a .New template, as template text which is just blanks + // or comments after parsing out defines just addes new named + // template definitions without changing the main template. + // https://pkg.go.dev/text/template#Template.Parse + // Use the parent's name for lack of a better way to identify the tpl + // text string. (Maybe we could use a hash appended to the name?) + t, err = t.New(parent.Name()).Parse(tpl) + if err != nil { + return "", errors.Wrapf(err, "cannot parse template %q", tpl) } - result, err := e.renderWithReferences(templates, referenceTpls) - if err != nil { + var buf strings.Builder + if err := t.Execute(&buf, vals); err != nil { return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl) } - return result[templateName.(string)], nil + + // See comment in renderWithReferences explaining the hack. + return strings.ReplaceAll(buf.String(), "", ""), nil } +} + +// initFunMap creates the Engine's FuncMap and adds context-specific functions. +func (e Engine) initFunMap(t *template.Template) { + funcMap := funcMap() + includedNames := make(map[string]int) + + // Add the template-rendering functions here so we can close over t. + funcMap["include"] = includeFun(t, includedNames) + funcMap["tpl"] = tplFun(t, includedNames, e.Strict) // Add the `required` function here so we can use lintMode funcMap["required"] = func(warn string, val interface{}) (interface{}, error) { @@ -194,8 +232,8 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render // If we are not linting and have a cluster connection, provide a Kubernetes-backed // implementation. - if !e.LintMode && e.config != nil { - funcMap["lookup"] = NewLookupFunction(e.config) + if !e.LintMode && e.clientProvider != nil { + funcMap["lookup"] = newLookupFunction(*e.clientProvider) } // When DNS lookups are not enabled override the sprig function and return @@ -210,13 +248,7 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render } // render takes a map of templates/values and renders them. -func (e Engine) render(tpls map[string]renderable) (map[string]string, error) { - return e.renderWithReferences(tpls, tpls) -} - -// renderWithReferences takes a map of templates/values to render, and a map of -// templates which can be referenced within them. -func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) (rendered map[string]string, err error) { +func (e Engine) render(tpls map[string]renderable) (rendered map[string]string, err error) { // Basically, what we do here is start with an empty parent template and then // build up a list of templates -- one for each file. Once all of the templates // have been parsed, we loop through again and execute every template. @@ -238,12 +270,11 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) t.Option("missingkey=zero") } - e.initFunMap(t, referenceTpls) + e.initFunMap(t) // We want to parse the templates in a predictable order. The order favors // higher-level (in file system) templates over deeply nested templates. keys := sortTemplates(tpls) - referenceKeys := sortTemplates(referenceTpls) for _, filename := range keys { r := tpls[filename] @@ -252,17 +283,6 @@ func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) } } - // Adding the reference templates to the template context - // so they can be referenced in the tpl function - for _, filename := range referenceKeys { - if t.Lookup(filename) == nil { - r := referenceTpls[filename] - if _, err := t.New(filename).Parse(r.tpl); err != nil { - return map[string]string{}, cleanupParseError(filename, err) - } - } - } - rendered = make(map[string]string, len(keys)) for _, filename := range keys { // Don't render partials. We don't care out the direct output of partials. diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 27bb9e78e..f8be52bf6 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -24,6 +24,12 @@ import ( "testing" "text/template" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/dynamic/fake" + "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" ) @@ -204,7 +210,7 @@ func TestRenderInternals(t *testing.T) { } } -func TestRenderWIthDNS(t *testing.T) { +func TestRenderWithDNS(t *testing.T) { c := &chart.Chart{ Metadata: &chart.Metadata{ Name: "moby", @@ -240,6 +246,178 @@ func TestRenderWIthDNS(t *testing.T) { } } +type kindProps struct { + shouldErr error + gvr schema.GroupVersionResource + namespaced bool +} + +type testClientProvider struct { + t *testing.T + scheme map[string]kindProps + objects []runtime.Object +} + +func (p *testClientProvider) GetClientFor(apiVersion, kind string) (dynamic.NamespaceableResourceInterface, bool, error) { + props := p.scheme[path.Join(apiVersion, kind)] + if props.shouldErr != nil { + return nil, false, props.shouldErr + } + return fake.NewSimpleDynamicClient(runtime.NewScheme(), p.objects...).Resource(props.gvr), props.namespaced, nil +} + +var _ ClientProvider = &testClientProvider{} + +// makeUnstructured is a convenience function for single-line creation of Unstructured objects. +func makeUnstructured(apiVersion, kind, name, namespace string) *unstructured.Unstructured { + ret := &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": apiVersion, + "kind": kind, + "metadata": map[string]interface{}{ + "name": name, + }, + }} + if namespace != "" { + ret.Object["metadata"].(map[string]interface{})["namespace"] = namespace + } + return ret +} + +func TestRenderWithClientProvider(t *testing.T) { + provider := &testClientProvider{ + t: t, + scheme: map[string]kindProps{ + "v1/Namespace": { + gvr: schema.GroupVersionResource{ + Version: "v1", + Resource: "namespaces", + }, + }, + "v1/Pod": { + gvr: schema.GroupVersionResource{ + Version: "v1", + Resource: "pods", + }, + namespaced: true, + }, + }, + objects: []runtime.Object{ + makeUnstructured("v1", "Namespace", "default", ""), + makeUnstructured("v1", "Pod", "pod1", "default"), + makeUnstructured("v1", "Pod", "pod2", "ns1"), + makeUnstructured("v1", "Pod", "pod3", "ns1"), + }, + } + + type testCase struct { + template string + output string + } + cases := map[string]testCase{ + "ns-single": { + template: `{{ (lookup "v1" "Namespace" "" "default").metadata.name }}`, + output: "default", + }, + "ns-list": { + template: `{{ (lookup "v1" "Namespace" "" "").items | len }}`, + output: "1", + }, + "ns-missing": { + template: `{{ (lookup "v1" "Namespace" "" "absent") }}`, + output: "map[]", + }, + "pod-single": { + template: `{{ (lookup "v1" "Pod" "default" "pod1").metadata.name }}`, + output: "pod1", + }, + "pod-list": { + template: `{{ (lookup "v1" "Pod" "ns1" "").items | len }}`, + output: "2", + }, + "pod-all": { + template: `{{ (lookup "v1" "Pod" "" "").items | len }}`, + output: "3", + }, + "pod-missing": { + template: `{{ (lookup "v1" "Pod" "" "ns2") }}`, + output: "map[]", + }, + } + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "moby", + Version: "1.2.3", + }, + Values: map[string]interface{}{}, + } + + for name, exp := range cases { + c.Templates = append(c.Templates, &chart.File{ + Name: path.Join("templates", name), + Data: []byte(exp.template), + }) + } + + vals := map[string]interface{}{ + "Values": map[string]interface{}{}, + } + + v, err := chartutil.CoalesceValues(c, vals) + if err != nil { + t.Fatalf("Failed to coalesce values: %s", err) + } + + out, err := RenderWithClientProvider(c, v, provider) + if err != nil { + t.Errorf("Failed to render templates: %s", err) + } + + for name, want := range cases { + t.Run(name, func(t *testing.T) { + key := path.Join("moby/templates", name) + if out[key] != want.output { + t.Errorf("Expected %q, got %q", want, out[key]) + } + }) + } +} + +func TestRenderWithClientProvider_error(t *testing.T) { + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "moby", + Version: "1.2.3", + }, + Templates: []*chart.File{ + {Name: "templates/error", Data: []byte(`{{ lookup "v1" "Error" "" "" }}`)}, + }, + Values: map[string]interface{}{}, + } + + vals := map[string]interface{}{ + "Values": map[string]interface{}{}, + } + + v, err := chartutil.CoalesceValues(c, vals) + if err != nil { + t.Fatalf("Failed to coalesce values: %s", err) + } + + provider := &testClientProvider{ + t: t, + scheme: map[string]kindProps{ + "v1/Error": { + shouldErr: fmt.Errorf("kaboom"), + }, + }, + } + _, err = RenderWithClientProvider(c, v, provider) + if err == nil || !strings.Contains(err.Error(), "kaboom") { + t.Errorf("Expected error from client provider when rendering, got %q", err) + } +} + func TestParallelRenderInternals(t *testing.T) { // Make sure that we can use one Engine to run parallel template renders. e := new(Engine) @@ -948,8 +1126,6 @@ func TestRenderTplTemplateNames(t *testing.T) { {Name: "templates/default-name", Data: []byte(`{{tpl "{{ .Template.Name }}" .}}`)}, {Name: "templates/modified-basepath", Data: []byte(`{{tpl "{{ .Template.BasePath }}" .Values.dot}}`)}, {Name: "templates/modified-name", Data: []byte(`{{tpl "{{ .Template.Name }}" .Values.dot}}`)}, - // Current implementation injects the 'tpl' template as if it were a template file, and - // so only BasePath and Name make it through. {Name: "templates/modified-field", Data: []byte(`{{tpl "{{ .Template.Field }}" .Values.dot}}`)}, }, } @@ -979,7 +1155,7 @@ func TestRenderTplTemplateNames(t *testing.T) { "TplTemplateNames/templates/default-name": "TplTemplateNames/templates/default-name", "TplTemplateNames/templates/modified-basepath": "path/to/template", "TplTemplateNames/templates/modified-name": "name-of-template", - "TplTemplateNames/templates/modified-field": "", + "TplTemplateNames/templates/modified-field": "extra-field", } for file, expect := range expects { if out[file] != expect { @@ -1001,13 +1177,17 @@ func TestRenderTplRedefines(t *testing.T) { `{{define "manifest"}}original-in-manifest{{end}}` + `before: {{include "manifest" .}}\n{{tpl .Values.manifestText .}}\nafter: {{include "manifest" .}}`, )}, - // The current implementation replaces the manifest text and re-parses, so a - // partial template defined only in the manifest invoking tpl cannot be accessed - // by that tpl call. - //{Name: "templates/manifest-only", Data: []byte( - // `{{define "manifest-only"}}only-in-manifest{{end}}` + - // `before: {{include "manifest-only" .}}\n{{tpl .Values.manifestOnlyText .}}\nafter: {{include "manifest-only" .}}`, - //)}, + {Name: "templates/manifest-only", Data: []byte( + `{{define "manifest-only"}}only-in-manifest{{end}}` + + `before: {{include "manifest-only" .}}\n{{tpl .Values.manifestOnlyText .}}\nafter: {{include "manifest-only" .}}`, + )}, + {Name: "templates/nested", Data: []byte( + `{{define "nested"}}original-in-manifest{{end}}` + + `{{define "nested-outer"}}original-outer-in-manifest{{end}}` + + `before: {{include "nested" .}} {{include "nested-outer" .}}\n` + + `{{tpl .Values.nestedText .}}\n` + + `after: {{include "nested" .}} {{include "nested-outer" .}}`, + )}, }, } v := chartutil.Values{ @@ -1015,6 +1195,12 @@ func TestRenderTplRedefines(t *testing.T) { "partialText": `{{define "partial"}}redefined-in-tpl{{end}}tpl: {{include "partial" .}}`, "manifestText": `{{define "manifest"}}redefined-in-tpl{{end}}tpl: {{include "manifest" .}}`, "manifestOnlyText": `tpl: {{include "manifest-only" .}}`, + "nestedText": `{{define "nested"}}redefined-in-tpl{{end}}` + + `{{define "nested-outer"}}redefined-outer-in-tpl{{end}}` + + `before-inner-tpl: {{include "nested" .}} {{include "nested-outer" . }}\n` + + `{{tpl .Values.innerText .}}\n` + + `after-inner-tpl: {{include "nested" .}} {{include "nested-outer" . }}`, + "innerText": `{{define "nested"}}redefined-in-inner-tpl{{end}}inner-tpl: {{include "nested" .}} {{include "nested-outer" . }}`, }, "Chart": c.Metadata, "Release": chartutil.Values{ @@ -1028,9 +1214,14 @@ func TestRenderTplRedefines(t *testing.T) { } expects := map[string]string{ - "TplRedefines/templates/partial": `before: original-in-partial\ntpl: original-in-partial\nafter: original-in-partial`, - "TplRedefines/templates/manifest": `before: original-in-manifest\ntpl: redefined-in-tpl\nafter: original-in-manifest`, - //"TplRedefines/templates/manifest-only": `before: only-in-manifest\ntpl: only-in-manifest\nafter: only-in-manifest`, + "TplRedefines/templates/partial": `before: original-in-partial\ntpl: redefined-in-tpl\nafter: original-in-partial`, + "TplRedefines/templates/manifest": `before: original-in-manifest\ntpl: redefined-in-tpl\nafter: original-in-manifest`, + "TplRedefines/templates/manifest-only": `before: only-in-manifest\ntpl: only-in-manifest\nafter: only-in-manifest`, + "TplRedefines/templates/nested": `before: original-in-manifest original-outer-in-manifest\n` + + `before-inner-tpl: redefined-in-tpl redefined-outer-in-tpl\n` + + `inner-tpl: redefined-in-inner-tpl redefined-outer-in-tpl\n` + + `after-inner-tpl: redefined-in-tpl redefined-outer-in-tpl\n` + + `after: original-in-manifest original-outer-in-manifest`, } for file, expect := range expects { if out[file] != expect { diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index b378ca9d6..86a7d698c 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -39,9 +39,28 @@ type lookupFunc = func(apiversion string, resource string, namespace string, nam // This function is considered deprecated, and will be renamed in Helm 4. It will no // longer be a public function. func NewLookupFunction(config *rest.Config) lookupFunc { - return func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) { + return newLookupFunction(clientProviderFromConfig{config: config}) +} + +type ClientProvider interface { + // GetClientFor returns a dynamic.NamespaceableResourceInterface suitable for interacting with resources + // corresponding to the provided apiVersion and kind, as well as a boolean indicating whether the resources + // are namespaced. + GetClientFor(apiVersion, kind string) (dynamic.NamespaceableResourceInterface, bool, error) +} + +type clientProviderFromConfig struct { + config *rest.Config +} + +func (c clientProviderFromConfig) GetClientFor(apiVersion, kind string) (dynamic.NamespaceableResourceInterface, bool, error) { + return getDynamicClientOnKind(apiVersion, kind, c.config) +} + +func newLookupFunction(clientProvider ClientProvider) lookupFunc { + return func(apiversion string, kind string, namespace string, name string) (map[string]interface{}, error) { var client dynamic.ResourceInterface - c, namespaced, err := getDynamicClientOnKind(apiversion, resource, config) + c, namespaced, err := clientProvider.GetClientFor(apiversion, kind) if err != nil { return map[string]interface{}{}, err } diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go index 0d13ade57..a371b52eb 100644 --- a/pkg/getter/plugingetter.go +++ b/pkg/getter/plugingetter.go @@ -17,6 +17,7 @@ package getter import ( "bytes" + "fmt" "os" "os/exec" "path/filepath" @@ -62,6 +63,13 @@ type pluginGetter struct { opts options } +func (p *pluginGetter) setupOptionsEnv(env []string) []string { + env = append(env, fmt.Sprintf("HELM_PLUGIN_USERNAME=%s", p.opts.username)) + env = append(env, fmt.Sprintf("HELM_PLUGIN_PASSWORD=%s", p.opts.password)) + env = append(env, fmt.Sprintf("HELM_PLUGIN_PASS_CREDENTIALS_ALL=%t", p.opts.passCredentialsAll)) + return env +} + // Get runs downloader plugin command func (p *pluginGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { for _, opt := range options { @@ -71,7 +79,7 @@ func (p *pluginGetter) Get(href string, options ...Option) (*bytes.Buffer, error argv := append(commands[1:], p.opts.certFile, p.opts.keyFile, p.opts.caFile, href) prog := exec.Command(filepath.Join(p.base, commands[0]), argv...) plugin.SetupPluginEnv(p.settings, p.name, p.base) - prog.Env = os.Environ() + prog.Env = p.setupOptionsEnv(os.Environ()) buf := bytes.NewBuffer(nil) prog.Stdout = buf prog.Stderr = os.Stderr diff --git a/internal/ignore/doc.go b/pkg/ignore/doc.go similarity index 97% rename from internal/ignore/doc.go rename to pkg/ignore/doc.go index a1f0fcfc8..5245d410e 100644 --- a/internal/ignore/doc.go +++ b/pkg/ignore/doc.go @@ -65,4 +65,4 @@ Notable differences from .gitignore: - The evaluation of escape sequences has not been tested for compatibility - There is no support for '\!' as a special leading sequence. */ -package ignore // import "helm.sh/helm/v3/internal/ignore" +package ignore // import "helm.sh/helm/v3/pkg/ignore" diff --git a/internal/ignore/rules.go b/pkg/ignore/rules.go similarity index 100% rename from internal/ignore/rules.go rename to pkg/ignore/rules.go diff --git a/internal/ignore/rules_test.go b/pkg/ignore/rules_test.go similarity index 100% rename from internal/ignore/rules_test.go rename to pkg/ignore/rules_test.go diff --git a/internal/ignore/testdata/.helmignore b/pkg/ignore/testdata/.helmignore similarity index 100% rename from internal/ignore/testdata/.helmignore rename to pkg/ignore/testdata/.helmignore diff --git a/internal/ignore/testdata/a.txt b/pkg/ignore/testdata/.joonix similarity index 100% rename from internal/ignore/testdata/a.txt rename to pkg/ignore/testdata/.joonix diff --git a/internal/ignore/testdata/cargo/a.txt b/pkg/ignore/testdata/a.txt similarity index 100% rename from internal/ignore/testdata/cargo/a.txt rename to pkg/ignore/testdata/a.txt diff --git a/internal/ignore/testdata/mast/a.txt b/pkg/ignore/testdata/cargo/a.txt similarity index 100% rename from internal/ignore/testdata/mast/a.txt rename to pkg/ignore/testdata/cargo/a.txt diff --git a/internal/ignore/testdata/cargo/b.txt b/pkg/ignore/testdata/cargo/b.txt similarity index 100% rename from internal/ignore/testdata/cargo/b.txt rename to pkg/ignore/testdata/cargo/b.txt diff --git a/internal/ignore/testdata/cargo/c.txt b/pkg/ignore/testdata/cargo/c.txt similarity index 100% rename from internal/ignore/testdata/cargo/c.txt rename to pkg/ignore/testdata/cargo/c.txt diff --git a/internal/ignore/testdata/helm.txt b/pkg/ignore/testdata/helm.txt similarity index 100% rename from internal/ignore/testdata/helm.txt rename to pkg/ignore/testdata/helm.txt diff --git a/internal/ignore/testdata/mast/b.txt b/pkg/ignore/testdata/mast/a.txt similarity index 100% rename from internal/ignore/testdata/mast/b.txt rename to pkg/ignore/testdata/mast/a.txt diff --git a/internal/ignore/testdata/mast/c.txt b/pkg/ignore/testdata/mast/b.txt similarity index 100% rename from internal/ignore/testdata/mast/c.txt rename to pkg/ignore/testdata/mast/b.txt diff --git a/internal/ignore/testdata/rudder.txt b/pkg/ignore/testdata/mast/c.txt similarity index 100% rename from internal/ignore/testdata/rudder.txt rename to pkg/ignore/testdata/mast/c.txt diff --git a/internal/ignore/testdata/templates/.dotfile b/pkg/ignore/testdata/rudder.txt similarity index 100% rename from internal/ignore/testdata/templates/.dotfile rename to pkg/ignore/testdata/rudder.txt diff --git a/internal/ignore/testdata/tiller.txt b/pkg/ignore/testdata/templates/.dotfile similarity index 100% rename from internal/ignore/testdata/tiller.txt rename to pkg/ignore/testdata/templates/.dotfile diff --git a/pkg/ignore/testdata/tiller.txt b/pkg/ignore/testdata/tiller.txt new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 0772678d1..9df833a43 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -467,7 +467,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err // if one or more fail and collect any errors. All successfully deleted items // will be returned in the `Deleted` ResourceList that is part of the result. func (c *Client) Delete(resources ResourceList) (*Result, []error) { - return delete(c, resources, metav1.DeletePropagationBackground) + return rdelete(c, resources, metav1.DeletePropagationBackground) } // Delete deletes Kubernetes resources specified in the resources list with @@ -475,10 +475,10 @@ func (c *Client) Delete(resources ResourceList) (*Result, []error) { // if one or more fail and collect any errors. All successfully deleted items // will be returned in the `Deleted` ResourceList that is part of the result. func (c *Client) DeleteWithPropagationPolicy(resources ResourceList, policy metav1.DeletionPropagation) (*Result, []error) { - return delete(c, resources, policy) + return rdelete(c, resources, policy) } -func delete(c *Client, resources ResourceList, propagation metav1.DeletionPropagation) (*Result, []error) { +func rdelete(c *Client, resources ResourceList, propagation metav1.DeletionPropagation) (*Result, []error) { var errs []error res := &Result{} mtx := sync.Mutex{} @@ -772,7 +772,7 @@ func (c *Client) waitForJob(obj runtime.Object, name string) (bool, error) { if c.Type == batch.JobComplete && c.Status == "True" { return true, nil } else if c.Type == batch.JobFailed && c.Status == "True" { - return true, errors.Errorf("job failed: %s", c.Reason) + return true, errors.Errorf("job %s failed: %s", name, c.Reason) } } diff --git a/pkg/kube/fake/printer.go b/pkg/kube/fake/printer.go index e6c4b6207..cc2c84b40 100644 --- a/pkg/kube/fake/printer.go +++ b/pkg/kube/fake/printer.go @@ -49,7 +49,7 @@ func (p *PrintingKubeClient) Create(resources kube.ResourceList) (*kube.Result, return &kube.Result{Created: resources}, nil } -func (p *PrintingKubeClient) Get(resources kube.ResourceList, related bool) (map[string][]runtime.Object, error) { +func (p *PrintingKubeClient) Get(resources kube.ResourceList, _ bool) (map[string][]runtime.Object, error) { _, err := io.Copy(p.Out, bufferize(resources)) if err != nil { return nil, err @@ -119,7 +119,7 @@ func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Durati // DeleteWithPropagationPolicy implements KubeClient delete. // // It only prints out the content to be deleted. -func (p *PrintingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, policy metav1.DeletionPropagation) (*kube.Result, []error) { +func (p *PrintingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, _ metav1.DeletionPropagation) (*kube.Result, []error) { _, err := io.Copy(p.Out, bufferize(resources)) if err != nil { return nil, []error{err} diff --git a/pkg/kube/ready.go b/pkg/kube/ready.go index 7172a42bc..b2d26ba76 100644 --- a/pkg/kube/ready.go +++ b/pkg/kube/ready.go @@ -90,13 +90,6 @@ type ReadyChecker struct { // IsReady will fetch the latest state of the object from the server prior to // performing readiness checks, and it will return any error encountered. func (c *ReadyChecker) IsReady(ctx context.Context, v *resource.Info) (bool, error) { - var ( - // This defaults to true, otherwise we get to a point where - // things will always return false unless one of the objects - // that manages pods has been hit - ok = true - err error - ) switch value := AsVersioned(v).(type) { case *corev1.Pod: pod, err := c.client.CoreV1().Pods(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) @@ -183,11 +176,30 @@ func (c *ReadyChecker) IsReady(ctx context.Context, v *resource.Info) (bool, err if !c.statefulSetReady(sts) { return false, nil } - case *corev1.ReplicationController, *extensionsv1beta1.ReplicaSet, *appsv1beta2.ReplicaSet, *appsv1.ReplicaSet: - ok, err = c.podsReadyForObject(ctx, v.Namespace, value) - } - if !ok || err != nil { - return false, err + case *corev1.ReplicationController: + rc, err := c.client.CoreV1().ReplicationControllers(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !c.replicationControllerReady(rc) { + return false, nil + } + ready, err := c.podsReadyForObject(ctx, v.Namespace, value) + if !ready || err != nil { + return false, err + } + case *extensionsv1beta1.ReplicaSet, *appsv1beta2.ReplicaSet, *appsv1.ReplicaSet: + rs, err := c.client.AppsV1().ReplicaSets(v.Namespace).Get(ctx, v.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !c.replicaSetReady(rs) { + return false, nil + } + ready, err := c.podsReadyForObject(ctx, v.Namespace, value) + if !ready || err != nil { + return false, err + } } return true, nil } @@ -276,6 +288,16 @@ func (c *ReadyChecker) volumeReady(v *corev1.PersistentVolumeClaim) bool { } func (c *ReadyChecker) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deployment) bool { + // Verify the replicaset readiness + if !c.replicaSetReady(rs) { + return false + } + // Verify the generation observed by the deployment controller matches the spec generation + if dep.Status.ObservedGeneration != dep.ObjectMeta.Generation { + c.log("Deployment is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", dep.Namespace, dep.Name, dep.Status.ObservedGeneration, dep.ObjectMeta.Generation) + return false + } + expectedReady := *dep.Spec.Replicas - deploymentutil.MaxUnavailable(*dep) if !(rs.Status.ReadyReplicas >= expectedReady) { c.log("Deployment is not ready: %s/%s. %d out of %d expected pods are ready", dep.Namespace, dep.Name, rs.Status.ReadyReplicas, expectedReady) @@ -285,6 +307,12 @@ func (c *ReadyChecker) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deploy } func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { + // Verify the generation observed by the daemonSet controller matches the spec generation + if ds.Status.ObservedGeneration != ds.ObjectMeta.Generation { + c.log("DaemonSet is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", ds.Namespace, ds.Name, ds.Status.ObservedGeneration, ds.ObjectMeta.Generation) + return false + } + // If the update strategy is not a rolling update, there will be nothing to wait for if ds.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType { return true @@ -355,22 +383,22 @@ func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool { } func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { + // Verify the generation observed by the statefulSet controller matches the spec generation + if sts.Status.ObservedGeneration != sts.ObjectMeta.Generation { + c.log("StatefulSet is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", sts.Namespace, sts.Name, sts.Status.ObservedGeneration, sts.ObjectMeta.Generation) + return false + } + // If the update strategy is not a rolling update, there will be nothing to wait for if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType { c.log("StatefulSet skipped ready check: %s/%s. updateStrategy is %v", sts.Namespace, sts.Name, sts.Spec.UpdateStrategy.Type) return true } - // Make sure the status is up-to-date with the StatefulSet changes - if sts.Status.ObservedGeneration < sts.Generation { - c.log("StatefulSet is not ready: %s/%s. update has not yet been observed", sts.Namespace, sts.Name) - return false - } - // Dereference all the pointers because StatefulSets like them var partition int // 1 is the default for replicas if not set - var replicas = 1 + replicas := 1 // For some reason, even if the update strategy is a rolling update, the // actual rollingUpdate field can be nil. If it is, we can safely assume // there is no partition value @@ -409,6 +437,24 @@ func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { return true } +func (c *ReadyChecker) replicationControllerReady(rc *corev1.ReplicationController) bool { + // Verify the generation observed by the replicationController controller matches the spec generation + if rc.Status.ObservedGeneration != rc.ObjectMeta.Generation { + c.log("ReplicationController is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", rc.Namespace, rc.Name, rc.Status.ObservedGeneration, rc.ObjectMeta.Generation) + return false + } + return true +} + +func (c *ReadyChecker) replicaSetReady(rs *appsv1.ReplicaSet) bool { + // Verify the generation observed by the replicaSet controller matches the spec generation + if rs.Status.ObservedGeneration != rs.ObjectMeta.Generation { + c.log("ReplicaSet is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", rs.Namespace, rs.Name, rs.Status.ObservedGeneration, rs.ObjectMeta.Generation) + return false + } + return true +} + func getPods(ctx context.Context, client kubernetes.Interface, namespace, selector string) ([]corev1.Pod, error) { list, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ LabelSelector: selector, diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go index e8e71d8aa..3b8c4b80a 100644 --- a/pkg/kube/ready_test.go +++ b/pkg/kube/ready_test.go @@ -43,27 +43,51 @@ func Test_ReadyChecker_deploymentReady(t *testing.T) { { name: "deployment is ready", args: args{ - rs: newReplicaSet("foo", 1, 1), - dep: newDeployment("foo", 1, 1, 0), + rs: newReplicaSet("foo", 1, 1, true), + dep: newDeployment("foo", 1, 1, 0, true), }, want: true, }, { name: "deployment is not ready", args: args{ - rs: newReplicaSet("foo", 0, 0), - dep: newDeployment("foo", 1, 1, 0), + rs: newReplicaSet("foo", 0, 0, true), + dep: newDeployment("foo", 1, 1, 0, true), }, want: false, }, { name: "deployment is ready when maxUnavailable is set", args: args{ - rs: newReplicaSet("foo", 2, 1), - dep: newDeployment("foo", 2, 1, 1), + rs: newReplicaSet("foo", 2, 1, true), + dep: newDeployment("foo", 2, 1, 1, true), }, want: true, }, + { + name: "deployment is not ready when replicaset generations are out of sync", + args: args{ + rs: newReplicaSet("foo", 1, 1, false), + dep: newDeployment("foo", 1, 1, 0, true), + }, + want: false, + }, + { + name: "deployment is not ready when deployment generations are out of sync", + args: args{ + rs: newReplicaSet("foo", 1, 1, true), + dep: newDeployment("foo", 1, 1, 0, false), + }, + want: false, + }, + { + name: "deployment is not ready when generations are out of sync", + args: args{ + rs: newReplicaSet("foo", 1, 1, false), + dep: newDeployment("foo", 1, 1, 0, false), + }, + want: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -75,6 +99,74 @@ func Test_ReadyChecker_deploymentReady(t *testing.T) { } } +func Test_ReadyChecker_replicaSetReady(t *testing.T) { + type args struct { + rs *appsv1.ReplicaSet + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "replicaSet is ready", + args: args{ + rs: newReplicaSet("foo", 1, 1, true), + }, + want: true, + }, + { + name: "replicaSet is not ready when generations are out of sync", + args: args{ + rs: newReplicaSet("foo", 1, 1, false), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := NewReadyChecker(fake.NewSimpleClientset(), nil) + if got := c.replicaSetReady(tt.args.rs); got != tt.want { + t.Errorf("replicaSetReady() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_ReadyChecker_replicationControllerReady(t *testing.T) { + type args struct { + rc *corev1.ReplicationController + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "replicationController is ready", + args: args{ + rc: newReplicationController("foo", true), + }, + want: true, + }, + { + name: "replicationController is not ready when generations are out of sync", + args: args{ + rc: newReplicationController("foo", false), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := NewReadyChecker(fake.NewSimpleClientset(), nil) + if got := c.replicationControllerReady(tt.args.rc); got != tt.want { + t.Errorf("replicationControllerReady() = %v, want %v", got, tt.want) + } + }) + } +} + func Test_ReadyChecker_daemonSetReady(t *testing.T) { type args struct { ds *appsv1.DaemonSet @@ -87,31 +179,38 @@ func Test_ReadyChecker_daemonSetReady(t *testing.T) { { name: "daemonset is ready", args: args{ - ds: newDaemonSet("foo", 0, 1, 1, 1), + ds: newDaemonSet("foo", 0, 1, 1, 1, true), }, want: true, }, { name: "daemonset is not ready", args: args{ - ds: newDaemonSet("foo", 0, 0, 1, 1), + ds: newDaemonSet("foo", 0, 0, 1, 1, true), }, want: false, }, { name: "daemonset pods have not been scheduled successfully", args: args{ - ds: newDaemonSet("foo", 0, 0, 1, 0), + ds: newDaemonSet("foo", 0, 0, 1, 0, true), }, want: false, }, { name: "daemonset is ready when maxUnavailable is set", args: args{ - ds: newDaemonSet("foo", 1, 1, 2, 2), + ds: newDaemonSet("foo", 1, 1, 2, 2, true), }, want: true, }, + { + name: "daemonset is not ready when generations are out of sync", + args: args{ + ds: newDaemonSet("foo", 0, 1, 1, 1, false), + }, + want: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -135,63 +234,56 @@ func Test_ReadyChecker_statefulSetReady(t *testing.T) { { name: "statefulset is ready", args: args{ - sts: newStatefulSet("foo", 1, 0, 1, 1), + sts: newStatefulSet("foo", 1, 0, 1, 1, true), }, want: true, }, { name: "statefulset is not ready", args: args{ - sts: newStatefulSet("foo", 1, 0, 0, 1), + sts: newStatefulSet("foo", 1, 0, 0, 1, true), }, want: false, }, { name: "statefulset is ready when partition is specified", args: args{ - sts: newStatefulSet("foo", 2, 1, 2, 1), + sts: newStatefulSet("foo", 2, 1, 2, 1, true), }, want: true, }, { name: "statefulset is not ready when partition is set", args: args{ - sts: newStatefulSet("foo", 2, 1, 1, 0), + sts: newStatefulSet("foo", 2, 1, 1, 0, true), }, want: false, }, { name: "statefulset is ready when partition is set and no change in template", args: args{ - sts: newStatefulSet("foo", 2, 1, 2, 2), + sts: newStatefulSet("foo", 2, 1, 2, 2, true), }, want: true, }, { name: "statefulset is ready when partition is greater than replicas", args: args{ - sts: newStatefulSet("foo", 1, 2, 1, 1), + sts: newStatefulSet("foo", 1, 2, 1, 1, true), }, want: true, }, { - name: "statefulset is not ready when status of latest generation has not yet been observed", + name: "statefulset is not ready when generations are out of sync", args: args{ - sts: newStatefulSetWithNewGeneration("foo", 1, 0, 1, 1), - }, - want: false, - }, - { - name: "statefulset is not ready when current revision for current replicas does not match update revision for updated replicas", - args: args{ - sts: newStatefulSetWithUpdateRevision("foo", 1, 0, 1, 1, "foo-bbbbbbb"), + sts: newStatefulSet("foo", 1, 0, 1, 1, false), }, want: false, }, { name: "statefulset is ready when current revision for current replicas does not match update revision for updated replicas when using partition !=0", args: args{ - sts: newStatefulSetWithUpdateRevision("foo", 3, 2, 3, 3, "foo-bbbbbbb"), + sts: newStatefulSetWithUpdateRevision("foo", 3, 2, 3, 3, "foo-bbbbbbb", true), }, want: true, }, @@ -222,7 +314,7 @@ func Test_ReadyChecker_podsReadyForObject(t *testing.T) { name: "pods ready for a replicaset", args: args{ namespace: defaultNamespace, - obj: newReplicaSet("foo", 1, 1), + obj: newReplicaSet("foo", 1, 1, true), }, existPods: []corev1.Pod{ *newPodWithCondition("foo", corev1.ConditionTrue), @@ -234,7 +326,7 @@ func Test_ReadyChecker_podsReadyForObject(t *testing.T) { name: "pods not ready for a replicaset", args: args{ namespace: defaultNamespace, - obj: newReplicaSet("foo", 1, 1), + obj: newReplicaSet("foo", 1, 1, true), }, existPods: []corev1.Pod{ *newPodWithCondition("foo", corev1.ConditionFalse), @@ -371,11 +463,22 @@ func Test_ReadyChecker_volumeReady(t *testing.T) { } } -func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberScheduled, updatedNumberScheduled int) *appsv1.DaemonSet { +func newStatefulSetWithUpdateRevision(name string, replicas, partition, readyReplicas, updatedReplicas int, updateRevision string, generationInSync bool) *appsv1.StatefulSet { + ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas, generationInSync) + ss.Status.UpdateRevision = updateRevision + return ss +} + +func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberScheduled, updatedNumberScheduled int, generationInSync bool) *appsv1.DaemonSet { + var generation, observedGeneration int64 = 1, 1 + if !generationInSync { + generation = 2 + } return &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, + Name: name, + Namespace: defaultNamespace, + Generation: generation, }, Spec: appsv1.DaemonSetSpec{ UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ @@ -403,16 +506,21 @@ func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberSchedul DesiredNumberScheduled: int32(desiredNumberScheduled), NumberReady: int32(numberReady), UpdatedNumberScheduled: int32(updatedNumberScheduled), + ObservedGeneration: observedGeneration, }, } } -func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet { +func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int, generationInSync bool) *appsv1.StatefulSet { + var generation, observedGeneration int64 = 1, 1 + if !generationInSync { + generation = 2 + } return &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: defaultNamespace, - Generation: int64(1), + Generation: generation, }, Spec: appsv1.StatefulSetSpec{ UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ @@ -438,32 +546,23 @@ func newStatefulSet(name string, replicas, partition, readyReplicas, updatedRepl }, }, Status: appsv1.StatefulSetStatus{ - ObservedGeneration: int64(1), - CurrentRevision: name + "-aaaaaaa", - UpdateRevision: name + "-aaaaaaa", UpdatedReplicas: int32(updatedReplicas), ReadyReplicas: int32(readyReplicas), + ObservedGeneration: observedGeneration, }, } } -func newStatefulSetWithNewGeneration(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet { - ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas) - ss.Generation++ - return ss -} - -func newStatefulSetWithUpdateRevision(name string, replicas, partition, readyReplicas, updatedReplicas int, updateRevision string) *appsv1.StatefulSet { - ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas) - ss.Status.UpdateRevision = updateRevision - return ss -} - -func newDeployment(name string, replicas, maxSurge, maxUnavailable int) *appsv1.Deployment { +func newDeployment(name string, replicas, maxSurge, maxUnavailable int, generationInSync bool) *appsv1.Deployment { + var generation, observedGeneration int64 = 1, 1 + if !generationInSync { + generation = 2 + } return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, + Name: name, + Namespace: defaultNamespace, + Generation: generation, }, Spec: appsv1.DeploymentSpec{ Strategy: appsv1.DeploymentStrategy{ @@ -489,17 +588,37 @@ func newDeployment(name string, replicas, maxSurge, maxUnavailable int) *appsv1. }, }, }, + Status: appsv1.DeploymentStatus{ + ObservedGeneration: observedGeneration, + }, } } -func newReplicaSet(name string, replicas int, readyReplicas int) *appsv1.ReplicaSet { - d := newDeployment(name, replicas, 0, 0) +func newReplicationController(name string, generationInSync bool) *corev1.ReplicationController { + var generation, observedGeneration int64 = 1, 1 + if !generationInSync { + generation = 2 + } + return &corev1.ReplicationController{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Generation: generation, + }, + Status: corev1.ReplicationControllerStatus{ + ObservedGeneration: observedGeneration, + }, + } +} + +func newReplicaSet(name string, replicas int, readyReplicas int, generationInSync bool) *appsv1.ReplicaSet { + d := newDeployment(name, replicas, 0, 0, generationInSync) return &appsv1.ReplicaSet{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: defaultNamespace, Labels: d.Spec.Selector.MatchLabels, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, d.GroupVersionKind())}, + Generation: d.Generation, }, Spec: appsv1.ReplicaSetSpec{ Selector: d.Spec.Selector, @@ -507,7 +626,8 @@ func newReplicaSet(name string, replicas int, readyReplicas int) *appsv1.Replica Template: d.Spec.Template, }, Status: appsv1.ReplicaSetStatus{ - ReadyReplicas: int32(readyReplicas), + ReadyReplicas: int32(readyReplicas), + ObservedGeneration: d.Status.ObservedGeneration, }, } } diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go index 67e76bd3d..c0e79f55b 100644 --- a/pkg/lint/lint.go +++ b/pkg/lint/lint.go @@ -19,19 +19,25 @@ package lint // import "helm.sh/helm/v3/pkg/lint" import ( "path/filepath" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/lint/rules" "helm.sh/helm/v3/pkg/lint/support" ) // All runs all of the available linters on the given base directory. -func All(basedir string, values map[string]interface{}, namespace string, strict bool) support.Linter { +func All(basedir string, values map[string]interface{}, namespace string, _ bool) support.Linter { + return AllWithKubeVersion(basedir, values, namespace, nil) +} + +// AllWithKubeVersion runs all the available linters on the given base directory, allowing to specify the kubernetes version. +func AllWithKubeVersion(basedir string, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) support.Linter { // Using abs path to get directory context chartDir, _ := filepath.Abs(basedir) linter := support.Linter{ChartDir: chartDir} rules.Chartfile(&linter) rules.ValuesWithOverrides(&linter, values) - rules.Templates(&linter, values, namespace, strict) + rules.TemplatesWithKubeVersion(&linter, values, namespace, kubeVersion) rules.Dependencies(&linter) return linter } diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go index 70532ad4f..910602b7d 100644 --- a/pkg/lint/rules/chartfile.go +++ b/pkg/lint/rules/chartfile.go @@ -106,6 +106,10 @@ func validateChartName(cf *chart.Metadata) error { if cf.Name == "" { return errors.New("name is required") } + name := filepath.Base(cf.Name) + if name != cf.Name { + return fmt.Errorf("chart name %q is invalid", cf.Name) + } return nil } diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go index 087cda047..a06d7dc31 100644 --- a/pkg/lint/rules/chartfile_test.go +++ b/pkg/lint/rules/chartfile_test.go @@ -30,16 +30,19 @@ import ( ) const ( + badCharNametDir = "testdata/badchartname" badChartDir = "testdata/badchartfile" anotherBadChartDir = "testdata/anotherbadchartfile" ) var ( + badChartNamePath = filepath.Join(badCharNametDir, "Chart.yaml") badChartFilePath = filepath.Join(badChartDir, "Chart.yaml") nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") ) var badChart, _ = chartutil.LoadChartfile(badChartFilePath) +var badChartName, _ = chartutil.LoadChartfile(badChartNamePath) // Validation functions Test func TestValidateChartYamlNotDirectory(t *testing.T) { @@ -69,6 +72,11 @@ func TestValidateChartName(t *testing.T) { if err == nil { t.Errorf("validateChartName to return a linter error, got no error") } + + err = validateChartName(badChartName) + if err == nil { + t.Error("expected validateChartName to return a linter error for an invalid name, got no error") + } } func TestValidateChartVersion(t *testing.T) { diff --git a/pkg/lint/rules/dependencies.go b/pkg/lint/rules/dependencies.go index abecd1feb..f1ab1dcad 100644 --- a/pkg/lint/rules/dependencies.go +++ b/pkg/lint/rules/dependencies.go @@ -37,6 +37,7 @@ func Dependencies(linter *support.Linter) { } linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependencyInMetadata(c)) + linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependenciesUnique(c)) linter.RunLinterRule(support.WarningSev, linter.ChartDir, validateDependencyInChartsDir(c)) } @@ -80,3 +81,23 @@ func validateDependencyInMetadata(c *chart.Chart) (err error) { } return err } + +func validateDependenciesUnique(c *chart.Chart) (err error) { + dependencies := map[string]*chart.Dependency{} + shadowing := []string{} + + for _, dep := range c.Metadata.Dependencies { + key := dep.Name + if dep.Alias != "" { + key = dep.Alias + } + if dependencies[key] != nil { + shadowing = append(shadowing, key) + } + dependencies[key] = dep + } + if len(shadowing) > 0 { + err = fmt.Errorf("multiple dependencies with name or alias: %s", strings.Join(shadowing, ",")) + } + return err +} diff --git a/pkg/lint/rules/dependencies_test.go b/pkg/lint/rules/dependencies_test.go index 24c5faf7f..c0afff133 100644 --- a/pkg/lint/rules/dependencies_test.go +++ b/pkg/lint/rules/dependencies_test.go @@ -76,6 +76,67 @@ func TestValidateDependencyInMetadata(t *testing.T) { } } +func TestValidateDependenciesUnique(t *testing.T) { + tests := []struct { + chart chart.Chart + }{ + {chart.Chart{ + Metadata: &chart.Metadata{ + Name: "badchart", + Version: "0.1.0", + APIVersion: "v2", + Dependencies: []*chart.Dependency{ + { + Name: "foo", + }, + { + Name: "foo", + }, + }, + }, + }}, + {chart.Chart{ + Metadata: &chart.Metadata{ + Name: "badchart", + Version: "0.1.0", + APIVersion: "v2", + Dependencies: []*chart.Dependency{ + { + Name: "foo", + Alias: "bar", + }, + { + Name: "bar", + }, + }, + }, + }}, + {chart.Chart{ + Metadata: &chart.Metadata{ + Name: "badchart", + Version: "0.1.0", + APIVersion: "v2", + Dependencies: []*chart.Dependency{ + { + Name: "foo", + Alias: "baz", + }, + { + Name: "bar", + Alias: "baz", + }, + }, + }, + }}, + } + + for _, tt := range tests { + if err := validateDependenciesUnique(&tt.chart); err == nil { + t.Errorf("chart should have been flagged for dependency shadowing") + } + } +} + func TestDependencies(t *testing.T) { tmp := t.TempDir() diff --git a/pkg/lint/rules/deprecations.go b/pkg/lint/rules/deprecations.go index ce19b91d5..90e7748a4 100644 --- a/pkg/lint/rules/deprecations.go +++ b/pkg/lint/rules/deprecations.go @@ -24,6 +24,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/endpoints/deprecation" kscheme "k8s.io/client-go/kubernetes/scheme" + + "helm.sh/helm/v3/pkg/chartutil" ) var ( @@ -45,7 +47,7 @@ func (e deprecatedAPIError) Error() string { return msg } -func validateNoDeprecations(resource *K8sYamlStruct) error { +func validateNoDeprecations(resource *K8sYamlStruct, kubeVersion *chartutil.KubeVersion) error { // if `resource` does not have an APIVersion or Kind, we cannot test it for deprecation if resource.APIVersion == "" { return nil @@ -54,6 +56,14 @@ func validateNoDeprecations(resource *K8sYamlStruct) error { return nil } + majorVersion := k8sVersionMajor + minorVersion := k8sVersionMinor + + if kubeVersion != nil { + majorVersion = kubeVersion.Major + minorVersion = kubeVersion.Minor + } + runtimeObject, err := resourceToRuntimeObject(resource) if err != nil { // do not error for non-kubernetes resources @@ -62,11 +72,12 @@ func validateNoDeprecations(resource *K8sYamlStruct) error { } return err } - maj, err := strconv.Atoi(k8sVersionMajor) + + maj, err := strconv.Atoi(majorVersion) if err != nil { return err } - min, err := strconv.Atoi(k8sVersionMinor) + min, err := strconv.Atoi(minorVersion) if err != nil { return err } diff --git a/pkg/lint/rules/deprecations_test.go b/pkg/lint/rules/deprecations_test.go index 96e072d14..cf2409007 100644 --- a/pkg/lint/rules/deprecations_test.go +++ b/pkg/lint/rules/deprecations_test.go @@ -23,7 +23,7 @@ func TestValidateNoDeprecations(t *testing.T) { APIVersion: "extensions/v1beta1", Kind: "Deployment", } - err := validateNoDeprecations(deprecated) + err := validateNoDeprecations(deprecated, nil) if err == nil { t.Fatal("Expected deprecated extension to be flagged") } @@ -35,7 +35,7 @@ func TestValidateNoDeprecations(t *testing.T) { if err := validateNoDeprecations(&K8sYamlStruct{ APIVersion: "v1", Kind: "Pod", - }); err != nil { + }, nil); err != nil { t.Errorf("Expected a v1 Pod to not be deprecated") } } diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 000f7ebcf..aa1dbb701 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -45,7 +45,12 @@ var ( ) // Templates lints the templates in the Linter. -func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) { +func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) { + TemplatesWithKubeVersion(linter, values, namespace, nil) +} + +// TemplatesWithKubeVersion lints the templates in the Linter, allowing to specify the kubernetes version. +func TemplatesWithKubeVersion(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) { fpath := "templates/" templatesPath := filepath.Join(linter.ChartDir, fpath) @@ -70,6 +75,11 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace Namespace: namespace, } + caps := chartutil.DefaultCapabilities.Copy() + if kubeVersion != nil { + caps.KubeVersion = *kubeVersion + } + // lint ignores import-values // See https://github.com/helm/helm/issues/9658 if err := chartutil.ProcessDependenciesWithMerge(chart, values); err != nil { @@ -80,7 +90,8 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace if err != nil { return } - valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, nil) + + valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, caps) if err != nil { linter.RunLinterRule(support.ErrorSev, fpath, err) return @@ -150,7 +161,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace // NOTE: set to warnings to allow users to support out-of-date kubernetes // Refs https://github.com/helm/helm/issues/8596 linter.RunLinterRule(support.WarningSev, fpath, validateMetadataName(yamlStruct)) - linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct)) + linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct, kubeVersion)) linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent)) linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent)) diff --git a/pkg/lint/rules/testdata/badchartname/Chart.yaml b/pkg/lint/rules/testdata/badchartname/Chart.yaml new file mode 100644 index 000000000..64f8fb8bf --- /dev/null +++ b/pkg/lint/rules/testdata/badchartname/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +version: 0.1.0 +name: "../badchartname" +type: application diff --git a/pkg/lint/rules/testdata/badchartname/values.yaml b/pkg/lint/rules/testdata/badchartname/values.yaml new file mode 100644 index 000000000..9f367033b --- /dev/null +++ b/pkg/lint/rules/testdata/badchartname/values.yaml @@ -0,0 +1 @@ +# Default values for badchartfile. diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go index 177227c5b..f0fe36ecd 100644 --- a/pkg/plugin/installer/http_installer_test.go +++ b/pkg/plugin/installer/http_installer_test.go @@ -44,7 +44,7 @@ type TestHTTPGetter struct { MockError error } -func (t *TestHTTPGetter) Get(href string, _ ...getter.Option) (*bytes.Buffer, error) { +func (t *TestHTTPGetter) Get(_ string, _ ...getter.Option) (*bytes.Buffer, error) { return t.MockResponse, t.MockError } diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index fb3bc5215..5bb743481 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -175,6 +175,10 @@ var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$") // validatePluginData validates a plugin's YAML data. func validatePluginData(plug *Plugin, filepath string) error { + // When metadata section missing, initialize with no data + if plug.Metadata == nil { + plug.Metadata = &Metadata{} + } if !validPluginName.MatchString(plug.Metadata.Name) { return fmt.Errorf("invalid plugin name at %q", filepath) } diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index abbc90f7d..725052346 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -91,6 +91,7 @@ func TestPlatformPrepareCommand(t *testing.T) { {OperatingSystem: "linux", Architecture: "arm64", Command: "echo -n linux-arm64"}, {OperatingSystem: "linux", Architecture: "ppc64le", Command: "echo -n linux-ppc64le"}, {OperatingSystem: "linux", Architecture: "s390x", Command: "echo -n linux-s390x"}, + {OperatingSystem: "linux", Architecture: "riscv64", Command: "echo -n linux-riscv64"}, {OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"}, }, }, @@ -108,6 +109,8 @@ func TestPlatformPrepareCommand(t *testing.T) { osStrCmp = "linux-ppc64le" } else if os == "linux" && arch == "s390x" { osStrCmp = "linux-s390x" + } else if os == "linux" && arch == "riscv64" { + osStrCmp = "linux-riscv64" } else if os == "windows" && arch == "amd64" { osStrCmp = "win-64" } else { @@ -350,6 +353,11 @@ func TestSetupEnvWithSpace(t *testing.T) { } func TestValidatePluginData(t *testing.T) { + // A mock plugin missing any metadata. + mockMissingMeta := &Plugin{ + Dir: "no-such-dir", + } + for i, item := range []struct { pass bool plug *Plugin @@ -360,6 +368,7 @@ func TestValidatePluginData(t *testing.T) { {false, mockPlugin("$foo -bar")}, // Test leading chars {false, mockPlugin("foo -bar ")}, // Test trailing chars {false, mockPlugin("foo\nbar")}, // Test newline + {false, mockMissingMeta}, // Test if the metadata section missing } { err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i)) if item.pass && err != nil { diff --git a/pkg/pusher/pusher.go b/pkg/pusher/pusher.go index c99d97b35..5b8a9160f 100644 --- a/pkg/pusher/pusher.go +++ b/pkg/pusher/pusher.go @@ -116,7 +116,7 @@ var ociProvider = Provider{ // All finds all of the registered pushers as a list of Provider instances. // Currently, just the built-in pushers are collected. -func All(settings *cli.EnvSettings) Providers { +func All(_ *cli.EnvSettings) Providers { result := Providers{ociProvider} return result } diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 0dfa6926f..7538cf69b 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -96,23 +96,8 @@ func NewClient(options ...ClientOption) (*Client, error) { return resolver, nil } } - headers := http.Header{} headers.Set("User-Agent", version.GetUserAgent()) - dockerClient, ok := client.authorizer.(*dockerauth.Client) - if ok { - username, password, err := dockerClient.Credential(ref.Registry) - if err != nil { - return nil, errors.New("unable to retrieve credentials") - } - // A blank returned username and password value is a bearer token - if username == "" && password != "" { - headers.Set("Authorization", fmt.Sprintf("Bearer %s", password)) - } else { - headers.Set("Authorization", fmt.Sprintf("Basic %s", basicAuth(username, password))) - } - } - opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)} if client.httpClient != nil { opts = append(opts, auth.WithResolverClient(client.httpClient)) @@ -144,6 +129,7 @@ func NewClient(options ...ClientOption) (*Client, error) { if !ok { return registryauth.EmptyCredential, errors.New("unable to obtain docker client") } + username, password, err := dockerClient.Credential(reg) if err != nil { return registryauth.EmptyCredential, errors.New("unable to retrieve credentials") @@ -607,6 +593,7 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu if err := memoryStore.StoreManifest(parsedRef.String(), manifest, manifestData); err != nil { return nil, err } + remotesResolver, err := c.resolver(parsedRef) if err != nil { return nil, err diff --git a/pkg/registry/util.go b/pkg/registry/util.go index ca93297e6..8baf0852a 100644 --- a/pkg/registry/util.go +++ b/pkg/registry/util.go @@ -19,7 +19,6 @@ package registry // import "helm.sh/helm/v3/pkg/registry" import ( "bytes" "context" - "encoding/base64" "fmt" "io" "net/http" @@ -246,13 +245,3 @@ func addToMap(inputMap map[string]string, newKey string, newValue string) map[st return inputMap } - -// See 2 (end of page 4) https://www.ietf.org/rfc/rfc2617.txt -// "To receive authorization, the client sends the userid and password, -// separated by a single colon (":") character, within a base64 -// encoded string in the credentials." -// It is not meant to be urlencoded. -func basicAuth(username, password string) string { - auth := username + ":" + password - return base64.StdEncoding.EncodeToString([]byte(auth)) -} diff --git a/pkg/registry/util_test.go b/pkg/registry/util_test.go index f08c1fef1..fdf09360b 100644 --- a/pkg/registry/util_test.go +++ b/pkg/registry/util_test.go @@ -238,31 +238,3 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) { } } - -func Test_basicAuth(t *testing.T) { - type args struct { - username string - password string - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Basic Auth", - args: args{ - username: "admin", - password: "passw0rd", - }, - want: "YWRtaW46cGFzc3cwcmQ=", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := basicAuth(tt.args.username, tt.args.password); got != tt.want { - t.Errorf("basicAuth() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/releaseutil/kind_sorter.go b/pkg/releaseutil/kind_sorter.go index b5d75b88b..bb8e84dda 100644 --- a/pkg/releaseutil/kind_sorter.go +++ b/pkg/releaseutil/kind_sorter.go @@ -132,7 +132,7 @@ func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.H return h } -func lessByKind(a interface{}, b interface{}, kindA string, kindB string, o KindSortOrder) bool { +func lessByKind(_ interface{}, _ interface{}, kindA string, kindB string, o KindSortOrder) bool { ordering := make(map[string]int, len(o)) for v, k := range o { ordering[k] = v diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index 4d4395c2d..343d5852c 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -115,7 +115,7 @@ type CustomGetter struct { repoUrls []string } -func (g *CustomGetter) Get(href string, options ...getter.Option) (*bytes.Buffer, error) { +func (g *CustomGetter) Get(href string, _ ...getter.Option) (*bytes.Buffer, error) { index := &IndexFile{ APIVersion: "v1", Generated: time.Now(), diff --git a/pkg/repo/index.go b/pkg/repo/index.go index 8a23ba060..40b11c5cf 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -359,10 +359,14 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { log.Printf("skipping loading invalid entry for chart %q from %s: empty entry", name, source) continue } + // When metadata section missing, initialize with no data + if cvs[idx].Metadata == nil { + cvs[idx].Metadata = &chart.Metadata{} + } if cvs[idx].APIVersion == "" { cvs[idx].APIVersion = chart.APIVersionV1 } - if err := cvs[idx].Validate(); err != nil { + if err := cvs[idx].Validate(); ignoreSkippableChartValidationError(err) != nil { log.Printf("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err) cvs = append(cvs[:idx], cvs[idx+1:]...) } @@ -388,3 +392,23 @@ func jsonOrYamlUnmarshal(b []byte, i interface{}) error { } return yaml.UnmarshalStrict(b, i) } + +// ignoreSkippableChartValidationError inspect the given error and returns nil if +// the error isn't important for index loading +// +// In particular, charts may introduce validations that don't impact repository indexes +// And repository indexes may be generated by older/non-complient software, which doesn't +// conform to all validations. +func ignoreSkippableChartValidationError(err error) error { + verr, ok := err.(chart.ValidationError) + if !ok { + return err + } + + // https://github.com/helm/helm/issues/12748 (JFrog repository strips alias field) + if strings.HasPrefix(verr.Error(), "validation: more than one dependency with name or alias") { + return nil + } + + return err +} diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go index efb50ba6a..91486670b 100644 --- a/pkg/repo/index_test.go +++ b/pkg/repo/index_test.go @@ -20,6 +20,7 @@ import ( "bufio" "bytes" "encoding/json" + "fmt" "net/http" "os" "path/filepath" @@ -69,6 +70,10 @@ entries: name: grafana foo: - + bar: + - digest: "sha256:1234567890abcdef" + urls: + - https://charts.helm.sh/stable/alpine-1.0.0.tgz ` ) @@ -445,7 +450,7 @@ func verifyLocalIndex(t *testing.T, i *IndexFile) { } func verifyLocalChartsFile(t *testing.T, chartsContent []byte, indexContent *IndexFile) { - var expected, real []string + var expected, reald []string for chart := range indexContent.Entries { expected = append(expected, chart) } @@ -453,12 +458,12 @@ func verifyLocalChartsFile(t *testing.T, chartsContent []byte, indexContent *Ind scanner := bufio.NewScanner(bytes.NewReader(chartsContent)) for scanner.Scan() { - real = append(real, scanner.Text()) + reald = append(reald, scanner.Text()) } - sort.Strings(real) + sort.Strings(reald) - if strings.Join(expected, " ") != strings.Join(real, " ") { - t.Errorf("Cached charts file content unexpected. Expected:\n%s\ngot:\n%s", expected, real) + if strings.Join(expected, " ") != strings.Join(reald, " ") { + t.Errorf("Cached charts file content unexpected. Expected:\n%s\ngot:\n%s", expected, reald) } } @@ -592,3 +597,50 @@ func TestAddFileIndexEntriesNil(t *testing.T) { } } } + +func TestIgnoreSkippableChartValidationError(t *testing.T) { + type TestCase struct { + Input error + ErrorSkipped bool + } + testCases := map[string]TestCase{ + "nil": { + Input: nil, + }, + "generic_error": { + Input: fmt.Errorf("foo"), + }, + "non_skipped_validation_error": { + Input: chart.ValidationError("chart.metadata.type must be application or library"), + }, + "skipped_validation_error": { + Input: chart.ValidationErrorf("more than one dependency with name or alias %q", "foo"), + ErrorSkipped: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + result := ignoreSkippableChartValidationError(tc.Input) + + if tc.Input == nil { + if result != nil { + t.Error("expected nil result for nil input") + } + return + } + + if tc.ErrorSkipped { + if result != nil { + t.Error("expected nil result for skipped error") + } + return + } + + if tc.Input != result { + t.Error("expected the result equal to input") + } + + }) + } +} diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 0f3ec38a0..5fd64ea59 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -107,7 +107,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas continue } - rls.Labels = filterSystemLabels(item.ObjectMeta.Labels) + rls.Labels = item.ObjectMeta.Labels if filter(rls) { results = append(results, rls) @@ -146,7 +146,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err cfgmaps.Log("query: failed to decode release: %s", err) continue } - rls.Labels = filterSystemLabels(item.ObjectMeta.Labels) + rls.Labels = item.ObjectMeta.Labels results = append(results, rls) } return results, nil diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index 626c36cb9..9a539d6ec 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -128,6 +128,16 @@ func TestConfigMapList(t *testing.T) { if len(ssd) != 2 { t.Errorf("Expected 2 superseded, got %d", len(ssd)) } + // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. + rls := ssd[0] + _, ok := rls.Labels["name"] + if !ok { + t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels) + } + _, ok = rls.Labels["key1"] + if !ok { + t.Fatalf("Expected 'key1' label in results, actual %v", rls.Labels) + } } func TestConfigMapQuery(t *testing.T) { diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 7a1541a02..1c13e1dba 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -253,7 +253,7 @@ func (mock *MockSecretsInterface) Delete(_ context.Context, name string, _ metav } // newTestFixtureSQL mocks the SQL database (for testing purposes) -func newTestFixtureSQL(t *testing.T, releases ...*rspb.Release) (*SQL, sqlmock.Sqlmock) { +func newTestFixtureSQL(t *testing.T, _ ...*rspb.Release) (*SQL, sqlmock.Sqlmock) { sqlDB, mock, err := sqlmock.New() if err != nil { t.Fatalf("error when opening stub database connection: %v", err) diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index 224026b07..9c2f805f2 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -99,7 +99,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, continue } - rls.Labels = filterSystemLabels(item.ObjectMeta.Labels) + rls.Labels = item.ObjectMeta.Labels if filter(rls) { results = append(results, rls) @@ -137,7 +137,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) secrets.Log("query: failed to decode release: %s", err) continue } - rls.Labels = filterSystemLabels(item.ObjectMeta.Labels) + rls.Labels = item.ObjectMeta.Labels results = append(results, rls) } return results, nil diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go index d509c7b3a..b4bf61d5b 100644 --- a/pkg/storage/driver/secrets_test.go +++ b/pkg/storage/driver/secrets_test.go @@ -128,6 +128,16 @@ func TestSecretList(t *testing.T) { if len(ssd) != 2 { t.Errorf("Expected 2 superseded, got %d", len(ssd)) } + // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. + rls := ssd[0] + _, ok := rls.Labels["name"] + if !ok { + t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels) + } + _, ok = rls.Labels["key1"] + if !ok { + t.Fatalf("Expected 'key1' label in results, actual %v", rls.Labels) + } } func TestSecretQuery(t *testing.T) { diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 743886e80..8f5714f15 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -19,6 +19,7 @@ package driver // import "helm.sh/helm/v3/pkg/storage/driver" import ( "fmt" "sort" + "strconv" "time" "github.com/jmoiron/sqlx" @@ -368,6 +369,9 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { s.Log("failed to get release %s/%s custom labels: %v", record.Namespace, record.Key, err) return nil, err } + for k, v := range getReleaseSystemLabels(release) { + release.Labels[k] = v + } if filter(release) { releases = append(releases, release) @@ -658,7 +662,7 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) { } // Get release custom labels from database -func (s *SQL) getReleaseCustomLabels(key string, namespace string) (map[string]string, error) { +func (s *SQL) getReleaseCustomLabels(key string, _ string) (map[string]string, error) { query, args, err := s.statementBuilder. Select(sqlCustomLabelsTableKeyColumn, sqlCustomLabelsTableValueColumn). From(sqlCustomLabelsTableName). @@ -681,3 +685,13 @@ func (s *SQL) getReleaseCustomLabels(key string, namespace string) (map[string]s return filterSystemLabels(labelsMap), nil } + +// Rebuild system labels from release object +func getReleaseSystemLabels(rls *rspb.Release) map[string]string { + return map[string]string{ + "name": rls.Name, + "owner": sqlReleaseDefaultOwner, + "status": rls.Info.Status.String(), + "version": strconv.Itoa(rls.Version), + } +} diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index 067b3cf47..0dfa85e38 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -157,6 +157,17 @@ func TestSQLList(t *testing.T) { if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("sql expectations weren't met: %v", err) } + + // Check if release having both system and custom labels, this is needed to ensure that selector filtering would work. + rls := ssd[0] + _, ok := rls.Labels["name"] + if !ok { + t.Fatalf("Expected 'name' label in results, actual %v", rls.Labels) + } + _, ok = rls.Labels["key1"] + if !ok { + t.Fatalf("Expected 'key1' label in results, actual %v", rls.Labels) + } } func TestSqlCreate(t *testing.T) { diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 058b077e8..d50e3fbfe 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -293,7 +293,7 @@ func (d *MaxHistoryMockDriver) Create(key string, rls *rspb.Release) error { func (d *MaxHistoryMockDriver) Update(key string, rls *rspb.Release) error { return d.Driver.Update(key, rls) } -func (d *MaxHistoryMockDriver) Delete(key string) (*rspb.Release, error) { +func (d *MaxHistoryMockDriver) Delete(_ string) (*rspb.Release, error) { return nil, errMaxHistoryMockDriverSomethingHappened } func (d *MaxHistoryMockDriver) Get(key string) (*rspb.Release, error) { diff --git a/scripts/get b/scripts/get index 594fd92c3..a65540e02 100755 --- a/scripts/get +++ b/scripts/get @@ -60,7 +60,7 @@ runAsRoot() { # verifySupported checks that the os/arch combination is supported for # binary builds. verifySupported() { - local supported="darwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nwindows-amd64" + local supported="darwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nlinux-riscv64\nwindows-amd64" if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then echo "No prebuilt binary for ${OS}-${ARCH}." echo "To build from source, go to https://github.com/helm/helm" diff --git a/scripts/get-helm-3 b/scripts/get-helm-3 index b5e53bafc..31678a177 100755 --- a/scripts/get-helm-3 +++ b/scripts/get-helm-3 @@ -68,7 +68,7 @@ runAsRoot() { # verifySupported checks that the os/arch combination is supported for # binary builds, as well whether or not necessary tools are present. verifySupported() { - local supported="darwin-amd64\ndarwin-arm64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nwindows-amd64" + local supported="darwin-amd64\ndarwin-arm64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nlinux-s390x\nlinux-riscv64\nwindows-amd64" if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then echo "No prebuilt binary for ${OS}-${ARCH}." echo "To build from source, go to https://github.com/helm/helm" @@ -108,14 +108,14 @@ verifySupported() { checkDesiredVersion() { if [ "x$DESIRED_VERSION" == "x" ]; then # Get tag from release URL - local latest_release_url="https://api.github.com/repos/helm/helm/releases/latest" + local latest_release_url="https://get.helm.sh/helm-latest-version" local latest_release_response="" if [ "${HAS_CURL}" == "true" ]; then latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true ) elif [ "${HAS_WGET}" == "true" ]; then - latest_release_response=$( wget "$latest_release_url" -O - 2>&1 || true ) + latest_release_response=$( wget "$latest_release_url" -q -O - 2>&1 || true ) fi - TAG=$( echo "$latest_release_response" | grep '"tag_name"' | sed -E 's/.*"(v[0-9\.]+)".*/\1/g' ) + TAG=$( echo "$latest_release_response" | grep '^v[0-9]' ) if [ "x$TAG" == "x" ]; then printf "Could not retrieve the latest release tag information from %s: %s\n" "${latest_release_url}" "${latest_release_response}" exit 1 diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh index d0dcca8ca..08c4a8b14 100755 --- a/scripts/release-notes.sh +++ b/scripts/release-notes.sh @@ -89,6 +89,7 @@ Download Helm ${RELEASE}. The common platform binaries are here: - [Linux i386](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-386.tar.gz.sha256)) - [Linux ppc64le](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256)) - [Linux s390x](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-s390x.tar.gz.sha256)) +- [Linux riscv64](https://get.helm.sh/helm-${RELEASE}-linux-riscv64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-riscv64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-riscv64.tar.gz.sha256)) - [Windows amd64](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip.sha256sum) / $(cat _dist/helm-${RELEASE}-windows-amd64.zip.sha256)) The [Quickstart Guide](https://helm.sh/docs/intro/quickstart/) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://helm.sh/docs/intro/install/). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3) on any system with \`bash\`.