Merge branch 'helm:main' into main

pull/10873/head
Tan Guofu 11 months ago committed by GitHub
commit 240449be9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,20 +0,0 @@
#!/usr/bin/env bash
# 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.
set -euo pipefail
curl -sSL https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz | tar xz
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

@ -1,43 +1,14 @@
--- ---
# 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 version: 2
jobs: jobs:
build: build:
working_directory: ~/helm.sh/helm
docker: docker:
- image: cimg/go:1.18 - image: cimg/go:1.18
auth:
username: $DOCKER_USER
password: $DOCKER_PASS
environment:
GOCACHE: "/tmp/go/cache"
GOLANGCI_LINT_VERSION: "1.46.2"
steps: steps:
- checkout - checkout
- run:
name: install test dependencies
command: .circleci/bootstrap.sh
- run:
name: test style
command: make test-style
- run:
name: test
command: make test-coverage
- run:
name: test build
command: make
- deploy:
name: deploy
command: .circleci/deploy.sh
workflows:
version: 2
build:
jobs:
- build:
filters:
tags:
only: /.*/

@ -1,53 +0,0 @@
#!/usr/bin/env bash
# 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.
set -euo pipefail
# Skip on pull request builds
if [[ -n "${CIRCLE_PR_NUMBER:-}" ]]; then
exit
fi
: ${AZURE_STORAGE_CONNECTION_STRING:?"AZURE_STORAGE_CONNECTION_STRING environment variable is not set"}
: ${AZURE_STORAGE_CONTAINER_NAME:?"AZURE_STORAGE_CONTAINER_NAME environment variable is not set"}
VERSION=
if [[ -n "${CIRCLE_TAG:-}" ]]; then
VERSION="${CIRCLE_TAG}"
elif [[ "${CIRCLE_BRANCH:-}" == "main" ]]; then
VERSION="canary"
else
echo "Skipping deploy step; this is neither a releasable branch or a tag"
exit
fi
echo "Installing Azure CLI"
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list
curl -L https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add
sudo apt install apt-transport-https
sudo apt update
sudo apt install azure-cli
echo "Building helm binaries"
make build-cross
make dist checksum VERSION="${VERSION}"
echo "Pushing binaries to Azure"
if [[ "${VERSION}" == "canary" ]]; then
az storage blob upload-batch -s _dist/ -d "$AZURE_STORAGE_CONTAINER_NAME" --pattern 'helm-*' --connection-string "$AZURE_STORAGE_CONNECTION_STRING" --overwrite
else
az storage blob upload-batch -s _dist/ -d "$AZURE_STORAGE_CONTAINER_NAME" --pattern 'helm-*' --connection-string "$AZURE_STORAGE_CONNECTION_STRING"
fi

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

@ -0,0 +1,18 @@
name: Publish Release Assets to Asset Transparency Log
on:
release:
types: [published, created, edited, released]
jobs:
github_release_asset_transparency_log_publish_job:
runs-on: ubuntu-latest
name: Publish GitHub release asset digests to https://beta-asset.transparencylog.net
steps:
- name: Gather URLs from GitHub release and publish
id: asset-transparency
uses: transparencylog/github-releases-asset-transparency-verify-action@c77874b4514ae4003994ece9582675195fe012e2 # v11
- name: List verified and published URLs
run: echo "Verified URLs ${{ steps.asset-transparency.outputs.verified }}"
- name: List failed URLs
run: echo "Failed URLs ${{ steps.asset-transparency.outputs.failed }}"

@ -1,18 +1,23 @@
name: build-pr name: build-test
on: on:
push:
branches:
- 'main'
- 'release-**'
pull_request: pull_request:
branches: branches:
- main - main
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@v2 uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0
with: with:
go-version: '1.18' go-version: '1.20'
- name: Install golangci-lint - name: Install golangci-lint
run: | run: |
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
@ -21,9 +26,11 @@ jobs:
sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint 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* rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
env: env:
GOLANGCI_LINT_VERSION: '1.46.2' GOLANGCI_LINT_VERSION: '1.51.2'
GOLANGCI_LINT_SHA256: '242cd4f2d6ac0556e315192e8555784d13da5d1874e51304711570769c4f2b9b' GOLANGCI_LINT_SHA256: '4de479eb9d9bc29da51aec1834e7c255b333723d38dbd56781c68e5dddc6a90b'
- name: Test style - name: Test style
run: make test-style run: make test-style
- name: Run unit tests - name: Run unit tests
run: make test-coverage run: make test-coverage
- name: Test build
run: make build

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

@ -0,0 +1,82 @@
name: release
on:
create:
tags:
- v*
push:
branches:
- main
# Note the only differences between release and canary-release jobs are:
# - only canary passes --overwrite flag
# - the VERSION make variable passed to 'make dist checksum' is expected to
# be "canary" if the job is triggered by a push to "main" branch. If the
# job is triggered by a tag push, VERSION should be the tag ref.
jobs:
release:
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0
with:
go-version: '1.20'
- name: Run unit tests
run: make test-coverage
- name: Build Helm Binaries
run: |
make build-cross
make dist checksum VERSION="${{ github.ref_name }}"
- name: Set latest version
run: |
# Push the latest semver tag, excluding prerelease tags
git tag | sort -r --version-sort | grep '^v[0-9]' | grep -v '-' | head -n1 > _dist/helm-latest-version
- name: Upload Binaries
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:
source_dir: _dist
container_name: ${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}
connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
extra_args: '--pattern helm-*'
canary-release:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout source code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@v3.6.0
- name: Setup Go
uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0
with:
go-version: '1.20'
- name: Run unit tests
run: make test-coverage
- name: Build Helm Binaries
run: |
make build-cross
make dist checksum VERSION="canary"
- name: Upload Binaries
uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0
with:
source_dir: _dist
container_name: ${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}
connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
extra_args: '--pattern helm-*'
# WARNING: this will overwrite existing blobs in your blob storage
overwrite: 'true'

@ -10,7 +10,7 @@ jobs:
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.'
exempt-issue-labels: 'keep open,v4.x' exempt-issue-labels: 'keep open,v4.x,in progress'
days-before-stale: 90 days-before-stale: 90
days-before-close: 30 days-before-close: 30
operations-per-run: 100 operations-per-run: 100

@ -4,7 +4,6 @@ run:
linters: linters:
disable-all: true disable-all: true
enable: enable:
- deadcode
- dupl - dupl
- gofmt - gofmt
- goimports - goimports
@ -14,9 +13,7 @@ linters:
- misspell - misspell
- nakedret - nakedret
- revive - revive
- structcheck
- unused - unused
- varcheck
- staticcheck - staticcheck
linters-settings: linters-settings:

