Merge remote-tracking branch 'upstream/main' into feat/toml-parsing

Signed-off-by: Joe Julian <me@joejulian.name>
pull/12026/head
Joe Julian 4 months ago
commit 1c73ea44ad
No known key found for this signature in database
GPG Key ID: FAB12BE0575D999B

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

@ -4,4 +4,18 @@ updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
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: "github-actions"
directory: "/"
schedule:
interval: "daily"

@ -7,6 +7,6 @@
**Special notes for your reviewer**:
**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 has been tested for backwards compatibility

@ -2,8 +2,8 @@ name: build-test
on:
push:
branches:
- 'main'
- 'release-**'
- "main"
- "release-**"
pull_request:
branches:
- main
@ -13,23 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
- name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # pin@5.0.2
with:
go-version: '1.20'
- name: Install golangci-lint
run: |
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
shasum -a 256 golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz | grep "^$GOLANGCI_LINT_SHA256 " > /dev/null
tar -xf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
env:
GOLANGCI_LINT_VERSION: '1.51.2'
GOLANGCI_LINT_SHA256: '4de479eb9d9bc29da51aec1834e7c255b333723d38dbd56781c68e5dddc6a90b'
- name: Test style
run: make test-style
go-version: '1.22.7'
- name: Test source headers are present
run: make test-source-headers
- name: Run unit tests
run: make test-coverage
- name: Test build

@ -35,11 +35,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # pinv2.1.37
uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # pinv3.26.6
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -50,7 +50,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # pinv2.1.37
uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # pinv3.26.6
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -64,4 +64,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # pinv2.1.37
uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # pinv3.26.6

@ -0,0 +1,22 @@
name: golangci-lint
on:
push:
pull_request:
jobs:
golangci:
name: golangci-lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
- name: Setup Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # pin@5.0.2
with:
go-version: "1.22.7"
- name: golangci-lint
uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 #pin@6.1.0
with:
version: v1.58

@ -0,0 +1,21 @@
name: govulncheck
on:
push:
paths:
- go.sum
schedule:
- cron: "0 0 * * *"
jobs:
govulncheck:
name: govulncheck
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # pin@5.0.2
with:
go-version: '1.22.7'
- name: govulncheck
uses: golang/govulncheck-action@dd0578b371c987f96d1185abb54344b44352bd58 # pin@1.0.3
with:
go-package: ./...

@ -18,21 +18,37 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # pin@5.0.2
with:
go-version: '1.20'
go-version: '1.22.7'
- name: Run unit tests
run: make test-coverage
- name: Build Helm Binaries
run: |
make build-cross
set -eu -o pipefail
make build-cross VERSION="${{ github.ref_name }}"
make dist checksum VERSION="${{ github.ref_name }}"
- name: Set latest version
run: |
set -eu -o pipefail
mkdir -p _dist_versions
# Push the latest semver tag, excluding prerelease tags
LATEST_VERSION="$(git tag | sort -r --version-sort | grep '^v[0-9]' | grep -v '-' | head -n1)"
echo "LATEST_VERSION=${LATEST_VERSION}"
echo "${LATEST_VERSION}" > _dist_versions/helm-latest-version
echo "${LATEST_VERSION}" > _dist_versions/helm3-latest-version
- name: Upload Binaries
uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0
env:
@ -44,17 +60,28 @@ jobs:
connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
extra_args: '--pattern helm-*'
- name: Upload Version tag files
uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0
env:
AZURE_STORAGE_CONNECTION_STRING: "${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}"
AZURE_STORAGE_CONTAINER_NAME: "${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}"
with:
overwrite: 'true'
source_dir: _dist_versions
container_name: ${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}
connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
canary-release:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout source code
uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4.1.7
- name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # pin@5.0.2
with:
go-version: '1.20'
go-version: '1.22.7'
- name: Run unit tests
run: make test-coverage

2
.gitignore vendored

@ -5,7 +5,9 @@
.idea/
.vimrc
.vscode/
.devcontainer/
_dist/
_dist_versions/
bin/
vendor/
# Ignores charts pulled for dependency build tests

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

