Merge branch 'main' of https://github.com/helm/helm into filter-dup-repos

pull/11112/head
Felipe Santos 7 months ago
commit f692751a46

@ -1,14 +0,0 @@
---
# This file can be removed when Helm no longer uses CircleCI on any release
# branches. Once CircleCI is turned off this file can be removed.
version: 2
jobs:
build:
docker:
- image: cimg/go:1.18
steps:
- checkout

@ -1,7 +1,24 @@
version: 2 version: 2
updates: updates:
- # Keep dev-v3 branch dependencies up to date, while Helm v3 is within support
package-ecosystem: "gomod"
target-branch: "dev-v3"
directory: "/"
schedule:
interval: "daily"
groups:
k8s.io:
patterns:
- "k8s.io/api"
- "k8s.io/apiextensions-apiserver"
- "k8s.io/apimachinery"
- "k8s.io/apiserver"
- "k8s.io/cli-runtime"
- "k8s.io/client-go"
- "k8s.io/kubectl"
- package-ecosystem: "gomod" - package-ecosystem: "gomod"
target-branch: "main"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
@ -16,6 +33,7 @@ updates:
- "k8s.io/client-go" - "k8s.io/client-go"
- "k8s.io/kubectl" - "k8s.io/kubectl"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
target-branch: "main"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"

@ -7,6 +7,6 @@
**Special notes for your reviewer**: **Special notes for your reviewer**:
**If applicable**: **If applicable**:
- [ ] this PR contains documentation - [ ] this PR contains user facing changes (the `docs needed` label should be applied if so)
- [ ] this PR contains unit tests - [ ] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility - [ ] this PR has been tested for backwards compatibility

@ -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 }}"

@ -2,34 +2,30 @@ name: build-test
on: on:
push: push:
branches: branches:
- 'main' - "main"
- 'release-**' - "dev-v3"
- "release-**"
pull_request: pull_request:
branches: branches:
- main - "main"
- "dev-v3"
permissions:
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0 uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
with: with:
go-version: '1.20' go-version: '1.23'
- name: Install golangci-lint check-latest: true
run: | - name: Test source headers are present
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz run: make test-source-headers
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
- name: Run unit tests - name: Run unit tests
run: make test-coverage run: make test-coverage
- name: Test build - name: Test build

@ -13,13 +13,21 @@ name: "CodeQL"
on: on:
push: push:
branches: [ main ] branches:
- main
- dev-v3
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [ main ] branches:
- main
- dev-v3
schedule: schedule:
- cron: '29 6 * * 6' - cron: '29 6 * * 6'
permissions:
contents: read
security-events: write
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
@ -35,11 +43,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@6a28655e3dcb49cb0840ea372fd6d17733edd8a4 # pinv2.21.8 uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # pinv3.26.6
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -50,7 +58,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@6a28655e3dcb49cb0840ea372fd6d17733edd8a4 # pinv2.21.8 uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # pinv3.26.6
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -64,4 +72,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@6a28655e3dcb49cb0840ea372fd6d17733edd8a4 # pinv2.21.8 uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # pinv3.26.6

@ -0,0 +1,26 @@
name: golangci-lint
on:
push:
pull_request:
permissions:
contents: read
jobs:
golangci:
name: golangci-lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
- name: Setup Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
with:
go-version: '1.23'
check-latest: true
- name: golangci-lint
uses: golangci/golangci-lint-action@051d91933864810ecd5e2ea2cfd98f6a5bca5347 #pin@6.3.2
with:
version: v1.62

@ -0,0 +1,24 @@
name: govulncheck
on:
push:
paths:
- go.sum
schedule:
- cron: "0 0 * * *"
permissions: read-all
jobs:
govulncheck:
name: govulncheck
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
with:
go-version: '1.23'
check-latest: true
- name: govulncheck
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # pin@1.0.4
with:
go-package: ./...

@ -7,6 +7,8 @@ on:
branches: branches:
- main - main
permissions: read-all
# Note the only differences between release and canary-release jobs are: # Note the only differences between release and canary-release jobs are:
# - only canary passes --overwrite flag # - only canary passes --overwrite flag
# - the VERSION make variable passed to 'make dist checksum' is expected to # - the VERSION make variable passed to 'make dist checksum' is expected to
@ -15,28 +17,39 @@ on:
jobs: jobs:
release: release:
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest runs-on: ubuntu-latest-16-cores
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
with:
fetch-depth: 0 fetch-depth: 0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0 uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
with: with:
go-version: '1.20' go-version: '1.23'
- name: Run unit tests - name: Run unit tests
run: make test-coverage run: make test-coverage
- name: Build Helm Binaries - name: Build Helm Binaries
run: | run: |
make build-cross set -eu -o pipefail
make build-cross VERSION="${{ github.ref_name }}"
make dist checksum VERSION="${{ github.ref_name }}" make dist checksum VERSION="${{ github.ref_name }}"
- name: Set latest version - name: Set latest version
run: |
set -eu -o pipefail
mkdir -p _dist_versions
# Push the latest semver tag, excluding prerelease tags # 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 - name: Upload Binaries
uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0 uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0
@ -49,17 +62,29 @@ jobs:
connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }} connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
extra_args: '--pattern helm-*' 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: canary-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest-16-cores
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # pin@v4.2.2
- name: Setup Go - name: Setup Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0 uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # pin@5.3.0
with: with:
go-version: '1.20' go-version: '1.23'
check-latest: true
- name: Run unit tests - name: Run unit tests
run: make test-coverage run: make test-coverage