@ -13,7 +13,7 @@ chance to try to fix the issue before it is exploited in the wild.
## Sign Your Work ## Sign Your Work
The sign-off is a simple line at the end of the explanation for a commit. All commits needs to be The sign-off is a simple line at the end of the explanation for a commit. All commits need to be
signed. Your signature certifies that you wrote the patch or otherwise have the right to contribute signed. Your signature certifies that you wrote the patch or otherwise have the right to contribute
the material. The rules are pretty simple, if you can certify the below (from the material. The rules are pretty simple, if you can certify the below (from
[developercertificate.org](https://developercertificate.org/)): [developercertificate.org](https://developercertificate.org/)):
@ -195,7 +195,7 @@ below.
See [Proposing an Idea](#proposing-an-idea). Smaller quality-of-life enhancements are exempt. See [Proposing an Idea](#proposing-an-idea). Smaller quality-of-life enhancements are exempt.
- Issues that are labeled as `feature` or `bug` should be connected to the PR that resolves it. - Issues that are labeled as `feature` or `bug` should be connected to the PR that resolves it.
- Whoever is working on a `feature` or `bug` issue (whether a maintainer or someone from the - Whoever is working on a `feature` or `bug` issue (whether a maintainer or someone from the
community), should either assign the issue to themself or make a comment in the issue saying community), should either assign the issue to themselves or make a comment in the issue saying
that they are taking it. that they are taking it.
- `proposal` and `support/question` issues should stay open until resolved or if they have not - `proposal` and `support/question` issues should stay open until resolved or if they have not
been active for more than 30 days. This will help keep the issue queue to a manageable size been active for more than 30 days. This will help keep the issue queue to a manageable size

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

@ -1,6 +1,6 @@
# Helm # Helm
[![CircleCI](https://circleci.com/gh/helm/helm.svg?style=shield)](https://circleci.com/gh/helm/helm) [![Build Status](https://github.com/helm/helm/workflows/release/badge.svg)](https://github.com/helm/helm/actions?workflow=release)
[![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm) [![Go Report Card](https://goreportcard.com/badge/github.com/helm/helm)](https://goreportcard.com/report/github.com/helm/helm)
[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v3) [![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/helm.sh/helm/v3)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131)
@ -30,7 +30,6 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Install ## Install
Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest). Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest).
Unpack the `helm` binary and add it to your PATH and you are good to go! 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`. - [Homebrew](https://brew.sh/) users can use `brew install helm`.
- [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`. - [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
- [Scoop](https://scoop.sh/) users can use `scoop install 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` - [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/). 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) - [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)) - 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 ### Code of conduct
Participation in the Helm community is governed by the [Code of Conduct](code-of-conduct.md). Participation in the Helm community is governed by the [Code of Conduct](code-of-conduct.md).

@ -18,7 +18,6 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -31,9 +30,9 @@ import (
) )
func TestCreateCmd(t *testing.T) { func TestCreateCmd(t *testing.T) {
defer ensure.HelmHome(t)() ensure.HelmHome(t)
cname := "testchart" cname := "testchart"
dir := ensure.TempDir(t) dir := t.TempDir()
defer testChdir(t, dir)() defer testChdir(t, dir)()
// Run a create // Run a create
@ -62,7 +61,7 @@ func TestCreateCmd(t *testing.T) {
} }
func TestCreateStarterCmd(t *testing.T) { func TestCreateStarterCmd(t *testing.T) {
defer ensure.HelmHome(t)() ensure.HelmHome(t)
cname := "testchart" cname := "testchart"
defer resetEnv()() defer resetEnv()()
os.MkdirAll(helmpath.CachePath(), 0755) os.MkdirAll(helmpath.CachePath(), 0755)
@ -77,7 +76,7 @@ func TestCreateStarterCmd(t *testing.T) {
t.Logf("Created %s", dest) t.Logf("Created %s", dest)
} }
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil { if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }
@ -128,7 +127,7 @@ func TestCreateStarterCmd(t *testing.T) {
func TestCreateStarterAbsoluteCmd(t *testing.T) { func TestCreateStarterAbsoluteCmd(t *testing.T) {
defer resetEnv()() defer resetEnv()()
defer ensure.HelmHome(t)() ensure.HelmHome(t)
cname := "testchart" cname := "testchart"
// Create a starter. // Create a starter.
@ -140,7 +139,7 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) {
t.Logf("Created %s", dest) t.Logf("Created %s", dest)
} }
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil { if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
t.Fatalf("Could not write template: %s", err) t.Fatalf("Could not write template: %s", err)
} }

@ -149,7 +149,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) { func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
defer resetEnv()() defer resetEnv()()
defer ensure.HelmHome(t)() ensure.HelmHome(t)
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz") srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz")
if err != nil { if err != nil {
@ -206,6 +206,61 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
} }
} }
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 // createTestingMetadata creates a basic chart that depends on reqtest-0.1.0
// //
// The baseURL can be used to point to a particular repository server. // The baseURL can be used to point to a particular repository server.

@ -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.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.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.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) { 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.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.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.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.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") f.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
} }

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

@ -59,7 +59,7 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return tpl(template, data, out) return tpl(template, data, out)
} }
return output.Table.Write(out, &statusPrinter{res, true, false, false}) return output.Table.Write(out, &statusPrinter{res, true, false, false, true})
}, },
} }

@ -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(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return compListReleases(toComplete, args, cfg)
},
RunE: func(cmd *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(cmd *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)
}

@ -18,7 +18,7 @@ package main // import "helm.sh/helm/v3/cmd/helm"
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"os" "os"
"strings" "strings"
@ -106,10 +106,10 @@ func loadReleasesInMemory(actionConfig *action.Configuration) {
return return
} }
actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard}
for _, path := range filePaths { for _, path := range filePaths {
b, err := ioutil.ReadFile(path) b, err := os.ReadFile(path)
if err != nil { if err != nil {
log.Fatal("Unable to read memory driver data", err) log.Fatal("Unable to read memory driver data", err)
} }

@ -18,7 +18,7 @@ package main
import ( import (
"bytes" "bytes"
"io/ioutil" "io"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@ -92,7 +92,7 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string)
actionConfig := &action.Configuration{ actionConfig := &action.Configuration{
Releases: store, Releases: store,
KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard}, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
Capabilities: chartutil.DefaultCapabilities, Capabilities: chartutil.DefaultCapabilities,
Log: func(format string, v ...interface{}) {}, Log: func(format string, v ...interface{}) {},
} }

@ -136,12 +136,25 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compInstall(args, toComplete, client) return compInstall(args, toComplete, client)
}, },
RunE: func(_ *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
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) rel, err := runInstall(args, client, valueOpts, out)
if err != nil { if err != nil {
return errors.Wrap(err, "INSTALLATION FAILED") 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})
}, },
} }
@ -154,7 +167,14 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) { 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.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.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production") f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
@ -169,6 +189,8 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used")
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
f.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")
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)
@ -244,6 +266,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
RepositoryConfig: settings.RepositoryConfig, RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache, RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug, Debug: settings.Debug,
RegistryClient: client.GetRegistryClient(),
} }
if err := man.Update(); err != nil { if err := man.Update(); err != nil {
return nil, err return nil, err
@ -260,6 +283,11 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
client.Namespace = settings.Namespace() 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 // Create context and prepare the handle of SIGTERM
ctx := context.Background() ctx := context.Background()
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
@ -300,3 +328,19 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
} }
return nil, cobra.ShellCompDirectiveNoFileComp 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
}

@ -53,6 +53,11 @@ func TestLintCmdWithQuietFlag(t *testing.T) {
name: "lint chart with warning using --quiet flag", name: "lint chart with warning using --quiet flag",
cmd: "lint --quiet testdata/testcharts/chart-with-only-crds", cmd: "lint --quiet testdata/testcharts/chart-with-only-crds",
golden: "output/lint-quiet-with-warning.txt", 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) runTestCmd(t, tests)