@ -13,7 +13,7 @@ chance to try to fix the issue before it is exploited in the wild.
## Sign Your Work
The sign-off is a simple line at the end of the explanation for a commit. All commits needs to be
The sign-off is a simple line at the end of the explanation for a commit. All commits need to be
signed. Your signature certifies that you wrote the patch or otherwise have the right to contribute
the material. The rules are pretty simple, if you can certify the below (from
[developercertificate.org](https://developercertificate.org/)):
@ -66,6 +66,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
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
commit will look something like this:
@ -115,8 +127,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
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/`
directory of our source code.
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.
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:
@ -126,30 +139,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
exploited a bug)
- 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.
## 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 are used as the primary method for tracking anything to do with the Helm project.
@ -195,7 +187,7 @@ below.
See [Proposing an Idea](#proposing-an-idea). Smaller quality-of-life enhancements are exempt.
- Issues that are labeled as `feature` or `bug` should be connected to the PR that resolves it.
- Whoever is working on a `feature` or `bug` issue (whether a maintainer or someone from the
community), should either assign the issue to themself or make a comment in the issue saying
community), should either assign the issue to themselves or make a comment in the issue saying
that they are taking it.
- `proposal` and `support/question` issues should stay open until resolved or if they have not
been active for more than 30 days. This will help keep the issue queue to a manageable size
@ -282,9 +274,9 @@ Like any good open source project, we use Pull Requests (PRs) to track code chan
#### Documentation PRs
Documentation PRs will follow the same lifecycle as other PRs. They will also be labeled with the
`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).
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 it is recommend all user facing changes. Accurate and helpful documentation is critical for effectively communicating Helm's behavior to a wide audience.
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
@ -327,6 +319,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 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 |
| `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

97
KEYS

@ -940,3 +940,100 @@ AirPev6SluPhLJ2mswaK3THlhOZulKO/VIEJ6g50m5Vj3hdYf6sR603yK9rP+3iu
IagTQt2SGfW3Ap0RO3Yt+w29BpZ1CZ5Ml4gAYkXz0hiiMnVRhlcLIOHoFw==
=h3+3
-----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-----

@ -1,8 +1,8 @@
BINDIR := $(CURDIR)/bin
INSTALL_PATH ?= /usr/local/bin
DIST_DIRS := find * -type d -exec
TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x windows/amd64
TARGET_OBJS ?= darwin-amd64.tar.gz darwin-amd64.tar.gz.sha256 darwin-amd64.tar.gz.sha256sum darwin-arm64.tar.gz darwin-arm64.tar.gz.sha256 darwin-arm64.tar.gz.sha256sum linux-amd64.tar.gz linux-amd64.tar.gz.sha256 linux-amd64.tar.gz.sha256sum linux-386.tar.gz linux-386.tar.gz.sha256 linux-386.tar.gz.sha256sum linux-arm.tar.gz linux-arm.tar.gz.sha256 linux-arm.tar.gz.sha256sum linux-arm64.tar.gz linux-arm64.tar.gz.sha256 linux-arm64.tar.gz.sha256sum linux-ppc64le.tar.gz linux-ppc64le.tar.gz.sha256 linux-ppc64le.tar.gz.sha256sum linux-s390x.tar.gz linux-s390x.tar.gz.sha256 linux-s390x.tar.gz.sha256sum windows-amd64.zip windows-amd64.zip.sha256 windows-amd64.zip.sha256sum
TARGETS := darwin/amd64 darwin/arm64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x linux/riscv64 windows/amd64 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 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
GOBIN = $(shell go env GOBIN)
@ -11,7 +11,7 @@ GOBIN = $(shell go env GOPATH)/bin
endif
GOX = $(GOBIN)/gox
GOIMPORTS = $(GOBIN)/goimports
ARCH = $(shell uname -p)
ARCH = $(shell go env GOARCH)
ACCEPTANCE_DIR:=../acceptance-testing
# To specify the subset of acceptance tests to run. '.' means all tests
@ -114,7 +114,11 @@ test-coverage:
.PHONY: test-style
test-style:
GO111MODULE=on golangci-lint run
golangci-lint run ./...
@scripts/validate-license.sh
.PHONY: test-source-headers
test-source-headers:
@scripts/validate-license.sh
.PHONY: test-acceptance
@ -155,7 +159,7 @@ gen-test-golden: test-unit
# without a go.mod file when downloading the following dependencies
$(GOX):
(cd /; GO111MODULE=on go install github.com/mitchellh/gox@latest)
(cd /; GO111MODULE=on go install github.com/mitchellh/gox@v1.0.2-0.20220701044238-9f712387e2d2)
$(GOIMPORTS):
(cd /; GO111MODULE=on go install golang.org/x/tools/cmd/goimports@latest)

@ -1,20 +1,21 @@
maintainers:
- adamreese
- bacongobbler
- hickeyma
- joejulian
- jdolitsky
- marckhouzam
- mattfarina
- sabre1041
- scottrigby
- SlickNik
- technosophos
triage:
- joejulian
- yxxhero
- zonggen
- gjenkins8
- z4ce
emeritus:
- adamreese
- bacongobbler
- fibonacci1729
- hickeyma
- jascott1
- michelleN
- migmartri
@ -22,6 +23,7 @@ emeritus:
- prydonius
- rimusz
- seh
- SlickNik
- thomastaylor312
- vaikas-google
- viglesiasce

@ -30,7 +30,6 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Install
Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest).
Unpack the `helm` binary and add it to your PATH and you are good to go!
@ -40,7 +39,6 @@ If you want to use a package manager:
- [Homebrew](https://brew.sh/) users can use `brew install helm`.
- [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
- [Scoop](https://scoop.sh/) users can use `scoop install helm`.
- [GoFish](https://gofi.sh/) users can use `gofish install helm`.
- [Snapcraft](https://snapcraft.io/) users can use `snap install helm --classic`
To rapidly get Helm up and running, start with the [Quick Start Guide](https://helm.sh/docs/intro/quickstart/).
@ -68,6 +66,10 @@ You can reach the Helm community and developers via the following channels:
- [Helm Mailing List](https://lists.cncf.io/g/cncf-helm)
- Developer Call: Thursdays at 9:30-10:00 Pacific ([meeting details](https://github.com/helm/community/blob/master/communication.md#meetings))
### Contribution
If you're interested in contributing, please refer to the [Contributing Guide](CONTRIBUTING.md) **before submitting a pull request**.
### Code of conduct
Participation in the Helm community is governed by the [Code of Conduct](code-of-conduct.md).

@ -102,8 +102,8 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
Short: "generate autocompletion script for bash",
Long: bashCompDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, _ []string) error {
return runCompletionBash(out, cmd)
},
}
@ -114,8 +114,8 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
Short: "generate autocompletion script for zsh",
Long: zshCompDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, _ []string) error {
return runCompletionZsh(out, cmd)
},
}
@ -126,8 +126,8 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
Short: "generate autocompletion script for fish",
Long: fishCompDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, _ []string) error {
return runCompletionFish(out, cmd)
},
}
@ -138,8 +138,8 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
Short: "generate autocompletion script for powershell",
Long: powershellCompDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, _ []string) error {
return runCompletionPowershell(out, cmd)
},
}
@ -209,7 +209,15 @@ func runCompletionPowershell(out io.Writer, cmd *cobra.Command) error {
return cmd.Root().GenPowerShellCompletionWithDesc(out)
}
// Function to disable file completion
func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
// noMoreArgsCompFunc deactivates file completion when doing argument shell completion.
// It also provides some ActiveHelp to indicate no more arguments are accepted.
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
}

@ -64,16 +64,16 @@ func newCreateCmd(out io.Writer) *cobra.Command {
Short: "create a new chart with the given name",
Long: createDesc,
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 {
// Allow file completion when completing the argument for the name
// which could be a path
return nil, cobra.ShellCompDirectiveDefault
}
// 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.starterDir = helmpath.DataPath("starters")
return o.run(out)

@ -30,9 +30,9 @@ import (
)
func TestCreateCmd(t *testing.T) {
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
cname := "testchart"
dir := ensure.TempDir(t)
dir := t.TempDir()
defer testChdir(t, dir)()
// Run a create
@ -61,7 +61,7 @@ func TestCreateCmd(t *testing.T) {
}
func TestCreateStarterCmd(t *testing.T) {
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
cname := "testchart"
defer resetEnv()()
os.MkdirAll(helmpath.CachePath(), 0755)
@ -127,7 +127,7 @@ func TestCreateStarterCmd(t *testing.T) {
func TestCreateStarterAbsoluteCmd(t *testing.T) {
defer resetEnv()()
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
cname := "testchart"
// Create a starter.

@ -106,7 +106,7 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
Short: "list the dependencies for the given chart",
Long: dependencyListDesc,
Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
chartpath := "."
if len(args) > 0 {
chartpath = filepath.Clean(args[0])

@ -49,7 +49,7 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm
Short: "rebuild the charts/ directory based on the Chart.lock file",
Long: dependencyBuildDesc,
Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
chartpath := "."
if len(args) > 0 {
chartpath = filepath.Clean(args[0])

@ -52,7 +52,7 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com
Short: "update charts/ based on the contents of Chart.yaml",
Long: dependencyUpDesc,
Args: require.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
chartpath := "."
if len(args) > 0 {
chartpath = filepath.Clean(args[0])

@ -149,7 +149,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
defer resetEnv()()
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz")
if err != nil {
@ -200,12 +200,68 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
}
}
// Make sure tmpcharts is deleted
if _, err := os.Stat(filepath.Join(dir(chartname), "tmpcharts")); !os.IsNotExist(err) {
// Make sure tmpcharts-x is deleted
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")
}
}
func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) {
srv := setupMockRepoServer(t)
srvForUnmanagedRepo := setupMockRepoServer(t)
defer srv.Stop()
defer srvForUnmanagedRepo.Stop()
dir := func(p ...string) string {
return filepath.Join(append([]string{srv.Root()}, p...)...)
}
chartname := "depup"
ch := createTestingMetadata(chartname, srv.URL())
chartDependency := &chart.Dependency{
Name: "signtest",
Version: "0.1.0",
Repository: srvForUnmanagedRepo.URL(),
}
ch.Metadata.Dependencies = append(ch.Metadata.Dependencies, chartDependency)
if err := chartutil.SaveDir(ch, dir()); err != nil {
t.Fatal(err)
}
_, out, err := executeActionCommand(
fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname),
dir("repositories.yaml"), dir()),
)
if err != nil {
t.Logf("Output: %s", out)
t.Fatal(err)
}
// This is written directly to stdout, so we have to capture as is
if !strings.Contains(out, `Getting updates for unmanaged Helm repositories...`) {
t.Errorf("No unmanaged Helm repo used in test chartdependency or it doesnt cause the creation "+
"of an ad hoc repo index cache file\n%s", out)
}
}
func setupMockRepoServer(t *testing.T) *repotest.Server {
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz")
if err != nil {
t.Fatal(err)
}
t.Logf("Listening on directory %s", srv.Root())
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
return srv
}
// createTestingMetadata creates a basic chart that depends on reqtest-0.1.0
//
// The baseURL can be used to point to a particular repository server.

@ -58,8 +58,8 @@ func newDocsCmd(out io.Writer) *cobra.Command {
Long: docsDesc,
Hidden: true,
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, _ []string) error {
o.topCmd = cmd.Root()
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.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 cmd
}
func (o *docsOptions) run(out io.Writer) error {
func (o *docsOptions) run(_ io.Writer) error {
switch o.docTypeString {
case "markdown", "mdown", "md":
if o.generateHeaders {

@ -36,15 +36,15 @@ func newEnvCmd(out io.Writer) *cobra.Command {
Short: "helm client environment information",
Long: envHelp,
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 {
keys := getSortedEnvVarKeys()
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()
if len(args) == 0 {

@ -48,6 +48,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)")
f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line")
}
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
@ -60,6 +61,7 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.BoolVar(&c.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download")
f.BoolVar(&c.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download")
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
}
@ -70,7 +72,7 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
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(), ", ")))
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
for format, desc := range output.FormatsWithDesc() {
formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc))
@ -193,7 +195,7 @@ func (p *postRendererArgsSlice) GetSlice() []string {
return p.options.args
}
func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellCompDirective) {
func compVersionFlag(chartRef string, _ string) ([]string, cobra.ShellCompDirective) {
chartInfo := strings.Split(chartRef, "/")
if len(chartInfo) != 2 {
return nil, cobra.ShellCompDirectiveNoFileComp

@ -33,6 +33,7 @@ get extended information about the release, including:
- The generated manifest file
- The notes provided by the chart of the release
- The hooks associated with the release
- The metadata of the release
`
func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
@ -48,6 +49,7 @@ func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cmd.AddCommand(newGetManifestCmd(cfg, out))
cmd.AddCommand(newGetHooksCmd(cfg, out))
cmd.AddCommand(newGetNotesCmd(cfg, out))
cmd.AddCommand(newGetMetadataCmd(cfg, out))
return cmd
}

@ -41,13 +41,13 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download all information for a named release",
Long: getAllHelp,
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
}
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])
if err != nil {
return err
@ -58,14 +58,13 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
return tpl(template, data, out)
}
return output.Table.Write(out, &statusPrinter{res, true, false, false})
return output.Table.Write(out, &statusPrinter{res, true, false, false, true, false})
},
}
f := cmd.Flags()
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 {
return compListRevisions(toComplete, cfg, args[0])
}