@ -0,0 +1,69 @@
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '25 7 * * 0'
push:
branches: [ "main" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
# Uncomment the permissions below if installing in a private repository.
# contents: read
# actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif

@ -2,11 +2,14 @@ name: "Close stale issues"
on: on:
schedule: schedule:
- cron: "0 0 * * *" - cron: "0 0 * * *"
permissions:
contents: read
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v3.0.14 - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.'

3
.gitignore vendored

@ -5,8 +5,11 @@
.idea/ .idea/
.vimrc .vimrc
.vscode/ .vscode/
.devcontainer/
_dist/ _dist/
_dist_versions/
bin/ bin/
vendor/ vendor/
# Ignores charts pulled for dependency build tests # Ignores charts pulled for dependency build tests
cmd/helm/testdata/testcharts/issue-7233/charts/* cmd/helm/testdata/testcharts/issue-7233/charts/*
.pre-commit-config.yaml

@ -20,6 +20,26 @@ linters-settings:
gofmt: gofmt:
simplify: true simplify: true
goimports: goimports:
local-prefixes: helm.sh/helm/v3 local-prefixes: helm.sh/helm/v4
dupl: dupl:
threshold: 400 threshold: 400
issues:
exclude-rules:
# Helm, and the Go source code itself, sometimes uses these names outside their built-in
# functions. As the Go source code has re-used these names it's ok for Helm to do the same.
# Linting will look for redefinition of built-in id's but we opt-in to the ones we choose to use.
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function append"
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function clear"
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function max"
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function min"
- linters:
- revive
text: "redefines-builtin-id: redefinition of the built-in function new"

@ -5,12 +5,21 @@
# Organizations Using Helm # Organizations Using Helm
- [Blood Orange](https://bloodorange.io)
- [IBM](https://www.ibm.com) - [IBM](https://www.ibm.com)
- [InfoCert](https://www.infocert.it/)
- [Intercept](https://Intercept.cloud)
- [Microsoft](https://microsoft.com) - [Microsoft](https://microsoft.com)
- [New Relic](https://www.newrelic.com)
- [Octopus Deploy](https://octopus.com/)
- [Omnistrate](https://omnistrate.com)
- [Oracle](www.oracle.com)
- [Percona](https://www.percona.com)
- [Qovery](https://www.qovery.com/) - [Qovery](https://www.qovery.com/)
- [Samsung SDS](https://www.samsungsds.com/) - [Samsung SDS](https://www.samsungsds.com/)
- [Softonic](https://hello.softonic.com/) - [Softonic](https://hello.softonic.com/)
- [SyncTune](https://mb-consulting.dev)
- [Syself](https://syself.com)
- [Ville de Montreal](https://montreal.ca) - [Ville de Montreal](https://montreal.ca)
_This file is part of the CNCF official documentation for projects._ _This file is part of the CNCF official documentation for projects._

@ -11,9 +11,13 @@ vulnerability_, please email a report to
[cncf-helm-security@lists.cncf.io](mailto:cncf-helm-security@lists.cncf.io). This will give us a [cncf-helm-security@lists.cncf.io](mailto:cncf-helm-security@lists.cncf.io). This will give us a
chance to try to fix the issue before it is exploited in the wild. chance to try to fix the issue before it is exploited in the wild.
## Helm v3 and v4
Helm v4 is currently under development on the `main` branch. During the development of Helm v4 and for some time after its released, Helm v3 will continue to be supported and developed on the `dev-v3` branch. Helm v3 will continue to get bug fixes and updates for new Kubernetes releases. Helm v4 is where new features and major changes will happen. For features to be backported to Helm v3, an exception will be needed. Bugs should first be fixed on Helm v4 and then backported to Helm v3.
## Sign Your Work ## 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 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 the material. The rules are pretty simple, if you can certify the below (from
[developercertificate.org](https://developercertificate.org/)): [developercertificate.org](https://developercertificate.org/)):
@ -66,6 +70,18 @@ Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your commit automatically If you set your `user.name` and `user.email` git configs, you can sign your commit automatically
with `git commit -s`. with `git commit -s`.
The following command will update your git config with `user.email`:
``` bash
git config --global user.email joe.smith@example.com
```
This command will update your git config with `user.name`:
``` bash
git config --global user.name "Joe Smith"
```
Note: If your git config information is set properly then viewing the `git log` information for your Note: If your git config information is set properly then viewing the `git log` information for your
commit will look something like this: commit will look something like this:
@ -115,8 +131,9 @@ Helm maintains a strong commitment to backward compatibility. All of our changes
formats are backward compatible from one major release to the next. No features, flags, or commands formats are backward compatible from one major release to the next. No features, flags, or commands
are removed or substantially modified (unless we need to fix a security issue). are removed or substantially modified (unless we need to fix a security issue).
We also try very hard to not change publicly accessible Go library definitions inside of the `pkg/` We also remain committed to not changing publicly accessible Go library definitions inside of the `pkg/` directory of our source code in a non-backwards-compatible way.
directory of our source code.
For more details on Helms minor and patch release backwards-compatibility rules, please read [HIP-0004](https://github.com/helm/community/blob/main/hips/hip-0004.md)
For a quick summary of our backward compatibility guidelines for releases between 3.0 and 4.0: For a quick summary of our backward compatibility guidelines for releases between 3.0 and 4.0:
@ -126,30 +143,9 @@ For a quick summary of our backward compatibility guidelines for releases betwee
(barring the cases where (a) Kubernetes itself changed, and (b) the chart worked because it (barring the cases where (a) Kubernetes itself changed, and (b) the chart worked because it
exploited a bug) exploited a bug)
- Chart repository functionality MUST be backward compatible - Chart repository functionality MUST be backward compatible
- Go libraries inside of `pkg/` SHOULD remain backward compatible, though code inside of `cmd/` and - Go libraries inside of `pkg/` MUST remain backward compatible, though code inside of `cmd/` and
`internal/` may be changed from release to release without notice. `internal/` may be changed from release to release without notice.
## Support Contract for Helm 2
With Helm 2's current release schedule, we want to take into account any migration issues for users
due to the upcoming holiday shopping season and tax season. We also want to clarify what actions may
occur after the support contract ends for Helm 2, so that users will not be surprised or caught off
guard.
After Helm 2.15.0 is released, Helm 2 will go into "maintenance mode". We will continue to accept
bug fixes and fix any security issues that arise, but no new features will be accepted for Helm 2.
All feature development will be moved over to Helm 3.
6 months after Helm 3.0.0's public release, Helm 2 will stop accepting bug fixes. Only security
issues will be accepted.
12 months after Helm 3.0.0's public release, support for Helm 2 will formally end. Download links
for the Helm 2 client through Google Cloud Storage, the Docker image for Tiller stored in Google
Container Registry, and the Google Cloud buckets for the stable and incubator chart repositories may
no longer work at any point. Client downloads through `get.helm.sh` will continue to work, and we
will distribute a Tiller image that will be made available at an alternative location which can be
updated with `helm init --tiller-image`.
## Issues ## Issues
Issues are used as the primary method for tracking anything to do with the Helm project. Issues are used as the primary method for tracking anything to do with the Helm project.
@ -195,7 +191,7 @@ below.
See [Proposing an Idea](#proposing-an-idea). Smaller quality-of-life enhancements are exempt. 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. - 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 - 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. that they are taking it.
- `proposal` and `support/question` issues should stay open until resolved or if they have not - `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 been active for more than 30 days. This will help keep the issue queue to a manageable size
@ -282,9 +278,9 @@ Like any good open source project, we use Pull Requests (PRs) to track code chan
#### Documentation PRs #### Documentation PRs
Documentation PRs will follow the same lifecycle as other PRs. They will also be labeled with the Documentation PRs should be made on the docs repo: <https://github.com/helm/helm-www>. Keeping Helm's documentation up to date is highly desirable, and is recommended for all user facing changes. Accurate and helpful documentation is critical for effectively communicating Helm's behavior to a wide audience.
`docs` label. For documentation, special attention will be paid to spelling, grammar, and clarity
(whereas those things don't matter *as* much for comments in code). Small, ad-hoc changes/PRs to Helm which introduce user facing changes, which would benefit from documentation changes, should apply the `docs needed` label. Larger changes associated with a HIP should track docs via that HIP. The `docs needed` label doesn't block PRs, and maintainers/PR reviewers should apply discretion judging in whether the `docs needed` label should be applied.
## The Triager ## The Triager
@ -327,6 +323,7 @@ The following tables define all label types used for Helm. It is split up by cat
| `needs rebase` | Indicates a PR needs to be rebased before it can be merged | | `needs rebase` | Indicates a PR needs to be rebased before it can be merged |
| `needs pick` | Indicates a PR needs to be cherry-picked into a feature branch (generally bugfix branches). Once it has been, the `picked` label should be applied and this one removed | | `needs pick` | Indicates a PR needs to be cherry-picked into a feature branch (generally bugfix branches). Once it has been, the `picked` label should be applied and this one removed |
| `picked` | This PR has been cherry-picked into a feature branch | | `picked` | This PR has been cherry-picked into a feature branch |
| `docs needed` | Tracks PRs that introduces a feature/change for which documentation update would be desirable (non-blocking). Once a suitable documentation PR has been created, then this label should be removed |
#### Size labels #### Size labels

118
KEYS

@ -940,3 +940,121 @@ AirPev6SluPhLJ2mswaK3THlhOZulKO/VIEJ6g50m5Vj3hdYf6sR603yK9rP+3iu
IagTQt2SGfW3Ap0RO3Yt+w29BpZ1CZ5Ml4gAYkXz0hiiMnVRhlcLIOHoFw== IagTQt2SGfW3Ap0RO3Yt+w29BpZ1CZ5Ml4gAYkXz0hiiMnVRhlcLIOHoFw==
=h3+3 =h3+3
-----END PGP PUBLIC KEY BLOCK----- -----END PGP PUBLIC KEY BLOCK-----
pub rsa4096 2018-12-08 [SC]
208DD36ED5BB3745A16743A4C7C6FBB5B91C1155
uid [ultimate] Scott Rigby <scott@r6by.com>
sig 3 C7C6FBB5B91C1155 2018-12-08 [self-signature]
sig 134FC1555856DA4F 2018-12-13 [User ID not found]
sig 62F49E747D911B60 2018-12-17 [User ID not found]
sig F54982D216088EE1 2019-01-05 [User ID not found]
sub rsa4096 2018-12-08 [E]
sig C7C6FBB5B91C1155 2018-12-08 [self-signature]
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFwMUAcBEADplQ+msULZ4kt01bXDvZ66MSVe5Fi1cPqAa/5/ZtaHZWSKrcN6
K0cadpozJp74HSZzORLYV/50EGwXU+OG1dFe73FbsTCgQyLCbh/OjT+Exq553g2D
/IB4/6/vCs9XXiYdKot3P2NsHI6RqeGqgW2IkFVsMXO2Lq1XKFTWQniO7PhHW8nG
Trub7HrxR6i9KHtVtxLs+XoXY7Jj0gB7WyRkYjHLXti4VtvcBq0WK3pSgEIy5MwR
WDepmle8n8EJZrh3T323YM41MXKGT00wCSKMbSHJO7QssiOda9XluC175HDfihm3
q5OKV2ZYIbChsQxuJz1Y97hwZ5KkLn//W2pxTdOElOcynFpQNx7D4b6UTP2DCCRc
n41SiDIyHg25cUXXAkJWlYRD1koGfLBipJA0DcKqlh3W+8zNfngZ0PSxwFtJwSre
Zx5I5uHAgKO5nS4hLxGYUMv+MsSKHMYR6qkqFg1Eal6tTa68bPFTbzypDmMUKXZT
sZtZ79WoIUU9D3O+F+Z9rxwaQ3Dv7J49FdbLPB3zqENqX7OWHZ38m5dsweTFhQi+
4AaDLEMiqMi27SfPkF1/+JDc1SOoLVo9QgukqhFlz6qEIbud7LUfpeKBRNJsfdr6
HE3cH8MWHInnlJ49De1oLl5bwAwScQig5jmv5DZxN5qdTg64vgoreBLgsQARAQAB
tBxTY290dCBSaWdieSA8c2NvdHRAcjZieS5jb20+iQJOBBMBCAA4FiEEII3TbtW7
N0WhZ0Okx8b7tbkcEVUFAlwMUAcCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AA
CgkQx8b7tbkcEVUIPg//cK8zaAOUIFClQ31TeQnwHsJwcBMeRRegUjt4cIYSjv3F
oNe5EQKxgpwT/1iegvRURjxHbhSE0paQ9wsi8Tr5Ygs0DJLDp4PJVZtVHJ5qB3XT
VuCiqyb90kIG+ok1m8FHBwpeU7o2a05InYwJLWowBQTulhrS4VKzrPs2kMu4z5Am
5sbs70f0hRLUXGmEAFUnZm+lpF48/PCMNSPrxgZ6rtNqtXq8oCyPNvyO8Ou130tv
TSoHx5Tobz3RnSeDXpidC/Z1rQGq4Spi+a0WwC9BCArDvtOrQBoeQFgdpy5OsE/t
QfQNEffyLRLlAZgXewOC+PeF+xrz+ku/rMBlt/h0h5UcMS3joUHTRZyQoA5dzWZh
K8pMXYppHXJtxl0fNSG1rH1vsTu+Mjxmd9eBwaDxnBFwzBOukiwSUgW1r2DyZ7S+
25ZW62lcz/E9+sgWV/PGR+YKGbsDdnraPdQEc95k8NQnYsX/7YO/WXJfP7Cdqd8X
dAAVW6/dGUp2i4QogEYk+GeJz3rJJK24Wzgf2FJ32FuSH8uOQUH93h5QZbJAD+pD
R2qZAZjCHYAgvSuqnmDlefvG95TwIVzy3OFWbLwp6YyBdyZrdpTafER4zk17f0HF
xS3z3LG0LsEWHShxccjzjoAUeppAU0Qojag3kKLoCwveambIdjXIxliBr/S2VSWJ
AjMEEAEKAB0WIQTlYbNM52c/3RO87D71SYLSFgiO4QUCXC/89gAKCRD1SYLSFgiO
4YfMEACS7/90VvyhbcQYB/2N6dYYuVd4tMRpM7jgWO6LqDDh3C9S2NI2bzwxGFBR
nqV9N6fD+yLug/dtAPq5D4i7AXzRqPA8XQ2ky/1EJOv5EmOl6NYnUZafEBMDMai6
F6XOji2JR7b6xlRC7GwzUdMR1rn8eyCxuJobgB+wMzfcSAnaDsH1qU7a5ohEewtQ
IgEcLLmiLapCqNXm0l5oIYQQypbRrogw4ePw8KcLDreRtPCpPdLIThqdfkXLQ69Z
ZwGd1+Pg6xgu7MJggnNq2RYortW/Jx9WUFs0Jj9c+Yz5pPeQmc1jjF4uHvxfi0gt
wFqL+bu+HfxI/Hiudkzzp3v+Llmjk6RypJowLLk5cxMxv7XMfK3cLLDO5uw0pPiq
l1f4u4P3YeRLv5YFh8SRrk/PadEqFr10mIOmreASq9LivJkhGK9eqQ0X7zQfHmDq
S3nw62ousqlmEld39MIAMn02Ak9i9CD2M3G2O5gCAcvnFlWblx5CN9Pjc6kOb52W
eDMYisUmKnIkChzvAlfh8PhvQfLUpKN1AmzUOcXJokpu1Yx7OGaoDnfpXSHS8fe8
pu0jMsEhlqsNxNS6y5N0tWjxjYg7D1Qpeq3O4oft4HiO3ZfMPI4V1tatfeohnjic
UJkpVsS4RZybu8aqNGc8i/ggagiWc50oydK8Lp906XfOcEert4kCMwQQAQoAHRYh
BKuiUpWY9mJsQg0zW2L0nnR9kRtgBQJcF85IAAoJEGL0nnR9kRtg0OMQAKqpxGtA
uaMknrZxnxu+y4FXXrX/W2TLlF7Ns71upXhitwSWk0pVJi+OZUvIGj+8yCj2MEg6
o5qBJPyo8TuwIh1YfxBYigY5Hmt/uVVbKBM/VXyKDxzGrSts+r73cxf3BpPfyANB
a90LjKHvFv0czu5sfiRMHU9GCOehnBukdZ9PhOOcRuUlHoHlSf21x9kxa1tUFPVJ
eeoVOOnONDK6Dzmi6GoGRTq3X/HZ2JjhcdYSn37z/KxmZ/SNiwat60gw5zJHTh0a
dM54hwsdsp48/avpF8BlTgXsoH5dVdbaOTyNGXBbQaoL09FY2x2eXaFCP3RbMWK8
TpWh0Ijs/3JLFJ20jrvZqsmxmwIX25TmBb4UoR3HSEHFasYoIxv/me6V4oB7D1SJ
F3q5scPnRzV3I5wCRljKJcNHQbNb/c9Xnt5UVnLHRtkZW/NUw93H5Bq15dkq4U6i
prC0EgjfiWy2Esd9TiGb5kRuN0/duUY/d7dewp1tJGDRZACwWwHyYkPiPA6gzQfN
83yk29evZeX1rSBslnXSLzuwhVJc28KNZCeEcC2o1JninqfqoYnypgSFOS5BK6ZG
5YJD0gkKFX+ImC8OSKsIJ6QyrzyOBb7UwRcK5qlvYZGYgrt+B+mFDpxWPkzgpfMe
CvE0nUCgKDNg0Yvhr5w9JhK+49Qn6TuTADMIiQIzBBABCAAdFiEE2o9xo8issez5
FC4eE0/BVVhW2k8FAlwSzMYACgkQE0/BVVhW2k8jEQ//cV4+ke8eHhwwxCPZd+lA
mvgzalwSiZZ8H0EgAB2cK0LXEFe07XGKxe2tDkf5FDIQcNm7sIk0OcLhJzYX0p9P
A7ZzO2tZu8QuZlUqt4VnDL7B3xeW2Sh3STEmw80wubkgauRRysflAHIw3edchnIX
9Hq55MLBBAplQFkpA/Y7arg3Bn8v/8YQlULc30xRO8EoyxD+zyl+Ic+xFtFUxNc/
2dkqkdjq0Ohq89wTGTy0jaSI8INhZTGqR0cEYQPKZD+PXUUym/TaPKJKXagqxmu2
XXBv6QPp46a58viBxMj6+fl1JJH3DxNF9YY++7Xp8CckA3TKDA9hxOJK6wbrTzDB
DB+tjcwR4ff8QLv/CV3psyk2fX9CGCBdr0k0SCMQSFcHM8pKagkySjG+EJ1Tcflf
UDY2LD33n2BBIdCaQTu6u+Zeqq2e6R3UXm/raXuGrxUxzvOQBIhb7XaC6nhfDu8k
07yN/Tjwp+rgHt9ouH4pfFbGpvaIomBJq6pkTOk9ywDtHlSatqoVkbrbKpNzmwf8
z7pt+ICtKqAAWQTPFPD83h6elP26GKlsyXyhT7HNmKUHaXInEbaD+IoCJ+wY/O3i
gHV2Dn4QllSBSBhYlhl6utmP1zqwJJ0rI39mPS+nMXOhGB+bJ8EzAF/3N5J6y3TG
FnMEJD8qgdpDEgztjHUSAy25Ag0EXAxQBwEQALqYikkk0Ur4gn9PjxtjW4OxS5J4
e/u0UyOsv8znFM7CG9Ndha9rQs/7c7NEf3e+K6a7XqhzDKtyGAFVFlZArxbe6X8e
UV1OidOaH46z2vmtWOJYHIupXHlXS9LeXNO+pJjCNEAzmHbGjpkjGtNz6Opl4Uae
LoMFubRViXhvD8pBF72dGUlp8m+U4yeXJ03/q0sR94AdTA+1OzGd2+1s7PvL5XAx
BwXqx9gccMYhrNRPyBo/yRA+Wf4ewwluIVBMi9cpR1sNF4ITIYCH4i3mf4NJvg7P
0sPBY0s9k2jvHGLpINbFk6PkMtaRpqmgw695szTz16Gp0j41hRnEh7KnGneEp+SU
6A8A9UGnjM9upR/d1591I5gT2U+6Q05B8RtJQUmd3HBeHEBgftjgBR0tstH8qeac
Xc4V81OGlf3tdYP/LVggIlv8V5cdSZ4Bd3BXYWj2TIc2RmwWA2LWf4SA6JYvhEfp
OxOzzphlgPtZF0kneEgV/b/D/KQqEk7MyZl3gN+LNk+zX7VJ2RDeUiUnoxZDFJGi
jsbZd7yoDLkYvGiFkcQXORs2zbucweVXXK1Gyskj0c3Ih4syYYmKS0WJHMEozJUl
b81oa7kSc2XFArcAnPz2c1yErfzcCAlg/HImkZmAgVqAfuRyxZ426F7CYucHAOcE
N7bpIrOkqFp3uUb3ABEBAAGJAjYEGAEIACAWIQQgjdNu1bs3RaFnQ6THxvu1uRwR
VQUCXAxQBwIbDAAKCRDHxvu1uRwRVbFaD/0e57rP3H+1rUoGhRO0oeIveQqIdd9V
LKXUYuwzoK3HLg3BYUDEN03RS0KyNMYlHpnjyFl5L2JuXqGiJd/eu2iRXCwUMRb7
SPvH7gypa1NUK5te85+Y8JhXOMjwZkly3zS2nRTyvHxMn9EV7NPlT/oEVu/woPrM
o7XzmPChuvnk8pLWBW04wg5G5atDbu5+QVZlecNCrtRYJg/Cd8alKpJSeZX7y3cy
fe2P20Gv0UOipKWaAFL55zFLbmu7HWVumYAKs6T+X/pZqmcfMaVwodIBeRJxRIvl
PkrBxljahaFGOdgJ6FVnmO34uoYcpd019NEr9gbPoaFWmw37h3Tnc6U5sLAouaV4
AERWmwBPIVTizYt1h8Qj4qyBhJ+QgZMjPlRqHWPZogHfMXDQV4gw3jgvVWTMVp1Z
gDQgrFNbw02CqPwgtFn15VNwAv/4vbyToRhc3pG54e3xwdAFM8R2uM9lHJKuHafW
7aFUk7aA20k8SG2BsZalb6tZLGxgcZOwMdO3lnLMPu1I5oOLl4cVoUIRZxtgmrbQ
ROaGdXGIgO7fJBXXogMxjUGhMola+v6ioFQpbOnJRAr2AUVBCrrEgHoodAufGTDu
nk38BkgHg3LHjCbCNEVkSK2TMT69A58iwpY9WUQlphsiz4WBpafSPbv/jSlsm7uK
TNWtbFGBRpJyEg==
=w141
-----END PGP PUBLIC KEY BLOCK-----
pub ed25519 2024-07-09 [SC]
7FEC81FACC7FFB2A010ADD13C2D40F4D8196E874
uid [ultimate] Robert Sirchia (I like turtles.) <rsirchia@outlook.com>
sig 3 C2D40F4D8196E874 2024-07-09 [self-signature]
sub cv25519 2024-07-09 [E]
sig C2D40F4D8196E874 2024-07-09 [self-signature]
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZo2C6xYJKwYBBAHaRw8BAQdA8kCWaI+FlCabcTw8EVeiMkokyWDalgl/Inbn
ACcGN1e0N1JvYmVydCBTaXJjaGlhIChJIGxpa2UgdHVydGxlcy4pIDxyc2lyY2hp
YUBvdXRsb29rLmNvbT6IkwQTFgoAOxYhBH/sgfrMf/sqAQrdE8LUD02Bluh0BQJm
jYLrAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEMLUD02Bluh0dyYA
/i7RB6m3MXNA8ei7GD8uQVpLfCRgEFsqSS/AzAOu8NGhAQCbw1kWL3AUll7KKtiQ
UE96nhCk+HnkQeVkWYS+MZ1tALg4BGaNgusSCisGAQQBl1UBBQEBB0CCA6Au4krL
YinQq9aAs29fFeRu/ye3PqQuz5jZ2r1ScAMBCAeIeAQYFgoAIBYhBH/sgfrMf/sq
AQrdE8LUD02Bluh0BQJmjYLrAhsMAAoJEMLUD02Bluh0KH4BAMSwEIGkoQl10LN3
K6V08VpFmniENmCDHshXYq0gGiTDAP9FsXl2UtmFU5xuYxH4fRKIxgmxJRAFMWI8
u3Rdu/s+DQ==
=smBO
-----END PGP PUBLIC KEY BLOCK-----

@ -1,8 +1,8 @@
BINDIR := $(CURDIR)/bin BINDIR := $(CURDIR)/bin
INSTALL_PATH ?= /usr/local/bin INSTALL_PATH ?= /usr/local/bin
DIST_DIRS := find * -type d -exec 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 TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x linux/riscv64 windows/amd64 windows/arm64
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 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 windows-arm64.zip windows-arm64.zip.sha256 windows-arm64.zip.sha256sum
BINNAME ?= helm BINNAME ?= helm
GOBIN = $(shell go env GOBIN) GOBIN = $(shell go env GOBIN)
@ -11,7 +11,7 @@ GOBIN = $(shell go env GOPATH)/bin
endif endif
GOX = $(GOBIN)/gox GOX = $(GOBIN)/gox
GOIMPORTS = $(GOBIN)/goimports GOIMPORTS = $(GOBIN)/goimports
ARCH = $(shell uname -p) ARCH = $(shell go env GOARCH)
ACCEPTANCE_DIR:=../acceptance-testing ACCEPTANCE_DIR:=../acceptance-testing
# To specify the subset of acceptance tests to run. '.' means all tests # To specify the subset of acceptance tests to run. '.' means all tests
@ -44,7 +44,7 @@ BINARY_VERSION ?= ${GIT_TAG}
# Only set Version if building a tag or VERSION is set # Only set Version if building a tag or VERSION is set
ifneq ($(BINARY_VERSION),) ifneq ($(BINARY_VERSION),)
LDFLAGS += -X helm.sh/helm/v3/internal/version.version=${BINARY_VERSION} LDFLAGS += -X helm.sh/helm/v4/internal/version.version=${BINARY_VERSION}
endif endif
VERSION_METADATA = unreleased VERSION_METADATA = unreleased
@ -53,9 +53,9 @@ ifneq ($(GIT_TAG),)
VERSION_METADATA = VERSION_METADATA =
endif endif
LDFLAGS += -X helm.sh/helm/v3/internal/version.metadata=${VERSION_METADATA} LDFLAGS += -X helm.sh/helm/v4/internal/version.metadata=${VERSION_METADATA}
LDFLAGS += -X helm.sh/helm/v3/internal/version.gitCommit=${GIT_COMMIT} LDFLAGS += -X helm.sh/helm/v4/internal/version.gitCommit=${GIT_COMMIT}
LDFLAGS += -X helm.sh/helm/v3/internal/version.gitTreeState=${GIT_DIRTY} LDFLAGS += -X helm.sh/helm/v4/internal/version.gitTreeState=${GIT_DIRTY}
LDFLAGS += $(EXT_LDFLAGS) LDFLAGS += $(EXT_LDFLAGS)
# Define constants based on the client-go version # Define constants based on the client-go version
@ -63,10 +63,10 @@ K8S_MODULES_VER=$(subst ., ,$(subst v,,$(shell go list -f '{{.Version}}' -m k8s.
K8S_MODULES_MAJOR_VER=$(shell echo $$(($(firstword $(K8S_MODULES_VER)) + 1))) K8S_MODULES_MAJOR_VER=$(shell echo $$(($(firstword $(K8S_MODULES_VER)) + 1)))
K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_VER)) K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_VER))
LDFLAGS += -X helm.sh/helm/v3/pkg/lint/rules.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER)
LDFLAGS += -X helm.sh/helm/v3/pkg/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/lint/rules.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
LDFLAGS += -X helm.sh/helm/v3/pkg/chartutil.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/chartutil.k8sVersionMajor=$(K8S_MODULES_MAJOR_VER)
LDFLAGS += -X helm.sh/helm/v3/pkg/chartutil.k8sVersionMinor=$(K8S_MODULES_MINOR_VER) LDFLAGS += -X helm.sh/helm/v4/pkg/chartutil.k8sVersionMinor=$(K8S_MODULES_MINOR_VER)
.PHONY: all .PHONY: all
all: build all: build
@ -78,7 +78,7 @@ all: build
build: $(BINDIR)/$(BINNAME) build: $(BINDIR)/$(BINNAME)
$(BINDIR)/$(BINNAME): $(SRC) $(BINDIR)/$(BINNAME): $(SRC)
GO111MODULE=on CGO_ENABLED=$(CGO_ENABLED) go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm CGO_ENABLED=$(CGO_ENABLED) go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# install # install
@ -104,7 +104,16 @@ test: test-unit
test-unit: test-unit:
@echo @echo
@echo "==> Running unit tests <==" @echo "==> Running unit tests <=="
GO111MODULE=on go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS) go test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS)
@echo
@echo "==> Running unit test(s) with ldflags <=="
# Test to check the deprecation warnings on Kubernetes templates created by `helm create` against the current Kubernetes
# version. Note: The version details are set in var LDFLAGS. To avoid the ldflags impact on other unit tests that are
# based on older versions, this is run separately. When run without the ldflags in the unit test (above) or coverage
# test, it still passes with a false-positive result as the resources shouldnt be deprecated in the older Kubernetes
# version if it only starts failing with the latest.
go test $(GOFLAGS) -run ^TestHelmCreateChart_CheckDeprecatedWarnings$$ ./pkg/lint/ $(TESTFLAGS) -ldflags '$(LDFLAGS)'
.PHONY: test-coverage .PHONY: test-coverage
test-coverage: test-coverage:
@ -114,7 +123,11 @@ test-coverage:
.PHONY: test-style .PHONY: test-style
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 @scripts/validate-license.sh
.PHONY: test-acceptance .PHONY: test-acceptance
@ -138,7 +151,7 @@ coverage:
.PHONY: format .PHONY: format
format: $(GOIMPORTS) format: $(GOIMPORTS)
GO111MODULE=on go list -f '{{.Dir}}' ./... | xargs $(GOIMPORTS) -w -local helm.sh/helm go list -f '{{.Dir}}' ./... | xargs $(GOIMPORTS) -w -local helm.sh/helm
# Generate golden files used in unit tests # Generate golden files used in unit tests
.PHONY: gen-test-golden .PHONY: gen-test-golden
@ -155,10 +168,10 @@ gen-test-golden: test-unit
# without a go.mod file when downloading the following dependencies # without a go.mod file when downloading the following dependencies
$(GOX): $(GOX):
(cd /; GO111MODULE=on go install github.com/mitchellh/gox@latest) (cd /; go install github.com/mitchellh/gox@v1.0.2-0.20220701044238-9f712387e2d2)
$(GOIMPORTS): $(GOIMPORTS):
(cd /; GO111MODULE=on go install golang.org/x/tools/cmd/goimports@latest) (cd /; go install golang.org/x/tools/cmd/goimports@latest)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# release # release
@ -166,7 +179,7 @@ $(GOIMPORTS):
.PHONY: build-cross .PHONY: build-cross
build-cross: LDFLAGS += -extldflags "-static" build-cross: LDFLAGS += -extldflags "-static"
build-cross: $(GOX) build-cross: $(GOX)
GOFLAGS="-trimpath" GO111MODULE=on CGO_ENABLED=0 $(GOX) -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/$(BINNAME)" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm GOFLAGS="-trimpath" CGO_ENABLED=0 $(GOX) -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/$(BINNAME)" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/helm
.PHONY: dist .PHONY: dist
dist: dist:

@ -1,22 +1,24 @@
maintainers: maintainers:
- hickeyma - gjenkins8
- joejulian - joejulian
- jdolitsky
- marckhouzam - marckhouzam
- mattfarina - mattfarina
- robertsirc
- sabre1041 - sabre1041
- scottrigby - scottrigby
- technosophos - technosophos
triage: triage:
- banjoh
- yxxhero - yxxhero
- zonggen - zonggen
- gjenkins8
- z4ce - z4ce
emeritus: emeritus:
- adamreese - adamreese
- bacongobbler - bacongobbler
- fibonacci1729 - fibonacci1729
- hickeyma
- jascott1 - jascott1
- jdolitsky
- michelleN - michelleN
- migmartri - migmartri
- nebril - nebril

@ -2,8 +2,9 @@
[![Build Status](https://github.com/helm/helm/workflows/release/badge.svg)](https://github.com/helm/helm/actions?workflow=release) [![Build Status](https://github.com/helm/helm/workflows/release/badge.svg)](https://github.com/helm/helm/actions?workflow=release)
[![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm) [![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm)
[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v3) [![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v4)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/helm/helm/badge)](https://scorecard.dev/viewer/?uri=github.com/helm/helm)
Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources. Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources.
@ -28,6 +29,11 @@ Think of it like apt/yum/homebrew for Kubernetes.
- Charts can be stored on disk, or fetched from remote chart repositories - Charts can be stored on disk, or fetched from remote chart repositories
(like Debian or RedHat packages) (like Debian or RedHat packages)
## Helm Development and Stable Versions
Helm v4 is currently under development on the `main` branch. This is unstable and the APIs within the Go SDK and at the command line are changing.
Helm v3 (current stable) is maintained on the `dev-v3` branch. APIs there follow semantic versioning.
## Install ## Install
Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest). Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest).
@ -38,8 +44,10 @@ If you want to use a package manager:
- [Homebrew](https://brew.sh/) users can use `brew install helm`. - [Homebrew](https://brew.sh/) users can use `brew install helm`.
- [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`. - [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
- [Winget](https://learn.microsoft.com/en-us/windows/package-manager/) users can use `winget install Helm.Helm`.
- [Scoop](https://scoop.sh/) users can use `scoop install helm`. - [Scoop](https://scoop.sh/) users can use `scoop install helm`.
- [Snapcraft](https://snapcraft.io/) users can use `snap install helm --classic` - [Snapcraft](https://snapcraft.io/) users can use `snap install helm --classic`.
- [Flox](https://flox.dev) users can use `flox install kubernetes-helm`.
To rapidly get Helm up and running, start with the [Quick Start Guide](https://helm.sh/docs/intro/quickstart/). To rapidly get Helm up and running, start with the [Quick Start Guide](https://helm.sh/docs/intro/quickstart/).
@ -54,6 +62,8 @@ Get started with the [Quick Start guide](https://helm.sh/docs/intro/quickstart/)
The [Helm roadmap uses GitHub milestones](https://github.com/helm/helm/milestones) to track the progress of the project. The [Helm roadmap uses GitHub milestones](https://github.com/helm/helm/milestones) to track the progress of the project.
The development of Helm v4 is currently happening on the `main` branch while the development of Helm v3, the stable branch, is happening on the `dev-v3` branch. Changes should be made to the `main` branch prior to being added to the `dev-v3` branch so that all changes are carried along to Helm v4.
## Community, discussion, contribution, and support ## Community, discussion, contribution, and support
You can reach the Helm community and developers via the following channels: You can reach the Helm community and developers via the following channels:

@ -23,7 +23,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
) )
const completionDesc = ` const completionDesc = `
@ -102,8 +102,8 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
Short: "generate autocompletion script for bash", Short: "generate autocompletion script for bash",
Long: bashCompDesc, Long: bashCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
return runCompletionBash(out, cmd) return runCompletionBash(out, cmd)
}, },
} }
@ -114,8 +114,8 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
Short: "generate autocompletion script for zsh", Short: "generate autocompletion script for zsh",
Long: zshCompDesc, Long: zshCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
return runCompletionZsh(out, cmd) return runCompletionZsh(out, cmd)
}, },
} }
@ -126,8 +126,8 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
Short: "generate autocompletion script for fish", Short: "generate autocompletion script for fish",
Long: fishCompDesc, Long: fishCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
return runCompletionFish(out, cmd) return runCompletionFish(out, cmd)
}, },
} }
@ -138,8 +138,8 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
Short: "generate autocompletion script for powershell", Short: "generate autocompletion script for powershell",
Long: powershellCompDesc, Long: powershellCompDesc,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
return runCompletionPowershell(out, cmd) return runCompletionPowershell(out, cmd)
}, },
} }
@ -209,7 +209,15 @@ func runCompletionPowershell(out io.Writer, cmd *cobra.Command) error {
return cmd.Root().GenPowerShellCompletionWithDesc(out) return cmd.Root().GenPowerShellCompletionWithDesc(out)
} }
// Function to disable file completion // noMoreArgsCompFunc deactivates file completion when doing argument shell completion.
func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { // It also provides some ActiveHelp to indicate no more arguments are accepted.
return nil, cobra.ShellCompDirectiveNoFileComp func noMoreArgsCompFunc(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return noMoreArgsComp()
}
// noMoreArgsComp deactivates file completion when doing argument shell completion.
// It also provides some ActiveHelp to indicate no more arguments are accepted.
func noMoreArgsComp() ([]string, cobra.ShellCompDirective) {
activeHelpMsg := "This command does not take any more arguments (but may accept flags)."
return cobra.AppendActiveHelp(nil, activeHelpMsg), cobra.ShellCompDirectiveNoFileComp
} }

@ -21,8 +21,8 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
// Check if file completion should be performed according to parameter 'shouldBePerformed' // Check if file completion should be performed according to parameter 'shouldBePerformed'

@ -23,10 +23,10 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v4/pkg/chartutil"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
) )
const createDesc = ` const createDesc = `
@ -64,16 +64,16 @@ func newCreateCmd(out io.Writer) *cobra.Command {
Short: "create a new chart with the given name", Short: "create a new chart with the given name",
Long: createDesc, Long: createDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 { if len(args) == 0 {
// Allow file completion when completing the argument for the name // Allow file completion when completing the argument for the name
// which could be a path // which could be a path
return nil, cobra.ShellCompDirectiveDefault return nil, cobra.ShellCompDirectiveDefault
} }
// No more completions, so disable file completion // No more completions, so disable file completion
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
o.name = args[0] o.name = args[0]
o.starterDir = helmpath.DataPath("starters") o.starterDir = helmpath.DataPath("starters")
return o.run(out) return o.run(out)

@ -22,11 +22,11 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v4/pkg/chartutil"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
) )
func TestCreateCmd(t *testing.T) { func TestCreateCmd(t *testing.T) {

@ -20,9 +20,10 @@ import (
"path/filepath" "path/filepath"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
const dependencyDesc = ` const dependencyDesc = `
@ -93,7 +94,7 @@ func newDependencyCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cmd.AddCommand(newDependencyListCmd(out)) cmd.AddCommand(newDependencyListCmd(out))
cmd.AddCommand(newDependencyUpdateCmd(cfg, out)) cmd.AddCommand(newDependencyUpdateCmd(cfg, out))
cmd.AddCommand(newDependencyBuildCmd(cfg, out)) cmd.AddCommand(newDependencyBuildCmd(out))
return cmd return cmd
} }
@ -106,7 +107,7 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
Short: "list the dependencies for the given chart", Short: "list the dependencies for the given chart",
Long: dependencyListDesc, Long: dependencyListDesc,
Args: require.MaximumNArgs(1), Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
chartpath := "." chartpath := "."
if len(args) > 0 { if len(args) > 0 {
chartpath = filepath.Clean(args[0]) chartpath = filepath.Clean(args[0])
@ -120,3 +121,16 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
f.UintVar(&client.ColumnWidth, "max-col-width", 80, "maximum column width for output table") f.UintVar(&client.ColumnWidth, "max-col-width", 80, "maximum column width for output table")
return cmd return cmd
} }
func addDependencySubcommandFlags(f *pflag.FlagSet, client *action.Dependency) {
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
f.StringVar(&client.Username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&client.Password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&client.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&client.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.BoolVar(&client.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download")
f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download")
f.StringVar(&client.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
}

@ -24,10 +24,10 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/client-go/util/homedir" "k8s.io/client-go/util/homedir"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v4/pkg/getter"
) )
const dependencyBuildDesc = ` const dependencyBuildDesc = `
@ -41,7 +41,7 @@ If no lock file is found, 'helm dependency build' will mirror the behavior
of 'helm dependency update'. of 'helm dependency update'.
` `
func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newDependencyBuildCmd(out io.Writer) *cobra.Command {
client := action.NewDependency() client := action.NewDependency()
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -49,18 +49,24 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm
Short: "rebuild the charts/ directory based on the Chart.lock file", Short: "rebuild the charts/ directory based on the Chart.lock file",
Long: dependencyBuildDesc, Long: dependencyBuildDesc,
Args: require.MaximumNArgs(1), Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
chartpath := "." chartpath := "."
if len(args) > 0 { if len(args) > 0 {
chartpath = filepath.Clean(args[0]) chartpath = filepath.Clean(args[0])
} }
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
ChartPath: chartpath, ChartPath: chartpath,
Keyring: client.Keyring, Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh, SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings), Getters: getter.All(settings),
RegistryClient: cfg.RegistryClient, RegistryClient: registryClient,
RepositoryConfig: settings.RepositoryConfig, RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache, RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug, Debug: settings.Debug,
@ -68,7 +74,7 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm
if client.Verify { if client.Verify {
man.Verify = downloader.VerifyIfPossible man.Verify = downloader.VerifyIfPossible
} }
err := man.Build() err = man.Build()
if e, ok := err.(downloader.ErrRepoNotFound); ok { if e, ok := err.(downloader.ErrRepoNotFound); ok {
return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error()) return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error())
} }
@ -77,9 +83,7 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures") addDependencySubcommandFlags(f, client)
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
return cmd return cmd
} }

@ -22,10 +22,10 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v4/pkg/chartutil"
"helm.sh/helm/v3/pkg/provenance" "helm.sh/helm/v4/pkg/provenance"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v4/pkg/repo/repotest"
) )
func TestDependencyBuildCmd(t *testing.T) { func TestDependencyBuildCmd(t *testing.T) {
@ -58,7 +58,7 @@ func TestDependencyBuildCmd(t *testing.T) {
createTestingChart(t, rootDir, chartname, srv.URL()) createTestingChart(t, rootDir, chartname, srv.URL())
repoFile := filepath.Join(rootDir, "repositories.yaml") repoFile := filepath.Join(rootDir, "repositories.yaml")
cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s", filepath.Join(rootDir, chartname), repoFile, rootDir) cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir)
_, out, err := executeActionCommand(cmd) _, out, err := executeActionCommand(cmd)
// In the first pass, we basically want the same results as an update. // In the first pass, we basically want the same results as an update.
@ -117,7 +117,7 @@ func TestDependencyBuildCmd(t *testing.T) {
t.Errorf("mismatched versions. Expected %q, got %q", "0.1.0", v) t.Errorf("mismatched versions. Expected %q, got %q", "0.1.0", v)
} }
skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s", filepath.Join(rootDir, chartname), repoFile, rootDir) skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir)
_, out, err = executeActionCommand(skipRefreshCmd) _, out, err = executeActionCommand(skipRefreshCmd)
// In this pass, we check --skip-refresh option becomes effective. // In this pass, we check --skip-refresh option becomes effective.
@ -134,7 +134,7 @@ func TestDependencyBuildCmd(t *testing.T) {
if err := chartutil.SaveDir(c, dir()); err != nil { if err := chartutil.SaveDir(c, dir()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
cmd = fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json", cmd = fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json --plain-http",
dir(ociChartName), dir(ociChartName),
dir("repositories.yaml"), dir("repositories.yaml"),
dir(), dir(),

@ -16,15 +16,16 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"io" "io"
"path/filepath" "path/filepath"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v4/pkg/getter"
) )
const dependencyUpDesc = ` const dependencyUpDesc = `
@ -43,7 +44,7 @@ in the Chart.yaml file, but (b) at the wrong version.
` `
// newDependencyUpdateCmd creates a new dependency update command. // newDependencyUpdateCmd creates a new dependency update command.
func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newDependencyUpdateCmd(_ *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewDependency() client := action.NewDependency()
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -52,18 +53,24 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com
Short: "update charts/ based on the contents of Chart.yaml", Short: "update charts/ based on the contents of Chart.yaml",
Long: dependencyUpDesc, Long: dependencyUpDesc,
Args: require.MaximumNArgs(1), Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
chartpath := "." chartpath := "."
if len(args) > 0 { if len(args) > 0 {
chartpath = filepath.Clean(args[0]) chartpath = filepath.Clean(args[0])
} }
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
man := &downloader.Manager{ man := &downloader.Manager{
Out: out, Out: out,
ChartPath: chartpath, ChartPath: chartpath,
Keyring: client.Keyring, Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh, SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings), Getters: getter.All(settings),
RegistryClient: cfg.RegistryClient, RegistryClient: registryClient,
RepositoryConfig: settings.RepositoryConfig, RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache, RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug, Debug: settings.Debug,
@ -76,9 +83,7 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com
} }
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures") addDependencySubcommandFlags(f, client)
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
return cmd return cmd
} }

@ -22,13 +22,13 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v4/pkg/chartutil"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v3/pkg/provenance" "helm.sh/helm/v4/pkg/provenance"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v4/pkg/repo/repotest"
) )
func TestDependencyUpdateCmd(t *testing.T) { func TestDependencyUpdateCmd(t *testing.T) {
@ -67,7 +67,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
} }
_, out, err := executeActionCommand( _, out, err := executeActionCommand(
fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()), fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()),
) )
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
@ -110,7 +110,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir())) _, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()))
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)
@ -131,7 +131,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
if err := chartutil.SaveDir(c, dir()); err != nil { if err := chartutil.SaveDir(c, dir()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json", cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json --plain-http",
dir(ociChartName), dir(ociChartName),
dir("repositories.yaml"), dir("repositories.yaml"),
dir(), dir(),
@ -169,7 +169,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
} }
createTestingChart(t, dir(), chartname, srv.URL()) createTestingChart(t, dir(), chartname, srv.URL())
_, output, err := executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir())) _, output, err := executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()))
if err != nil { if err != nil {
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal(err) t.Fatal(err)
@ -178,7 +178,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
// Chart repo is down // Chart repo is down
srv.Stop() srv.Stop()
_, output, err = executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir())) _, output, err = executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()))
if err == nil { if err == nil {
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal("Expected error, got nil") t.Fatal("Expected error, got nil")
@ -200,8 +200,9 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
} }
} }
// Make sure tmpcharts is deleted // Make sure tmpcharts-x is deleted
if _, err := os.Stat(filepath.Join(dir(chartname), "tmpcharts")); !os.IsNotExist(err) { tmpPath := filepath.Join(dir(chartname), fmt.Sprintf("tmpcharts-%d", os.Getpid()))
if _, err := os.Stat(tmpPath); !os.IsNotExist(err) {
t.Fatalf("tmpcharts dir still exists") t.Fatalf("tmpcharts dir still exists")
} }
} }

@ -28,7 +28,7 @@ import (
"golang.org/x/text/cases" "golang.org/x/text/cases"
"golang.org/x/text/language" "golang.org/x/text/language"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
) )
const docsDesc = ` const docsDesc = `
@ -58,8 +58,8 @@ func newDocsCmd(out io.Writer) *cobra.Command {
Long: docsDesc, Long: docsDesc,
Hidden: true, Hidden: true,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
o.topCmd = cmd.Root() o.topCmd = cmd.Root()
return o.run(out) return o.run(out)
}, },
@ -70,14 +70,14 @@ func newDocsCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.docTypeString, "type", "markdown", "the type of documentation to generate (markdown, man, bash)") f.StringVar(&o.docTypeString, "type", "markdown", "the type of documentation to generate (markdown, man, bash)")
f.BoolVar(&o.generateHeaders, "generate-headers", false, "generate standard headers for markdown files") f.BoolVar(&o.generateHeaders, "generate-headers", false, "generate standard headers for markdown files")
cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { cmd.RegisterFlagCompletionFunc("type", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return []string{"bash", "man", "markdown"}, cobra.ShellCompDirectiveNoFileComp return []string{"bash", "man", "markdown"}, cobra.ShellCompDirectiveNoFileComp
}) })
return cmd return cmd
} }
func (o *docsOptions) run(out io.Writer) error { func (o *docsOptions) run(_ io.Writer) error {
switch o.docTypeString { switch o.docTypeString {
case "markdown", "mdown", "md": case "markdown", "mdown", "md":
if o.generateHeaders { if o.generateHeaders {

@ -23,7 +23,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
) )
var envHelp = ` var envHelp = `
@ -36,15 +36,15 @@ func newEnvCmd(out io.Writer) *cobra.Command {
Short: "helm client environment information", Short: "helm client environment information",
Long: envHelp, Long: envHelp,
Args: require.MaximumNArgs(1), Args: require.MaximumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 { if len(args) == 0 {
keys := getSortedEnvVarKeys() keys := getSortedEnvVarKeys()
return keys, cobra.ShellCompDirectiveNoFileComp return keys, cobra.ShellCompDirectiveNoFileComp
} }
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
}, },
Run: func(cmd *cobra.Command, args []string) { Run: func(_ *cobra.Command, args []string) {
envVars := settings.EnvVars() envVars := settings.EnvVars()
if len(args) == 0 { if len(args) == 0 {

@ -28,12 +28,12 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v4/pkg/cli/values"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v4/pkg/postrender"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
const ( const (
@ -72,7 +72,7 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
cmd.Flags().VarP(newOutputValue(output.Table, varRef), outputFlag, "o", cmd.Flags().VarP(newOutputValue(output.Table, varRef), outputFlag, "o",
fmt.Sprintf("prints the output in the specified format. Allowed values: %s", strings.Join(output.Formats(), ", "))) fmt.Sprintf("prints the output in the specified format. Allowed values: %s", strings.Join(output.Formats(), ", ")))
err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc(outputFlag, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
var formatNames []string var formatNames []string
for format, desc := range output.FormatsWithDesc() { for format, desc := range output.FormatsWithDesc() {
formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc)) formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc))
@ -195,7 +195,7 @@ func (p *postRendererArgsSlice) GetSlice() []string {
return p.options.args 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, "/") chartInfo := strings.Split(chartRef, "/")
if len(chartInfo) != 2 { if len(chartInfo) != 2 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp

@ -20,9 +20,9 @@ import (
"fmt" "fmt"
"testing" "testing"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
func outputFlagCompletionTest(t *testing.T, cmdName string) { func outputFlagCompletionTest(t *testing.T, cmdName string) {

@ -21,8 +21,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
var getHelp = ` var getHelp = `

@ -22,9 +22,9 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
) )
var getAllHelp = ` var getAllHelp = `
@ -41,13 +41,13 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download all information for a named release", Short: "download all information for a named release",
Long: getAllHelp, Long: getAllHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
res, err := client.Run(args[0]) res, err := client.Run(args[0])
if err != nil { if err != nil {
return err return err
@ -58,20 +58,23 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
return tpl(template, data, out) return tpl(template, data, out)
} }
return output.Table.Write(out, &statusPrinter{
return output.Table.Write(out, &statusPrinter{res, true, false, false, true}) release: res,
debug: true,
showMetadata: true,
hideNotes: false,
})
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision") f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 { if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0]) return compListRevisions(toComplete, cfg, args[0])
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -19,7 +19,7 @@ package main
import ( import (
"testing" "testing"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
func TestGetCmd(t *testing.T) { func TestGetCmd(t *testing.T) {

@ -23,8 +23,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
const getHooksHelp = ` const getHooksHelp = `
@ -41,13 +41,13 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download all hooks for a named release", Short: "download all hooks for a named release",
Long: getHooksHelp, Long: getHooksHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
res, err := client.Run(args[0]) res, err := client.Run(args[0])
if err != nil { if err != nil {
return err return err
@ -60,7 +60,7 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision") cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 { if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0]) return compListRevisions(toComplete, cfg, args[0])
} }

@ -19,7 +19,7 @@ package main
import ( import (
"testing" "testing"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
func TestGetHooks(t *testing.T) { func TestGetHooks(t *testing.T) {

@ -23,8 +23,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
var getManifestHelp = ` var getManifestHelp = `
@ -43,13 +43,13 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
Short: "download the manifest for a named release", Short: "download the manifest for a named release",
Long: getManifestHelp, Long: getManifestHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
res, err := client.Run(args[0]) res, err := client.Run(args[0])
if err != nil { if err != nil {
return err return err
@ -60,7 +60,7 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
} }
cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision") cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 { if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0]) return compListRevisions(toComplete, cfg, args[0])
} }

@ -19,7 +19,7 @@ package main
import ( import (
"testing" "testing"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
func TestGetManifest(t *testing.T) { func TestGetManifest(t *testing.T) {

@ -22,10 +22,11 @@ import (
"log" "log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
k8sLabels "k8s.io/apimachinery/pkg/labels"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
) )
type metadataWriter struct { type metadataWriter struct {
@ -40,13 +41,13 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
Use: "metadata RELEASE_NAME", Use: "metadata RELEASE_NAME",
Short: "This command fetches metadata for a given release", Short: "This command fetches metadata for a given release",
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
releaseMetadata, err := client.Run(args[0]) releaseMetadata, err := client.Run(args[0])
if err != nil { if err != nil {
return err return err
@ -57,7 +58,7 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
f := cmd.Flags() f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "specify release revision") f.IntVar(&client.Version, "revision", 0, "specify release revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 { if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0]) return compListRevisions(toComplete, cfg, args[0])
} }
@ -78,10 +79,13 @@ func (w metadataWriter) WriteTable(out io.Writer) error {
_, _ = fmt.Fprintf(out, "CHART: %v\n", w.metadata.Chart) _, _ = fmt.Fprintf(out, "CHART: %v\n", w.metadata.Chart)
_, _ = fmt.Fprintf(out, "VERSION: %v\n", w.metadata.Version) _, _ = fmt.Fprintf(out, "VERSION: %v\n", w.metadata.Version)
_, _ = fmt.Fprintf(out, "APP_VERSION: %v\n", w.metadata.AppVersion) _, _ = fmt.Fprintf(out, "APP_VERSION: %v\n", w.metadata.AppVersion)
_, _ = fmt.Fprintf(out, "ANNOTATIONS: %v\n", k8sLabels.Set(w.metadata.Annotations).String())
_, _ = fmt.Fprintf(out, "DEPENDENCIES: %v\n", w.metadata.FormattedDepNames())
_, _ = fmt.Fprintf(out, "NAMESPACE: %v\n", w.metadata.Namespace) _, _ = fmt.Fprintf(out, "NAMESPACE: %v\n", w.metadata.Namespace)
_, _ = fmt.Fprintf(out, "REVISION: %v\n", w.metadata.Revision) _, _ = fmt.Fprintf(out, "REVISION: %v\n", w.metadata.Revision)
_, _ = fmt.Fprintf(out, "STATUS: %v\n", w.metadata.Status) _, _ = fmt.Fprintf(out, "STATUS: %v\n", w.metadata.Status)
_, _ = fmt.Fprintf(out, "DEPLOYED_AT: %v\n", w.metadata.DeployedAt) _, _ = fmt.Fprintf(out, "DEPLOYED_AT: %v\n", w.metadata.DeployedAt)
return nil return nil
} }

@ -19,7 +19,7 @@ package main
import ( import (
"testing" "testing"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
func TestGetMetadataCmd(t *testing.T) { func TestGetMetadataCmd(t *testing.T) {

@ -23,8 +23,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
var getNotesHelp = ` var getNotesHelp = `
@ -39,13 +39,13 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download the notes for a named release", Short: "download the notes for a named release",
Long: getNotesHelp, Long: getNotesHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
res, err := client.Run(args[0]) res, err := client.Run(args[0])
if err != nil { if err != nil {
return err return err
@ -59,7 +59,7 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision") f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 { if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0]) return compListRevisions(toComplete, cfg, args[0])
} }

@ -19,7 +19,7 @@ package main
import ( import (
"testing" "testing"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
func TestGetNotesCmd(t *testing.T) { func TestGetNotesCmd(t *testing.T) {

@ -23,9 +23,9 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
) )
var getValuesHelp = ` var getValuesHelp = `
@ -46,13 +46,13 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download the values file for a named release", Short: "download the values file for a named release",
Long: getValuesHelp, Long: getValuesHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
vals, err := client.Run(args[0]) vals, err := client.Run(args[0])
if err != nil { if err != nil {
return err return err
@ -63,7 +63,7 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "get the named release with revision") f.IntVar(&client.Version, "revision", 0, "get the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 { if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0]) return compListRevisions(toComplete, cfg, args[0])
} }

@ -19,7 +19,7 @@ package main
import ( import (
"testing" "testing"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
func TestGetValuesCmd(t *testing.T) { func TestGetValuesCmd(t *testing.T) {

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package main // import "helm.sh/helm/v3/cmd/helm" package main // import "helm.sh/helm/v4/cmd/helm"
import ( import (
"fmt" "fmt"
@ -22,6 +22,7 @@ import (
"log" "log"
"os" "os"
"strings" "strings"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@ -29,12 +30,12 @@ import (
// Import to initialize client auth plugins. // Import to initialize client auth plugins.
_ "k8s.io/client-go/plugin/pkg/client/auth" _ "k8s.io/client-go/plugin/pkg/client/auth"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v3/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
) )
var settings = cli.New() var settings = cli.New()
@ -45,7 +46,8 @@ func init() {
func debug(format string, v ...interface{}) { func debug(format string, v ...interface{}) {
if settings.Debug { if settings.Debug {
format = fmt.Sprintf("[debug] %s\n", format) timeNow := time.Now().String()
format = fmt.Sprintf("%s [debug] %s\n", timeNow, format)
log.Output(2, fmt.Sprintf(format, v...)) log.Output(2, fmt.Sprintf(format, v...))
} }
} }

@ -28,15 +28,15 @@ import (
shellwords "github.com/mattn/go-shellwords" shellwords "github.com/mattn/go-shellwords"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/internal/test" "helm.sh/helm/v4/internal/test"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v4/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v4/pkg/cli"
kubefake "helm.sh/helm/v3/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v4/pkg/storage"
"helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
"helm.sh/helm/v3/pkg/time" "helm.sh/helm/v4/pkg/time"
) )
func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() } func testTimestamper() time.Time { return time.Unix(242085845, 0).UTC() }
@ -94,7 +94,7 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string)
Releases: store, Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
Capabilities: chartutil.DefaultCapabilities, Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {}, Log: func(_ string, _ ...interface{}) {},
} }
root, err := newRootCmd(actionConfig, buf, args) root, err := newRootCmd(actionConfig, buf, args)

@ -25,13 +25,13 @@ import (
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v4/pkg/releaseutil"
helmtime "helm.sh/helm/v3/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
var historyHelp = ` var historyHelp = `
@ -60,13 +60,13 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "fetch release history", Short: "fetch release history",
Aliases: []string{"hist"}, Aliases: []string{"hist"},
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
history, err := getHistory(client, args[0]) history, err := getHistory(client, args[0])
if err != nil { if err != nil {
return err return err
@ -136,7 +136,7 @@ func getHistory(client *action.History, name string) (releaseHistory, error) {
func getReleaseHistory(rls []*release.Release) (history releaseHistory) { func getReleaseHistory(rls []*release.Release) (history releaseHistory) {
for i := len(rls) - 1; i >= 0; i-- { for i := len(rls) - 1; i >= 0; i-- {
r := rls[i] r := rls[i]
c := formatChartname(r.Chart) c := formatChartName(r.Chart)
s := r.Info.Status.String() s := r.Info.Status.String()
v := r.Version v := r.Version
d := r.Info.Description d := r.Info.Description
@ -159,7 +159,7 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) {
return history return history
} }
func formatChartname(c *chart.Chart) string { func formatChartName(c *chart.Chart) string {
if c == nil || c.Metadata == nil { if c == nil || c.Metadata == nil {
// This is an edge case that has happened in prod, though we don't // This is an edge case that has happened in prod, though we don't
// know how: https://github.com/helm/helm/issues/1347 // know how: https://github.com/helm/helm/issues/1347
@ -177,22 +177,15 @@ func formatAppVersion(c *chart.Chart) string {
return c.AppVersion() return c.AppVersion()
} }
func min(x, y int) int { func compListRevisions(_ string, cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) {
if x < y {
return x
}
return y
}
func compListRevisions(toComplete string, cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) {
client := action.NewHistory(cfg) client := action.NewHistory(cfg)
var revisions []string var revisions []string
if hist, err := client.Run(releaseName); err == nil { if hist, err := client.Run(releaseName); err == nil {
for _, release := range hist { for _, version := range hist {
appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion) appVersion := fmt.Sprintf("App: %s", version.Chart.Metadata.AppVersion)
chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version) chartDesc := fmt.Sprintf("Chart: %s-%s", version.Chart.Metadata.Name, version.Chart.Metadata.Version)
revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", strconv.Itoa(release.Version), appVersion, chartDesc)) revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", strconv.Itoa(version.Version), appVersion, chartDesc))
} }
return revisions, cobra.ShellCompDirectiveNoFileComp return revisions, cobra.ShellCompDirectiveNoFileComp
} }

@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
func TestHistoryCmd(t *testing.T) { func TestHistoryCmd(t *testing.T) {

@ -30,15 +30,15 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/loader"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v4/pkg/cli/values"
"helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
const installDesc = ` const installDesc = `
@ -94,7 +94,11 @@ And in the following example, 'foo' is set to '{"key1":"value1","key2":"bar"}':
$ helm install --set-json='foo={"key1":"value1","key2":"value2"}' --set-json='foo.key2="bar"' myredis ./redis $ helm install --set-json='foo={"key1":"value1","key2":"value2"}' --set-json='foo.key2="bar"' myredis ./redis
To check the generated manifests of a release without installing the chart, To check the generated manifests of a release without installing the chart,
the '--debug' and '--dry-run' flags can be combined. the --debug and --dry-run flags can be combined.
The --dry-run flag will output all generated chart manifests, including Secrets
which can contain sensitive values. To hide Kubernetes Secrets use the
--hide-secret flag. Please carefully consider how and when these flags are used.
If --verify is set, the chart MUST have a provenance file, and the provenance If --verify is set, the chart MUST have a provenance file, and the provenance
file MUST pass all verification steps. file MUST pass all verification steps.
@ -132,12 +136,12 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "install a chart", Short: "install a chart",
Long: installDesc, Long: installDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compInstall(args, toComplete, client) return compInstall(args, toComplete, client)
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP) client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password)
if err != nil { if err != nil {
return fmt.Errorf("missing registry client: %w", err) return fmt.Errorf("missing registry client: %w", err)
} }
@ -154,11 +158,20 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return errors.Wrap(err, "INSTALLATION FAILED") return errors.Wrap(err, "INSTALLATION FAILED")
} }
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false}) return outfmt.Write(out, &statusPrinter{
release: rel,
debug: settings.Debug,
showMetadata: false,
hideNotes: client.HideNotes,
})
}, },
} }
addInstallFlags(cmd, cmd.Flags(), client, valueOpts) addInstallFlags(cmd, cmd.Flags(), client, valueOpts)
// hide-secret is not available in all places the install flags are used so
// it is added separately
f := cmd.Flags()
f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag")
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)
bindPostRenderFlag(cmd, &client.PostRenderer) bindPostRenderFlag(cmd, &client.PostRenderer)
@ -176,7 +189,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.Lookup("dry-run").NoOptDefVal = "client" f.Lookup("dry-run").NoOptDefVal = "client"
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production") f.BoolVar(&client.Replace, "replace", false, "reuse the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
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.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.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")
@ -189,12 +202,15 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used")
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation")
f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be divided by comma.") f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be divided by comma.")
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")
f.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in install output. Does not affect presence in chart metadata")
f.BoolVar(&client.TakeOwnership, "take-ownership", false, "if set, install will ignore the check for helm annotations and take ownership of the existing resources")
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
requiredArgs := 2 requiredArgs := 2
if client.GenerateName { if client.GenerateName {
requiredArgs = 1 requiredArgs = 1
@ -204,7 +220,6 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
} }
return compVersionFlag(args[requiredArgs-1], toComplete) return compVersionFlag(args[requiredArgs-1], toComplete)
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -23,7 +23,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v4/pkg/repo/repotest"
) )
func TestInstall(t *testing.T) { func TestInstall(t *testing.T) {
@ -33,7 +33,7 @@ func TestInstall(t *testing.T) {
} }
defer srv.Stop() defer srv.Stop()
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv.WithMiddleware(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth() username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" { if !ok || username != "username" || password != "password" {
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
@ -96,12 +96,18 @@ func TestInstall(t *testing.T) {
golden: "output/install-no-args.txt", golden: "output/install-no-args.txt",
wantError: true, wantError: true,
}, },
// Install, re-use name // Install, reuse name
{ {
name: "install and replace release", name: "install and replace release",
cmd: "install aeneas testdata/testcharts/empty --replace", cmd: "install aeneas testdata/testcharts/empty --replace",
golden: "output/install-and-replace.txt", golden: "output/install-and-replace.txt",
}, },
// Install, take ownership
{
name: "install and replace release",
cmd: "install aeneas-take-ownership testdata/testcharts/empty --take-ownership",
golden: "output/install-and-take-ownership.txt",
},
// Install, with timeout // Install, with timeout
{ {
name: "install with a timeout", name: "install with a timeout",
@ -225,6 +231,12 @@ func TestInstall(t *testing.T) {
wantError: true, wantError: true,
golden: "output/subchart-schema-cli-negative.txt", golden: "output/subchart-schema-cli-negative.txt",
}, },
// Install, values from yaml, schematized with errors but skip schema validation, expect success
{
name: "install with schema file and schematized subchart, extra values from cli, skip schema validation",
cmd: "install schema testdata/testcharts/chart-with-schema-and-subchart --set lastname=doe --set subchart-with-schema.age=-25 --skip-schema-validation",
golden: "output/schema.txt",
},
// Install deprecated chart // Install deprecated chart
{ {
name: "install with warning about deprecated chart", name: "install with warning about deprecated chart",
@ -252,6 +264,22 @@ func TestInstall(t *testing.T) {
cmd: fmt.Sprintf("install aeneas test/reqtest --username username --password password --repository-config %s --repository-cache %s", repoFile, srv.Root()), cmd: fmt.Sprintf("install aeneas test/reqtest --username username --password password --repository-config %s --repository-cache %s", repoFile, srv.Root()),
golden: "output/install.txt", golden: "output/install.txt",
}, },
{
name: "dry-run displaying secret",
cmd: "install secrets testdata/testcharts/chart-with-secret --dry-run",
golden: "output/install-dry-run-with-secret.txt",
},
{
name: "dry-run hiding secret",
cmd: "install secrets testdata/testcharts/chart-with-secret --dry-run --hide-secret",
golden: "output/install-dry-run-with-secret-hidden.txt",
},
{
name: "hide-secret error without dry-run",
cmd: "install secrets testdata/testcharts/chart-with-secret --hide-secret",
wantError: true,
golden: "output/install-hide-secret.txt",
},
} }
runTestCmd(t, tests) runTestCmd(t, tests)

@ -26,10 +26,11 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v4/pkg/chartutil"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v4/pkg/cli/values"
"helm.sh/helm/v3/pkg/lint/support" "helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/lint/support"
) )
var longLintHelp = ` var longLintHelp = `
@ -44,19 +45,29 @@ or recommendation, it will emit [WARNING] messages.
func newLintCmd(out io.Writer) *cobra.Command { func newLintCmd(out io.Writer) *cobra.Command {
client := action.NewLint() client := action.NewLint()
valueOpts := &values.Options{} valueOpts := &values.Options{}
var kubeVersion string
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "lint PATH", Use: "lint PATH",
Short: "examine a chart for possible issues", Short: "examine a chart for possible issues",
Long: longLintHelp, Long: longLintHelp,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
paths := []string{"."} paths := []string{"."}
if len(args) > 0 { if len(args) > 0 {
paths = args 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 { if client.WithSubcharts {
for _, p := range paths { for _, p := range paths {
filepath.Walk(filepath.Join(p, "charts"), func(path string, info os.FileInfo, err error) error { filepath.Walk(filepath.Join(p, "charts"), func(path string, info os.FileInfo, _ error) error {
if info != nil { if info != nil {
if info.Name() == "Chart.yaml" { if info.Name() == "Chart.yaml" {
paths = append(paths, filepath.Dir(path)) paths = append(paths, filepath.Dir(path))
@ -137,6 +148,8 @@ func newLintCmd(out io.Writer) *cobra.Command {
f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts") f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts")
f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors") f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors")
f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation")
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for capabilities and deprecation checks")
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
return cmd return cmd

@ -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) { func TestLintFileCompletion(t *testing.T) {
checkFileCompletion(t, "lint", true) checkFileCompletion(t, "lint", true)
checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given

@ -25,10 +25,10 @@ import (
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
var listHelp = ` var listHelp = `
@ -68,8 +68,8 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: listHelp, Long: listHelp,
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, _ []string) error {
if client.AllNamespaces { if client.AllNamespaces {
if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), debug); err != nil { if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), debug); err != nil {
return err return err
@ -156,7 +156,7 @@ func newReleaseListWriter(releases []*release.Release, timeFormat string, noHead
Namespace: r.Namespace, Namespace: r.Namespace,
Revision: strconv.Itoa(r.Version), Revision: strconv.Itoa(r.Version),
Status: r.Info.Status.String(), Status: r.Info.Status.String(),
Chart: formatChartname(r.Chart), Chart: formatChartName(r.Chart),
AppVersion: formatAppVersion(r.Chart), AppVersion: formatAppVersion(r.Chart),
} }

@ -19,9 +19,9 @@ package main
import ( import (
"testing" "testing"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
"helm.sh/helm/v3/pkg/time" "helm.sh/helm/v4/pkg/time"
) )
func TestListCmd(t *testing.T) { func TestListCmd(t *testing.T) {

@ -31,7 +31,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/plugin" "helm.sh/helm/v4/pkg/plugin"
) )
const ( const (
@ -154,7 +154,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
func manuallyProcessArgs(args []string) ([]string, []string) { func manuallyProcessArgs(args []string) ([]string, []string) {
known := []string{} known := []string{}
unknown := []string{} unknown := []string{}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config", "--insecure-skip-tls-verify", "--tls-server-name"} kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config", "--kube-insecure-skip-tls-verify", "--kube-tls-server-name"}
knownArg := func(a string) bool { knownArg := func(a string) bool {
for _, pre := range kvargs { for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") { if strings.HasPrefix(a, pre+"=") {
@ -286,7 +286,7 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
f.BoolP(longs[i], shorts[i], false, "") f.BoolP(longs[i], shorts[i], false, "")
} else { } else {
// Create a long flag with the same name as the short flag. // Create a long flag with the same name as the short flag.
// Not a perfect solution, but its better than ignoring the extra short flags. // Not a perfect solution, but it's better than ignoring the extra short flags.
f.BoolP(shorts[i], shorts[i], false, "") f.BoolP(shorts[i], shorts[i], false, "")
} }
} }
@ -301,7 +301,7 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
// to the dynamic completion script of the plugin. // to the dynamic completion script of the plugin.
DisableFlagParsing: true, DisableFlagParsing: true,
// A Run is required for it to be a valid command without subcommands // A Run is required for it to be a valid command without subcommands
Run: func(cmd *cobra.Command, args []string) {}, Run: func(_ *cobra.Command, _ []string) {},
} }
baseCmd.AddCommand(subCmd) baseCmd.AddCommand(subCmd)
addPluginCommands(plugin, subCmd, &cmd) addPluginCommands(plugin, subCmd, &cmd)

@ -25,10 +25,10 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v4/pkg/cli/values"
"helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v4/pkg/downloader"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v4/pkg/getter"
) )
const packageDesc = ` const packageDesc = `
@ -47,7 +47,7 @@ If '--keyring' is not specified, Helm usually defaults to the public keyring
unless your environment is otherwise configured. unless your environment is otherwise configured.
` `
func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newPackageCmd(out io.Writer) *cobra.Command {
client := action.NewPackage() client := action.NewPackage()
valueOpts := &values.Options{} valueOpts := &values.Options{}
@ -55,7 +55,7 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Use: "package [CHART_PATH] [...]", Use: "package [CHART_PATH] [...]",
Short: "package a chart directory into a chart archive", Short: "package a chart directory into a chart archive",
Long: packageDesc, Long: packageDesc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {
return errors.Errorf("need at least one argument, the path to the chart") return errors.Errorf("need at least one argument, the path to the chart")
} }
@ -75,6 +75,12 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return err return err
} }
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
path, err := filepath.Abs(args[i]) path, err := filepath.Abs(args[i])
if err != nil { if err != nil {
@ -91,7 +97,7 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Keyring: client.Keyring, Keyring: client.Keyring,
Getters: p, Getters: p,
Debug: settings.Debug, Debug: settings.Debug,
RegistryClient: cfg.RegistryClient, RegistryClient: registryClient,
RepositoryConfig: settings.RepositoryConfig, RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache, RepositoryCache: settings.RepositoryCache,
} }
@ -119,6 +125,13 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version") f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version")
f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.") f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.")
f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`) f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`)
f.StringVar(&client.Username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&client.Password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&client.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&client.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.BoolVar(&client.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download")
f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download")
f.StringVar(&client.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
return cmd return cmd
} }

@ -23,8 +23,8 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v4/pkg/chart/loader"
) )
func TestPackage(t *testing.T) { func TestPackage(t *testing.T) {

@ -23,7 +23,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/plugin" "helm.sh/helm/v4/pkg/plugin"
) )
const pluginHelp = ` const pluginHelp = `
@ -47,19 +47,27 @@ func newPluginCmd(out io.Writer) *cobra.Command {
// runHook will execute a plugin hook. // runHook will execute a plugin hook.
func runHook(p *plugin.Plugin, event string) error { func runHook(p *plugin.Plugin, event string) error {
hook := p.Metadata.Hooks[event] plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir)
if hook == "" {
cmds := p.Metadata.PlatformHooks[event]
expandArgs := true
if len(cmds) == 0 && len(p.Metadata.Hooks) > 0 {
cmd := p.Metadata.Hooks[event]
if len(cmd) > 0 {
cmds = []plugin.PlatformCommand{{Command: "sh", Args: []string{"-c", cmd}}}
expandArgs = false
}
}
main, argv, err := plugin.PrepareCommands(cmds, expandArgs, []string{})
if err != nil {
return nil return nil
} }
prog := exec.Command("sh", "-c", hook) prog := exec.Command(main, argv...)
// TODO make this work on windows
// I think its ... ¯\_(ツ)_/¯
// prog := exec.Command("cmd", "/C", p.Metadata.Hooks.Install())
debug("running %s hook: %s", event, prog) debug("running %s hook: %s", event, prog)
plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir)
prog.Stdout, prog.Stderr = os.Stdout, os.Stderr prog.Stdout, prog.Stderr = os.Stdout, os.Stderr
if err := prog.Run(); err != nil { if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok { if eerr, ok := err.(*exec.ExitError); ok {

@ -22,9 +22,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/plugin" "helm.sh/helm/v4/pkg/plugin"
"helm.sh/helm/v3/pkg/plugin/installer" "helm.sh/helm/v4/pkg/plugin/installer"
) )
type pluginInstallOptions struct { type pluginInstallOptions struct {
@ -39,23 +39,23 @@ This command allows you to install a plugin from a url to a VCS repo or a local
func newPluginInstallCmd(out io.Writer) *cobra.Command { func newPluginInstallCmd(out io.Writer) *cobra.Command {
o := &pluginInstallOptions{} o := &pluginInstallOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "install [options] <path|url>...", Use: "install [options] <path|url>",
Short: "install one or more Helm plugins", Short: "install a Helm plugin",
Long: pluginInstallDesc, Long: pluginInstallDesc,
Aliases: []string{"add"}, Aliases: []string{"add"},
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 { if len(args) == 0 {
// We do file completion, in case the plugin is local // We do file completion, in case the plugin is local
return nil, cobra.ShellCompDirectiveDefault return nil, cobra.ShellCompDirectiveDefault
} }
// No more completion once the plugin path has been specified // No more completion once the plugin path has been specified
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
}, },
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(_ *cobra.Command, args []string) error {
return o.complete(args) return o.complete(args)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, _ []string) error {
return o.run(out) return o.run(out)
}, },
} }

@ -22,7 +22,7 @@ import (
"github.com/gosuri/uitable" "github.com/gosuri/uitable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/plugin" "helm.sh/helm/v4/pkg/plugin"
) )
func newPluginListCmd(out io.Writer) *cobra.Command { func newPluginListCmd(out io.Writer) *cobra.Command {
@ -30,8 +30,8 @@ func newPluginListCmd(out io.Writer) *cobra.Command {
Use: "list", Use: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Short: "list installed Helm plugins", Short: "list installed Helm plugins",
ValidArgsFunction: noCompletions, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, _ []string) error {
debug("pluginDirs: %s", settings.PluginsDirectory) debug("pluginDirs: %s", settings.PluginsDirectory)
plugins, err := plugin.FindPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err != nil { if err != nil {
@ -75,7 +75,7 @@ func filterPlugins(plugins []*plugin.Plugin, ignoredPluginNames []string) []*plu
} }
// Provide dynamic auto-completion for plugin names // Provide dynamic auto-completion for plugin names
func compListPlugins(toComplete string, ignoredPluginNames []string) []string { func compListPlugins(_ string, ignoredPluginNames []string) []string {
var pNames []string var pNames []string
plugins, err := plugin.FindPlugins(settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory)
if err == nil && len(plugins) > 0 { if err == nil && len(plugins) > 0 {

@ -26,7 +26,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
func TestManuallyProcessArgs(t *testing.T) { func TestManuallyProcessArgs(t *testing.T) {

@ -24,7 +24,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/plugin" "helm.sh/helm/v4/pkg/plugin"
) )
type pluginUninstallOptions struct { type pluginUninstallOptions struct {
@ -38,13 +38,13 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command {
Use: "uninstall <plugin>...", Use: "uninstall <plugin>...",
Aliases: []string{"rm", "remove"}, Aliases: []string{"rm", "remove"},
Short: "uninstall one or more Helm plugins", Short: "uninstall one or more Helm plugins",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compListPlugins(toComplete, args), cobra.ShellCompDirectiveNoFileComp return compListPlugins(toComplete, args), cobra.ShellCompDirectiveNoFileComp
}, },
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(_ *cobra.Command, args []string) error {
return o.complete(args) return o.complete(args)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, _ []string) error {
return o.run(out) return o.run(out)
}, },
} }
@ -78,7 +78,7 @@ func (o *pluginUninstallOptions) run(out io.Writer) error {
} }
} }
if len(errorPlugins) > 0 { if len(errorPlugins) > 0 {
return errors.Errorf(strings.Join(errorPlugins, "\n")) return errors.New(strings.Join(errorPlugins, "\n"))
} }
return nil return nil
} }

@ -24,8 +24,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/plugin" "helm.sh/helm/v4/pkg/plugin"
"helm.sh/helm/v3/pkg/plugin/installer" "helm.sh/helm/v4/pkg/plugin/installer"
) )
type pluginUpdateOptions struct { type pluginUpdateOptions struct {
@ -39,13 +39,13 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
Use: "update <plugin>...", Use: "update <plugin>...",
Aliases: []string{"up"}, Aliases: []string{"up"},
Short: "update one or more Helm plugins", Short: "update one or more Helm plugins",
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compListPlugins(toComplete, args), cobra.ShellCompDirectiveNoFileComp return compListPlugins(toComplete, args), cobra.ShellCompDirectiveNoFileComp
}, },
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(_ *cobra.Command, args []string) error {
return o.complete(args) return o.complete(args)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, _ []string) error {
return o.run(out) return o.run(out)
}, },
} }
@ -81,7 +81,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error {
} }
} }
if len(errorPlugins) > 0 { if len(errorPlugins) > 0 {
return errors.Errorf(strings.Join(errorPlugins, "\n")) return errors.New(strings.Join(errorPlugins, "\n"))
} }
return nil return nil
} }

@ -23,8 +23,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
const pullDesc = ` const pullDesc = `
@ -43,7 +43,7 @@ result in an error, and the chart will not be saved locally.
` `
func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPullWithOpts(action.WithConfig(cfg)) client := action.NewPull(action.WithConfig(cfg))
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "pull [chart URL | repo/chartname] [...]", Use: "pull [chart URL | repo/chartname] [...]",
@ -51,13 +51,13 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Aliases: []string{"fetch"}, Aliases: []string{"fetch"},
Long: pullDesc, Long: pullDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
return compListCharts(toComplete, false) return compListCharts(toComplete, false)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
client.Settings = settings client.Settings = settings
if client.Version == "" && client.Devel { if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0") debug("setting version to >0.0.0-0")
@ -65,7 +65,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP) client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password)
if err != nil { if err != nil {
return fmt.Errorf("missing registry client: %w", err) return fmt.Errorf("missing registry client: %w", err)
} }
@ -90,7 +90,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and untardir are specified, untardir is appended to this") f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and untardir are specified, untardir is appended to this")
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 1 { if len(args) != 1 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }

@ -24,7 +24,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v4/pkg/repo/repotest"
) )
func TestPullCmd(t *testing.T) { func TestPullCmd(t *testing.T) {
@ -183,14 +183,19 @@ func TestPullCmd(t *testing.T) {
wantError: true, wantError: true,
}, },
{ {
name: "Fail fetching OCI chart without version specified", name: "Fetching OCI chart without version option specified",
args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0", ociSrv.RegistryURL), args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0", ociSrv.RegistryURL),
wantErrorMsg: "Error: --version flag is explicitly required for OCI registries", expectFile: "./oci-dependent-chart-0.1.0.tgz",
wantError: true,
}, },
{ {
name: "Fail fetching OCI chart without version specified", name: "Fetching OCI chart with version specified",
args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0 --version 0.1.0", ociSrv.RegistryURL), args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.1.0 --version 0.1.0", ociSrv.RegistryURL),
expectFile: "./oci-dependent-chart-0.1.0.tgz",
},
{
name: "Fail fetching OCI chart with version mismatch",
args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart:0.2.0 --version 0.1.0", ociSrv.RegistryURL),
wantErrorMsg: "Error: chart reference and version mismatch: 0.2.0 is not 0.1.0",
wantError: true, wantError: true,
}, },
} }
@ -198,7 +203,7 @@ func TestPullCmd(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
outdir := srv.Root() outdir := srv.Root()
cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s", cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s --plain-http",
tt.args, tt.args,
outdir, outdir,
filepath.Join(outdir, "repositories.yaml"), filepath.Join(outdir, "repositories.yaml"),
@ -258,7 +263,7 @@ func TestPullWithCredentialsCmd(t *testing.T) {
} }
defer srv.Stop() defer srv.Stop()
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv.WithMiddleware(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth() username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" { if !ok || username != "username" || password != "password" {
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)

@ -22,9 +22,9 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/pusher" "helm.sh/helm/v4/pkg/pusher"
) )
const pushDesc = ` const pushDesc = `
@ -40,6 +40,8 @@ type registryPushOptions struct {
caFile string caFile string
insecureSkipTLSverify bool insecureSkipTLSverify bool
plainHTTP bool plainHTTP bool
password string
username string
} }
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
@ -50,7 +52,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "push a chart to remote", Short: "push a chart to remote",
Long: pushDesc, Long: pushDesc,
Args: require.MinimumNArgs(2), Args: require.MinimumNArgs(2),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 { if len(args) == 0 {
// Do file completion for the chart file to push // Do file completion for the chart file to push
return nil, cobra.ShellCompDirectiveDefault return nil, cobra.ShellCompDirectiveDefault
@ -65,10 +67,13 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
return comps, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace return comps, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
} }
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify, o.plainHTTP) registryClient, err := newRegistryClient(
o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify, o.plainHTTP, o.username, o.password,
)
if err != nil { if err != nil {
return fmt.Errorf("missing registry client: %w", err) return fmt.Errorf("missing registry client: %w", err)
} }
@ -96,6 +101,8 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart upload") f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart upload")
f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload") f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload")
f.StringVar(&o.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&o.password, "password", "", "chart repository password where to locate the requested chart")
return cmd return cmd
} }

@ -20,7 +20,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
const registryHelp = ` const registryHelp = `

@ -27,8 +27,8 @@ import (
"github.com/moby/term" "github.com/moby/term"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
const registryLoginDesc = ` const registryLoginDesc = `
@ -43,6 +43,7 @@ type registryLoginOptions struct {
keyFile string keyFile string
caFile string caFile string
insecure bool insecure bool
plainHTTP bool
} }
func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
@ -53,8 +54,8 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
Short: "login to a registry", Short: "login to a registry",
Long: registryLoginDesc, Long: registryLoginDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
ValidArgsFunction: noCompletions, ValidArgsFunction: cobra.NoFileCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
hostname := args[0] hostname := args[0]
username, password, err := getUsernamePassword(o.username, o.password, o.passwordFromStdinOpt) username, password, err := getUsernamePassword(o.username, o.password, o.passwordFromStdinOpt)
@ -66,7 +67,8 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
action.WithCertFile(o.certFile), action.WithCertFile(o.certFile),
action.WithKeyFile(o.keyFile), action.WithKeyFile(o.keyFile),
action.WithCAFile(o.caFile), action.WithCAFile(o.caFile),
action.WithInsecure(o.insecure)) action.WithInsecure(o.insecure),
action.WithPlainHTTPLogin(o.plainHTTP))
}, },
} }
@ -78,6 +80,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file") f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file") f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload")
return cmd return cmd
} }

@ -21,8 +21,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
const registryLogoutDesc = ` const registryLogoutDesc = `
@ -35,8 +35,8 @@ func newRegistryLogoutCmd(cfg *action.Configuration, out io.Writer) *cobra.Comma
Short: "logout from a registry", Short: "logout from a registry",
Long: registryLogoutDesc, Long: registryLogoutDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
ValidArgsFunction: noCompletions, ValidArgsFunction: cobra.NoFileCompletions,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
hostname := args[0] hostname := args[0]
return action.NewRegistryLogout(cfg).Run(out, hostname) return action.NewRegistryLogout(cfg).Run(out, hostname)
}, },

@ -25,9 +25,9 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
) )
const releaseTestHelp = ` const releaseTestHelp = `
@ -39,7 +39,7 @@ The tests to be run are defined in the chart that was installed.
func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewReleaseTesting(cfg) client := action.NewReleaseTesting(cfg)
var outfmt = output.Table outfmt := output.Table
var outputLogs bool var outputLogs bool
var filter []string var filter []string
@ -48,13 +48,13 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
Short: "run tests for a release", Short: "run tests for a release",
Long: releaseTestHelp, Long: releaseTestHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
client.Namespace = settings.Namespace() client.Namespace = settings.Namespace()
notName := regexp.MustCompile(`^!\s?name=`) notName := regexp.MustCompile(`^!\s?name=`)
for _, f := range filter { for _, f := range filter {
@ -72,7 +72,12 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
return runErr return runErr
} }
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false}); err != nil { if err := outfmt.Write(out, &statusPrinter{
release: rel,
debug: settings.Debug,
showMetadata: false,
hideNotes: client.HideNotes,
}); err != nil {
return err return err
} }
@ -92,6 +97,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&outputLogs, "logs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)") f.BoolVar(&outputLogs, "logs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)")
f.StringSliceVar(&filter, "filter", []string{}, "specify tests by attribute (currently \"name\") using attribute=value syntax or '!attribute=value' to exclude a test (can specify multiple or separate values with commas: name=test1,name=test2)") f.StringSliceVar(&filter, "filter", []string{}, "specify tests by attribute (currently \"name\") using attribute=value syntax or '!attribute=value' to exclude a test (can specify multiple or separate values with commas: name=test1,name=test2)")
f.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in test output. Does not affect presence in chart metadata")
return cmd return cmd
} }

@ -23,7 +23,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
) )
var repoHelm = ` var repoHelm = `

@ -31,9 +31,9 @@ import (
"golang.org/x/term" "golang.org/x/term"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
// Repositories that have been permanently deleted and no longer work // Repositories that have been permanently deleted and no longer work
@ -59,9 +59,6 @@ type repoAddOptions struct {
repoFile string repoFile string
repoCache string repoCache string
// Deprecated, but cannot be removed until Helm 4
deprecatedNoUpdate bool
} }
func newRepoAddCmd(out io.Writer) *cobra.Command { func newRepoAddCmd(out io.Writer) *cobra.Command {
@ -71,8 +68,13 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
Use: "add [NAME] [URL]", Use: "add [NAME] [URL]",
Short: "add a chart repository", Short: "add a chart repository",
Args: require.ExactArgs(2), Args: require.ExactArgs(2),
ValidArgsFunction: noCompletions, ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 1 {
return noMoreArgsComp()
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(_ *cobra.Command, args []string) error {
o.name = args[0] o.name = args[0]
o.url = args[1] o.url = args[1]
o.repoFile = settings.RepositoryConfig o.repoFile = settings.RepositoryConfig
@ -87,7 +89,6 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.password, "password", "", "chart repository password") f.StringVar(&o.password, "password", "", "chart repository password")
f.BoolVarP(&o.passwordFromStdinOpt, "password-stdin", "", false, "read chart repository password from stdin") f.BoolVarP(&o.passwordFromStdinOpt, "password-stdin", "", false, "read chart repository password from stdin")
f.BoolVar(&o.forceUpdate, "force-update", false, "replace (overwrite) the repo if it already exists") f.BoolVar(&o.forceUpdate, "force-update", false, "replace (overwrite) the repo if it already exists")
f.BoolVar(&o.deprecatedNoUpdate, "no-update", false, "Ignored. Formerly, it would disabled forced updates. It is deprecated by force-update.")
f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")

@ -27,10 +27,10 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v3/pkg/helmpath/xdg" "helm.sh/helm/v4/pkg/helmpath/xdg"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v4/pkg/repo/repotest"
) )
func TestRepoAddCmd(t *testing.T) { func TestRepoAddCmd(t *testing.T) {
@ -96,7 +96,6 @@ func TestRepoAdd(t *testing.T) {
name: testRepoName, name: testRepoName,
url: ts.URL(), url: ts.URL(),
forceUpdate: false, forceUpdate: false,
deprecatedNoUpdate: true,
repoFile: repoFile, repoFile: repoFile,
} }
os.Setenv(xdg.CacheHomeEnvVar, rootDir) os.Setenv(xdg.CacheHomeEnvVar, rootDir)
@ -151,7 +150,6 @@ func TestRepoAddCheckLegalName(t *testing.T) {
name: testRepoName, name: testRepoName,
url: ts.URL(), url: ts.URL(),
forceUpdate: false, forceUpdate: false,
deprecatedNoUpdate: true,
repoFile: repoFile, repoFile: repoFile,
} }
os.Setenv(xdg.CacheHomeEnvVar, rootDir) os.Setenv(xdg.CacheHomeEnvVar, rootDir)
@ -206,7 +204,6 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) {
o := &repoAddOptions{ o := &repoAddOptions{
name: name, name: name,
url: ts.URL(), url: ts.URL(),
deprecatedNoUpdate: true,
forceUpdate: false, forceUpdate: false,
repoFile: repoFile, repoFile: repoFile,
} }

@ -24,19 +24,21 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
const repoIndexDesc = ` const repoIndexDesc = `
Read the current directory and generate an index file based on the charts found. Read the current directory, generate an index file based on the charts found
and write the result to 'index.yaml' in the current directory.
This tool is used for creating an 'index.yaml' file for a chart repository. To This tool is used for creating an 'index.yaml' file for a chart repository. To
set an absolute URL to the charts, use '--url' flag. set an absolute URL to the charts, use '--url' flag.
To merge the generated index with an existing index file, use the '--merge' To merge the generated index with an existing index file, use the '--merge'
flag. In this case, the charts found in the current directory will be merged flag. In this case, the charts found in the current directory will be merged
into the existing index, with local charts taking priority over existing charts. into the index passed in with --merge, with local charts taking priority over
existing charts.
` `
type repoIndexOptions struct { type repoIndexOptions struct {
@ -54,15 +56,15 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command {
Short: "generate an index file given a directory containing packaged charts", Short: "generate an index file given a directory containing packaged charts",
Long: repoIndexDesc, Long: repoIndexDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 { if len(args) == 0 {
// Allow file completion when completing the argument for the directory // Allow file completion when completing the argument for the directory
return nil, cobra.ShellCompDirectiveDefault return nil, cobra.ShellCompDirectiveDefault
} }
// No more completions, so disable file completion // No more completions, so disable file completion
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
o.dir = args[0] o.dir = args[0]
return o.run(out) return o.run(out)
}, },
@ -76,7 +78,7 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command {
return cmd return cmd
} }
func (i *repoIndexOptions) run(out io.Writer) error { func (i *repoIndexOptions) run(_ io.Writer) error {
path, err := filepath.Abs(i.dir) path, err := filepath.Abs(i.dir)
if err != nil { if err != nil {
return err return err

@ -24,7 +24,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
func TestRepoIndexCmd(t *testing.T) { func TestRepoIndexCmd(t *testing.T) {
@ -162,9 +162,9 @@ func TestRepoIndexCmd(t *testing.T) {
} }
} }
func linkOrCopy(old, new string) error { func linkOrCopy(source, target string) error {
if err := os.Link(old, new); err != nil { if err := os.Link(source, target); err != nil {
return copyFile(old, new) return copyFile(source, target)
} }
return nil return nil

@ -24,9 +24,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
func newRepoListCmd(out io.Writer) *cobra.Command { func newRepoListCmd(out io.Writer) *cobra.Command {
@ -36,8 +36,8 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Short: "list chart repositories", Short: "list chart repositories",
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, _ []string) error {
f, _ := repo.LoadFile(settings.RepositoryConfig) f, _ := repo.LoadFile(settings.RepositoryConfig)
if len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML) { if len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML) {
return errors.New("no repositories to show") return errors.New("no repositories to show")
@ -123,7 +123,7 @@ func filterRepos(repos []*repo.Entry, ignoredRepoNames []string) []*repo.Entry {
} }
// Provide dynamic auto-completion for repo names // Provide dynamic auto-completion for repo names
func compListRepos(prefix string, ignoredRepoNames []string) []string { func compListRepos(_ string, ignoredRepoNames []string) []string {
var rNames []string var rNames []string
f, err := repo.LoadFile(settings.RepositoryConfig) f, err := repo.LoadFile(settings.RepositoryConfig)

@ -25,9 +25,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
type repoRemoveOptions struct { type repoRemoveOptions struct {
@ -44,10 +44,10 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Short: "remove one or more chart repositories", Short: "remove one or more chart repositories",
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig o.repoFile = settings.RepositoryConfig
o.repoCache = settings.RepositoryCache o.repoCache = settings.RepositoryCache
o.names = args o.names = args

@ -24,9 +24,9 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v4/pkg/repo/repotest"
) )
func TestRepoRemove(t *testing.T) { func TestRepoRemove(t *testing.T) {

@ -19,14 +19,15 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"slices"
"sync" "sync"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
const updateDesc = ` const updateDesc = `
@ -57,10 +58,10 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
Short: "update information of available charts locally from chart repositories", Short: "update information of available charts locally from chart repositories",
Long: updateDesc, Long: updateDesc,
Args: require.MinimumNArgs(0), Args: require.MinimumNArgs(0),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig o.repoFile = settings.RepositoryConfig
o.repoCache = settings.RepositoryCache o.repoCache = settings.RepositoryCache
o.names = args o.names = args
@ -158,10 +159,5 @@ func checkRequestedRepos(requestedRepos []string, validRepos []*repo.Entry) erro
} }
func isRepoRequested(repoName string, requestedRepos []string) bool { func isRepoRequested(repoName string, requestedRepos []string) bool {
for _, requestedRepo := range requestedRepos { return slices.Contains(requestedRepos, repoName)
if repoName == requestedRepo {
return true
}
}
return false
} }

@ -24,17 +24,17 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v4/pkg/repo/repotest"
) )
func TestUpdateCmd(t *testing.T) { func TestUpdateCmd(t *testing.T) {
var out bytes.Buffer var out bytes.Buffer
// Instead of using the HTTP updater, we provide our own for this test. // Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently. // The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error { updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error {
for _, re := range repos { for _, re := range repos {
fmt.Fprintln(out, re.Config.Name) fmt.Fprintln(out, re.Config.Name)
} }
@ -59,7 +59,7 @@ func TestUpdateCmdMultiple(t *testing.T) {
var out bytes.Buffer var out bytes.Buffer
// Instead of using the HTTP updater, we provide our own for this test. // Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently. // The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error { updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error {
for _, re := range repos { for _, re := range repos {
fmt.Fprintln(out, re.Config.Name) fmt.Fprintln(out, re.Config.Name)
} }
@ -85,7 +85,7 @@ func TestUpdateCmdInvalid(t *testing.T) {
var out bytes.Buffer var out bytes.Buffer
// Instead of using the HTTP updater, we provide our own for this test. // Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently. // The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error { updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error {
for _, re := range repos { for _, re := range repos {
fmt.Fprintln(out, re.Config.Name) fmt.Fprintln(out, re.Config.Name)
} }

@ -24,8 +24,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
const rollbackDesc = ` const rollbackDesc = `
@ -46,7 +46,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "roll back a release to a previous revision", Short: "roll back a release to a previous revision",
Long: rollbackDesc, Long: rollbackDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 { if len(args) == 0 {
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
} }
@ -55,9 +55,9 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compListRevisions(toComplete, cfg, args[0]) return compListRevisions(toComplete, cfg, args[0])
} }
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
if len(args) > 1 { if len(args) > 1 {
ver, err := strconv.Atoi(args[1]) ver, err := strconv.Atoi(args[1])
if err != nil { if err != nil {

@ -17,10 +17,12 @@ limitations under the License.
package main package main
import ( import (
"fmt"
"reflect"
"testing" "testing"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
func TestRollbackCmd(t *testing.T) { func TestRollbackCmd(t *testing.T) {
@ -121,3 +123,44 @@ func TestRollbackFileCompletion(t *testing.T) {
checkFileCompletion(t, "rollback myrelease", false) checkFileCompletion(t, "rollback myrelease", false)
checkFileCompletion(t, "rollback myrelease 1", 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)
}
}

@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package main // import "helm.sh/helm/v3/cmd/helm" package main // import "helm.sh/helm/v4/cmd/helm"
import ( import (
"context" "context"
"fmt" "fmt"
"io" "io"
"log" "log"
"net/http"
"os" "os"
"strings" "strings"
@ -29,9 +30,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/internal/tlsutil"
"helm.sh/helm/v3/pkg/registry" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/registry"
"helm.sh/helm/v4/pkg/repo"
) )
var globalUsage = `The Kubernetes package manager var globalUsage = `The Kubernetes package manager
@ -46,7 +48,7 @@ Common actions for Helm:
Environment variables: Environment variables:
| Name | Description | | Name | Description |
|------------------------------------|---------------------------------------------------------------------------------------------------| |------------------------------------|------------------------------------------------------------------------------------------------------------|
| $HELM_CACHE_HOME | set an alternative location for storing cached files. | | $HELM_CACHE_HOME | set an alternative location for storing cached files. |
| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. | | $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. |
| $HELM_DATA_HOME | set an alternative location for storing Helm data. | | $HELM_DATA_HOME | set an alternative location for storing Helm data. |
@ -69,7 +71,8 @@ Environment variables:
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. | | $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_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_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_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: Helm stores cache, configuration, and data based on the following configuration order:
@ -99,7 +102,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
addKlogFlags(flags) addKlogFlags(flags)
// Setup shell completion for the namespace flag // Setup shell completion for the namespace flag
err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("namespace", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
if client, err := actionConfig.KubernetesClientSet(); err == nil { if client, err := actionConfig.KubernetesClientSet(); err == nil {
// Choose a long enough timeout that the user notices something is not working // Choose a long enough timeout that the user notices something is not working
// but short enough that the user is not made to wait very long // but short enough that the user is not made to wait very long
@ -122,7 +125,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
} }
// Setup shell completion for the kube-context flag // Setup shell completion for the kube-context flag
err = cmd.RegisterFlagCompletionFunc("kube-context", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err = cmd.RegisterFlagCompletionFunc("kube-context", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
cobra.CompDebugln("About to get the different kube-contexts", settings.Debug) cobra.CompDebugln("About to get the different kube-contexts", settings.Debug)
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
@ -152,7 +155,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
flags.ParseErrorsWhitelist.UnknownFlags = true flags.ParseErrorsWhitelist.UnknownFlags = true
flags.Parse(args) flags.Parse(args)
registryClient, err := newDefaultRegistryClient(false) registryClient, err := newDefaultRegistryClient(false, "", "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -166,7 +169,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
newPullCmd(actionConfig, out), newPullCmd(actionConfig, out),
newShowCmd(actionConfig, out), newShowCmd(actionConfig, out),
newLintCmd(out), newLintCmd(out),
newPackageCmd(actionConfig, out), newPackageCmd(out),
newRepoCmd(out), newRepoCmd(out),
newSearchCmd(out), newSearchCmd(out),
newVerifyCmd(out), newVerifyCmd(out),
@ -200,9 +203,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
// Find and add plugins // Find and add plugins
loadPlugins(cmd, out) loadPlugins(cmd, out)
// Check permissions on critical files
checkPerms()
// Check for expired repositories // Check for expired repositories
checkForExpiredRepos(settings.RepositoryConfig) checkForExpiredRepos(settings.RepositoryConfig)
@ -229,7 +229,7 @@ func checkForExpiredRepos(repofile string) {
} }
// parse repo file. // parse repo file.
// Ignore the error because it is okay for a repo file to be unparseable at this // Ignore the error because it is okay for a repo file to be unparsable at this
// stage. Later checks will trap the error and respond accordingly. // stage. Later checks will trap the error and respond accordingly.
repoFile, err := repo.LoadFile(repofile) repoFile, err := repo.LoadFile(repofile)
if err != nil { if err != nil {
@ -257,27 +257,30 @@ func checkForExpiredRepos(repofile string) {
} }
func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify, plainHTTP bool) (*registry.Client, error) { func newRegistryClient(
certFile, keyFile, caFile string, insecureSkipTLSverify, plainHTTP bool, username, password string,
) (*registry.Client, error) {
if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSverify { if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSverify {
registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSverify) registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSverify, username, password)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return registryClient, nil return registryClient, nil
} }
registryClient, err := newDefaultRegistryClient(plainHTTP) registryClient, err := newDefaultRegistryClient(plainHTTP, username, password)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return registryClient, nil return registryClient, nil
} }
func newDefaultRegistryClient(plainHTTP bool) (*registry.Client, error) { func newDefaultRegistryClient(plainHTTP bool, username, password string) (*registry.Client, error) {
opts := []registry.ClientOption{ opts := []registry.ClientOption{
registry.ClientOptDebug(settings.Debug), registry.ClientOptDebug(settings.Debug),
registry.ClientOptEnableCache(true), registry.ClientOptEnableCache(true),
registry.ClientOptWriter(os.Stderr), registry.ClientOptWriter(os.Stderr),
registry.ClientOptCredentialsFile(settings.RegistryConfig), registry.ClientOptCredentialsFile(settings.RegistryConfig),
registry.ClientOptBasicAuth(username, password),
} }
if plainHTTP { if plainHTTP {
opts = append(opts, registry.ClientOptPlainHTTP()) opts = append(opts, registry.ClientOptPlainHTTP())
@ -291,10 +294,31 @@ func newDefaultRegistryClient(plainHTTP bool) (*registry.Client, error) {
return registryClient, nil return registryClient, nil
} }
func newRegistryClientWithTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*registry.Client, error) { func newRegistryClientWithTLS(
certFile, keyFile, caFile string, insecureSkipTLSverify bool, username, password string,
) (*registry.Client, error) {
tlsConf, err := tlsutil.NewTLSConfig(
tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify),
tlsutil.WithCertKeyPairFiles(certFile, keyFile),
tlsutil.WithCAFile(caFile),
)
if err != nil {
return nil, fmt.Errorf("can't create TLS config for client: %w", err)
}
// Create a new registry client // Create a new registry client
registryClient, err := registry.NewRegistryClientWithTLS(os.Stderr, certFile, keyFile, caFile, insecureSkipTLSverify, registryClient, err := registry.NewClient(
settings.RegistryConfig, settings.Debug, registry.ClientOptDebug(settings.Debug),
registry.ClientOptEnableCache(true),
registry.ClientOptWriter(os.Stderr),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
registry.ClientOptHTTPClient(&http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConf,
},
}),
registry.ClientOptBasicAuth(username, password),
) )
if err != nil { if err != nil {
return nil, err return nil, err

@ -21,9 +21,9 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/v3/internal/test/ensure" "helm.sh/helm/v4/internal/test/ensure"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v3/pkg/helmpath/xdg" "helm.sh/helm/v4/pkg/helmpath/xdg"
) )
func TestRootCmd(t *testing.T) { func TestRootCmd(t *testing.T) {

@ -1,58 +0,0 @@
//go:build !windows
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"os"
"os/user"
"path/filepath"
)
func checkPerms() {
// This function MUST NOT FAIL, as it is just a check for a common permissions problem.
// If for some reason the function hits a stopping condition, it may panic. But only if
// we can be sure that it is panicking because Helm cannot proceed.
kc := settings.KubeConfig
if kc == "" {
kc = os.Getenv("KUBECONFIG")
}
if kc == "" {
u, err := user.Current()
if err != nil {
// No idea where to find KubeConfig, so return silently. Many helm commands
// can proceed happily without a KUBECONFIG, so this is not a fatal error.
return
}
kc = filepath.Join(u.HomeDir, ".kube", "config")
}
fi, err := os.Stat(kc)
if err != nil {
// DO NOT error if no KubeConfig is found. Not all commands require one.
return
}
perm := fi.Mode().Perm()
if perm&0040 > 0 {
warning("Kubernetes configuration file is group-readable. This is insecure. Location: %s", kc)
}
if perm&0004 > 0 {
warning("Kubernetes configuration file is world-readable. This is insecure. Location: %s", kc)
}
}

@ -1,82 +0,0 @@
//go:build !windows
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bytes"
"io"
"os"
"path/filepath"
"strings"
"testing"
)
func checkPermsStderr() (string, error) {
r, w, err := os.Pipe()
if err != nil {
return "", err
}
stderr := os.Stderr
os.Stderr = w
defer func() {
os.Stderr = stderr
}()
checkPerms()
w.Close()
var text bytes.Buffer
io.Copy(&text, r)
return text.String(), nil
}
func TestCheckPerms(t *testing.T) {
tdir := t.TempDir()
tfile := filepath.Join(tdir, "testconfig")
fh, err := os.OpenFile(tfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0440)
if err != nil {
t.Errorf("Failed to create temp file: %s", err)
}
tconfig := settings.KubeConfig
settings.KubeConfig = tfile
defer func() { settings.KubeConfig = tconfig }()
text, err := checkPermsStderr()
if err != nil {
t.Fatalf("could not read from stderr: %s", err)
}
expectPrefix := "WARNING: Kubernetes configuration file is group-readable. This is insecure. Location:"
if !strings.HasPrefix(text, expectPrefix) {
t.Errorf("Expected to get a warning for group perms. Got %q", text)
}
if err := fh.Chmod(0404); err != nil {
t.Errorf("Could not change mode on file: %s", err)
}
text, err = checkPermsStderr()
if err != nil {
t.Fatalf("could not read from stderr: %s", err)
}
expectPrefix = "WARNING: Kubernetes configuration file is world-readable. This is insecure. Location:"
if !strings.HasPrefix(text, expectPrefix) {
t.Errorf("Expected to get a warning for world perms. Got %q", text)
}
}

@ -31,7 +31,7 @@ import (
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
// Result is a search result. // Result is a search result.

@ -20,8 +20,8 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
func TestSortScore(t *testing.T) { func TestSortScore(t *testing.T) {
@ -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 := NewIndex()
i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all) i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all)
i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{ i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{

@ -25,8 +25,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/internal/monocular" "helm.sh/helm/v4/internal/monocular"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
) )
const searchHubDesc = ` const searchHubDesc = `
@ -64,7 +64,7 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
Use: "hub [KEYWORD]", Use: "hub [KEYWORD]",
Short: "search for charts in the Artifact Hub or your own hub instance", Short: "search for charts in the Artifact Hub or your own hub instance",
Long: searchHubDesc, Long: searchHubDesc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
return o.run(out, args) return o.run(out, args)
}, },
} }

@ -27,7 +27,7 @@ func TestSearchHubCmd(t *testing.T) {
// Setup a mock search service // Setup a mock search service
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, searchResult) fmt.Fprintln(w, searchResult)
})) }))
defer ts.Close() defer ts.Close()
@ -57,7 +57,7 @@ func TestSearchHubListRepoCmd(t *testing.T) {
// Setup a mock search service // Setup a mock search service
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, searchResult) fmt.Fprintln(w, searchResult)
})) }))
defer ts.Close() defer ts.Close()
@ -155,7 +155,7 @@ func TestSearchHubCmd_FailOnNoResponseTests(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Setup a mock search service // Setup a mock search service
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, tt.response) fmt.Fprintln(w, tt.response)
})) }))
defer ts.Close() defer ts.Close()

@ -30,10 +30,10 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/search" "helm.sh/helm/v4/cmd/helm/search"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v4/pkg/helmpath"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v4/pkg/repo"
) )
const searchRepoDesc = ` const searchRepoDesc = `
@ -81,7 +81,7 @@ func newSearchRepoCmd(out io.Writer) *cobra.Command {
Use: "repo [keyword]", Use: "repo [keyword]",
Short: "search repositories for a keyword in charts", Short: "search repositories for a keyword in charts",
Long: searchRepoDesc, Long: searchRepoDesc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig o.repoFile = settings.RepositoryConfig
o.repoCacheDir = settings.RepositoryCache o.repoCacheDir = settings.RepositoryCache
return o.run(out, args) return o.run(out, args)
@ -139,7 +139,7 @@ func (o *searchRepoOptions) setupSearchedVersion() {
if o.devel { // search for releases and prereleases (alpha, beta, and release candidate releases). if o.devel { // search for releases and prereleases (alpha, beta, and release candidate releases).
debug("setting version to >0.0.0-0") debug("setting version to >0.0.0-0")
o.version = ">0.0.0-0" o.version = ">0.0.0-0"
} else { // search only for stable releases, prerelease versions will be skip } else { // search only for stable releases, prerelease versions will be skipped
debug("setting version to >0.0.0") debug("setting version to >0.0.0")
o.version = ">0.0.0" o.version = ">0.0.0"
} }

@ -23,8 +23,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
) )
const showDesc = ` const showDesc = `
@ -57,7 +57,7 @@ of the CustomResourceDefinition files
` `
func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewShowWithConfig(action.ShowAll, cfg) client := action.NewShow(action.ShowAll, cfg)
showCommand := &cobra.Command{ showCommand := &cobra.Command{
Use: "show", Use: "show",
@ -65,13 +65,12 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Aliases: []string{"inspect"}, Aliases: []string{"inspect"},
Long: showDesc, Long: showDesc,
Args: require.NoArgs, Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
} }
// Function providing dynamic auto-completion // Function providing dynamic auto-completion
validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { validArgsFunc := func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListCharts(toComplete, true) return compListCharts(toComplete, true)
} }
@ -82,7 +81,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: showAllDesc, Long: showAllDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowAll client.OutputFormat = action.ShowAll
err := addRegistryClient(client) err := addRegistryClient(client)
if err != nil { if err != nil {
@ -103,7 +102,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: showValuesDesc, Long: showValuesDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowValues client.OutputFormat = action.ShowValues
err := addRegistryClient(client) err := addRegistryClient(client)
if err != nil { if err != nil {
@ -124,7 +123,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: showChartDesc, Long: showChartDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowChart client.OutputFormat = action.ShowChart
err := addRegistryClient(client) err := addRegistryClient(client)
if err != nil { if err != nil {
@ -145,7 +144,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: readmeChartDesc, Long: readmeChartDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowReadme client.OutputFormat = action.ShowReadme
err := addRegistryClient(client) err := addRegistryClient(client)
if err != nil { if err != nil {
@ -166,7 +165,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: showCRDsDesc, Long: showCRDsDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowCRDs client.OutputFormat = action.ShowCRDs
err := addRegistryClient(client) err := addRegistryClient(client)
if err != nil { if err != nil {
@ -199,7 +198,7 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) {
} }
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)
err := subCmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := subCmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 1 { if len(args) != 1 {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
} }
@ -227,7 +226,7 @@ func runShow(args []string, client *action.Show) (string, error) {
func addRegistryClient(client *action.Show) error { func addRegistryClient(client *action.Show) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP) client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password)
if err != nil { if err != nil {
return fmt.Errorf("missing registry client: %w", err) return fmt.Errorf("missing registry client: %w", err)
} }

@ -22,7 +22,7 @@ import (
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v4/pkg/repo/repotest"
) )
func TestShowPreReleaseChart(t *testing.T) { func TestShowPreReleaseChart(t *testing.T) {

@ -28,11 +28,11 @@ import (
"k8s.io/kubectl/pkg/cmd/get" "k8s.io/kubectl/pkg/cmd/get"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v4/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli/output" "helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
) )
// NOTE: Keep the list of statuses up-to-date with pkg/release/status.go. // NOTE: Keep the list of statuses up-to-date with pkg/release/status.go.
@ -43,8 +43,8 @@ The status consists of:
- k8s namespace in which the release lives - k8s namespace in which the release lives
- state of the release (can be: unknown, deployed, uninstalled, superseded, failed, uninstalling, pending-install, pending-upgrade or pending-rollback) - state of the release (can be: unknown, deployed, uninstalled, superseded, failed, uninstalling, pending-install, pending-upgrade or pending-rollback)
- revision of the release - revision of the release
- description of the release (can be completion message or error message, need to enable --show-desc) - description of the release (can be completion message or error message)
- list of resources that this release consists of (need to enable --show-resources) - list of resources that this release consists of
- details on last test suite run, if applicable - details on last test suite run, if applicable
- additional notes provided by the chart - additional notes provided by the chart
` `
@ -58,14 +58,13 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "display the status of the named release", Short: "display the status of the named release",
Long: statusHelp, Long: statusHelp,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 { if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp return noMoreArgsComp()
} }
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
// When the output format is a table the resources should be fetched // When the output format is a table the resources should be fetched
// and displayed as a table. When YAML or JSON the resources will be // and displayed as a table. When YAML or JSON the resources will be
// returned. This mirrors the handling in kubectl. // returned. This mirrors the handling in kubectl.
@ -80,7 +79,12 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
// strip chart metadata from the output // strip chart metadata from the output
rel.Chart = nil rel.Chart = nil
return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription, client.ShowResources, false}) return outfmt.Write(out, &statusPrinter{
release: rel,
debug: false,
showMetadata: false,
hideNotes: false,
})
}, },
} }
@ -88,21 +92,17 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.IntVar(&client.Version, "revision", 0, "if set, display the status of the named release with revision") f.IntVar(&client.Version, "revision", 0, "if set, display the status of the named release with revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 { if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0]) return compListRevisions(toComplete, cfg, args[0])
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)
f.BoolVar(&client.ShowDescription, "show-desc", false, "if set, display the description message of the named release")
f.BoolVar(&client.ShowResources, "show-resources", false, "if set, display the resources of the named release")
return cmd return cmd
} }
@ -110,9 +110,8 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
type statusPrinter struct { type statusPrinter struct {
release *release.Release release *release.Release
debug bool debug bool
showDescription bool
showResources bool
showMetadata bool showMetadata bool
hideNotes bool
} }
func (s statusPrinter) WriteJSON(out io.Writer) error { func (s statusPrinter) WriteJSON(out io.Writer) error {
@ -139,11 +138,9 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
_, _ = fmt.Fprintf(out, "VERSION: %s\n", s.release.Chart.Metadata.Version) _, _ = fmt.Fprintf(out, "VERSION: %s\n", s.release.Chart.Metadata.Version)
_, _ = fmt.Fprintf(out, "APP_VERSION: %s\n", s.release.Chart.Metadata.AppVersion) _, _ = fmt.Fprintf(out, "APP_VERSION: %s\n", s.release.Chart.Metadata.AppVersion)
} }
if s.showDescription {
_, _ = fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description) _, _ = fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description)
}
if s.showResources && s.release.Info.Resources != nil && len(s.release.Info.Resources) > 0 { if len(s.release.Info.Resources) > 0 {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
printFlags := get.NewHumanPrintFlags() printFlags := get.NewHumanPrintFlags()
typePrinter, _ := printFlags.ToPrinter("") typePrinter, _ := printFlags.ToPrinter("")
@ -219,8 +216,9 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
_, _ = fmt.Fprintf(out, "MANIFEST:\n%s\n", s.release.Manifest) _, _ = fmt.Fprintf(out, "MANIFEST:\n%s\n", s.release.Manifest)
} }
if len(s.release.Info.Notes) > 0 { // Hide notes from output - option in install and upgrades
_, _ = fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(s.release.Info.Notes)) if !s.hideNotes && len(s.release.Info.Notes) > 0 {
fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(s.release.Info.Notes))
} }
return nil return nil
} }

@ -20,9 +20,9 @@ import (
"testing" "testing"
"time" "time"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
helmtime "helm.sh/helm/v3/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
) )
func TestStatusCmd(t *testing.T) { func TestStatusCmd(t *testing.T) {
@ -46,7 +46,7 @@ func TestStatusCmd(t *testing.T) {
}), }),
}, { }, {
name: "get status of a deployed release, with desc", name: "get status of a deployed release, with desc",
cmd: "status --show-desc flummoxed-chickadee", cmd: "status flummoxed-chickadee",
golden: "output/status-with-desc.txt", golden: "output/status-with-desc.txt",
rels: releasesMockWithStatus(&release.Info{ rels: releasesMockWithStatus(&release.Info{
Status: release.StatusDeployed, Status: release.StatusDeployed,
@ -70,7 +70,7 @@ func TestStatusCmd(t *testing.T) {
}), }),
}, { }, {
name: "get status of a deployed release with resources", name: "get status of a deployed release with resources",
cmd: "status --show-resources flummoxed-chickadee", cmd: "status flummoxed-chickadee",
golden: "output/status-with-resources.txt", golden: "output/status-with-resources.txt",
rels: releasesMockWithStatus( rels: releasesMockWithStatus(
&release.Info{ &release.Info{
@ -79,7 +79,7 @@ func TestStatusCmd(t *testing.T) {
), ),
}, { }, {
name: "get status of a deployed release with resources in json", name: "get status of a deployed release with resources in json",
cmd: "status --show-resources flummoxed-chickadee -o json", cmd: "status flummoxed-chickadee -o json",
golden: "output/status-with-resources.json", golden: "output/status-with-resources.json",
rels: releasesMockWithStatus( rels: releasesMockWithStatus(
&release.Info{ &release.Info{

@ -24,18 +24,19 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"regexp" "regexp"
"slices"
"sort" "sort"
"strings" "strings"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v4/pkg/release"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v4/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v4/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v4/pkg/cli/values"
"helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v4/pkg/releaseutil"
) )
const templateDesc = ` const templateDesc = `
@ -61,7 +62,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "locally render templates", Short: "locally render templates",
Long: templateDesc, Long: templateDesc,
Args: require.MinimumNArgs(1), Args: require.MinimumNArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return compInstall(args, toComplete, client) return compInstall(args, toComplete, client)
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
@ -74,7 +75,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP) client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password)
if err != nil { if err != nil {
return fmt.Errorf("missing registry client: %w", err) return fmt.Errorf("missing registry client: %w", err)
} }
@ -206,12 +207,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
func isTestHook(h *release.Hook) bool { func isTestHook(h *release.Hook) bool {
for _, e := range h.Events { return slices.Contains(h.Events, release.HookTest)
if e == release.HookTest {
return true
}
}
return false
} }
// The following functions (writeToFile, createOrOpenFile, and ensureDirectoryForFile) // The following functions (writeToFile, createOrOpenFile, and ensureDirectoryForFile)
@ -219,7 +215,7 @@ func isTestHook(h *release.Hook) bool {
// bug introduced by #8156. As part of the todo to refactor renderResources // bug introduced by #8156. As part of the todo to refactor renderResources
// this duplicate code should be removed. It is added here so that the API // this duplicate code should be removed. It is added here so that the API
// surface area is as minimally impacted as possible in fixing the issue. // surface area is as minimally impacted as possible in fixing the issue.
func writeToFile(outputDir string, name string, data string, append bool) error { func writeToFile(outputDir string, name string, data string, appendData bool) error {
outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator))
err := ensureDirectoryForFile(outfileName) err := ensureDirectoryForFile(outfileName)
@ -227,7 +223,7 @@ func writeToFile(outputDir string, name string, data string, append bool) error
return err return err
} }
f, err := createOrOpenFile(outfileName, append) f, err := createOrOpenFile(outfileName, appendData)
if err != nil { if err != nil {
return err return err
} }
@ -244,8 +240,8 @@ func writeToFile(outputDir string, name string, data string, append bool) error
return nil return nil
} }
func createOrOpenFile(filename string, append bool) (*os.File, error) { func createOrOpenFile(filename string, appendData bool) (*os.File, error) {
if append { if appendData {
return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600)
} }
return os.Create(filename) return os.Create(filename)

@ -22,6 +22,18 @@ import (
"testing" "testing"
) )
func TestTemplateCmdWithToml(t *testing.T) {
tests := []cmdTestCase{
{
name: "check toToml function rendering",
cmd: fmt.Sprintf("template '%s'", "testdata/testcharts/issue-totoml"),
golden: "output/issue-totoml.txt",
},
}
runTestCmd(t, tests)
}
var chartPath = "testdata/testcharts/subchart" var chartPath = "testdata/testcharts/subchart"
func TestTemplateCmd(t *testing.T) { func TestTemplateCmd(t *testing.T) {
@ -142,7 +154,7 @@ func TestTemplateCmd(t *testing.T) {
golden: "output/issue-9027.txt", 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", name: "template with imported subchart values ensuring import",
cmd: fmt.Sprintf("template '%s' --set configmap.enabled=true --set subchartb.enabled=true", chartPath), cmd: fmt.Sprintf("template '%s' --set configmap.enabled=true --set subchartb.enabled=true", chartPath),
golden: "output/template-subchart-cm.txt", golden: "output/template-subchart-cm.txt",

@ -3,6 +3,7 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
REVISION: 1 REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None TEST SUITE: None
NOTES: NOTES:
PARENT NOTES PARENT NOTES

@ -3,4 +3,5 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default NAMESPACE: default
STATUS: deployed STATUS: deployed
REVISION: 1 REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None TEST SUITE: None

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save