@ -19,7 +19,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
@ -129,7 +128,8 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
env = append(env, fmt.Sprintf("%s=%s", k, v)) 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.Env = env
prog.Stdin = os.Stdin prog.Stdin = os.Stdin
prog.Stdout = out prog.Stdout = out
@ -311,7 +311,7 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
// loadFile takes a yaml file at the given path, parses it and returns a pluginCommand object // loadFile takes a yaml file at the given path, parses it and returns a pluginCommand object
func loadFile(path string) (*pluginCommand, error) { func loadFile(path string) (*pluginCommand, error) {
cmds := new(pluginCommand) cmds := new(pluginCommand)
b, err := ioutil.ReadFile(path) b, err := os.ReadFile(path)
if err != nil { if err != nil {
return cmds, fmt.Errorf("file (%s) not provided by plugin. No plugin auto-completion possible", path) return cmds, fmt.Errorf("file (%s) not provided by plugin. No plugin auto-completion possible", path)
} }

@ -19,7 +19,6 @@ package main
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -87,7 +86,7 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if client.DependencyUpdate { if client.DependencyUpdate {
downloadManager := &downloader.Manager{ downloadManager := &downloader.Manager{
Out: ioutil.Discard, Out: io.Discard,
ChartPath: path, ChartPath: path,
Keyring: client.Keyring, Keyring: client.Keyring,
Getters: p, Getters: p,

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

@ -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 { type staticCompletionDetails struct {
use string use string
validArgs []string validArgs []string

@ -64,6 +64,13 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ">0.0.0-0" client.Version = ">0.0.0-0"
} }
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)
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
output, err := client.Run(args[i]) output, err := client.Run(args[i])
if err != nil { if err != nil {

@ -34,8 +34,16 @@ If the chart has an associated provenance file,
it will also be uploaded. it will also be uploaded.
` `
type registryPushOptions struct {
certFile string
keyFile string
caFile string
insecureSkipTLSverify bool
plainHTTP bool
}
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client := action.NewPushWithOpts(action.WithPushConfig(cfg)) o := &registryPushOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "push [chart] [remote]", Use: "push [chart] [remote]",
@ -60,8 +68,18 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *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)
}
cfg.RegistryClient = registryClient
chartRef := args[0] chartRef := args[0]
remote := args[1] remote := args[1]
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 client.Settings = settings
output, err := client.Run(chartRef, remote) output, err := client.Run(chartRef, remote)
if err != nil { if err != nil {
@ -72,5 +90,12 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}, },
} }
f := cmd.Flags()
f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file")
f.StringVar(&o.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 return cmd
} }

@ -21,7 +21,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"strings" "strings"
@ -36,9 +35,18 @@ const registryLoginDesc = `
Authenticate to a remote registry. Authenticate to a remote registry.
` `
type registryLoginOptions struct {
username string
password string
passwordFromStdinOpt bool
certFile string
keyFile string
caFile string
insecure bool
}
func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
var usernameOpt, passwordOpt string o := &registryLoginOptions{}
var passwordFromStdinOpt, insecureOpt bool
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "login [host]", Use: "login [host]",
@ -49,20 +57,27 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
hostname := args[0] hostname := args[0]
username, password, err := getUsernamePassword(usernameOpt, passwordOpt, passwordFromStdinOpt) username, password, err := getUsernamePassword(o.username, o.password, o.passwordFromStdinOpt)
if err != nil { if err != nil {
return err return err
} }
return action.NewRegistryLogin(cfg).Run(out, hostname, username, password, insecureOpt) return action.NewRegistryLogin(cfg).Run(out, hostname, username, password,
action.WithCertFile(o.certFile),
action.WithKeyFile(o.keyFile),
action.WithCAFile(o.caFile),
action.WithInsecure(o.insecure))
}, },
} }
f := cmd.Flags() f := cmd.Flags()
f.StringVarP(&usernameOpt, "username", "u", "", "registry username") f.StringVarP(&o.username, "username", "u", "", "registry username")
f.StringVarP(&passwordOpt, "password", "p", "", "registry password or identity token") f.StringVarP(&o.password, "password", "p", "", "registry password or identity token")
f.BoolVarP(&passwordFromStdinOpt, "password-stdin", "", false, "read password or identity token from stdin") f.BoolVarP(&o.passwordFromStdinOpt, "password-stdin", "", false, "read password or identity token from stdin")
f.BoolVarP(&insecureOpt, "insecure", "", false, "allow connections to TLS registry without certs") f.BoolVarP(&o.insecure, "insecure", "", false, "allow connections to TLS registry without certs")
f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
return cmd return cmd
} }
@ -74,7 +89,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd
password := passwordOpt password := passwordOpt
if passwordFromStdinOpt { if passwordFromStdinOpt {
passwordFromStdin, err := ioutil.ReadAll(os.Stdin) passwordFromStdin, err := io.ReadAll(os.Stdin)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }

@ -59,9 +59,9 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
notName := regexp.MustCompile(`^!\s?name=`) notName := regexp.MustCompile(`^!\s?name=`)
for _, f := range filter { for _, f := range filter {
if strings.HasPrefix(f, "name=") { 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) { } 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]) rel, runErr := client.Run(args[0])
@ -72,7 +72,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
return runErr return runErr
} }
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false}); err != nil { if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false}); err != nil {
return err return err
} }

@ -20,7 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -134,7 +133,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
return err return err
} }
b, err := ioutil.ReadFile(o.repoFile) b, err := os.ReadFile(o.repoFile)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return err return err
} }
@ -213,7 +212,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
f.Update(&c) f.Update(&c)
if err := f.WriteFile(o.repoFile, 0644); err != nil { if err := f.WriteFile(o.repoFile, 0600); err != nil {
return err return err
} }
fmt.Fprintf(out, "%q has been added to your repositories\n", o.name) fmt.Fprintf(out, "%q has been added to your repositories\n", o.name)

@ -18,7 +18,7 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -27,7 +27,6 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
"helm.sh/helm/v3/pkg/helmpath/xdg" "helm.sh/helm/v3/pkg/helmpath/xdg"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
@ -48,7 +47,7 @@ func TestRepoAddCmd(t *testing.T) {
} }
defer srv2.Stop() 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) err = os.MkdirAll(tmpdir, 0777)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -88,7 +87,7 @@ func TestRepoAdd(t *testing.T) {
} }
defer ts.Stop() defer ts.Stop()
rootDir := ensure.TempDir(t) rootDir := t.TempDir()
repoFile := filepath.Join(rootDir, "repositories.yaml") repoFile := filepath.Join(rootDir, "repositories.yaml")
const testRepoName = "test-name" const testRepoName = "test-name"
@ -102,7 +101,7 @@ func TestRepoAdd(t *testing.T) {
} }
os.Setenv(xdg.CacheHomeEnvVar, rootDir) os.Setenv(xdg.CacheHomeEnvVar, rootDir)
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
t.Error(err) t.Error(err)
} }
@ -126,11 +125,11 @@ func TestRepoAdd(t *testing.T) {
o.forceUpdate = true o.forceUpdate = true
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
t.Errorf("Repository was not updated: %s", err) t.Errorf("Repository was not updated: %s", err)
} }
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
t.Errorf("Duplicate repository name was added") t.Errorf("Duplicate repository name was added")
} }
} }
@ -145,8 +144,8 @@ func TestRepoAddCheckLegalName(t *testing.T) {
const testRepoName = "test-hub/test-name" const testRepoName = "test-hub/test-name"
rootDir := ensure.TempDir(t) rootDir := t.TempDir()
repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml") repoFile := filepath.Join(t.TempDir(), "repositories.yaml")
o := &repoAddOptions{ o := &repoAddOptions{
name: testRepoName, name: testRepoName,
@ -159,7 +158,7 @@ func TestRepoAddCheckLegalName(t *testing.T) {
wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName) wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName)
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
if wantErrorMsg != err.Error() { if wantErrorMsg != err.Error() {
t.Fatalf("Actual error %s, not equal to expected error %s", err, wantErrorMsg) t.Fatalf("Actual error %s, not equal to expected error %s", err, wantErrorMsg)
} }
@ -170,25 +169,25 @@ func TestRepoAddCheckLegalName(t *testing.T) {
func TestRepoAddConcurrentGoRoutines(t *testing.T) { func TestRepoAddConcurrentGoRoutines(t *testing.T) {
const testName = "test-name" const testName = "test-name"
repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml") repoFile := filepath.Join(t.TempDir(), "repositories.yaml")
repoAddConcurrent(t, testName, repoFile) repoAddConcurrent(t, testName, repoFile)
} }
func TestRepoAddConcurrentDirNotExist(t *testing.T) { func TestRepoAddConcurrentDirNotExist(t *testing.T) {
const testName = "test-name-2" 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) repoAddConcurrent(t, testName, repoFile)
} }
func TestRepoAddConcurrentNoFileExtension(t *testing.T) { func TestRepoAddConcurrentNoFileExtension(t *testing.T) {
const testName = "test-name-3" const testName = "test-name-3"
repoFile := filepath.Join(ensure.TempDir(t), "repositories") repoFile := filepath.Join(t.TempDir(), "repositories")
repoAddConcurrent(t, testName, repoFile) repoAddConcurrent(t, testName, repoFile)
} }
func TestRepoAddConcurrentHiddenFile(t *testing.T) { func TestRepoAddConcurrentHiddenFile(t *testing.T) {
const testName = "test-name-4" const testName = "test-name-4"
repoFile := filepath.Join(ensure.TempDir(t), ".repositories") repoFile := filepath.Join(t.TempDir(), ".repositories")
repoAddConcurrent(t, testName, repoFile) repoAddConcurrent(t, testName, repoFile)
} }
@ -211,14 +210,14 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) {
forceUpdate: false, forceUpdate: false,
repoFile: repoFile, repoFile: repoFile,
} }
if err := o.run(ioutil.Discard); err != nil { if err := o.run(io.Discard); err != nil {
t.Error(err) t.Error(err)
} }
}(fmt.Sprintf("%s-%d", testName, i)) }(fmt.Sprintf("%s-%d", testName, i))
} }
wg.Wait() wg.Wait()
b, err := ioutil.ReadFile(repoFile) b, err := os.ReadFile(repoFile)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -254,7 +253,7 @@ func TestRepoAddWithPasswordFromStdin(t *testing.T) {
t.Errorf("unexpected error, got '%v'", err) t.Errorf("unexpected error, got '%v'", err)
} }
tmpdir := ensure.TempDir(t) tmpdir := t.TempDir()
repoFile := filepath.Join(tmpdir, "repositories.yaml") repoFile := filepath.Join(tmpdir, "repositories.yaml")
store := storageFixture() store := storageFixture()

@ -43,6 +43,7 @@ type repoIndexOptions struct {
dir string dir string
url string url string
merge string merge string
json bool
} }
func newRepoIndexCmd(out io.Writer) *cobra.Command { func newRepoIndexCmd(out io.Writer) *cobra.Command {
@ -70,6 +71,7 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.StringVar(&o.url, "url", "", "url of chart repository") f.StringVar(&o.url, "url", "", "url of chart repository")
f.StringVar(&o.merge, "merge", "", "merge the generated index into the given index") 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 return cmd
} }
@ -80,10 +82,10 @@ func (i *repoIndexOptions) run(out io.Writer) error {
return err 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") out := filepath.Join(dir, "index.yaml")
i, err := repo.IndexDirectory(dir, url) i, err := repo.IndexDirectory(dir, url)
@ -95,7 +97,7 @@ func index(dir, url, mergeTo string) error {
var i2 *repo.IndexFile var i2 *repo.IndexFile
if _, err := os.Stat(mergeTo); os.IsNotExist(err) { if _, err := os.Stat(mergeTo); os.IsNotExist(err) {
i2 = repo.NewIndexFile() i2 = repo.NewIndexFile()
i2.WriteFile(mergeTo, 0644) writeIndexFile(i2, mergeTo, json)
} else { } else {
i2, err = repo.LoadIndexFile(mergeTo) i2, err = repo.LoadIndexFile(mergeTo)
if err != nil { if err != nil {
@ -105,5 +107,12 @@ func index(dir, url, mergeTo string) error {
i.Merge(i2) i.Merge(i2)
} }
i.SortEntries() 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) return i.WriteFile(out, 0644)
} }

@ -18,18 +18,18 @@ package main
import ( import (
"bytes" "bytes"
"encoding/json"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/repo"
) )
func TestRepoIndexCmd(t *testing.T) { func TestRepoIndexCmd(t *testing.T) {
dir := ensure.TempDir(t) dir := t.TempDir()
comp := filepath.Join(dir, "compressedchart-0.1.0.tgz") comp := filepath.Join(dir, "compressedchart-0.1.0.tgz")
if err := linkOrCopy("testdata/testcharts/compressedchart-0.1.0.tgz", comp); err != nil { 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) 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` // Test with `--merge`
// Remove first two charts. // Remove first two charts.

@ -67,7 +67,7 @@ func (o *repoRemoveOptions) run(out io.Writer) error {
if !r.Remove(name) { if !r.Remove(name) {
return errors.Errorf("no repo named %q found", 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 return err
} }

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

@ -19,7 +19,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -103,10 +102,9 @@ func TestUpdateCmdInvalid(t *testing.T) {
} }
func TestUpdateCustomCacheCmd(t *testing.T) { func TestUpdateCustomCacheCmd(t *testing.T) {
rootDir := ensure.TempDir(t) rootDir := t.TempDir()
cachePath := filepath.Join(rootDir, "updcustomcache") cachePath := filepath.Join(rootDir, "updcustomcache")
os.Mkdir(cachePath, os.ModePerm) os.Mkdir(cachePath, os.ModePerm)
defer os.RemoveAll(cachePath)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil { if err != nil {
@ -119,7 +117,7 @@ func TestUpdateCustomCacheCmd(t *testing.T) {
repoFile: filepath.Join(ts.Root(), "repositories.yaml"), repoFile: filepath.Join(ts.Root(), "repositories.yaml"),
repoCache: cachePath, repoCache: cachePath,
} }
b := ioutil.Discard b := io.Discard
if err := o.run(b); err != nil { if err := o.run(b); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -130,7 +128,7 @@ func TestUpdateCustomCacheCmd(t *testing.T) {
func TestUpdateCharts(t *testing.T) { func TestUpdateCharts(t *testing.T) {
defer resetEnv()() defer resetEnv()()
defer ensure.HelmHome(t)() ensure.HelmHome(t)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil { if err != nil {
@ -165,7 +163,7 @@ func TestRepoUpdateFileCompletion(t *testing.T) {
func TestUpdateChartsFail(t *testing.T) { func TestUpdateChartsFail(t *testing.T) {
defer resetEnv()() defer resetEnv()()
defer ensure.HelmHome(t)() ensure.HelmHome(t)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil { if err != nil {
@ -198,7 +196,7 @@ func TestUpdateChartsFail(t *testing.T) {
func TestUpdateChartsFailWithError(t *testing.T) { func TestUpdateChartsFailWithError(t *testing.T) {
defer resetEnv()() defer resetEnv()()
defer ensure.HelmHome(t)() ensure.HelmHome(t)
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*") ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
if err != nil { if err != nil {

@ -17,7 +17,7 @@ package require
import ( import (
"fmt" "fmt"
"io/ioutil" "io"
"strings" "strings"
"testing" "testing"
@ -71,7 +71,7 @@ func runTestCases(t *testing.T, testCases []testCase) {
Args: tc.validateFunc, Args: tc.validateFunc,
} }
cmd.SetArgs(tc.args) cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(io.Discard)
err := cmd.Execute() err := cmd.Execute()
if tc.wantError == "" { if tc.wantError == "" {

@ -32,8 +32,8 @@ const rollbackDesc = `
This command rolls back a release to a previous revision. 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 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 second is a revision (version) number. If this argument is omitted or set to
roll back to the previous release. 0, it will roll back to the previous release.
To see revision numbers, run 'helm history RELEASE'. To see revision numbers, run 'helm history RELEASE'.
` `

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

@ -152,12 +152,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
flags.ParseErrorsWhitelist.UnknownFlags = true flags.ParseErrorsWhitelist.UnknownFlags = true
flags.Parse(args) flags.Parse(args)
registryClient, err := registry.NewClient( registryClient, err := newDefaultRegistryClient(false)
registry.ClientOptDebug(settings.Debug),
registry.ClientOptEnableCache(true),
registry.ClientOptWriter(os.Stderr),
registry.ClientOptCredentialsFile(settings.RegistryConfig),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -261,3 +256,48 @@ func checkForExpiredRepos(repofile string) {
} }
} }
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 {
return nil, err
}
return registryClient, nil
}
registryClient, err := newDefaultRegistryClient(plainHTTP)
if err != nil {
return nil, err
}
return registryClient, nil
}
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
}
return registryClient, nil
}
func newRegistryClientWithTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*registry.Client, error) {
// Create a new registry client
registryClient, err := registry.NewRegistryClientWithTLS(os.Stderr, certFile, keyFile, caFile, insecureSkipTLSverify,
settings.RegistryConfig, settings.Debug,
)
if err != nil {
return nil, err
}
return registryClient, nil
}

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

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package search provides client-side repository searching. /*
Package search provides client-side repository searching.
This supports building an in-memory search index based on the contents of This supports building an in-memory search index based on the contents of
multiple repositories, and then using string matching or regular expressions multiple repositories, and then using string matching or regular expressions
@ -146,11 +147,10 @@ func (i *Index) SearchLiteral(term string, threshold int) []*Result {
term = strings.ToLower(term) term = strings.ToLower(term)
buf := []*Result{} buf := []*Result{}
for k, v := range i.lines { for k, v := range i.lines {
lk := strings.ToLower(k)
lv := strings.ToLower(v) lv := strings.ToLower(v)
res := strings.Index(lv, term) res := strings.Index(lv, term)
if score := i.calcScore(res, lv); res != -1 && score < threshold { 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]}) buf = append(buf, &Result{Name: parts[0], Score: score, Chart: i.charts[k]})
} }
} }

@ -105,11 +105,11 @@ func loadTestIndex(t *testing.T, all bool) *Index {
i := NewIndex() i := NewIndex()
i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all) i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all)
i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{ i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{
"pinta": { "Pinta": {
{ {
URLs: []string{"http://example.com/charts/pinta-2.0.0.tgz"}, URLs: []string{"http://example.com/charts/pinta-2.0.0.tgz"},
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
Name: "pinta", Name: "Pinta",
Version: "2.0.0", Version: "2.0.0",
Description: "Two ship, version two", Description: "Two ship, version two",
}, },
@ -170,14 +170,14 @@ func TestSearchByName(t *testing.T) {
query: "pinta", query: "pinta",
expect: []*Result{ expect: []*Result{
{Name: "testing/pinta"}, {Name: "testing/pinta"},
{Name: "ztesting/pinta"}, {Name: "ztesting/Pinta"},
}, },
}, },
{ {
name: "repo-specific search for one result", name: "repo-specific search for one result",
query: "ztesting/pinta", query: "ztesting/pinta",
expect: []*Result{ expect: []*Result{
{Name: "ztesting/pinta"}, {Name: "ztesting/Pinta"},
}, },
}, },
{ {
@ -199,7 +199,15 @@ func TestSearchByName(t *testing.T) {
query: "two", query: "two",
expect: []*Result{ expect: []*Result{
{Name: "testing/pinta"}, {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", query: "TWO",
expect: []*Result{ expect: []*Result{
{Name: "testing/pinta"}, {Name: "testing/pinta"},
{Name: "ztesting/pinta"}, {Name: "ztesting/Pinta"},
}, },
}, },
{ {

@ -54,6 +54,7 @@ type searchHubOptions struct {
maxColWidth uint maxColWidth uint
outputFormat output.Format outputFormat output.Format
listRepoURL bool listRepoURL bool
failOnNoResult bool
} }
func newSearchHubCmd(out io.Writer) *cobra.Command { func newSearchHubCmd(out io.Writer) *cobra.Command {
@ -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.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.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.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) 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 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 { type hubChartRepo struct {
@ -108,12 +110,13 @@ type hubChartElement struct {
} }
type hubSearchWriter struct { type hubSearchWriter struct {
elements []hubChartElement elements []hubChartElement
columnWidth uint columnWidth uint
listRepoURL bool 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 var elements []hubChartElement
for _, r := range results { for _, r := range results {
// Backwards compatibility for Monocular // 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}}) 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 { func (h *hubSearchWriter) WriteTable(out io.Writer) error {
if len(h.elements) == 0 { 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")) _, err := out.Write([]byte("No results found\n"))
if err != nil { if err != nil {
return fmt.Errorf("unable to write results: %s", err) 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 { 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 // Initialize the array so no results returns an empty array instead of null
chartList := make([]hubChartElement, 0, len(h.elements)) chartList := make([]hubChartElement, 0, len(h.elements))

@ -90,3 +90,98 @@ func TestSearchHubOutputCompletion(t *testing.T) {
func TestSearchHubFileCompletion(t *testing.T) { func TestSearchHubFileCompletion(t *testing.T) {
checkFileCompletion(t, "search hub", true) // File completion may be useful when inputting a keyword 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, r *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)
}
})
}
}

@ -21,7 +21,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -64,14 +63,15 @@ Repositories are managed with 'helm repo' commands.
const searchMaxScore = 25 const searchMaxScore = 25
type searchRepoOptions struct { type searchRepoOptions struct {
versions bool versions bool
regexp bool regexp bool
devel bool devel bool
version string version string
maxColWidth uint maxColWidth uint
repoFile string repoFile string
repoCacheDir string repoCacheDir string
outputFormat output.Format outputFormat output.Format
failOnNoResult bool
} }
func newSearchRepoCmd(out io.Writer) *cobra.Command { func newSearchRepoCmd(out io.Writer) *cobra.Command {
@ -94,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.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.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.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) bindOutputFlag(cmd, &o.outputFormat)
return cmd return cmd
@ -124,7 +126,7 @@ func (o *searchRepoOptions) run(out io.Writer, args []string) error {
return err 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() { func (o *searchRepoOptions) setupSearchedVersion() {
@ -205,12 +207,18 @@ type repoChartElement struct {
} }
type repoSearchWriter struct { type repoSearchWriter struct {
results []*search.Result results []*search.Result
columnWidth uint columnWidth uint
failOnNoResult bool
} }
func (r *repoSearchWriter) WriteTable(out io.Writer) error { func (r *repoSearchWriter) WriteTable(out io.Writer) error {
if len(r.results) == 0 { 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")) _, err := out.Write([]byte("No results found\n"))
if err != nil { if err != nil {
return fmt.Errorf("unable to write results: %s", err) return fmt.Errorf("unable to write results: %s", err)
@ -235,6 +243,11 @@ func (r *repoSearchWriter) WriteYAML(out io.Writer) error {
} }
func (r *repoSearchWriter) encodeByFormat(out io.Writer, format output.Format) 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 // Initialize the array so no results returns an empty array instead of null
chartList := make([]repoChartElement, 0, len(r.results)) chartList := make([]repoChartElement, 0, len(r.results))
@ -259,7 +272,7 @@ func compListChartsOfRepo(repoName string, prefix string) []string {
var charts []string var charts []string
path := filepath.Join(settings.RepositoryCache, helmpath.CacheChartsFile(repoName)) path := filepath.Join(settings.RepositoryCache, helmpath.CacheChartsFile(repoName))
content, err := ioutil.ReadFile(path) content, err := os.ReadFile(path)
if err == nil { if err == nil {
scanner := bufio.NewScanner(bytes.NewReader(content)) scanner := bufio.NewScanner(bytes.NewReader(content))
for scanner.Scan() { for scanner.Scan() {

@ -56,6 +56,20 @@ func TestSearchRepositoriesCmd(t *testing.T) {
name: "search for 'syzygy', expect no matches", name: "search for 'syzygy', expect no matches",
cmd: "search repo syzygy", cmd: "search repo syzygy",
golden: "output/search-not-found.txt", 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", name: "search for 'alp[a-z]+', expect two matches",
cmd: "search repo alp[a-z]+ --regexp", cmd: "search repo alp[a-z]+ --regexp",

@ -84,6 +84,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowAll client.OutputFormat = action.ShowAll
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -101,6 +105,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowValues client.OutputFormat = action.ShowValues
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -118,6 +126,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowChart client.OutputFormat = action.ShowChart
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -135,6 +147,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowReadme client.OutputFormat = action.ShowReadme
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -152,6 +168,10 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
ValidArgsFunction: validArgsFunc, ValidArgsFunction: validArgsFunc,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowCRDs client.OutputFormat = action.ShowCRDs
err := addRegistryClient(client)
if err != nil {
return err
}
output, err := runShow(args, client) output, err := runShow(args, client)
if err != nil { if err != nil {
return err return err
@ -204,3 +224,13 @@ func runShow(args []string, client *action.Show) (string, error) {
} }
return client.Run(cp) return client.Run(cp)
} }
func addRegistryClient(client *action.Show) error {
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)
return nil
}

@ -65,6 +65,13 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *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
// returned. This mirrors the handling in kubectl.
if outfmt == output.Table {
client.ShowResourcesTable = true
}
rel, err := client.Run(args[0]) rel, err := client.Run(args[0])
if err != nil { if err != nil {
return err return err
@ -73,7 +80,7 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
// strip chart metadata from the output // strip chart metadata from the output
rel.Chart = nil rel.Chart = nil
return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription, client.ShowResources}) return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription, client.ShowResources, false})
}, },
} }
@ -105,6 +112,7 @@ type statusPrinter struct {
debug bool debug bool
showDescription bool showDescription bool
showResources bool showResources bool
showMetadata bool
} }
func (s statusPrinter) WriteJSON(out io.Writer) error { func (s statusPrinter) WriteJSON(out io.Writer) error {
@ -119,15 +127,20 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
if s.release == nil { if s.release == nil {
return 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() { 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 { if s.showDescription {
fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description) _, _ = fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description)
} }
if s.showResources && s.release.Info.Resources != nil && len(s.release.Info.Resources) > 0 { if s.showResources && s.release.Info.Resources != nil && len(s.release.Info.Resources) > 0 {
@ -142,31 +155,31 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
} }
for _, t := range keys { for _, t := range keys {
fmt.Fprintf(buf, "==> %s\n", t) _, _ = fmt.Fprintf(buf, "==> %s\n", t)
vk := s.release.Info.Resources[t] vk := s.release.Info.Resources[t]
for _, resource := range vk { for _, resource := range vk {
if err := printer.PrintObj(resource, buf); err != nil { 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") 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) executions := executionsByHookEvent(s.release)
if tests, ok := executions[release.HookTest]; !ok || len(tests) == 0 { if tests, ok := executions[release.HookTest]; !ok || len(tests) == 0 {
fmt.Fprintln(out, "TEST SUITE: None") _, _ = fmt.Fprintln(out, "TEST SUITE: None")
} else { } else {
for _, h := range tests { for _, h := range tests {
// Don't print anything if hook has not been initiated // Don't print anything if hook has not been initiated
if h.LastRun.StartedAt.IsZero() { if h.LastRun.StartedAt.IsZero() {
continue 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, h.Name,
fmt.Sprintf("Last Started: %s", h.LastRun.StartedAt.Format(time.ANSIC)), fmt.Sprintf("Last Started: %s", h.LastRun.StartedAt.Format(time.ANSIC)),
fmt.Sprintf("Last Completed: %s", h.LastRun.CompletedAt.Format(time.ANSIC)), fmt.Sprintf("Last Completed: %s", h.LastRun.CompletedAt.Format(time.ANSIC)),
@ -176,38 +189,38 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
} }
if s.debug { if s.debug {
fmt.Fprintln(out, "USER-SUPPLIED VALUES:") _, _ = fmt.Fprintln(out, "USER-SUPPLIED VALUES:")
err := output.EncodeYAML(out, s.release.Config) err := output.EncodeYAML(out, s.release.Config)
if err != nil { if err != nil {
return err return err
} }
// Print an extra newline // Print an extra newline
fmt.Fprintln(out) _, _ = fmt.Fprintln(out)
cfg, err := chartutil.CoalesceValues(s.release.Chart, s.release.Config) cfg, err := chartutil.CoalesceValues(s.release.Chart, s.release.Config)
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintln(out, "COMPUTED VALUES:") _, _ = fmt.Fprintln(out, "COMPUTED VALUES:")
err = output.EncodeYAML(out, cfg.AsMap()) err = output.EncodeYAML(out, cfg.AsMap())
if err != nil { if err != nil {
return err return err
} }
// Print an extra newline // Print an extra newline
fmt.Fprintln(out) _, _ = fmt.Fprintln(out)
} }
if strings.EqualFold(s.release.Info.Description, "Dry run complete") || s.debug { 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 { 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 { if len(s.release.Info.Notes) > 0 {
fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(s.release.Info.Notes)) _, _ = fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(s.release.Info.Notes))
} }
return nil return nil
} }

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

@ -73,6 +73,19 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.KubeVersion = parsedKubeVersion client.KubeVersion = parsedKubeVersion
} }
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.DryRun = true
client.ReleaseName = "release-name" client.ReleaseName = "release-name"
client.Replace = true // Skip the name check client.Replace = true // Skip the name check
@ -106,11 +119,15 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if client.UseReleaseName { if client.UseReleaseName {
newDir = filepath.Join(client.OutputDir, client.ReleaseName) newDir = filepath.Join(client.OutputDir, client.ReleaseName)
} }
_, err := os.Stat(filepath.Join(newDir, m.Path))
if err == nil {
fileWritten[m.Path] = true
}
err = writeToFile(newDir, m.Path, m.Manifest, fileWritten[m.Path]) err = writeToFile(newDir, m.Path, m.Manifest, fileWritten[m.Path])
if err != nil { if err != nil {
return err return err
} }
fileWritten[m.Path] = true
} }
} }
@ -181,7 +198,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&skipTests, "skip-tests", false, "skip tests from templated output") f.BoolVar(&skipTests, "skip-tests", false, "skip tests from templated output")
f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall") f.BoolVar(&client.IsUpgrade, "is-upgrade", false, "set .Release.IsUpgrade instead of .Release.IsInstall")
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion") f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion")
f.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions") f.StringSliceVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.") f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
bindPostRenderFlag(cmd, &client.PostRenderer) bindPostRenderFlag(cmd, &client.PostRenderer)

@ -25,6 +25,8 @@ import (
var chartPath = "testdata/testcharts/subchart" var chartPath = "testdata/testcharts/subchart"
func TestTemplateCmd(t *testing.T) { func TestTemplateCmd(t *testing.T) {
deletevalchart := "testdata/testcharts/issue-9027"
tests := []cmdTestCase{ tests := []cmdTestCase{
{ {
name: "check name", name: "check name",
@ -131,6 +133,34 @@ func TestTemplateCmd(t *testing.T) {
cmd: fmt.Sprintf(`template '%s' --skip-tests`, chartPath), cmd: fmt.Sprintf(`template '%s' --skip-tests`, chartPath),
golden: "output/template-skip-tests.txt", 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) 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

@ -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 NAMESPACE: default
STATUS: deployed STATUS: deployed
REVISION: 1 REVISION: 1
CHART: foo
VERSION: 0.1.0-beta.1
APP_VERSION: 1.0
TEST SUITE: None TEST SUITE: None
USER-SUPPLIED VALUES: USER-SUPPLIED VALUES:
name: value name: value

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

@ -1,6 +1,6 @@
==> Linting testdata/testcharts/chart-with-bad-subcharts ==> Linting testdata/testcharts/chart-with-bad-subcharts
[INFO] Chart.yaml: icon is recommended [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] : unable to load chart
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required

@ -1,7 +1,7 @@
==> Linting testdata/testcharts/chart-bad-requirements ==> Linting testdata/testcharts/chart-bad-requirements
[ERROR] Chart.yaml: unable to parse YAML [ERROR] Chart.yaml: unable to parse YAML
error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
[WARNING] templates/: directory not found [ERROR] templates/: cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
[ERROR] : unable to load chart [ERROR] : unable to load chart
cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator

@ -1,4 +0,0 @@
==> Linting testdata/testcharts/chart-with-only-crds
[WARNING] templates/: directory not found
1 chart(s) linted, 0 chart(s) failed

@ -0,0 +1 @@
Error: release has no 3 version

@ -0,0 +1 @@
Error: no results found

@ -0,0 +1,122 @@
---
# Source: subchart/templates/subdir/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: subchart-sa
---
# Source: subchart/templates/subdir/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: subchart-cm
data:
value: qux
---
# Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: subchart-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: subchart-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: subchart-role
subjects:
- kind: ServiceAccount
name: subchart-sa
namespace: default
---
# Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
helm.sh/chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app.kubernetes.io/name: subcharta
---
# Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
helm.sh/chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchartb
---
# Source: subchart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart
labels:
helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "release-name"
kube-version/major: "1"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "release-name-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "release-name-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "release-name-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -0,0 +1,122 @@
---
# Source: subchart/templates/subdir/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: subchart-sa
---
# Source: subchart/templates/subdir/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: subchart-cm
data:
value: baz
---
# Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: subchart-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: subchart-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: subchart-role
subjects:
- kind: ServiceAccount
name: subchart-sa
namespace: default
---
# Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
helm.sh/chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app.kubernetes.io/name: subcharta
---
# Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
helm.sh/chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchartb
---
# Source: subchart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart
labels:
helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "release-name"
kube-version/major: "1"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "release-name-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "release-name-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "release-name-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -0,0 +1,122 @@
---
# Source: subchart/templates/subdir/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: subchart-sa
---
# Source: subchart/templates/subdir/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: subchart-cm
data:
value: foo
---
# Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: subchart-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
---
# Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: subchart-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: subchart-role
subjects:
- kind: ServiceAccount
name: subchart-sa
namespace: default
---
# Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subcharta
labels:
helm.sh/chart: "subcharta-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: apache
selector:
app.kubernetes.io/name: subcharta
---
# Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchartb
labels:
helm.sh/chart: "subchartb-0.1.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchartb
---
# Source: subchart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: subchart
labels:
helm.sh/chart: "subchart-0.1.0"
app.kubernetes.io/instance: "release-name"
kube-version/major: "1"
kube-version/minor: "20"
kube-version/version: "v1.20.0"
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: nginx
selector:
app.kubernetes.io/name: subchart
---
# Source: subchart/templates/tests/test-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: "release-name-testconfig"
annotations:
"helm.sh/hook": test
data:
message: Hello World
---
# Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1
kind: Pod
metadata:
name: "release-name-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: test
image: "alpine:latest"
envFrom:
- configMapRef:
name: "release-name-testconfig"
command:
- echo
- "$message"
restartPolicy: Never

@ -1,5 +1,5 @@
--- ---
# Source: crds/crdA.yaml # Source: subchart/crds/crdA.yaml
apiVersion: apiextensions.k8s.io/v1beta1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:

@ -1 +1 @@
version.BuildInfo{Version:"v3.10", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.13", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1 +1 @@
version.BuildInfo{Version:"v3.10", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.13", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -1 +1 @@
Version: v3.10 Version: v3.13

@ -1 +1 @@
version.BuildInfo{Version:"v3.10", GitCommit:"", GitTreeState:"", GoVersion:""} version.BuildInfo{Version:"v3.13", GitCommit:"", GitTreeState:"", GoVersion:""}

@ -0,0 +1,6 @@
apiVersion: v2
name: issue-9027
version: 0.1.0
dependencies:
- name: subchart
version: 0.1.0

@ -0,0 +1,3 @@
apiVersion: v2
name: subchart
version: 0.1.0

@ -0,0 +1,17 @@
global:
hash:
key1: 1
key2: 2
key3: 3
key4: 4
key5: 5
key6: 6
hash:
key1: 1
key2: 2
key3: 3
key4: 4
key5: 5
key6: 6

@ -0,0 +1,11 @@
global:
hash:
key1: null
key2: null
key3: 13
subchart:
hash:
key1: null
key2: null
key3: 13

@ -29,6 +29,9 @@ dependencies:
parent: imported-chartA-B parent: imported-chartA-B
- child: exports.SCBexported2 - child: exports.SCBexported2
parent: exports.SCBexported2 parent: exports.SCBexported2
# - child: exports.configmap
# parent: configmap
- configmap
- SCBexported1 - SCBexported1
tags: tags:

@ -20,6 +20,10 @@ exports:
SCBexported2: SCBexported2:
SCBexported2A: "blaster" SCBexported2A: "blaster"
configmap:
configmap:
value: "bar"
global: global:
kolla: kolla:

@ -0,0 +1,5 @@
# This file is used to test values passed by file at the command line
configmap:
enabled: true
value: "qux"

@ -0,0 +1,8 @@
{{ if .Values.configmap.enabled -}}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Chart.Name }}-cm
data:
value: {{ .Values.configmap.value }}
{{- end }}

@ -53,3 +53,7 @@ exports:
SC1exported2: SC1exported2:
all: all:
SC1exported3: "SC1expstr" SC1exported3: "SC1expstr"
configmap:
enabled: false
value: "foo"

@ -51,6 +51,10 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return compListReleases(toComplete, args, cfg) return compListReleases(toComplete, args, cfg)
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
validationErr := validateCascadeFlag(client)
if validationErr != nil {
return validationErr
}
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
res, err := client.Run(args[i]) res, err := client.Run(args[i])
@ -70,10 +74,19 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall") f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall")
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")
f.BoolVar(&client.IgnoreNotFound, "ignore-not-found", false, `Treat "release not found" as a successful uninstall`)
f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history") f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout")
f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.")
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.StringVar(&client.Description, "description", "", "add a custom description") f.StringVar(&client.Description, "description", "", "add a custom description")
return cmd return cmd
} }
func validateCascadeFlag(client *action.Uninstall) error {
if client.DeletionPropagation != "background" && client.DeletionPropagation != "foreground" && client.DeletionPropagation != "orphan" {
return fmt.Errorf("invalid cascade value (%s). Must be \"background\", \"foreground\", or \"orphan\"", client.DeletionPropagation)
}
return nil
}

@ -65,6 +65,13 @@ last (right-most) set specified. For example, if both 'bar' and 'newbar' values
set for a key called 'foo', the 'newbar' value would take precedence: set for a key called 'foo', the 'newbar' value would take precedence:
$ helm upgrade --set foo=bar --set foo=newbar redis ./redis $ helm upgrade --set foo=bar --set foo=newbar redis ./redis
You can update the values for an existing release with this command as well via the
'--reuse-values' flag. The 'RELEASE' and 'CHART' arguments should be set to the original
parameters, and existing values will be merged with any values set via '--values'/'-f'
or '--set' flags. Priority is given to new values.
$ helm upgrade --reuse-values --set foo=bar --set foo=newbar redis ./redis
` `
func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
@ -90,6 +97,19 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
client.Namespace = settings.Namespace() client.Namespace = settings.Namespace()
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"
}
// Fixes #7002 - Support reading values from STDIN for `upgrade` command // Fixes #7002 - Support reading values from STDIN for `upgrade` command
// Must load values AFTER determining if we have to call install so that values loaded from stdin are are not read twice // Must load values AFTER determining if we have to call install so that values loaded from stdin are are not read twice
if client.Install { if client.Install {
@ -104,7 +124,9 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient := action.NewInstall(cfg) instClient := action.NewInstall(cfg)
instClient.CreateNamespace = createNamespace instClient.CreateNamespace = createNamespace
instClient.ChartPathOptions = client.ChartPathOptions instClient.ChartPathOptions = client.ChartPathOptions
instClient.Force = client.Force
instClient.DryRun = client.DryRun instClient.DryRun = client.DryRun
instClient.DryRunOption = client.DryRunOption
instClient.DisableHooks = client.DisableHooks instClient.DisableHooks = client.DisableHooks
instClient.SkipCRDs = client.SkipCRDs instClient.SkipCRDs = client.SkipCRDs
instClient.Timeout = client.Timeout instClient.Timeout = client.Timeout
@ -118,12 +140,14 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.SubNotes = client.SubNotes instClient.SubNotes = client.SubNotes
instClient.Description = client.Description instClient.Description = client.Description
instClient.DependencyUpdate = client.DependencyUpdate instClient.DependencyUpdate = client.DependencyUpdate
instClient.Labels = client.Labels
instClient.EnableDNS = client.EnableDNS
rel, err := runInstall(args, instClient, valueOpts, out) rel, err := runInstall(args, instClient, valueOpts, out)
if err != nil { if err != nil {
return err return err
} }
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false}) return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false})
} else if err != nil { } else if err != nil {
return err return err
} }
@ -138,6 +162,10 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if err != nil { if err != nil {
return err return err
} }
// Validate dry-run flag value is one of the allowed values
if err := validateDryRunOptionFlag(client.DryRunOption); err != nil {
return err
}
p := getter.All(settings) p := getter.All(settings)
vals, err := valueOpts.MergeValues(p) vals, err := valueOpts.MergeValues(p)
@ -205,7 +233,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0]) fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0])
} }
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false}) return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false})
}, },
} }
@ -213,7 +241,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present") f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present")
f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install") f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an upgrade") 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.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods") f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods")
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
@ -223,14 +252,17 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored")
f.BoolVar(&client.ResetThenReuseValues, "reset-then-reuse-values", false, "when upgrading, reset the values to the ones built into the chart, apply the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' or '--reuse-values' is specified, this is ignored")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used")
f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit")
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails")
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be separated by comma. Original release labels will be merged with upgrade labels. You can unset label using null.")
f.StringVar(&client.Description, "description", "", "add a custom description") f.StringVar(&client.Description, "description", "", "add a custom description")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)

@ -18,13 +18,12 @@ package main
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"testing" "testing"
"helm.sh/helm/v3/internal/test/ensure"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
@ -33,7 +32,7 @@ import (
func TestUpgradeCmd(t *testing.T) { func TestUpgradeCmd(t *testing.T) {
tmpChart := ensure.TempDir(t) tmpChart := t.TempDir()
cfile := &chart.Chart{ cfile := &chart.Chart{
Metadata: &chart.Metadata{ Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV1, APIVersion: chart.APIVersionV1,
@ -358,8 +357,8 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) {
} }
func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { func prepareMockRelease(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) {
tmpChart := ensure.TempDir(t) tmpChart := t.TempDir()
configmapData, err := ioutil.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml") configmapData, err := os.ReadFile("testdata/testcharts/upgradetest/templates/configmap.yaml")
if err != nil { if err != nil {
t.Fatalf("Error loading template yaml %v", err) t.Fatalf("Error loading template yaml %v", err)
} }
@ -431,3 +430,31 @@ func TestUpgradeFileCompletion(t *testing.T) {
checkFileCompletion(t, "upgrade myrelease", true) checkFileCompletion(t, "upgrade myrelease", true)
checkFileCompletion(t, "upgrade myrelease repo/chart", false) checkFileCompletion(t, "upgrade myrelease repo/chart", false)
} }
func TestUpgradeInstallWithLabels(t *testing.T) {
releaseName := "funny-bunny-labels"
_, _, chartPath := prepareMockRelease(releaseName, t)
defer resetEnv()()
store := storageFixture()
expectedLabels := map[string]string{
"key1": "val1",
"key2": "val2",
}
cmd := fmt.Sprintf("upgrade %s --install --labels key1=val1,key2=val2 '%s'", releaseName, chartPath)
_, _, err := executeActionCommandC(store, cmd)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := store.Get(releaseName, 1)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
if !reflect.DeepEqual(updatedRel.Labels, expectedLabels) {
t.Errorf("Expected {%v}, got {%v}", expectedLabels, updatedRel.Labels)
}
}

177
go.mod

@ -1,122 +1,120 @@
module helm.sh/helm/v3 module helm.sh/helm/v3
go 1.17 go 1.19
require ( require (
github.com/BurntSushi/toml v1.2.0 github.com/BurntSushi/toml v1.3.2
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Masterminds/semver/v3 v3.2.0 github.com/Masterminds/semver/v3 v3.2.1
github.com/Masterminds/sprig/v3 v3.2.3 github.com/Masterminds/sprig/v3 v3.2.3
github.com/Masterminds/squirrel v1.5.3 github.com/Masterminds/squirrel v1.5.4
github.com/Masterminds/vcs v1.13.3 github.com/Masterminds/vcs v1.13.3
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/containerd/containerd v1.6.6 github.com/containerd/containerd v1.7.6
github.com/cyphar/filepath-securejoin v0.2.3 github.com/cyphar/filepath-securejoin v0.2.4
github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2
github.com/evanphx/json-patch v5.6.0+incompatible github.com/evanphx/json-patch v5.7.0+incompatible
github.com/foxcpp/go-mockdns v1.0.0
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/gofrs/flock v0.8.1 github.com/gofrs/flock v0.8.1
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
github.com/hashicorp/go-multierror v1.1.1
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/lib/pq v1.10.7 github.com/lib/pq v1.10.9
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/copystructure v1.2.0
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 github.com/moby/term v0.5.0
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 github.com/opencontainers/image-spec v1.1.0-rc5
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rubenv/sql-migrate v1.2.0 github.com/rubenv/sql-migrate v1.5.2
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.4 github.com/stretchr/testify v1.8.4
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.3.0 golang.org/x/crypto v0.14.0
golang.org/x/term v0.2.0 golang.org/x/term v0.13.0
golang.org/x/text v0.5.0 golang.org/x/text v0.13.0
k8s.io/api v0.25.2 k8s.io/api v0.28.2
k8s.io/apiextensions-apiserver v0.25.2 k8s.io/apiextensions-apiserver v0.28.2
k8s.io/apimachinery v0.25.2 k8s.io/apimachinery v0.28.2
k8s.io/apiserver v0.25.2 k8s.io/apiserver v0.28.2
k8s.io/cli-runtime v0.25.2 k8s.io/cli-runtime v0.28.2
k8s.io/client-go v0.25.2 k8s.io/client-go v0.28.2
k8s.io/klog/v2 v2.70.1 k8s.io/klog/v2 v2.100.1
k8s.io/kubectl v0.25.2 k8s.io/kubectl v0.28.2
oras.land/oras-go v1.2.0 oras.land/oras-go v1.2.4
sigs.k8s.io/yaml v1.3.0 sigs.k8s.io/yaml v1.3.0
) )
require ( require (
cloud.google.com/go v0.99.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/Microsoft/hcsshim v0.11.0 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd // indirect
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect github.com/docker/cli v24.0.6+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v20.10.17+incompatible // indirect github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fvbommel/sortorder v1.0.1 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.0.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/logr v1.2.4 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/swag v0.19.14 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/gomodule/redigo v1.8.2 // indirect github.com/gomodule/redigo v1.8.2 // indirect
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.5.8 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/huandu/xstrings v1.3.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.13.6 // indirect github.com/klauspost/compress v1.16.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/miekg/dns v1.1.25 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect github.com/moby/spdystream v0.2.0 // indirect
@ -128,38 +126,39 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.10.1 // indirect
github.com/russross/blackfriday v1.5.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.4.1 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xlab/treeprint v1.1.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 // indirect
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 // indirect
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect go.opentelemetry.io/otel v1.14.0 // indirect
golang.org/x/net v0.2.0 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.2.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
google.golang.org/grpc v1.47.0 // indirect google.golang.org/grpc v1.56.3 // indirect
google.golang.org/protobuf v1.28.0 // indirect google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.25.2 // indirect k8s.io/component-base v0.28.2 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
) )

1723
go.sum

File diff suppressed because it is too large Load Diff

@ -18,7 +18,6 @@ package fileutil
import ( import (
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
@ -28,7 +27,7 @@ import (
// AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a // AtomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a
// disk. // disk.
func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error { func AtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error {
tempFile, err := ioutil.TempFile(filepath.Split(filename)) tempFile, err := os.CreateTemp(filepath.Split(filename))
if err != nil { if err != nil {
return err return err
} }

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

Loading…
Cancel
Save