@ -41,13 +41,13 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download all hooks for a named release",
Long: getHooksHelp,
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
}
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])
if err != nil {
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")
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 {
return compListRevisions(toComplete, cfg, args[0])
}

@ -43,13 +43,13 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
Short: "download the manifest for a named release",
Long: getManifestHelp,
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
}
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])
if err != nil {
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")
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 {
return compListRevisions(toComplete, cfg, args[0])
}

@ -0,0 +1,94 @@
/*
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 (
"fmt"
"io"
"log"
"github.com/spf13/cobra"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/cli/output"
)
type metadataWriter struct {
metadata *action.Metadata
}
func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var outfmt output.Format
client := action.NewGetMetadata(cfg)
cmd := &cobra.Command{
Use: "metadata RELEASE_NAME",
Short: "This command fetches metadata for a given release",
Args: require.ExactArgs(1),
ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return noMoreArgsComp()
}
return compListReleases(toComplete, args, cfg)
},
RunE: func(_ *cobra.Command, args []string) error {
releaseMetadata, err := client.Run(args[0])
if err != nil {
return err
}
return outfmt.Write(out, &metadataWriter{releaseMetadata})
},
}
f := cmd.Flags()
f.IntVar(&client.Version, "revision", 0, "specify release revision")
err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 1 {
return compListRevisions(toComplete, cfg, args[0])
}
return nil, cobra.ShellCompDirectiveNoFileComp
})
if err != nil {
log.Fatal(err)
}
bindOutputFlag(cmd, &outfmt)
return cmd
}
func (w metadataWriter) WriteTable(out io.Writer) error {
_, _ = fmt.Fprintf(out, "NAME: %v\n", w.metadata.Name)
_, _ = fmt.Fprintf(out, "CHART: %v\n", w.metadata.Chart)
_, _ = fmt.Fprintf(out, "VERSION: %v\n", w.metadata.Version)
_, _ = fmt.Fprintf(out, "APP_VERSION: %v\n", w.metadata.AppVersion)
_, _ = fmt.Fprintf(out, "NAMESPACE: %v\n", w.metadata.Namespace)
_, _ = fmt.Fprintf(out, "REVISION: %v\n", w.metadata.Revision)
_, _ = fmt.Fprintf(out, "STATUS: %v\n", w.metadata.Status)
_, _ = fmt.Fprintf(out, "DEPLOYED_AT: %v\n", w.metadata.DeployedAt)
return nil
}
func (w metadataWriter) WriteJSON(out io.Writer) error {
return output.EncodeJSON(out, w.metadata)
}
func (w metadataWriter) WriteYAML(out io.Writer) error {
return output.EncodeYAML(out, w.metadata)
}

@ -0,0 +1,66 @@
/*
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 (
"testing"
"helm.sh/helm/v3/pkg/release"
)
func TestGetMetadataCmd(t *testing.T) {
tests := []cmdTestCase{{
name: "get metadata with a release",
cmd: "get metadata thomas-guide",
golden: "output/get-metadata.txt",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
}, {
name: "get metadata requires release name arg",
cmd: "get metadata",
golden: "output/get-metadata-args.txt",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
wantError: true,
}, {
name: "get metadata to json",
cmd: "get metadata thomas-guide --output json",
golden: "output/get-metadata.json",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
}, {
name: "get metadata to yaml",
cmd: "get metadata thomas-guide --output yaml",
golden: "output/get-metadata.yaml",
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
}}
runTestCmd(t, tests)
}
func TestGetMetadataCompletion(t *testing.T) {
checkReleaseCompletion(t, "get metadata", false)
}
func TestGetMetadataRevisionCompletion(t *testing.T) {
revisionFlagCompletionTest(t, "get metadata")
}
func TestGetMetadataOutputCompletion(t *testing.T) {
outputFlagCompletionTest(t, "get metadata")
}
func TestGetMetadataFileCompletion(t *testing.T) {
checkFileCompletion(t, "get metadata", false)
checkFileCompletion(t, "get metadata myrelease", false)
}

@ -39,13 +39,13 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download the notes for a named release",
Long: getNotesHelp,
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
}
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])
if err != nil {
return err
@ -59,7 +59,7 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags()
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 {
return compListRevisions(toComplete, cfg, args[0])
}

@ -46,13 +46,13 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "download the values file for a named release",
Long: getValuesHelp,
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
}
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])
if err != nil {
return err
@ -63,7 +63,7 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags()
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 {
return compListRevisions(toComplete, cfg, args[0])
}

@ -22,6 +22,7 @@ import (
"log"
"os"
"strings"
"time"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
@ -45,7 +46,8 @@ func init() {
func debug(format string, v ...interface{}) {
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...))
}
}

@ -94,7 +94,7 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string)
Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {},
Log: func(_ string, _ ...interface{}) {},
}
root, err := newRootCmd(actionConfig, buf, args)

@ -60,13 +60,13 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "fetch release history",
Aliases: []string{"hist"},
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
}
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])
if err != nil {
return err
@ -184,7 +184,7 @@ func min(x, y int) int {
return y
}
func compListRevisions(toComplete string, cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) {
func compListRevisions(_ string, cfg *action.Configuration, releaseName string) ([]string, cobra.ShellCompDirective) {
client := action.NewHistory(cfg)
var revisions []string

@ -85,7 +85,7 @@ set for a key called 'foo', the 'newbar' value would take precedence:
$ helm install --set foo=bar --set foo=newbar myredis ./redis
Similarly, in the following example 'foo' is set to '["four"]':
Similarly, in the following example 'foo' is set to '["four"]':
$ helm install --set-json='foo=["one", "two", "three"]' --set-json='foo=["four"]' myredis ./redis
@ -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
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
file MUST pass all verification steps.
@ -132,26 +136,37 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "install a chart",
Long: installDesc,
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)
},
RunE: func(_ *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
// This is for the case where "" is specifically passed in as a
// value. When there is no value passed in NoOptDefVal will be used
// and it is set to client. See addInstallFlags.
if client.DryRunOption == "" {
client.DryRunOption = "none"
}
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
return errors.Wrap(err, "INSTALLATION FAILED")
}
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, client.HideNotes})
},
}
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)
bindPostRenderFlag(cmd, &client.PostRenderer)
@ -160,7 +175,13 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
// --dry-run options with expected outcome:
// - Not set means no dry run and server is contacted.
// - Set with no value, a value of client, or a value of true and the server is not contacted
// - Set with a value of false, none, or false and the server is contacted
// The true/false part is meant to reflect some legacy behavior while none is equal to "".
f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
f.Lookup("dry-run").NoOptDefVal = "client"
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.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")
@ -176,11 +197,14 @@ 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.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.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.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")
addValueOptionsFlags(f, valueOpts)
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
if client.GenerateName {
requiredArgs = 1
@ -252,6 +276,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
RegistryClient: client.GetRegistryClient(),
}
if err := man.Update(); err != nil {
return nil, err
@ -268,6 +293,11 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
client.Namespace = settings.Namespace()
// Validate DryRunOption member is one of the allowed values
if err := validateDryRunOptionFlag(client.DryRunOption); err != nil {
return nil, err
}
// Create context and prepare the handle of SIGTERM
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
@ -308,3 +338,19 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
func validateDryRunOptionFlag(dryRunOptionFlagValue string) error {
// Validate dry-run flag value with a set of allowed value
allowedDryRunValues := []string{"false", "true", "none", "client", "server"}
isAllowed := false
for _, v := range allowedDryRunValues {
if dryRunOptionFlagValue == v {
isAllowed = true
break
}
}
if !isAllowed {
return errors.New("Invalid dry-run flag. Flag must one of the following: false, true, none, client, server")
}
return nil
}

@ -33,7 +33,7 @@ func TestInstall(t *testing.T) {
}
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()
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)
@ -225,6 +225,12 @@ func TestInstall(t *testing.T) {
wantError: true,
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
{
name: "install with warning about deprecated chart",
@ -252,6 +258,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()),
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)

@ -27,6 +27,7 @@ import (
"github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/cli/values"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/lint/support"
@ -44,19 +45,29 @@ or recommendation, it will emit [WARNING] messages.
func newLintCmd(out io.Writer) *cobra.Command {
client := action.NewLint()
valueOpts := &values.Options{}
var kubeVersion string
cmd := &cobra.Command{
Use: "lint PATH",
Short: "examine a chart for possible issues",
Long: longLintHelp,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
paths := []string{"."}
if len(args) > 0 {
paths = args
}
if kubeVersion != "" {
parsedKubeVersion, err := chartutil.ParseKubeVersion(kubeVersion)
if err != nil {
return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err)
}
client.KubeVersion = parsedKubeVersion
}
if client.WithSubcharts {
for _, p := range paths {
filepath.Walk(filepath.Join(p, "charts"), func(path string, info os.FileInfo, err error) error {
filepath.Walk(filepath.Join(p, "charts"), func(path string, info os.FileInfo, _ error) error {
if info != nil {
if info.Name() == "Chart.yaml" {
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.WithSubcharts, "with-subcharts", false, "lint dependent charts")
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)
return cmd

@ -53,11 +53,44 @@ func TestLintCmdWithQuietFlag(t *testing.T) {
name: "lint chart with warning using --quiet flag",
cmd: "lint --quiet testdata/testcharts/chart-with-only-crds",
golden: "output/lint-quiet-with-warning.txt",
}, {
name: "lint non-existent chart using --quiet flag",
cmd: "lint --quiet thischartdoesntexist/",
golden: "",
wantError: true,
}}
runTestCmd(t, tests)
}
func TestLintCmdWithKubeVersionFlag(t *testing.T) {
testChart := "testdata/testcharts/chart-with-deprecated-api"
tests := []cmdTestCase{{
name: "lint chart with deprecated api version using kube version flag",
cmd: fmt.Sprintf("lint --kube-version 1.22.0 %s", testChart),
golden: "output/lint-chart-with-deprecated-api.txt",
wantError: false,
}, {
name: "lint chart with deprecated api version using kube version and strict flag",
cmd: fmt.Sprintf("lint --kube-version 1.22.0 --strict %s", testChart),
golden: "output/lint-chart-with-deprecated-api-strict.txt",
wantError: true,
}, {
// the test builds will use the default k8sVersionMinor const in deprecations.go and capabilities.go
// which is "20"
name: "lint chart with deprecated api version without kube version",
cmd: fmt.Sprintf("lint %s", testChart),
golden: "output/lint-chart-with-deprecated-api-old-k8s.txt",
wantError: false,
}, {
name: "lint chart with deprecated api version with older kube version",
cmd: fmt.Sprintf("lint --kube-version 1.21.0 --strict %s", testChart),
golden: "output/lint-chart-with-deprecated-api-old-k8s.txt",
wantError: false,
}}
runTestCmd(t, tests)
}
func TestLintFileCompletion(t *testing.T) {
checkFileCompletion(t, "lint", true)
checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given

@ -68,8 +68,8 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: listHelp,
Aliases: []string{"ls"},
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(cmd *cobra.Command, _ []string) error {
if client.AllNamespaces {
if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), debug); err != nil {
return err

@ -128,7 +128,8 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
env = append(env, fmt.Sprintf("%s=%s", k, v))
}
prog := exec.Command(main, argv...)
mainCmdExp := os.ExpandEnv(main)
prog := exec.Command(mainCmdExp, argv...)
prog.Env = env
prog.Stdin = os.Stdin
prog.Stdout = out
@ -153,7 +154,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
func manuallyProcessArgs(args []string) ([]string, []string) {
known := []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 {
for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") {
@ -300,7 +301,7 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
// to the dynamic completion script of the plugin.
DisableFlagParsing: true,
// 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)
addPluginCommands(plugin, subCmd, &cmd)

@ -55,7 +55,7 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Use: "package [CHART_PATH] [...]",
Short: "package a chart directory into a chart archive",
Long: packageDesc,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.Errorf("need at least one argument, the path to the chart")
}

@ -23,7 +23,6 @@ import (
"strings"
"testing"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
)
@ -111,7 +110,7 @@ func TestPackage(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cachePath := ensure.TempDir(t)
cachePath := t.TempDir()
defer testChdir(t, cachePath)()
if err := os.MkdirAll("toot", 0777); err != nil {
@ -170,7 +169,7 @@ func TestSetAppVersion(t *testing.T) {
var ch *chart.Chart
expectedAppVersion := "app-version-foo"
chartToPackage := "testdata/testcharts/alpine"
dir := ensure.TempDir(t)
dir := t.TempDir()
cmd := fmt.Sprintf("package %s --destination=%s --app-version=%s", chartToPackage, dir, expectedAppVersion)
_, output, err := executeActionCommand(cmd)
if err != nil {

@ -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 {
o := &pluginInstallOptions{}
cmd := &cobra.Command{
Use: "install [options] <path|url>...",
Short: "install one or more Helm plugins",
Use: "install [options] <path|url>",
Short: "install a Helm plugin",
Long: pluginInstallDesc,
Aliases: []string{"add"},
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 {
// We do file completion, in case the plugin is local
return nil, cobra.ShellCompDirectiveDefault
}
// 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)
},
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
return o.run(out)
},
}

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

@ -161,6 +161,81 @@ func TestLoadPlugins(t *testing.T) {
}
}
func TestLoadPluginsWithSpace(t *testing.T) {
settings.PluginsDirectory = "testdata/helm home with space/helm/plugins"
settings.RepositoryConfig = "testdata/helm home with space/helm/repositories.yaml"
settings.RepositoryCache = "testdata/helm home with space/helm/repository"
var (
out bytes.Buffer
cmd cobra.Command
)
loadPlugins(&cmd, &out)
envs := strings.Join([]string{
"fullenv",
"testdata/helm home with space/helm/plugins/fullenv",
"testdata/helm home with space/helm/plugins",
"testdata/helm home with space/helm/repositories.yaml",
"testdata/helm home with space/helm/repository",
os.Args[0],
}, "\n")
// Test that the YAML file was correctly converted to a command.
tests := []struct {
use string
short string
long string
expect string
args []string
code int
}{
{"fullenv", "show env vars", "show all env vars", envs + "\n", []string{}, 0},
}
plugins := cmd.Commands()
if len(plugins) != len(tests) {
t.Fatalf("Expected %d plugins, got %d", len(tests), len(plugins))
}
for i := 0; i < len(plugins); i++ {
out.Reset()
tt := tests[i]
pp := plugins[i]
if pp.Use != tt.use {
t.Errorf("%d: Expected Use=%q, got %q", i, tt.use, pp.Use)
}
if pp.Short != tt.short {
t.Errorf("%d: Expected Use=%q, got %q", i, tt.short, pp.Short)
}
if pp.Long != tt.long {
t.Errorf("%d: Expected Use=%q, got %q", i, tt.long, pp.Long)
}
// Currently, plugins assume a Linux subsystem. Skip the execution
// tests until this is fixed
if runtime.GOOS != "windows" {
if err := pp.RunE(pp, tt.args); err != nil {
if tt.code > 0 {
perr, ok := err.(pluginError)
if !ok {
t.Errorf("Expected %s to return pluginError: got %v(%T)", tt.use, err, err)
}
if perr.code != tt.code {
t.Errorf("Expected %s to return %d: got %d", tt.use, tt.code, perr.code)
}
} else {
t.Errorf("Error running %s: %+v", tt.use, err)
}
}
if out.String() != tt.expect {
t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String())
}
}
}
}
type staticCompletionDetails struct {
use string
validArgs []string

@ -38,13 +38,13 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command {
Use: "uninstall <plugin>...",
Aliases: []string{"rm", "remove"},
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
},
PreRunE: func(cmd *cobra.Command, args []string) error {
PreRunE: func(_ *cobra.Command, args []string) error {
return o.complete(args)
},
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
return o.run(out)
},
}

@ -39,13 +39,13 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command {
Use: "update <plugin>...",
Aliases: []string{"up"},
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
},
PreRunE: func(cmd *cobra.Command, args []string) error {
PreRunE: func(_ *cobra.Command, args []string) error {
return o.complete(args)
},
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, _ []string) error {
return o.run(out)
},
}

@ -51,20 +51,21 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Aliases: []string{"fetch"},
Long: pullDesc,
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListCharts(toComplete, false)
},
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
client.Settings = settings
if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0")
client.Version = ">0.0.0-0"
}
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
@ -89,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")
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
}

@ -258,7 +258,7 @@ func TestPullWithCredentialsCmd(t *testing.T) {
}
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()
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)

@ -39,6 +39,7 @@ type registryPushOptions struct {
keyFile string
caFile string
insecureSkipTLSverify bool
plainHTTP bool
}
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
@ -49,7 +50,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "push a chart to remote",
Long: pushDesc,
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 {
// Do file completion for the chart file to push
return nil, cobra.ShellCompDirectiveDefault
@ -64,10 +65,10 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
return comps, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
}
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
},
RunE: func(cmd *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify)
RunE: func(_ *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify, o.plainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
@ -77,6 +78,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPushWithOpts(action.WithPushConfig(cfg),
action.WithTLSClientConfig(o.certFile, o.keyFile, o.caFile),
action.WithInsecureSkipTLSVerify(o.insecureSkipTLSverify),
action.WithPlainHTTP(o.plainHTTP),
action.WithPushOptWriter(out))
client.Settings = settings
output, err := client.Run(chartRef, remote)
@ -93,6 +95,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
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.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")
return cmd
}

@ -53,8 +53,8 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
Short: "login to a registry",
Long: registryLoginDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
ValidArgsFunction: cobra.NoFileCompletions,
RunE: func(_ *cobra.Command, args []string) error {
hostname := args[0]
username, password, err := getUsernamePassword(o.username, o.password, o.passwordFromStdinOpt)

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

@ -48,20 +48,20 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
Short: "run tests for a release",
Long: releaseTestHelp,
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
}
return compListReleases(toComplete, args, cfg)
},
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
client.Namespace = settings.Namespace()
notName := regexp.MustCompile(`^!\s?name=`)
for _, f := range filter {
if strings.HasPrefix(f, "name=") {
client.Filters["name"] = append(client.Filters["name"], strings.TrimPrefix(f, "name="))
client.Filters[action.IncludeNameFilter] = append(client.Filters[action.IncludeNameFilter], strings.TrimPrefix(f, "name="))
} else if notName.MatchString(f) {
client.Filters["!name"] = append(client.Filters["!name"], notName.ReplaceAllLiteralString(f, ""))
client.Filters[action.ExcludeNameFilter] = append(client.Filters[action.ExcludeNameFilter], notName.ReplaceAllLiteralString(f, ""))
}
}
rel, runErr := client.Run(args[0])
@ -72,7 +72,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
return runErr
}
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false}); err != nil {
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, client.HideNotes}); err != nil {
return err
}
@ -92,6 +92,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.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.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in test output. Does not affect presence in chart metadata")
return cmd
}

@ -68,11 +68,16 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
o := &repoAddOptions{}
cmd := &cobra.Command{
Use: "add [NAME] [URL]",
Short: "add a chart repository",
Args: require.ExactArgs(2),
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
Use: "add [NAME] [URL]",
Short: "add a chart repository",
Args: require.ExactArgs(2),
ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) > 1 {
return noMoreArgsComp()
}
return nil, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(_ *cobra.Command, args []string) error {
o.name = args[0]
o.url = args[1]
o.repoFile = settings.RepositoryConfig
@ -212,7 +217,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
f.Update(&c)
if err := f.WriteFile(o.repoFile, 0644); err != nil {
if err := f.WriteFile(o.repoFile, 0600); err != nil {
return err
}
fmt.Fprintf(out, "%q has been added to your repositories\n", o.name)

@ -27,7 +27,6 @@ import (
"sigs.k8s.io/yaml"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/helmpath/xdg"
"helm.sh/helm/v3/pkg/repo"
@ -48,7 +47,7 @@ func TestRepoAddCmd(t *testing.T) {
}
defer srv2.Stop()
tmpdir := filepath.Join(ensure.TempDir(t), "path-component.yaml/data")
tmpdir := filepath.Join(t.TempDir(), "path-component.yaml/data")
err = os.MkdirAll(tmpdir, 0777)
if err != nil {
t.Fatal(err)
@ -88,7 +87,7 @@ func TestRepoAdd(t *testing.T) {
}
defer ts.Stop()
rootDir := ensure.TempDir(t)
rootDir := t.TempDir()
repoFile := filepath.Join(rootDir, "repositories.yaml")
const testRepoName = "test-name"
@ -145,8 +144,8 @@ func TestRepoAddCheckLegalName(t *testing.T) {
const testRepoName = "test-hub/test-name"
rootDir := ensure.TempDir(t)
repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
rootDir := t.TempDir()
repoFile := filepath.Join(t.TempDir(), "repositories.yaml")
o := &repoAddOptions{
name: testRepoName,
@ -170,25 +169,25 @@ func TestRepoAddCheckLegalName(t *testing.T) {
func TestRepoAddConcurrentGoRoutines(t *testing.T) {
const testName = "test-name"
repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
repoFile := filepath.Join(t.TempDir(), "repositories.yaml")
repoAddConcurrent(t, testName, repoFile)
}
func TestRepoAddConcurrentDirNotExist(t *testing.T) {
const testName = "test-name-2"
repoFile := filepath.Join(ensure.TempDir(t), "foo", "repositories.yaml")
repoFile := filepath.Join(t.TempDir(), "foo", "repositories.yaml")
repoAddConcurrent(t, testName, repoFile)
}
func TestRepoAddConcurrentNoFileExtension(t *testing.T) {
const testName = "test-name-3"
repoFile := filepath.Join(ensure.TempDir(t), "repositories")
repoFile := filepath.Join(t.TempDir(), "repositories")
repoAddConcurrent(t, testName, repoFile)
}
func TestRepoAddConcurrentHiddenFile(t *testing.T) {
const testName = "test-name-4"
repoFile := filepath.Join(ensure.TempDir(t), ".repositories")
repoFile := filepath.Join(t.TempDir(), ".repositories")
repoAddConcurrent(t, testName, repoFile)
}
@ -254,7 +253,7 @@ func TestRepoAddWithPasswordFromStdin(t *testing.T) {
t.Errorf("unexpected error, got '%v'", err)
}
tmpdir := ensure.TempDir(t)
tmpdir := t.TempDir()
repoFile := filepath.Join(tmpdir, "repositories.yaml")
store := storageFixture()

@ -43,6 +43,7 @@ type repoIndexOptions struct {
dir string
url string
merge string
json bool
}
func newRepoIndexCmd(out io.Writer) *cobra.Command {
@ -53,15 +54,15 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command {
Short: "generate an index file given a directory containing packaged charts",
Long: repoIndexDesc,
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 {
// Allow file completion when completing the argument for the directory
return nil, cobra.ShellCompDirectiveDefault
}
// 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]
return o.run(out)
},
@ -70,20 +71,21 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command {
f := cmd.Flags()
f.StringVar(&o.url, "url", "", "url of chart repository")
f.StringVar(&o.merge, "merge", "", "merge the generated index into the given index")
f.BoolVar(&o.json, "json", false, "output in JSON format")
return cmd
}
func (i *repoIndexOptions) run(out io.Writer) error {
func (i *repoIndexOptions) run(_ io.Writer) error {
path, err := filepath.Abs(i.dir)
if err != nil {
return err
}
return index(path, i.url, i.merge)
return index(path, i.url, i.merge, i.json)
}
func index(dir, url, mergeTo string) error {
func index(dir, url, mergeTo string, json bool) error {
out := filepath.Join(dir, "index.yaml")
i, err := repo.IndexDirectory(dir, url)
@ -95,7 +97,7 @@ func index(dir, url, mergeTo string) error {
var i2 *repo.IndexFile
if _, err := os.Stat(mergeTo); os.IsNotExist(err) {
i2 = repo.NewIndexFile()
i2.WriteFile(mergeTo, 0644)
writeIndexFile(i2, mergeTo, json)
} else {
i2, err = repo.LoadIndexFile(mergeTo)
if err != nil {
@ -105,5 +107,12 @@ func index(dir, url, mergeTo string) error {
i.Merge(i2)
}
i.SortEntries()
return writeIndexFile(i, out, json)
}
func writeIndexFile(i *repo.IndexFile, out string, json bool) error {
if json {
return i.WriteJSONFile(out, 0644)
}
return i.WriteFile(out, 0644)
}

@ -18,18 +18,18 @@ package main
import (
"bytes"
"encoding/json"
"io"
"os"
"path/filepath"
"testing"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/repo"
)
func TestRepoIndexCmd(t *testing.T) {
dir := ensure.TempDir(t)
dir := t.TempDir()
comp := filepath.Join(dir, "compressedchart-0.1.0.tgz")
if err := linkOrCopy("testdata/testcharts/compressedchart-0.1.0.tgz", comp); err != nil {
@ -68,6 +68,28 @@ func TestRepoIndexCmd(t *testing.T) {
t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version)
}
b, err := os.ReadFile(destIndex)
if err != nil {
t.Fatal(err)
}
if json.Valid(b) {
t.Error("did not expect index file to be valid json")
}
// Test with `--json`
c.ParseFlags([]string{"--json", "true"})
if err := c.RunE(c, []string{dir}); err != nil {
t.Error(err)
}
if b, err = os.ReadFile(destIndex); err != nil {
t.Fatal(err)
}
if !json.Valid(b) {
t.Error("index file is not valid json")
}
// Test with `--merge`
// Remove first two charts.

@ -36,8 +36,8 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
Aliases: []string{"ls"},
Short: "list chart repositories",
Args: require.NoArgs,
ValidArgsFunction: noCompletions,
RunE: func(cmd *cobra.Command, args []string) error {
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(_ *cobra.Command, _ []string) error {
f, _ := repo.LoadFile(settings.RepositoryConfig)
if len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML) {
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
func compListRepos(prefix string, ignoredRepoNames []string) []string {
func compListRepos(_ string, ignoredRepoNames []string) []string {
var rNames []string
f, err := repo.LoadFile(settings.RepositoryConfig)

@ -44,10 +44,10 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command {
Aliases: []string{"rm"},
Short: "remove one or more chart repositories",
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
},
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig
o.repoCache = settings.RepositoryCache
o.names = args
@ -67,7 +67,7 @@ func (o *repoRemoveOptions) run(out io.Writer) error {
if !r.Remove(name) {
return errors.Errorf("no repo named %q found", name)
}
if err := r.WriteFile(o.repoFile, 0644); err != nil {
if err := r.WriteFile(o.repoFile, 0600); err != nil {
return err
}

@ -24,7 +24,6 @@ import (
"strings"
"testing"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/repo"
"helm.sh/helm/v3/pkg/repo/repotest"
@ -37,7 +36,7 @@ func TestRepoRemove(t *testing.T) {
}
defer ts.Stop()
rootDir := ensure.TempDir(t)
rootDir := t.TempDir()
repoFile := filepath.Join(rootDir, "repositories.yaml")
const testRepoName = "test-name"
@ -169,7 +168,7 @@ func TestRepoRemoveCompletion(t *testing.T) {
}
defer ts.Stop()
rootDir := ensure.TempDir(t)
rootDir := t.TempDir()
repoFile := filepath.Join(rootDir, "repositories.yaml")
repoCache := filepath.Join(rootDir, "cache/")

@ -57,10 +57,10 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command {
Short: "update information of available charts locally from chart repositories",
Long: updateDesc,
Args: require.MinimumNArgs(0),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
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.repoCache = settings.RepositoryCache
o.names = args

@ -34,7 +34,7 @@ func TestUpdateCmd(t *testing.T) {
var out bytes.Buffer
// Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error {
updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error {
for _, re := range repos {
fmt.Fprintln(out, re.Config.Name)
}
@ -59,7 +59,7 @@ func TestUpdateCmdMultiple(t *testing.T) {
var out bytes.Buffer
// Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error {
updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error {
for _, re := range repos {
fmt.Fprintln(out, re.Config.Name)
}
@ -85,7 +85,7 @@ func TestUpdateCmdInvalid(t *testing.T) {
var out bytes.Buffer
// Instead of using the HTTP updater, we provide our own for this test.
// The TestUpdateCharts test verifies the HTTP behavior independently.
updater := func(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error {
updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error {
for _, re := range repos {
fmt.Fprintln(out, re.Config.Name)
}
@ -102,10 +102,9 @@ func TestUpdateCmdInvalid(t *testing.T) {
}
func TestUpdateCustomCacheCmd(t *testing.T) {
rootDir := ensure.TempDir(t)
rootDir := t.TempDir()
cachePath := filepath.Join(rootDir, "updcustomcache")
os.Mkdir(cachePath, os.ModePerm)
defer os.RemoveAll(cachePath)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
@ -129,7 +128,7 @@ func TestUpdateCustomCacheCmd(t *testing.T) {
func TestUpdateCharts(t *testing.T) {
defer resetEnv()()
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
@ -164,7 +163,7 @@ func TestRepoUpdateFileCompletion(t *testing.T) {
func TestUpdateChartsFail(t *testing.T) {
defer resetEnv()()
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {
@ -197,7 +196,7 @@ func TestUpdateChartsFail(t *testing.T) {
func TestUpdateChartsFailWithError(t *testing.T) {
defer resetEnv()()
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil {

@ -32,8 +32,8 @@ const rollbackDesc = `
This command rolls back a release to a previous revision.
The first argument of the rollback command is the name of a release, and the
second is a revision (version) number. If this argument is omitted, it will
roll back to the previous release.
second is a revision (version) number. If this argument is omitted or set to
0, it will roll back to the previous release.
To see revision numbers, run 'helm history RELEASE'.
`
@ -46,7 +46,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "roll back a release to a previous revision",
Long: rollbackDesc,
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 {
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 nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
},
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
if len(args) > 1 {
ver, err := strconv.Atoi(args[1])
if err != nil {

@ -17,6 +17,8 @@ limitations under the License.
package main
import (
"fmt"
"reflect"
"testing"
"helm.sh/helm/v3/pkg/chart"
@ -64,6 +66,12 @@ func TestRollbackCmd(t *testing.T) {
cmd: "rollback funny-honey",
golden: "output/rollback-no-revision.txt",
rels: rels,
}, {
name: "rollback a release with non-existent version",
cmd: "rollback funny-honey 3",
golden: "output/rollback-non-existent-version.txt",
rels: rels,
wantError: true,
}, {
name: "rollback a release without release name",
cmd: "rollback",
@ -115,3 +123,44 @@ func TestRollbackFileCompletion(t *testing.T) {
checkFileCompletion(t, "rollback myrelease", false)
checkFileCompletion(t, "rollback myrelease 1", false)
}
func TestRollbackWithLabels(t *testing.T) {
labels1 := map[string]string{"operation": "install", "firstLabel": "firstValue"}
labels2 := map[string]string{"operation": "upgrade", "secondLabel": "secondValue"}
releaseName := "funny-bunny-labels"
rels := []*release.Release{
{
Name: releaseName,
Info: &release.Info{Status: release.StatusSuperseded},
Chart: &chart.Chart{},
Version: 1,
Labels: labels1,
},
{
Name: releaseName,
Info: &release.Info{Status: release.StatusDeployed},
Chart: &chart.Chart{},
Version: 2,
Labels: labels2,
},
}
storage := storageFixture()
for _, rel := range rels {
if err := storage.Create(rel); err != nil {
t.Fatal(err)
}
}
_, _, err := executeActionCommandC(storage, fmt.Sprintf("rollback %s 1", releaseName))
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := storage.Get(releaseName, 3)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
if !reflect.DeepEqual(updatedRel.Labels, labels1) {
t.Errorf("Expected {%v}, got {%v}", labels1, updatedRel.Labels)
}
}

@ -45,31 +45,32 @@ Common actions for Helm:
Environment variables:
| Name | Description |
|------------------------------------|---------------------------------------------------------------------------------------------------|
| $HELM_CACHE_HOME | set an alternative location for storing cached files. |
| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. |
| $HELM_DATA_HOME | set an alternative location for storing Helm data. |
| $HELM_DEBUG | indicate whether or not Helm is running in Debug mode |
| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, sql. |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. |
| $HELM_MAX_HISTORY | set the maximum number of helm release history. |
| $HELM_NAMESPACE | set the namespace used for the helm operations. |
| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. |
| $HELM_PLUGINS | set the path to the plugins directory |
| $HELM_REGISTRY_CONFIG | set the path to the registry config file. |
| $HELM_REPOSITORY_CACHE | set the path to the repository cache directory |
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication |
| $HELM_KUBECAFILE | set the Kubernetes certificate authority file. |
| $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. |
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. |
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure) |
| $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate |
| $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable)|
| Name | Description |
|------------------------------------|------------------------------------------------------------------------------------------------------------|
| $HELM_CACHE_HOME | set an alternative location for storing cached files. |
| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. |
| $HELM_DATA_HOME | set an alternative location for storing Helm data. |
| $HELM_DEBUG | indicate whether or not Helm is running in Debug mode |
| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, sql. |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. |
| $HELM_MAX_HISTORY | set the maximum number of helm release history. |
| $HELM_NAMESPACE | set the namespace used for the helm operations. |
| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. |
| $HELM_PLUGINS | set the path to the plugins directory |
| $HELM_REGISTRY_CONFIG | set the path to the registry config file. |
| $HELM_REPOSITORY_CACHE | set the path to the repository cache directory |
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication |
| $HELM_KUBECAFILE | set the Kubernetes certificate authority file. |
| $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. |
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. |
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure) |
| $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate |
| $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable) |
| $HELM_QPS | set the Queries Per Second in cases where a high number of calls exceed the option for higher burst values |
Helm stores cache, configuration, and data based on the following configuration order:
@ -99,7 +100,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
addKlogFlags(flags)
// 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 {
// 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
@ -122,7 +123,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
}
// 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)
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
@ -152,7 +153,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
flags.ParseErrorsWhitelist.UnknownFlags = true
flags.Parse(args)
registryClient, err := newDefaultRegistryClient()
registryClient, err := newDefaultRegistryClient(false)
if err != nil {
return nil, err
}
@ -257,7 +258,7 @@ func checkForExpiredRepos(repofile string) {
}
func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*registry.Client, error) {
func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify, plainHTTP bool) (*registry.Client, error) {
if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSverify {
registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSverify)
if err != nil {
@ -265,21 +266,26 @@ func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify b
}
return registryClient, nil
}
registryClient, err := newDefaultRegistryClient()
registryClient, err := newDefaultRegistryClient(plainHTTP)
if err != nil {
return nil, err
}
return registryClient, nil
}
func newDefaultRegistryClient() (*registry.Client, error) {
// Create a new registry client
registryClient, err := registry.NewClient(
func newDefaultRegistryClient(plainHTTP bool) (*registry.Client, error) {
opts := []registry.ClientOption{
registry.ClientOptDebug(settings.Debug),
registry.ClientOptEnableCache(true),
registry.ClientOptWriter(os.Stderr),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
)
}
if plainHTTP {
opts = append(opts, registry.ClientOptPlainHTTP())
}
// Create a new registry client
registryClient, err := registry.NewClient(opts...)
if err != nil {
return nil, err
}

@ -77,7 +77,7 @@ func TestRootCmd(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer ensure.HelmHome(t)()
ensure.HelmHome(t)
for k, v := range tt.envvars {
os.Setenv(k, v)

@ -147,11 +147,10 @@ func (i *Index) SearchLiteral(term string, threshold int) []*Result {
term = strings.ToLower(term)
buf := []*Result{}
for k, v := range i.lines {
lk := strings.ToLower(k)
lv := strings.ToLower(v)
res := strings.Index(lv, term)
if score := i.calcScore(res, lv); res != -1 && score < threshold {
parts := strings.Split(lk, verSep) // Remove version, if it is there.
parts := strings.Split(k, verSep) // Remove version, if it is there.
buf = append(buf, &Result{Name: parts[0], Score: score, Chart: i.charts[k]})
}
}

@ -101,15 +101,15 @@ var indexfileEntries = map[string]repo.ChartVersions{
},
}
func loadTestIndex(t *testing.T, all bool) *Index {
func loadTestIndex(_ *testing.T, all bool) *Index {
i := NewIndex()
i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all)
i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{
"pinta": {
"Pinta": {
{
URLs: []string{"http://example.com/charts/pinta-2.0.0.tgz"},
Metadata: &chart.Metadata{
Name: "pinta",
Name: "Pinta",
Version: "2.0.0",
Description: "Two ship, version two",
},
@ -170,14 +170,14 @@ func TestSearchByName(t *testing.T) {
query: "pinta",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
{Name: "ztesting/Pinta"},
},
},
{
name: "repo-specific search for one result",
query: "ztesting/pinta",
expect: []*Result{
{Name: "ztesting/pinta"},
{Name: "ztesting/Pinta"},
},
},
{
@ -199,7 +199,15 @@ func TestSearchByName(t *testing.T) {
query: "two",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
{Name: "ztesting/Pinta"},
},
},
{
name: "search mixedCase and result should be mixedCase too",
query: "pinta",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/Pinta"},
},
},
{
@ -207,7 +215,7 @@ func TestSearchByName(t *testing.T) {
query: "TWO",
expect: []*Result{
{Name: "testing/pinta"},
{Name: "ztesting/pinta"},
{Name: "ztesting/Pinta"},
},
},
{

@ -54,6 +54,7 @@ type searchHubOptions struct {
maxColWidth uint
outputFormat output.Format
listRepoURL bool
failOnNoResult bool
}
func newSearchHubCmd(out io.Writer) *cobra.Command {
@ -63,7 +64,7 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
Use: "hub [KEYWORD]",
Short: "search for charts in the Artifact Hub or your own hub instance",
Long: searchHubDesc,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
return o.run(out, args)
},
}
@ -72,6 +73,7 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "Hub instance to query for charts")
f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
f.BoolVar(&o.listRepoURL, "list-repo-url", false, "print charts repository URL")
f.BoolVar(&o.failOnNoResult, "fail-on-no-result", false, "search fails if no results are found")
bindOutputFlag(cmd, &o.outputFormat)
@ -91,7 +93,7 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error {
return fmt.Errorf("unable to perform search against %q", o.searchEndpoint)
}
return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth, o.listRepoURL))
return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth, o.listRepoURL, o.failOnNoResult))
}
type hubChartRepo struct {
@ -108,12 +110,13 @@ type hubChartElement struct {
}
type hubSearchWriter struct {
elements []hubChartElement
columnWidth uint
listRepoURL bool
elements []hubChartElement
columnWidth uint
listRepoURL bool
failOnNoResult bool
}
func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint, listRepoURL bool) *hubSearchWriter {
func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint, listRepoURL, failOnNoResult bool) *hubSearchWriter {
var elements []hubChartElement
for _, r := range results {
// Backwards compatibility for Monocular
@ -126,11 +129,16 @@ func newHubSearchWriter(results []monocular.SearchResult, endpoint string, colum
elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description, hubChartRepo{URL: r.Attributes.Repo.URL, Name: r.Attributes.Repo.Name}})
}
return &hubSearchWriter{elements, columnWidth, listRepoURL}
return &hubSearchWriter{elements, columnWidth, listRepoURL, failOnNoResult}
}
func (h *hubSearchWriter) WriteTable(out io.Writer) error {
if len(h.elements) == 0 {
// Fail if no results found and --fail-on-no-result is enabled
if h.failOnNoResult {
return fmt.Errorf("no results found")
}
_, err := out.Write([]byte("No results found\n"))
if err != nil {
return fmt.Errorf("unable to write results: %s", err)
@ -165,6 +173,11 @@ func (h *hubSearchWriter) WriteYAML(out io.Writer) error {
}
func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) error {
// Fail if no results found and --fail-on-no-result is enabled
if len(h.elements) == 0 && h.failOnNoResult {
return fmt.Errorf("no results found")
}
// Initialize the array so no results returns an empty array instead of null
chartList := make([]hubChartElement, 0, len(h.elements))

@ -27,7 +27,7 @@ func TestSearchHubCmd(t *testing.T) {
// 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"}}}}]}`
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)
}))
defer ts.Close()
@ -57,7 +57,7 @@ func TestSearchHubListRepoCmd(t *testing.T) {
// 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"}}}}]}`
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)
}))
defer ts.Close()
@ -90,3 +90,98 @@ func TestSearchHubOutputCompletion(t *testing.T) {
func TestSearchHubFileCompletion(t *testing.T) {
checkFileCompletion(t, "search hub", true) // File completion may be useful when inputting a keyword
}
func TestSearchHubCmd_FailOnNoResponseTests(t *testing.T) {
var (
searchResult = `{"data":[]}`
noResultFoundErr = "Error: no results found\n"
noResultFoundWarn = "No results found\n"
noResultFoundWarnInList = "[]\n"
)
type testCase struct {
name string
cmd string
response string
expected string
wantErr bool
}
var tests = []testCase{
{
name: "Search hub with no results in response",
cmd: `search hub maria`,
response: searchResult,
expected: noResultFoundWarn,
wantErr: false,
},
{
name: "Search hub with no results in response and output JSON",
cmd: `search hub maria --output json`,
response: searchResult,
expected: noResultFoundWarnInList,
wantErr: false,
},
{
name: "Search hub with no results in response and output YAML",
cmd: `search hub maria --output yaml`,
response: searchResult,
expected: noResultFoundWarnInList,
wantErr: false,
},
{
name: "Search hub with no results in response and --fail-on-no-result enabled, expected failure",
cmd: `search hub maria --fail-on-no-result`,
response: searchResult,
expected: noResultFoundErr,
wantErr: true,
},
{
name: "Search hub with no results in response, output JSON and --fail-on-no-result enabled, expected failure",
cmd: `search hub maria --fail-on-no-result --output json`,
response: searchResult,
expected: noResultFoundErr,
wantErr: true,
},
{
name: "Search hub with no results in response, output YAML and --fail-on-no-result enabled, expected failure",
cmd: `search hub maria --fail-on-no-result --output yaml`,
response: searchResult,
expected: noResultFoundErr,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup a mock search service
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, tt.response)
}))
defer ts.Close()
// Add mock server URL to command
tt.cmd += " --endpoint " + ts.URL
storage := storageFixture()
_, out, err := executeActionCommandC(storage, tt.cmd)
if tt.wantErr {
if err == nil {
t.Errorf("expected error due to no record in response, got nil")
}
} else {
if err != nil {
t.Errorf("unexpected error, got %q", err)
}
}
if out != tt.expected {
t.Errorf("expected and actual output did not match\n"+
"expected: %q\n"+
"actual : %q",
tt.expected, out)
}
})
}
}

@ -63,14 +63,15 @@ Repositories are managed with 'helm repo' commands.
const searchMaxScore = 25
type searchRepoOptions struct {
versions bool
regexp bool
devel bool
version string
maxColWidth uint
repoFile string
repoCacheDir string
outputFormat output.Format
versions bool
regexp bool
devel bool
version string
maxColWidth uint
repoFile string
repoCacheDir string
outputFormat output.Format
failOnNoResult bool
}
func newSearchRepoCmd(out io.Writer) *cobra.Command {
@ -80,7 +81,7 @@ func newSearchRepoCmd(out io.Writer) *cobra.Command {
Use: "repo [keyword]",
Short: "search repositories for a keyword in charts",
Long: searchRepoDesc,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
o.repoFile = settings.RepositoryConfig
o.repoCacheDir = settings.RepositoryCache
return o.run(out, args)
@ -93,6 +94,8 @@ func newSearchRepoCmd(out io.Writer) *cobra.Command {
f.BoolVar(&o.devel, "devel", false, "use development versions (alpha, beta, and release candidate releases), too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.StringVar(&o.version, "version", "", "search using semantic versioning constraints on repositories you have added")
f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
f.BoolVar(&o.failOnNoResult, "fail-on-no-result", false, "search fails if no results are found")
bindOutputFlag(cmd, &o.outputFormat)
return cmd
@ -123,7 +126,7 @@ func (o *searchRepoOptions) run(out io.Writer, args []string) error {
return err
}
return o.outputFormat.Write(out, &repoSearchWriter{data, o.maxColWidth})
return o.outputFormat.Write(out, &repoSearchWriter{data, o.maxColWidth, o.failOnNoResult})
}
func (o *searchRepoOptions) setupSearchedVersion() {
@ -204,12 +207,18 @@ type repoChartElement struct {
}
type repoSearchWriter struct {
results []*search.Result
columnWidth uint
results []*search.Result
columnWidth uint
failOnNoResult bool
}
func (r *repoSearchWriter) WriteTable(out io.Writer) error {
if len(r.results) == 0 {
// Fail if no results found and --fail-on-no-result is enabled
if r.failOnNoResult {
return fmt.Errorf("no results found")
}
_, err := out.Write([]byte("No results found\n"))
if err != nil {
return fmt.Errorf("unable to write results: %s", err)
@ -234,6 +243,11 @@ func (r *repoSearchWriter) WriteYAML(out io.Writer) error {
}
func (r *repoSearchWriter) encodeByFormat(out io.Writer, format output.Format) error {
// Fail if no results found and --fail-on-no-result is enabled
if len(r.results) == 0 && r.failOnNoResult {
return fmt.Errorf("no results found")
}
// Initialize the array so no results returns an empty array instead of null
chartList := make([]repoChartElement, 0, len(r.results))

@ -56,6 +56,20 @@ func TestSearchRepositoriesCmd(t *testing.T) {
name: "search for 'syzygy', expect no matches",
cmd: "search repo syzygy",
golden: "output/search-not-found.txt",
}, {
name: "search for 'syzygy' with --fail-on-no-result, expect failure for no results",
cmd: "search repo syzygy --fail-on-no-result",
golden: "output/search-not-found-error.txt",
wantError: true,
}, {name: "search for 'syzygy' with json output and --fail-on-no-result, expect failure for no results",
cmd: "search repo syzygy --output json --fail-on-no-result",
golden: "output/search-not-found-error.txt",
wantError: true,
}, {
name: "search for 'syzygy' with yaml output --fail-on-no-result, expect failure for no results",
cmd: "search repo syzygy --output yaml --fail-on-no-result",
golden: "output/search-not-found-error.txt",
wantError: true,
}, {
name: "search for 'alp[a-z]+', expect two matches",
cmd: "search repo alp[a-z]+ --regexp",

@ -60,18 +60,17 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewShowWithConfig(action.ShowAll, cfg)
showCommand := &cobra.Command{
Use: "show",
Short: "show information of a chart",
Aliases: []string{"inspect"},
Long: showDesc,
Args: require.NoArgs,
ValidArgsFunction: noCompletions, // Disable file completion
Use: "show",
Short: "show information of a chart",
Aliases: []string{"inspect"},
Long: showDesc,
Args: require.NoArgs,
}
// 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 {
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
}
return compListCharts(toComplete, true)
}
@ -82,7 +81,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: showAllDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowAll
err := addRegistryClient(client)
if err != nil {
@ -103,7 +102,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: showValuesDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowValues
err := addRegistryClient(client)
if err != nil {
@ -124,7 +123,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: showChartDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowChart
err := addRegistryClient(client)
if err != nil {
@ -145,7 +144,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: readmeChartDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowReadme
err := addRegistryClient(client)
if err != nil {
@ -166,7 +165,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Long: showCRDsDesc,
Args: require.ExactArgs(1),
ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
client.OutputFormat = action.ShowCRDs
err := addRegistryClient(client)
if err != nil {
@ -199,7 +198,7 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) {
}
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
@ -226,7 +225,8 @@ func runShow(args []string, client *action.Show) (string, error) {
}
func addRegistryClient(client *action.Show) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}

@ -58,13 +58,13 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "display the status of the named release",
Long: statusHelp,
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 {
return nil, cobra.ShellCompDirectiveNoFileComp
return noMoreArgsComp()
}
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
// and displayed as a table. When YAML or JSON the resources will be
@ -80,7 +80,7 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
// strip chart metadata from the output
rel.Chart = nil
return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription, client.ShowResources})
return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription, client.ShowResources, false, false})
},
}
@ -88,7 +88,7 @@ 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")
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 {
return compListRevisions(toComplete, cfg, args[0])
}
@ -112,6 +112,8 @@ type statusPrinter struct {
debug bool
showDescription bool
showResources bool
showMetadata bool
hideNotes bool
}
func (s statusPrinter) WriteJSON(out io.Writer) error {
@ -126,15 +128,20 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
if s.release == nil {
return nil
}
fmt.Fprintf(out, "NAME: %s\n", s.release.Name)
_, _ = fmt.Fprintf(out, "NAME: %s\n", s.release.Name)
if !s.release.Info.LastDeployed.IsZero() {
fmt.Fprintf(out, "LAST DEPLOYED: %s\n", s.release.Info.LastDeployed.Format(time.ANSIC))
_, _ = fmt.Fprintf(out, "LAST DEPLOYED: %s\n", s.release.Info.LastDeployed.Format(time.ANSIC))
}
_, _ = fmt.Fprintf(out, "NAMESPACE: %s\n", s.release.Namespace)
_, _ = fmt.Fprintf(out, "STATUS: %s\n", s.release.Info.Status.String())
_, _ = fmt.Fprintf(out, "REVISION: %d\n", s.release.Version)
if s.showMetadata {
_, _ = fmt.Fprintf(out, "CHART: %s\n", s.release.Chart.Metadata.Name)
_, _ = 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, "NAMESPACE: %s\n", s.release.Namespace)
fmt.Fprintf(out, "STATUS: %s\n", s.release.Info.Status.String())
fmt.Fprintf(out, "REVISION: %d\n", s.release.Version)
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 {
@ -149,31 +156,31 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
}
for _, t := range keys {
fmt.Fprintf(buf, "==> %s\n", t)
_, _ = fmt.Fprintf(buf, "==> %s\n", t)
vk := s.release.Info.Resources[t]
for _, resource := range vk {
if err := printer.PrintObj(resource, buf); err != nil {
fmt.Fprintf(buf, "failed to print object type %s: %v\n", t, err)
_, _ = fmt.Fprintf(buf, "failed to print object type %s: %v\n", t, err)
}
}
buf.WriteString("\n")
}
fmt.Fprintf(out, "RESOURCES:\n%s\n", buf.String())
_, _ = fmt.Fprintf(out, "RESOURCES:\n%s\n", buf.String())
}
executions := executionsByHookEvent(s.release)
if tests, ok := executions[release.HookTest]; !ok || len(tests) == 0 {
fmt.Fprintln(out, "TEST SUITE: None")
_, _ = fmt.Fprintln(out, "TEST SUITE: None")
} else {
for _, h := range tests {
// Don't print anything if hook has not been initiated
if h.LastRun.StartedAt.IsZero() {
continue
}
fmt.Fprintf(out, "TEST SUITE: %s\n%s\n%s\n%s\n",
_, _ = fmt.Fprintf(out, "TEST SUITE: %s\n%s\n%s\n%s\n",
h.Name,
fmt.Sprintf("Last Started: %s", h.LastRun.StartedAt.Format(time.ANSIC)),
fmt.Sprintf("Last Completed: %s", h.LastRun.CompletedAt.Format(time.ANSIC)),
@ -183,37 +190,38 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
}
if s.debug {
fmt.Fprintln(out, "USER-SUPPLIED VALUES:")
_, _ = fmt.Fprintln(out, "USER-SUPPLIED VALUES:")
err := output.EncodeYAML(out, s.release.Config)
if err != nil {
return err
}
// Print an extra newline
fmt.Fprintln(out)
_, _ = fmt.Fprintln(out)
cfg, err := chartutil.CoalesceValues(s.release.Chart, s.release.Config)
if err != nil {
return err
}
fmt.Fprintln(out, "COMPUTED VALUES:")
_, _ = fmt.Fprintln(out, "COMPUTED VALUES:")
err = output.EncodeYAML(out, cfg.AsMap())
if err != nil {
return err
}
// Print an extra newline
fmt.Fprintln(out)
_, _ = fmt.Fprintln(out)
}
if strings.EqualFold(s.release.Info.Description, "Dry run complete") || s.debug {
fmt.Fprintln(out, "HOOKS:")
_, _ = fmt.Fprintln(out, "HOOKS:")
for _, h := range s.release.Hooks {
fmt.Fprintf(out, "---\n# Source: %s\n%s\n", h.Path, h.Manifest)
_, _ = fmt.Fprintf(out, "---\n# Source: %s\n%s\n", h.Path, h.Manifest)
}
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
if !s.hideNotes && len(s.release.Info.Notes) > 0 {
fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(s.release.Info.Notes))
}
return nil

@ -32,7 +32,7 @@ func TestStatusCmd(t *testing.T) {
Name: "flummoxed-chickadee",
Namespace: "default",
Info: info,
Chart: &chart.Chart{},
Chart: &chart.Chart{Metadata: &chart.Metadata{Name: "name", Version: "1.2.3", AppVersion: "3.2.1"}},
Hooks: hooks,
}}
}

@ -61,7 +61,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
Short: "locally render templates",
Long: templateDesc,
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)
},
RunE: func(_ *cobra.Command, args []string) error {
@ -73,12 +73,19 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.KubeVersion = parsedKubeVersion
}
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSverify)
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
client.InsecureSkipTLSverify, client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
client.SetRegistryClient(registryClient)
// This is for the case where "" is specifically passed in as a
// value. When there is no value passed in NoOptDefVal will be used
// and it is set to client. See addInstallFlags.
if client.DryRunOption == "" {
client.DryRunOption = "true"
}
client.DryRun = true
client.ReleaseName = "release-name"
client.Replace = true // Skip the name check

@ -25,6 +25,8 @@ import (
var chartPath = "testdata/testcharts/subchart"
func TestTemplateCmd(t *testing.T) {
deletevalchart := "testdata/testcharts/issue-9027"
tests := []cmdTestCase{
{
name: "check name",
@ -131,6 +133,34 @@ func TestTemplateCmd(t *testing.T) {
cmd: fmt.Sprintf(`template '%s' --skip-tests`, chartPath),
golden: "output/template-skip-tests.txt",
},
{
// This test case is to ensure the case where specified dependencies
// in the Chart.yaml and those where the Chart.yaml don't have them
// specified are the same.
name: "ensure nil/null values pass to subcharts delete values",
cmd: fmt.Sprintf("template '%s'", deletevalchart),
golden: "output/issue-9027.txt",
},
{
// Ensure that parent chart values take precedence over imported values
name: "template with imported subchart values ensuring import",
cmd: fmt.Sprintf("template '%s' --set configmap.enabled=true --set subchartb.enabled=true", chartPath),
golden: "output/template-subchart-cm.txt",
},
{
// Ensure that user input values take precedence over imported
// values from sub-charts.
name: "template with imported subchart values set with --set",
cmd: fmt.Sprintf("template '%s' --set configmap.enabled=true --set subchartb.enabled=true --set configmap.value=baz", chartPath),
golden: "output/template-subchart-cm-set.txt",
},
{
// Ensure that user input values take precedence over imported
// values from sub-charts when passed by file
name: "template with imported subchart values set with --set",
cmd: fmt.Sprintf("template '%s' -f %s/extra_values.yaml", chartPath, chartPath),
golden: "output/template-subchart-cm-set-file.txt",
},
}
runTestCmd(t, tests)
}

@ -0,0 +1,19 @@
name: wrongname
commands:
- name: empty
- name: full
commands:
- name: more
validArgs:
- one
- two
flags:
- b
- ball
- name: less
flags:
- a
- all
flags:
- z
- q

@ -0,0 +1,7 @@
#!/bin/sh
echo $HELM_PLUGIN_NAME
echo $HELM_PLUGIN_DIR
echo $HELM_PLUGINS
echo $HELM_REPOSITORY_CONFIG
echo $HELM_REPOSITORY_CACHE
echo $HELM_BIN

@ -0,0 +1,4 @@
name: fullenv
usage: "show env vars"
description: "show all env vars"
command: "$HELM_PLUGIN_DIR/fullenv.sh"

@ -0,0 +1,6 @@
apiVersion: v1
generated: 2016-10-03T16:03:10.640376913-06:00
repositories:
- cache: testing-index.yaml
name: testing
url: http://example.com/charts

@ -0,0 +1,3 @@
apiVersion: v1
entries: {}
generated: "2020-09-09T19:50:50.198347916-04:00"

@ -0,0 +1,66 @@
apiVersion: v1
entries:
alpine:
- name: alpine
url: https://charts.helm.sh/stable/alpine-0.1.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
created: "2018-06-27T10:00:18.230700509Z"
deprecated: true
home: https://helm.sh/helm
sources:
- https://github.com/helm/helm
version: 0.1.0
appVersion: 1.2.3
description: Deploy a basic Alpine Linux pod
keywords: []
maintainers: []
icon: ""
apiVersion: v2
- name: alpine
url: https://charts.helm.sh/stable/alpine-0.2.0.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
created: "2018-07-09T11:34:37.797864902Z"
home: https://helm.sh/helm
sources:
- https://github.com/helm/helm
version: 0.2.0
appVersion: 2.3.4
description: Deploy a basic Alpine Linux pod
keywords: []
maintainers: []
icon: ""
apiVersion: v2
- name: alpine
url: https://charts.helm.sh/stable/alpine-0.3.0-rc.1.tgz
checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d
created: "2020-11-12T08:44:58.872726222Z"
home: https://helm.sh/helm
sources:
- https://github.com/helm/helm
version: 0.3.0-rc.1
appVersion: 3.0.0
description: Deploy a basic Alpine Linux pod
keywords: []
maintainers: []
icon: ""
apiVersion: v2
mariadb:
- name: mariadb
url: https://charts.helm.sh/stable/mariadb-0.3.0.tgz
checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56
created: "2018-04-23T08:20:27.160959131Z"
home: https://mariadb.org
sources:
- https://github.com/bitnami/bitnami-docker-mariadb
version: 0.3.0
description: Chart for MariaDB
keywords:
- mariadb
- mysql
- database
- sql
maintainers:
- name: Bitnami
email: containers@bitnami.com
icon: ""
apiVersion: v2

@ -1,2 +1,3 @@
_activeHelp_ This command does not take any more arguments (but may accept flags).
:4
Completion ended with directive: ShellCompDirectiveNoFileComp

@ -15,6 +15,7 @@ HELM_KUBETOKEN
HELM_MAX_HISTORY
HELM_NAMESPACE
HELM_PLUGINS
HELM_QPS
HELM_REGISTRY_CONFIG
HELM_REPOSITORY_CACHE
HELM_REPOSITORY_CONFIG

@ -0,0 +1,3 @@
Error: "helm get metadata" requires 1 argument
Usage: helm get metadata RELEASE_NAME [flags]

@ -0,0 +1 @@
{"name":"thomas-guide","chart":"foo","version":"0.1.0-beta.1","appVersion":"1.0","namespace":"default","revision":1,"status":"deployed","deployedAt":"1977-09-02T22:04:05Z"}

@ -0,0 +1,8 @@
NAME: thomas-guide
CHART: foo
VERSION: 0.1.0-beta.1
APP_VERSION: 1.0
NAMESPACE: default
REVISION: 1
STATUS: deployed
DEPLOYED_AT: 1977-09-02T22:04:05Z

@ -0,0 +1,8 @@
appVersion: "1.0"
chart: foo
deployedAt: "1977-09-02T22:04:05Z"
name: thomas-guide
namespace: default
revision: 1
status: deployed
version: 0.1.0-beta.1

@ -3,6 +3,9 @@ LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default
STATUS: deployed
REVISION: 1
CHART: foo
VERSION: 0.1.0-beta.1
APP_VERSION: 1.0
TEST SUITE: None
USER-SUPPLIED VALUES:
name: value

@ -0,0 +1,20 @@
NAME: secrets
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
HOOKS:
MANIFEST:
---
# Source: chart-with-secret/templates/secret.yaml
# HIDDEN: The Secret output has been suppressed
---
# Source: chart-with-secret/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: test-configmap
data:
foo: bar

@ -0,0 +1,25 @@
NAME: secrets
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
NAMESPACE: default
STATUS: pending-install
REVISION: 1
TEST SUITE: None
HOOKS:
MANIFEST:
---
# Source: chart-with-secret/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: test-secret
stringData:
foo: bar
---
# Source: chart-with-secret/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: test-configmap
data:
foo: bar

@ -0,0 +1 @@
Error: INSTALLATION FAILED: Hiding Kubernetes secrets requires a dry-run mode

@ -0,0 +1,32 @@
---
# Source: issue-9027/charts/subchart/templates/values.yaml
global:
hash:
key3: 13
key4: 4
key5: 5
key6: 6
hash:
key3: 13
key4: 4
key5: 5
key6: 6
---
# Source: issue-9027/templates/values.yaml
global:
hash:
key1: null
key2: null
key3: 13
subchart:
global:
hash:
key3: 13
key4: 4
key5: 5
key6: 6
hash:
key3: 13
key4: 4
key5: 5
key6: 6

@ -1,6 +1,6 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
[ERROR] templates/: error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
@ -9,12 +9,11 @@
[ERROR] Chart.yaml: apiVersion is required. The value must be either "v1" or "v2"
[ERROR] Chart.yaml: version is required
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
[ERROR] templates/: validation: chart.metadata.name is required
[ERROR] : unable to load chart
validation: chart.metadata.name is required
==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
Error: 3 chart(s) linted, 2 chart(s) failed

@ -1,6 +1,6 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended
[WARNING] templates/: directory not found
[ERROR] templates/: error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
[ERROR] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required

@ -0,0 +1,4 @@
==> Linting testdata/testcharts/chart-with-deprecated-api
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed

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

Loading…
Cancel
Save