Merge remote-tracking branch 'upstream/main' into iss-11553

pull/11569/head
Alex Petrov 3 years ago
commit 5616713d10
No known key found for this signature in database
GPG Key ID: 2202312B7E9A7B11

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

@ -1,16 +1,21 @@
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@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
with: with:
go-version: '1.18' go-version: '1.18'
- name: Install golangci-lint - name: Install golangci-lint
@ -27,3 +32,5 @@ jobs:
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 test build

@ -35,11 +35,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.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@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # pinv2.1.37
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@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # pinv2.1.37
# 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@959cbb7472c4d4ad70cdfe6f4976053fe48ab394 # pinv2.1.37

@ -0,0 +1,75 @@
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@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
- name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
with:
go-version: '1.18'
- name: Run unit tests
run: make test-coverage
- name: Build Helm Binaries
run: |
make build-cross
make dist checksum VERSION="${{ github.ref_name }}"
- 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@755da8c3cf115ac066823e79a1e1788f8940201b # pin@v3.2.0
- name: Setup Go
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@3.5.0
with:
go-version: '1.18'
- 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:

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

@ -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}) return output.Table.Write(out, &statusPrinter{res, true, false, false})
}, },
} }

@ -141,7 +141,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
return errors.Wrap(err, "INSTALLATION FAILED") return errors.Wrap(err, "INSTALLATION FAILED")
} }
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}) return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
}, },
} }
@ -155,6 +155,7 @@ 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") f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
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 +170,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used")
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)

@ -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}); err != nil { if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false}); err != nil {
return err return err
} }

@ -17,6 +17,7 @@ limitations under the License.
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -25,6 +26,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubectl/pkg/cmd/get"
"helm.sh/helm/v3/cmd/helm/require" "helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
@ -41,7 +44,7 @@ The status consists of:
- state of the release (can be: unknown, deployed, uninstalled, superseded, failed, uninstalling, pending-install, pending-upgrade or pending-rollback) - state of the release (can be: unknown, deployed, uninstalled, superseded, failed, uninstalling, pending-install, pending-upgrade or pending-rollback)
- revision of the release - revision of the release
- description of the release (can be completion message or error message, need to enable --show-desc) - description of the release (can be completion message or error message, need to enable --show-desc)
- list of resources that this release consists of, sorted by kind - list of resources that this release consists of (need to enable --show-resources)
- details on last test suite run, if applicable - details on last test suite run, if applicable
- additional notes provided by the chart - additional notes provided by the chart
` `
@ -62,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
@ -70,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}) return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription, client.ShowResources})
}, },
} }
@ -92,6 +102,8 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
bindOutputFlag(cmd, &outfmt) bindOutputFlag(cmd, &outfmt)
f.BoolVar(&client.ShowDescription, "show-desc", false, "if set, display the description message of the named release") f.BoolVar(&client.ShowDescription, "show-desc", false, "if set, display the description message of the named release")
f.BoolVar(&client.ShowResources, "show-resources", false, "if set, display the resources of the named release")
return cmd return cmd
} }
@ -99,6 +111,7 @@ type statusPrinter struct {
release *release.Release release *release.Release
debug bool debug bool
showDescription bool showDescription bool
showResources bool
} }
func (s statusPrinter) WriteJSON(out io.Writer) error { func (s statusPrinter) WriteJSON(out io.Writer) error {
@ -124,6 +137,33 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
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 {
buf := new(bytes.Buffer)
printFlags := get.NewHumanPrintFlags()
typePrinter, _ := printFlags.ToPrinter("")
printer := &get.TablePrinter{Delegate: typePrinter}
var keys []string
for key := range s.release.Info.Resources {
keys = append(keys, key)
}
for _, t := range keys {
fmt.Fprintf(buf, "==> %s\n", t)
vk := s.release.Info.Resources[t]
for _, resource := range vk {
if err := printer.PrintObj(resource, buf); err != nil {
fmt.Fprintf(buf, "failed to print object type %s: %v\n", t, err)
}
}
buf.WriteString("\n")
}
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")

@ -68,6 +68,24 @@ func TestStatusCmd(t *testing.T) {
Status: release.StatusDeployed, Status: release.StatusDeployed,
Notes: "release notes", Notes: "release notes",
}), }),
}, {
name: "get status of a deployed release with resources",
cmd: "status --show-resources flummoxed-chickadee",
golden: "output/status-with-resources.txt",
rels: releasesMockWithStatus(
&release.Info{
Status: release.StatusDeployed,
},
),
}, {
name: "get status of a deployed release with resources in json",
cmd: "status --show-resources flummoxed-chickadee -o json",
golden: "output/status-with-resources.json",
rels: releasesMockWithStatus(
&release.Info{
Status: release.StatusDeployed,
},
),
}, { }, {
name: "get status of a deployed release with test suite", name: "get status of a deployed release with test suite",
cmd: "status flummoxed-chickadee", cmd: "status flummoxed-chickadee",

@ -106,11 +106,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 +185,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)

@ -0,0 +1 @@
{"name":"flummoxed-chickadee","info":{"first_deployed":"","last_deployed":"2016-01-16T00:00:00Z","deleted":"","status":"deployed"},"namespace":"default"}

@ -0,0 +1,6 @@
NAME: flummoxed-chickadee
LAST DEPLOYED: Sat Jan 16 00:00:00 2016
NAMESPACE: default
STATUS: deployed
REVISION: 0
TEST SUITE: None

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

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

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

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

@ -106,6 +106,7 @@ 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.DisableHooks = client.DisableHooks instClient.DisableHooks = client.DisableHooks
instClient.SkipCRDs = client.SkipCRDs instClient.SkipCRDs = client.SkipCRDs
@ -120,6 +121,8 @@ 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.EnableDNS = client.EnableDNS
if isReleaseUninstalled(versions) { if isReleaseUninstalled(versions) {
instClient.Replace = true instClient.Replace = true
} }
@ -128,7 +131,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if err != nil { if err != nil {
return err return err
} }
return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}) return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
} else if err != nil { } else if err != nil {
return err return err
} }
@ -210,7 +213,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}) return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false})
}, },
} }
@ -236,6 +239,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
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.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)

121
go.mod

@ -1,18 +1,18 @@
module helm.sh/helm/v3 module helm.sh/helm/v3
go 1.18 go 1.17
require ( require (
github.com/BurntSushi/toml v1.2.0 github.com/BurntSushi/toml v1.2.1
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Masterminds/semver/v3 v3.1.1 github.com/Masterminds/semver/v3 v3.2.0
github.com/Masterminds/sprig/v3 v3.2.2 github.com/Masterminds/sprig/v3 v3.2.3
github.com/Masterminds/squirrel v1.5.3 github.com/Masterminds/squirrel v1.5.3
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.6.15
github.com/cyphar/filepath-securejoin v0.2.3 github.com/cyphar/filepath-securejoin v0.2.3
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.6.0+incompatible
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
@ -21,44 +21,35 @@ require (
github.com/lib/pq v1.10.7 github.com/lib/pq v1.10.7
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.0.0-20221205130635-1aeaba878587
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 github.com/opencontainers/image-spec v1.1.0-rc2
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.3.1
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.1
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e golang.org/x/crypto v0.5.0
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 golang.org/x/term v0.4.0
golang.org/x/text v0.3.7 golang.org/x/text v0.6.0
k8s.io/api v0.25.2 k8s.io/api v0.26.0
k8s.io/apiextensions-apiserver v0.25.2 k8s.io/apiextensions-apiserver v0.26.0
k8s.io/apimachinery v0.25.2 k8s.io/apimachinery v0.26.0
k8s.io/apiserver v0.25.2 k8s.io/apiserver v0.26.0
k8s.io/cli-runtime v0.25.2 k8s.io/cli-runtime v0.26.0
k8s.io/client-go v0.25.2 k8s.io/client-go v0.26.0
k8s.io/klog/v2 v2.70.1 k8s.io/klog/v2 v2.80.1
k8s.io/kubectl v0.25.2 k8s.io/kubectl v0.26.0
oras.land/oras-go v1.2.0 oras.land/oras-go v1.2.2
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/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/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
@ -69,52 +60,53 @@ require (
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 v20.10.21+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.17+incompatible // indirect github.com/docker/docker v20.10.21+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.4.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.9.0 // 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/go-errors/errors v1.0.1 // indirect github.com/go-errors/errors v1.0.1 // indirect
github.com/go-gorp/gorp/v3 v3.0.2 // indirect github.com/go-gorp/gorp/v3 v3.0.5 // indirect
github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.14 // indirect github.com/go-openapi/swag v0.19.14 // 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.2 // 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 v0.5.7-v3refs // 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.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/imdario/mergo v0.3.12 // indirect github.com/huandu/xstrings v1.4.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // 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.11.13 // 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.6 // 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/mitchellh/go-wordwrap v1.0.0 // 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
@ -127,14 +119,13 @@ 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.14.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.8.0 // 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-20180127040702-4e3ac2762d5f // 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.1.0 // indirect
@ -142,21 +133,21 @@ require (
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.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/net v0.5.0 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/sys v0.4.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // 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 v0.0.0-20220502173005-c8bf987b8c21 // indirect
google.golang.org/grpc v1.47.0 // indirect google.golang.org/grpc v1.49.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect google.golang.org/protobuf v1.28.1 // 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.26.0 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect

1079
go.sum

File diff suppressed because it is too large Load Diff

@ -29,7 +29,7 @@ var (
// //
// Increment major number for new feature additions and behavioral changes. // Increment major number for new feature additions and behavioral changes.
// Increment minor number for bug fixes and performance enhancements. // Increment minor number for bug fixes and performance enhancements.
version = "v3.10" version = "v3.11"
// metadata is extra build time data // metadata is extra build time data
metadata = "" metadata = ""

@ -101,8 +101,9 @@ type Configuration struct {
// //
// TODO: This function is badly in need of a refactor. // TODO: This function is badly in need of a refactor.
// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed // TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed
// This code has to do with writing files to disk. //
func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) { // This code has to do with writing files to disk.
func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun, enableDNS bool) ([]*release.Hook, *bytes.Buffer, string, error) {
hs := []*release.Hook{} hs := []*release.Hook{}
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
@ -130,9 +131,13 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
if err != nil { if err != nil {
return hs, b, "", err return hs, b, "", err
} }
files, err2 = engine.RenderWithClient(ch, values, restConfig) e := engine.New(restConfig)
e.EnableDNS = enableDNS
files, err2 = e.Render(ch, values)
} else { } else {
files, err2 = engine.Render(ch, values) var e engine.Engine
e.EnableDNS = enableDNS
files, err2 = e.Render(ch, values)
} }
if err2 != nil { if err2 != nil {

@ -69,6 +69,7 @@ type Install struct {
ChartPathOptions ChartPathOptions
ClientOnly bool ClientOnly bool
Force bool
CreateNamespace bool CreateNamespace bool
DryRun bool DryRun bool
DisableHooks bool DisableHooks bool
@ -96,6 +97,8 @@ type Install struct {
APIVersions chartutil.VersionSet APIVersions chartutil.VersionSet
// Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false // Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false
IsUpgrade bool IsUpgrade bool
// Enable DNS lookups when rendering templates
EnableDNS bool
// Used by helm template to add the release as part of OutputDir path // Used by helm template to add the release as part of OutputDir path
// OutputDir/<ReleaseName> // OutputDir/<ReleaseName>
UseReleaseName bool UseReleaseName bool
@ -256,7 +259,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
rel := i.createRelease(chrt, vals) rel := i.createRelease(chrt, vals)
var manifestDoc *bytes.Buffer var manifestDoc *bytes.Buffer
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun) rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun, i.EnableDNS)
// Even for errors, attach this if available // Even for errors, attach this if available
if manifestDoc != nil { if manifestDoc != nil {
rel.Manifest = manifestDoc.String() rel.Manifest = manifestDoc.String()
@ -372,7 +375,7 @@ func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, t
return return
} }
} else if len(resources) > 0 { } else if len(resources) > 0 {
if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil { if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, i.Force); err != nil {
i.reportToRun(c, rel, err) i.reportToRun(c, rel, err)
return return
} }
@ -456,10 +459,10 @@ func (i *Install) failRelease(rel *release.Release, err error) (*release.Release
// //
// Roughly, this will return an error if name is // Roughly, this will return an error if name is
// //
// - empty // - empty
// - too long // - too long
// - already in use, and not deleted // - already in use, and not deleted
// - used by a deleted release, and i.Replace is false // - used by a deleted release, and i.Replace is false
func (i *Install) availableName() error { func (i *Install) availableName() error {
start := i.ReleaseName start := i.ReleaseName

@ -17,6 +17,10 @@ limitations under the License.
package action package action
import ( import (
"bytes"
"errors"
"helm.sh/helm/v3/pkg/kube"
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
) )
@ -32,6 +36,14 @@ type Status struct {
// only affect print type table. // only affect print type table.
// TODO Helm 4: Remove this flag and output the description by default. // TODO Helm 4: Remove this flag and output the description by default.
ShowDescription bool ShowDescription bool
// ShowResources sets if the resources should be retrieved with the status.
// TODO Helm 4: Remove this flag and output the resources by default.
ShowResources bool
// ShowResourcesTable is used with ShowResources. When true this will cause
// the resulting objects to be retrieved as a kind=table.
ShowResourcesTable bool
} }
// NewStatus creates a new Status object with the given configuration. // NewStatus creates a new Status object with the given configuration.
@ -47,5 +59,37 @@ func (s *Status) Run(name string) (*release.Release, error) {
return nil, err return nil, err
} }
return s.cfg.releaseContent(name, s.Version) if !s.ShowResources {
return s.cfg.releaseContent(name, s.Version)
}
rel, err := s.cfg.releaseContent(name, s.Version)
if err != nil {
return nil, err
}
if kubeClient, ok := s.cfg.KubeClient.(kube.InterfaceResources); ok {
var resources kube.ResourceList
if s.ShowResourcesTable {
resources, err = kubeClient.BuildTable(bytes.NewBufferString(rel.Manifest), false)
if err != nil {
return nil, err
}
} else {
resources, err = s.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), false)
if err != nil {
return nil, err
}
}
resp, err := kubeClient.Get(resources, true)
if err != nil {
return nil, err
}
rel.Info.Resources = resp
return rel, nil
}
return nil, errors.New("unable to get kubeClient with interface InterfaceResources")
} }

@ -103,6 +103,8 @@ type Upgrade struct {
DependencyUpdate bool DependencyUpdate bool
// Lock to control raceconditions when the process receives a SIGTERM // Lock to control raceconditions when the process receives a SIGTERM
Lock sync.Mutex Lock sync.Mutex
// Enable DNS lookups when rendering templates
EnableDNS bool
} }
type resultMessage struct { type resultMessage struct {
@ -231,7 +233,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
return nil, nil, err return nil, nil, err
} }
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun) hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun, u.EnableDNS)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

@ -54,7 +54,7 @@ type Dependency struct {
// loaded. // loaded.
func (d *Dependency) Validate() error { func (d *Dependency) Validate() error {
if d == nil { if d == nil {
return ValidationError("dependency cannot be an empty list") return ValidationError("dependencies must not contain empty or null nodes")
} }
d.Name = sanitizeString(d.Name) d.Name = sanitizeString(d.Name)
d.Version = sanitizeString(d.Version) d.Version = sanitizeString(d.Version)

@ -35,7 +35,7 @@ type Maintainer struct {
// Validate checks valid data and sanitizes string characters. // Validate checks valid data and sanitizes string characters.
func (m *Maintainer) Validate() error { func (m *Maintainer) Validate() error {
if m == nil { if m == nil {
return ValidationError("maintainer cannot be an empty list") return ValidationError("maintainers must not contain empty or null nodes")
} }
m.Name = sanitizeString(m.Name) m.Name = sanitizeString(m.Name)
m.Email = sanitizeString(m.Email) m.Email = sanitizeString(m.Email)

@ -82,7 +82,7 @@ func TestValidate(t *testing.T) {
nil, nil,
}, },
}, },
ValidationError("dependency cannot be an empty list"), ValidationError("dependencies must not contain empty or null nodes"),
}, },
{ {
&Metadata{ &Metadata{
@ -94,7 +94,7 @@ func TestValidate(t *testing.T) {
nil, nil,
}, },
}, },
ValidationError("maintainer cannot be an empty list"), ValidationError("maintainers must not contain empty or null nodes"),
}, },
{ {
&Metadata{APIVersion: "v2", Name: "test", Version: "1.2.3.4"}, &Metadata{APIVersion: "v2", Name: "test", Version: "1.2.3.4"},

@ -62,8 +62,8 @@ func TestDefaultCapabilities(t *testing.T) {
func TestDefaultCapabilitiesHelmVersion(t *testing.T) { func TestDefaultCapabilitiesHelmVersion(t *testing.T) {
hv := DefaultCapabilities.HelmVersion hv := DefaultCapabilities.HelmVersion
if hv.Version != "v3.10" { if hv.Version != "v3.11" {
t.Errorf("Expected default HelmVersion to be v3.10, got %q", hv.Version) t.Errorf("Expected default HelmVersion to be v3.11, got %q", hv.Version)
} }
} }

@ -55,7 +55,13 @@ func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) err
} }
// ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema // ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema
func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) error { func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) (reterr error) {
defer func() {
if r := recover(); r != nil {
reterr = fmt.Errorf("unable to validate schema: %s", r)
}
}()
valuesData, err := yaml.Marshal(values) valuesData, err := yaml.Marshal(values)
if err != nil { if err != nil {
return err return err

@ -38,6 +38,30 @@ func TestValidateAgainstSingleSchema(t *testing.T) {
} }
} }
func TestValidateAgainstInvalidSingleSchema(t *testing.T) {
values, err := ReadValuesFile("./testdata/test-values.yaml")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
schema, err := ioutil.ReadFile("./testdata/test-values-invalid.schema.json")
if err != nil {
t.Fatalf("Error reading YAML file: %s", err)
}
var errString string
if err := ValidateAgainstSingleSchema(values, schema); err == nil {
t.Fatalf("Expected an error, but got nil")
} else {
errString = err.Error()
}
expectedErrString := "unable to validate schema: runtime error: invalid " +
"memory address or nil pointer dereference"
if errString != expectedErrString {
t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString)
}
}
func TestValidateAgainstSingleSchemaNegative(t *testing.T) { func TestValidateAgainstSingleSchemaNegative(t *testing.T) {
values, err := ReadValuesFile("./testdata/test-values-negative.yaml") values, err := ReadValuesFile("./testdata/test-values-negative.yaml")
if err != nil { if err != nil {

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/*Package cli describes the operating environment for the Helm CLI. /*
Package cli describes the operating environment for the Helm CLI.
Helm's environment encapsulates all of the service dependencies Helm has. Helm's environment encapsulates all of the service dependencies Helm has.
These dependencies are expressed as interfaces so that alternate implementations These dependencies are expressed as interfaces so that alternate implementations
@ -33,6 +34,7 @@ import (
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"helm.sh/helm/v3/internal/version"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
) )
@ -120,6 +122,7 @@ func New() *EnvSettings {
config.Wrap(func(rt http.RoundTripper) http.RoundTripper { config.Wrap(func(rt http.RoundTripper) http.RoundTripper {
return &retryingRoundTripper{wrapped: rt} return &retryingRoundTripper{wrapped: rt}
}) })
config.UserAgent = version.GetUserAgent()
return config return config
}, },
} }

@ -23,6 +23,8 @@ import (
"testing" "testing"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"helm.sh/helm/v3/internal/version"
) )
func TestSetNamespace(t *testing.T) { func TestSetNamespace(t *testing.T) {
@ -231,6 +233,21 @@ func TestEnvOrBool(t *testing.T) {
} }
} }
func TestUserAgentHeaderInK8sRESTClientConfig(t *testing.T) {
defer resetEnv()()
settings := New()
restConfig, err := settings.RESTClientGetter().ToRESTConfig()
if err != nil {
t.Fatal(err)
}
expectedUserAgent := version.GetUserAgent()
if restConfig.UserAgent != expectedUserAgent {
t.Errorf("expected User-Agent header %q in K8s REST client config, got %q", expectedUserAgent, restConfig.UserAgent)
}
}
func resetEnv() func() { func resetEnv() func() {
origEnv := os.Environ() origEnv := os.Environ()

@ -40,6 +40,9 @@ func (rt *retryingRoundTripper) roundTrip(req *http.Request, retry int, prevResp
if rtErr != nil { if rtErr != nil {
return resp, rtErr return resp, rtErr
} }
if resp.StatusCode < 500 {
return resp, rtErr
}
if resp.Header.Get("content-type") != "application/json" { if resp.Header.Get("content-type") != "application/json" {
return resp, rtErr return resp, rtErr
} }

@ -29,16 +29,17 @@ import (
"helm.sh/helm/v3/pkg/strvals" "helm.sh/helm/v3/pkg/strvals"
) )
// Options captures the different ways to specify values
type Options struct { type Options struct {
ValueFiles []string ValueFiles []string // -f/--values
StringValues []string StringValues []string // --set-string
Values []string Values []string // --set
FileValues []string FileValues []string // --set-file
JSONValues []string JSONValues []string // --set-json
} }
// MergeValues merges values from files specified via -f/--values and directly // MergeValues merges values from files specified via -f/--values and directly
// via --set, --set-string, or --set-file, marshaling them to YAML // via --set-json, --set, --set-string, or --set-file, marshaling them to YAML
func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) { func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) {
base := map[string]interface{}{} base := map[string]interface{}{}

@ -294,32 +294,13 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
} }
// TODO: Seems that picking first URL is not fully correct // TODO: Seems that picking first URL is not fully correct
u, err = url.Parse(cv.URLs[0]) resolvedURL, err := repo.ResolveReferenceURL(rc.URL, cv.URLs[0])
if err != nil { if err != nil {
return u, errors.Errorf("invalid chart URL format: %s", ref) return u, errors.Errorf("invalid chart URL format: %s", ref)
} }
// If the URL is relative (no scheme), prepend the chart repo's base URL return url.Parse(resolvedURL)
if !u.IsAbs() {
repoURL, err := url.Parse(rc.URL)
if err != nil {
return repoURL, err
}
q := repoURL.Query()
// We need a trailing slash for ResolveReference to work, but make sure there isn't already one
repoURL.RawPath = strings.TrimSuffix(repoURL.RawPath, "/") + "/"
repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/"
u = repoURL.ResolveReference(u)
u.RawQuery = q.Encode()
// TODO add user-agent
if _, err := getter.NewHTTPGetter(getter.WithURL(rc.URL)); err != nil {
return repoURL, err
}
return u, err
}
// TODO add user-agent
return u, nil
} }
// VerifyChart takes a path to a chart archive and a keyring, and verifies the chart. // VerifyChart takes a path to a chart archive and a keyring, and verifies the chart.

@ -42,6 +42,15 @@ type Engine struct {
LintMode bool LintMode bool
// the rest config to connect to the kubernetes api // the rest config to connect to the kubernetes api
config *rest.Config config *rest.Config
// EnableDNS tells the engine to allow DNS lookups when rendering templates
EnableDNS bool
}
// New creates a new instance of Engine using the passed in rest config.
func New(config *rest.Config) Engine {
return Engine{
config: config,
}
} }
// Render takes a chart, optional values, and value overrides, and attempts to render the Go templates. // Render takes a chart, optional values, and value overrides, and attempts to render the Go templates.
@ -189,6 +198,14 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render
funcMap["lookup"] = NewLookupFunction(e.config) funcMap["lookup"] = NewLookupFunction(e.config)
} }
// When DNS lookups are not enabled override the sprig function and return
// an empty string.
if !e.EnableDNS {
funcMap["getHostByName"] = func(name string) string {
return ""
}
}
t.Funcs(funcMap) t.Funcs(funcMap)
} }

@ -18,6 +18,7 @@ package engine
import ( import (
"fmt" "fmt"
"path"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@ -89,6 +90,7 @@ func TestRender(t *testing.T) {
{Name: "templates/test2", Data: []byte("{{.Values.global.callme | lower }}")}, {Name: "templates/test2", Data: []byte("{{.Values.global.callme | lower }}")},
{Name: "templates/test3", Data: []byte("{{.noValue}}")}, {Name: "templates/test3", Data: []byte("{{.noValue}}")},
{Name: "templates/test4", Data: []byte("{{toJson .Values}}")}, {Name: "templates/test4", Data: []byte("{{toJson .Values}}")},
{Name: "templates/test5", Data: []byte("{{getHostByName \"helm.sh\"}}")},
}, },
Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"}, Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"},
} }
@ -117,6 +119,7 @@ func TestRender(t *testing.T) {
"moby/templates/test2": "ishmael", "moby/templates/test2": "ishmael",
"moby/templates/test3": "", "moby/templates/test3": "",
"moby/templates/test4": `{"global":{"callme":"Ishmael"},"inner":"inn","outer":"spouter"}`, "moby/templates/test4": `{"global":{"callme":"Ishmael"},"inner":"inn","outer":"spouter"}`,
"moby/templates/test5": "",
} }
for name, data := range expect { for name, data := range expect {
@ -200,6 +203,42 @@ func TestRenderInternals(t *testing.T) {
} }
} }
func TestRenderWIthDNS(t *testing.T) {
c := &chart.Chart{
Metadata: &chart.Metadata{
Name: "moby",
Version: "1.2.3",
},
Templates: []*chart.File{
{Name: "templates/test1", Data: []byte("{{getHostByName \"helm.sh\"}}")},
},
Values: map[string]interface{}{},
}
vals := map[string]interface{}{
"Values": map[string]interface{}{},
}
v, err := chartutil.CoalesceValues(c, vals)
if err != nil {
t.Fatalf("Failed to coalesce values: %s", err)
}
var e Engine
e.EnableDNS = true
out, err := e.Render(c, v)
if err != nil {
t.Errorf("Failed to render templates: %s", err)
}
for _, val := range c.Templates {
fp := path.Join("moby", val.Name)
if out[fp] == "" {
t.Errorf("Expected IP address, got %q", out[fp])
}
}
}
func TestParallelRenderInternals(t *testing.T) { func TestParallelRenderInternals(t *testing.T) {
// Make sure that we can use one Engine to run parallel template renders. // Make sure that we can use one Engine to run parallel template renders.
e := new(Engine) e := new(Engine)

@ -155,9 +155,8 @@ func TestHTTPGetter(t *testing.T) {
func TestDownload(t *testing.T) { func TestDownload(t *testing.T) {
expect := "Call me Ishmael" expect := "Call me Ishmael"
expectedUserAgent := "I am Groot"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defaultUserAgent := "Helm/" + strings.TrimPrefix(version.GetVersion(), "v") defaultUserAgent := version.GetUserAgent()
if r.UserAgent() != defaultUserAgent { if r.UserAgent() != defaultUserAgent {
t.Errorf("Expected '%s', got '%s'", defaultUserAgent, r.UserAgent()) t.Errorf("Expected '%s', got '%s'", defaultUserAgent, r.UserAgent())
} }
@ -179,6 +178,7 @@ func TestDownload(t *testing.T) {
} }
// test with http server // test with http server
const expectedUserAgent = "I am Groot"
basicAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { basicAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth() username, password, ok := r.BasicAuth()
if !ok || username != "username" || password != "password" { if !ok || username != "username" || password != "password" {

@ -17,12 +17,14 @@ limitations under the License.
package kube // import "helm.sh/helm/v3/pkg/kube" package kube // import "helm.sh/helm/v3/pkg/kube"
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -38,7 +40,9 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
@ -47,6 +51,7 @@ import (
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
cachetools "k8s.io/client-go/tools/cache" cachetools "k8s.io/client-go/tools/cache"
watchtools "k8s.io/client-go/tools/watch" watchtools "k8s.io/client-go/tools/watch"
cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdutil "k8s.io/kubectl/pkg/cmd/util"
@ -132,6 +137,141 @@ func (c *Client) Create(resources ResourceList) (*Result, error) {
return &Result{Created: resources}, nil return &Result{Created: resources}, nil
} }
func transformRequests(req *rest.Request) {
tableParam := strings.Join([]string{
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1.SchemeGroupVersion.Version, metav1.GroupName),
fmt.Sprintf("application/json;as=Table;v=%s;g=%s", metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName),
"application/json",
}, ",")
req.SetHeader("Accept", tableParam)
// if sorting, ensure we receive the full object in order to introspect its fields via jsonpath
req.Param("includeObject", "Object")
}
// Get retrieves the resource objects supplied. If related is set to true the
// related pods are fetched as well. If the passed in resources are a table kind
// the related resources will also be fetched as kind=table.
func (c *Client) Get(resources ResourceList, related bool) (map[string][]runtime.Object, error) {
buf := new(bytes.Buffer)
objs := make(map[string][]runtime.Object)
podSelectors := []map[string]string{}
err := resources.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
}
gvk := info.ResourceMapping().GroupVersionKind
vk := gvk.Version + "/" + gvk.Kind
obj, err := getResource(info)
if err != nil {
fmt.Fprintf(buf, "Get resource %s failed, err:%v\n", info.Name, err)
} else {
objs[vk] = append(objs[vk], obj)
// Only fetch related pods if they are requested
if related {
// Discover if the existing object is a table. If it is, request
// the pods as Tables. Otherwise request them normally.
objGVK := obj.GetObjectKind().GroupVersionKind()
var isTable bool
if objGVK.Kind == "Table" {
isTable = true
}
objs, err = c.getSelectRelationPod(info, objs, isTable, &podSelectors)
if err != nil {
c.Log("Warning: get the relation pod is failed, err:%s", err.Error())
}
}
}
return nil
})
if err != nil {
return nil, err
}
return objs, nil
}
func (c *Client) getSelectRelationPod(info *resource.Info, objs map[string][]runtime.Object, table bool, podSelectors *[]map[string]string) (map[string][]runtime.Object, error) {
if info == nil {
return objs, nil
}
c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name)
selector, ok, _ := getSelectorFromObject(info.Object)
if !ok {
return objs, nil
}
for index := range *podSelectors {
if reflect.DeepEqual((*podSelectors)[index], selector) {
// check if pods for selectors are already added. This avoids duplicate printing of pods
return objs, nil
}
}
*podSelectors = append(*podSelectors, selector)
var infos []*resource.Info
var err error
if table {
infos, err = c.Factory.NewBuilder().
Unstructured().
ContinueOnError().
NamespaceParam(info.Namespace).
DefaultNamespace().
ResourceTypes("pods").
LabelSelector(labels.Set(selector).AsSelector().String()).
TransformRequests(transformRequests).
Do().Infos()
if err != nil {
return objs, err
}
} else {
infos, err = c.Factory.NewBuilder().
Unstructured().
ContinueOnError().
NamespaceParam(info.Namespace).
DefaultNamespace().
ResourceTypes("pods").
LabelSelector(labels.Set(selector).AsSelector().String()).
Do().Infos()
if err != nil {
return objs, err
}
}
vk := "v1/Pod(related)"
for _, info := range infos {
objs[vk] = append(objs[vk], info.Object)
}
return objs, nil
}
func getSelectorFromObject(obj runtime.Object) (map[string]string, bool, error) {
typed := obj.(*unstructured.Unstructured)
kind := typed.Object["kind"]
switch kind {
case "ReplicaSet", "Deployment", "StatefulSet", "DaemonSet", "Job":
return unstructured.NestedStringMap(typed.Object, "spec", "selector", "matchLabels")
case "ReplicationController":
return unstructured.NestedStringMap(typed.Object, "spec", "selector")
default:
return nil, false, nil
}
}
func getResource(info *resource.Info) (runtime.Object, error) {
obj, err := resource.NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name)
if err != nil {
return nil, err
}
return obj, nil
}
// Wait waits up to the given timeout for the specified resources to be ready. // Wait waits up to the given timeout for the specified resources to be ready.
func (c *Client) Wait(resources ResourceList, timeout time.Duration) error { func (c *Client) Wait(resources ResourceList, timeout time.Duration) error {
cs, err := c.getKubeClient() cs, err := c.getKubeClient()
@ -215,6 +355,33 @@ func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) {
return result, scrubValidationError(err) return result, scrubValidationError(err)
} }
// BuildTable validates for Kubernetes objects and returns unstructured infos.
// The returned kind is a Table.
func (c *Client) BuildTable(reader io.Reader, validate bool) (ResourceList, error) {
validationDirective := metav1.FieldValidationIgnore
if validate {
validationDirective = metav1.FieldValidationStrict
}
dynamicClient, err := c.Factory.DynamicClient()
if err != nil {
return nil, err
}
verifier := resource.NewQueryParamVerifier(dynamicClient, c.Factory.OpenAPIGetter(), resource.QueryParamFieldValidation)
schema, err := c.Factory.Validator(validationDirective, verifier)
if err != nil {
return nil, err
}
result, err := c.newBuilder().
Unstructured().
Schema(schema).
Stream(reader, "").
TransformRequests(transformRequests).
Do().Infos()
return result, scrubValidationError(err)
}
// Update takes the current list of objects and target list of objects and // Update takes the current list of objects and target list of objects and
// creates resources that don't already exist, updates resources that have been // creates resources that don't already exist, updates resources that have been
// modified in the target configuration, and deletes resources from the current // modified in the target configuration, and deletes resources from the current
@ -352,10 +519,10 @@ func (c *Client) watchTimeout(t time.Duration) func(*resource.Info) error {
// For most kinds, it checks to see if the resource is marked as Added or Modified // For most kinds, it checks to see if the resource is marked as Added or Modified
// by the Kubernetes event stream. For some kinds, it does more: // by the Kubernetes event stream. For some kinds, it does more:
// //
// - Jobs: A job is marked "Ready" when it has successfully completed. This is // - Jobs: A job is marked "Ready" when it has successfully completed. This is
// ascertained by watching the Status fields in a job's output. // ascertained by watching the Status fields in a job's output.
// - Pods: A pod is marked "Ready" when it has successfully completed. This is // - Pods: A pod is marked "Ready" when it has successfully completed. This is
// ascertained by watching the status.phase field in a pod's output. // ascertained by watching the status.phase field in a pod's output.
// //
// Handling for other kinds will be added as necessary. // Handling for other kinds will be added as necessary.
func (c *Client) WatchUntilReady(resources ResourceList, timeout time.Duration) error { func (c *Client) WatchUntilReady(resources ResourceList, timeout time.Duration) error {

@ -253,6 +253,45 @@ func TestBuild(t *testing.T) {
} }
} }
func TestBuildTable(t *testing.T) {
tests := []struct {
name string
namespace string
reader io.Reader
count int
err bool
}{
{
name: "Valid input",
namespace: "test",
reader: strings.NewReader(guestbookManifest),
count: 6,
}, {
name: "Valid input, deploying resources into different namespaces",
namespace: "test",
reader: strings.NewReader(namespacedGuestbookManifest),
count: 1,
},
}
c := newTestClient(t)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test for an invalid manifest
infos, err := c.BuildTable(tt.reader, false)
if err != nil && !tt.err {
t.Errorf("Got error message when no error should have occurred: %v", err)
} else if err != nil && strings.Contains(err.Error(), "--validate=false") {
t.Error("error message was not scrubbed")
}
if len(infos) != tt.count {
t.Errorf("expected %d result objects, got %d", tt.count, len(infos))
}
})
}
}
func TestPerform(t *testing.T) { func TestPerform(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -402,7 +441,7 @@ spec:
spec: spec:
containers: containers:
- name: master - name: master
image: k8s.gcr.io/redis:e2e # or just image: redis image: registry.k8s.io/redis:e2e # or just image: redis
resources: resources:
requests: requests:
cpu: 100m cpu: 100m

@ -22,6 +22,7 @@ import (
"time" "time"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/kube"
@ -33,11 +34,13 @@ import (
type FailingKubeClient struct { type FailingKubeClient struct {
PrintingKubeClient PrintingKubeClient
CreateError error CreateError error
GetError error
WaitError error WaitError error
DeleteError error DeleteError error
WatchUntilReadyError error WatchUntilReadyError error
UpdateError error UpdateError error
BuildError error BuildError error
BuildTableError error
BuildUnstructuredError error BuildUnstructuredError error
WaitAndGetCompletedPodPhaseError error WaitAndGetCompletedPodPhaseError error
WaitDuration time.Duration WaitDuration time.Duration
@ -51,6 +54,14 @@ func (f *FailingKubeClient) Create(resources kube.ResourceList) (*kube.Result, e
return f.PrintingKubeClient.Create(resources) return f.PrintingKubeClient.Create(resources)
} }
// Get returns the configured error if set or prints
func (f *FailingKubeClient) Get(resources kube.ResourceList, related bool) (map[string][]runtime.Object, error) {
if f.GetError != nil {
return nil, f.GetError
}
return f.PrintingKubeClient.Get(resources, related)
}
// Waits the amount of time defined on f.WaitDuration, then returns the configured error if set or prints. // Waits the amount of time defined on f.WaitDuration, then returns the configured error if set or prints.
func (f *FailingKubeClient) Wait(resources kube.ResourceList, d time.Duration) error { func (f *FailingKubeClient) Wait(resources kube.ResourceList, d time.Duration) error {
time.Sleep(f.WaitDuration) time.Sleep(f.WaitDuration)
@ -108,6 +119,14 @@ func (f *FailingKubeClient) Build(r io.Reader, _ bool) (kube.ResourceList, error
return f.PrintingKubeClient.Build(r, false) return f.PrintingKubeClient.Build(r, false)
} }
// BuildTable returns the configured error if set or prints
func (f *FailingKubeClient) BuildTable(r io.Reader, _ bool) (kube.ResourceList, error) {
if f.BuildTableError != nil {
return []*resource.Info{}, f.BuildTableError
}
return f.PrintingKubeClient.BuildTable(r, false)
}
// WaitAndGetCompletedPodPhase returns the configured error if set or prints // WaitAndGetCompletedPodPhase returns the configured error if set or prints
func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duration) (v1.PodPhase, error) { func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duration) (v1.PodPhase, error) {
if f.WaitAndGetCompletedPodPhaseError != nil { if f.WaitAndGetCompletedPodPhaseError != nil {

@ -22,6 +22,7 @@ import (
"time" "time"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/v3/pkg/kube" "helm.sh/helm/v3/pkg/kube"
@ -47,6 +48,14 @@ func (p *PrintingKubeClient) Create(resources kube.ResourceList) (*kube.Result,
return &kube.Result{Created: resources}, nil return &kube.Result{Created: resources}, nil
} }
func (p *PrintingKubeClient) Get(resources kube.ResourceList, related bool) (map[string][]runtime.Object, error) {
_, err := io.Copy(p.Out, bufferize(resources))
if err != nil {
return nil, err
}
return make(map[string][]runtime.Object), nil
}
func (p *PrintingKubeClient) Wait(resources kube.ResourceList, _ time.Duration) error { func (p *PrintingKubeClient) Wait(resources kube.ResourceList, _ time.Duration) error {
_, err := io.Copy(p.Out, bufferize(resources)) _, err := io.Copy(p.Out, bufferize(resources))
return err return err
@ -96,6 +105,11 @@ func (p *PrintingKubeClient) Build(_ io.Reader, _ bool) (kube.ResourceList, erro
return []*resource.Info{}, nil return []*resource.Info{}, nil
} }
// BuildTable implements KubeClient BuildTable.
func (p *PrintingKubeClient) BuildTable(_ io.Reader, _ bool) (kube.ResourceList, error) {
return []*resource.Info{}, nil
}
// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase. // WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase.
func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Duration) (v1.PodPhase, error) { func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Duration) (v1.PodPhase, error) {
return v1.PodSucceeded, nil return v1.PodSucceeded, nil

@ -21,6 +21,7 @@ import (
"time" "time"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
) )
// Interface represents a client capable of communicating with the Kubernetes API. // Interface represents a client capable of communicating with the Kubernetes API.
@ -78,5 +79,28 @@ type InterfaceExt interface {
WaitForDelete(resources ResourceList, timeout time.Duration) error WaitForDelete(resources ResourceList, timeout time.Duration) error
} }
// InterfaceResources is introduced to avoid breaking backwards compatibility for Interface implementers.
//
// TODO Helm 4: Remove InterfaceResources and integrate its method(s) into the Interface.
type InterfaceResources interface {
// Get details of deployed resources.
// The first argument is a list of resources to get. The second argument
// specifies if related pods should be fetched. For example, the pods being
// managed by a deployment.
Get(resources ResourceList, related bool) (map[string][]runtime.Object, error)
// BuildTable creates a resource list from a Reader. This differs from
// Interface.Build() in that a table kind is returned. A table is useful
// if you want to use a printer to display the information.
//
// Reader must contain a YAML stream (one or more YAML documents separated
// by "\n---\n")
//
// Validates against OpenAPI schema if validate is true.
// TODO Helm 4: Integrate into Build with an argument
BuildTable(reader io.Reader, validate bool) (ResourceList, error)
}
var _ Interface = (*Client)(nil) var _ Interface = (*Client)(nil)
var _ InterfaceExt = (*Client)(nil) var _ InterfaceExt = (*Client)(nil)
var _ InterfaceResources = (*Client)(nil)

@ -291,7 +291,7 @@ func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool {
c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", ds.Namespace, ds.Name, ds.Status.UpdatedNumberScheduled, ds.Status.DesiredNumberScheduled) c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", ds.Namespace, ds.Name, ds.Status.UpdatedNumberScheduled, ds.Status.DesiredNumberScheduled)
return false return false
} }
maxUnavailable, err := intstr.GetValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true) maxUnavailable, err := intstr.GetScaledValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true)
if err != nil { if err != nil {
// If for some reason the value is invalid, set max unavailable to the // If for some reason the value is invalid, set max unavailable to the
// number of desired replicas. This is the same behavior as the // number of desired replicas. This is the same behavior as the

@ -22,6 +22,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// ErrPluginNotAFolder indicates that the plugin path is not a folder.
var ErrPluginNotAFolder = errors.New("expected plugin to be a folder")
// LocalInstaller installs plugins from the filesystem. // LocalInstaller installs plugins from the filesystem.
type LocalInstaller struct { type LocalInstaller struct {
base base
@ -43,6 +46,14 @@ func NewLocalInstaller(source string) (*LocalInstaller, error) {
// //
// Implements Installer. // Implements Installer.
func (i *LocalInstaller) Install() error { func (i *LocalInstaller) Install() error {
stat, err := os.Stat(i.Source)
if err != nil {
return err
}
if !stat.IsDir() {
return ErrPluginNotAFolder
}
if !isPlugin(i.Source) { if !isPlugin(i.Source) {
return ErrMissingMetadata return ErrMissingMetadata
} }

@ -48,3 +48,19 @@ func TestLocalInstaller(t *testing.T) {
} }
defer os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm defer os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm
} }
func TestLocalInstallerNotAFolder(t *testing.T) {
source := "../testdata/plugdir/good/echo/plugin.yaml"
i, err := NewForSource(source, "")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
err = Install(i)
if err == nil {
t.Fatal("expected error")
}
if err != ErrPluginNotAFolder {
t.Fatalf("expected error to equal: %q", err)
}
}

@ -16,6 +16,8 @@ limitations under the License.
package release package release
import ( import (
"k8s.io/apimachinery/pkg/runtime"
"helm.sh/helm/v3/pkg/time" "helm.sh/helm/v3/pkg/time"
) )
@ -33,4 +35,6 @@ type Info struct {
Status Status `json:"status,omitempty"` Status Status `json:"status,omitempty"`
// Contains the rendered templates/NOTES.txt if available // Contains the rendered templates/NOTES.txt if available
Notes string `json:"notes,omitempty"` Notes string `json:"notes,omitempty"`
// Contains the deployed resources information
Resources map[string][]runtime.Object `json:"resources,omitempty"`
} }

@ -25,7 +25,6 @@ import (
"log" "log"
"net/url" "net/url"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -116,15 +115,11 @@ func (r *ChartRepository) Load() error {
// DownloadIndexFile fetches the index from a repository. // DownloadIndexFile fetches the index from a repository.
func (r *ChartRepository) DownloadIndexFile() (string, error) { func (r *ChartRepository) DownloadIndexFile() (string, error) {
parsedURL, err := url.Parse(r.Config.URL) indexURL, err := ResolveReferenceURL(r.Config.URL, "index.yaml")
if err != nil { if err != nil {
return "", err return "", err
} }
parsedURL.RawPath = path.Join(parsedURL.RawPath, "index.yaml")
parsedURL.Path = path.Join(parsedURL.Path, "index.yaml")
indexURL := parsedURL.String()
// TODO add user-agent
resp, err := r.Client.Get(indexURL, resp, err := r.Client.Get(indexURL,
getter.WithURL(r.Config.URL), getter.WithURL(r.Config.URL),
getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify), getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify),
@ -219,7 +214,7 @@ func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion
// but it also receives credentials and TLS verify flag for the chart repository. // but it also receives credentials and TLS verify flag for the chart repository.
// TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL. // TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL.
func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) { func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) {
return FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, false, getters) return FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, insecureSkipTLSverify, false, getters)
} }
// FindChartInAuthAndTLSAndPassRepoURL finds chart in chart repository pointed by repoURL // FindChartInAuthAndTLSAndPassRepoURL finds chart in chart repository pointed by repoURL
@ -290,18 +285,27 @@ func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName,
// ResolveReferenceURL resolves refURL relative to baseURL. // ResolveReferenceURL resolves refURL relative to baseURL.
// If refURL is absolute, it simply returns refURL. // If refURL is absolute, it simply returns refURL.
func ResolveReferenceURL(baseURL, refURL string) (string, error) { func ResolveReferenceURL(baseURL, refURL string) (string, error) {
// We need a trailing slash for ResolveReference to work, but make sure there isn't already one parsedRefURL, err := url.Parse(refURL)
parsedBaseURL, err := url.Parse(strings.TrimSuffix(baseURL, "/") + "/")
if err != nil { if err != nil {
return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL) return "", errors.Wrapf(err, "failed to parse %s as URL", refURL)
} }
parsedRefURL, err := url.Parse(refURL) if parsedRefURL.IsAbs() {
return refURL, nil
}
parsedBaseURL, err := url.Parse(baseURL)
if err != nil { if err != nil {
return "", errors.Wrapf(err, "failed to parse %s as URL", refURL) return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL)
} }
return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil // We need a trailing slash for ResolveReference to work, but make sure there isn't already one
parsedBaseURL.RawPath = strings.TrimSuffix(parsedBaseURL.RawPath, "/") + "/"
parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/"
resolvedURL := parsedBaseURL.ResolveReference(parsedRefURL)
resolvedURL.RawQuery = parsedBaseURL.RawQuery
return resolvedURL.String(), nil
} }
func (e *Entry) String() string { func (e *Entry) String() string {

@ -385,35 +385,21 @@ func TestErrorFindChartInRepoURL(t *testing.T) {
} }
func TestResolveReferenceURL(t *testing.T) { func TestResolveReferenceURL(t *testing.T) {
chartURL, err := ResolveReferenceURL("http://localhost:8123/charts/", "nginx-0.2.0.tgz") for _, tt := range []struct {
if err != nil { baseURL, refURL, chartURL string
t.Errorf("%s", err) }{
} {"http://localhost:8123/charts/", "nginx-0.2.0.tgz", "http://localhost:8123/charts/nginx-0.2.0.tgz"},
if chartURL != "http://localhost:8123/charts/nginx-0.2.0.tgz" { {"http://localhost:8123/charts-with-no-trailing-slash", "nginx-0.2.0.tgz", "http://localhost:8123/charts-with-no-trailing-slash/nginx-0.2.0.tgz"},
t.Errorf("%s", chartURL) {"http://localhost:8123", "https://charts.helm.sh/stable/nginx-0.2.0.tgz", "https://charts.helm.sh/stable/nginx-0.2.0.tgz"},
} {"http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz", "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz"},
{"http://localhost:8123/charts?with=queryparameter", "nginx-0.2.0.tgz", "http://localhost:8123/charts/nginx-0.2.0.tgz?with=queryparameter"},
chartURL, err = ResolveReferenceURL("http://localhost:8123/charts-with-no-trailing-slash", "nginx-0.2.0.tgz") } {
if err != nil { chartURL, err := ResolveReferenceURL(tt.baseURL, tt.refURL)
t.Errorf("%s", err) if err != nil {
} t.Errorf("unexpected error in ResolveReferenceURL(%q, %q): %s", tt.baseURL, tt.refURL, err)
if chartURL != "http://localhost:8123/charts-with-no-trailing-slash/nginx-0.2.0.tgz" { }
t.Errorf("%s", chartURL) if chartURL != tt.chartURL {
} t.Errorf("expected ResolveReferenceURL(%q, %q) to equal %q, got %q", tt.baseURL, tt.refURL, tt.chartURL, chartURL)
}
chartURL, err = ResolveReferenceURL("http://localhost:8123", "https://charts.helm.sh/stable/nginx-0.2.0.tgz")
if err != nil {
t.Errorf("%s", err)
}
if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" {
t.Errorf("%s", chartURL)
}
chartURL, err = ResolveReferenceURL("http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz")
if err != nil {
t.Errorf("%s", err)
}
if chartURL != "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz" {
t.Errorf("%s", chartURL)
} }
} }

@ -118,6 +118,10 @@ func LoadIndexFile(path string) (*IndexFile, error) {
// MustAdd adds a file to the index // MustAdd adds a file to the index
// This can leave the index in an unsorted state // This can leave the index in an unsorted state
func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) error { func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) error {
if i.Entries == nil {
return errors.New("entries not initialized")
}
if md.APIVersion == "" { if md.APIVersion == "" {
md.APIVersion = chart.APIVersionV1 md.APIVersion = chart.APIVersionV1
} }
@ -339,6 +343,10 @@ func loadIndex(data []byte, source string) (*IndexFile, error) {
for name, cvs := range i.Entries { for name, cvs := range i.Entries {
for idx := len(cvs) - 1; idx >= 0; idx-- { for idx := len(cvs) - 1; idx >= 0; idx-- {
if cvs[idx] == nil {
log.Printf("skipping loading invalid entry for chart %q from %s: empty entry", name, source)
continue
}
if cvs[idx].APIVersion == "" { if cvs[idx].APIVersion == "" {
cvs[idx].APIVersion = chart.APIVersionV1 cvs[idx].APIVersion = chart.APIVersionV1
} }

@ -59,6 +59,15 @@ entries:
version: 1.0.0 version: 1.0.0
home: https://github.com/something home: https://github.com/something
digest: "sha256:1234567890abcdef" digest: "sha256:1234567890abcdef"
`
indexWithEmptyEntry = `
apiVersion: v1
entries:
grafana:
- apiVersion: v2
name: grafana
foo:
-
` `
) )
@ -152,6 +161,12 @@ func TestLoadIndex_Duplicates(t *testing.T) {
} }
} }
func TestLoadIndex_EmptyEntry(t *testing.T) {
if _, err := loadIndex([]byte(indexWithEmptyEntry), "indexWithEmptyEntry"); err != nil {
t.Errorf("unexpected error: %s", err)
}
}
func TestLoadIndex_Empty(t *testing.T) { func TestLoadIndex_Empty(t *testing.T) {
if _, err := loadIndex([]byte(""), "indexWithEmpty"); err == nil { if _, err := loadIndex([]byte(""), "indexWithEmpty"); err == nil {
t.Errorf("Expected an error when index.yaml is empty.") t.Errorf("Expected an error when index.yaml is empty.")
@ -526,3 +541,21 @@ func TestIndexWrite(t *testing.T) {
t.Fatal("Index files doesn't contain expected content") t.Fatal("Index files doesn't contain expected content")
} }
} }
func TestAddFileIndexEntriesNil(t *testing.T) {
i := NewIndexFile()
i.APIVersion = chart.APIVersionV1
i.Entries = nil
for _, x := range []struct {
md *chart.Metadata
filename string
baseURL string
digest string
}{
{&chart.Metadata{APIVersion: "v2", Name: " ", Version: "8033-5.apinie+s.r"}, "setter-0.1.9+beta.tgz", "http://example.com/charts", "sha256:1234567890abc"},
} {
if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err == nil {
t.Errorf("expected err to be non-nil when entries not initialized")
}
}
}

@ -100,6 +100,9 @@ func (r *File) Remove(name string) bool {
cp := []*Entry{} cp := []*Entry{}
found := false found := false
for _, rf := range r.Repositories { for _, rf := range r.Repositories {
if rf == nil {
continue
}
if rf.Name == name { if rf.Name == name {
found = true found = true
continue continue

@ -225,3 +225,34 @@ func TestRepoNotExists(t *testing.T) {
t.Errorf("expected prompt `couldn't load repositories file`") t.Errorf("expected prompt `couldn't load repositories file`")
} }
} }
func TestRemoveRepositoryInvalidEntries(t *testing.T) {
sampleRepository := NewFile()
sampleRepository.Add(
&Entry{
Name: "stable",
URL: "https://example.com/stable/charts",
},
&Entry{
Name: "incubator",
URL: "https://example.com/incubator",
},
&Entry{},
nil,
&Entry{
Name: "test",
URL: "https://example.com/test",
},
)
removeRepository := "stable"
found := sampleRepository.Remove(removeRepository)
if !found {
t.Errorf("expected repository %s not found", removeRepository)
}
found = sampleRepository.Has(removeRepository)
if found {
t.Errorf("repository %s not deleted", removeRepository)
}
}

@ -36,6 +36,10 @@ var ErrNotList = errors.New("not a list")
// The default value 65536 = 1024 * 64 // The default value 65536 = 1024 * 64
var MaxIndex = 65536 var MaxIndex = 65536
// MaxNestedNameLevel is the maximum level of nesting for a value name that
// will be allowed.
var MaxNestedNameLevel = 30
// ToYAML takes a string of arguments and converts to a YAML document. // ToYAML takes a string of arguments and converts to a YAML document.
func ToYAML(s string) (string, error) { func ToYAML(s string) (string, error) {
m, err := Parse(s) m, err := Parse(s)
@ -155,7 +159,7 @@ func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesVa
func (t *parser) parse() error { func (t *parser) parse() error {
for { for {
err := t.key(t.data) err := t.key(t.data, 0)
if err == nil { if err == nil {
continue continue
} }
@ -174,7 +178,7 @@ func runeSet(r []rune) map[rune]bool {
return s return s
} }
func (t *parser) key(data map[string]interface{}) (reterr error) { func (t *parser) key(data map[string]interface{}, nestedNameLevel int) (reterr error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
reterr = fmt.Errorf("unable to parse key: %s", r) reterr = fmt.Errorf("unable to parse key: %s", r)
@ -204,7 +208,7 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
} }
// Now we need to get the value after the ]. // Now we need to get the value after the ].
list, err = t.listItem(list, i) list, err = t.listItem(list, i, nestedNameLevel)
set(data, kk, list) set(data, kk, list)
return err return err
case last == '=': case last == '=':
@ -261,6 +265,12 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
set(data, string(k), "") set(data, string(k), "")
return errors.Errorf("key %q has no value (cannot end with ,)", string(k)) return errors.Errorf("key %q has no value (cannot end with ,)", string(k))
case last == '.': case last == '.':
// Check value name is within the maximum nested name level
nestedNameLevel++
if nestedNameLevel > MaxNestedNameLevel {
return fmt.Errorf("value name nested level is greater than maximum supported nested level of %d", MaxNestedNameLevel)
}
// First, create or find the target map. // First, create or find the target map.
inner := map[string]interface{}{} inner := map[string]interface{}{}
if _, ok := data[string(k)]; ok { if _, ok := data[string(k)]; ok {
@ -268,11 +278,13 @@ func (t *parser) key(data map[string]interface{}) (reterr error) {
} }
// Recurse // Recurse
e := t.key(inner) e := t.key(inner, nestedNameLevel)
if len(inner) == 0 { if e == nil && len(inner) == 0 {
return errors.Errorf("key map %q has no value", string(k)) return errors.Errorf("key map %q has no value", string(k))
} }
set(data, string(k), inner) if len(inner) != 0 {
set(data, string(k), inner)
}
return e return e
} }
} }
@ -322,7 +334,7 @@ func (t *parser) keyIndex() (int, error) {
return strconv.Atoi(string(v)) return strconv.Atoi(string(v))
} }
func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { func (t *parser) listItem(list []interface{}, i, nestedNameLevel int) ([]interface{}, error) {
if i < 0 { if i < 0 {
return list, fmt.Errorf("negative %d index not allowed", i) return list, fmt.Errorf("negative %d index not allowed", i)
} }
@ -395,7 +407,7 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
} }
} }
// Now we need to get the value after the ]. // Now we need to get the value after the ].
list2, err := t.listItem(crtList, nextI) list2, err := t.listItem(crtList, nextI, nestedNameLevel)
if err != nil { if err != nil {
return list, err return list, err
} }
@ -414,7 +426,7 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
} }
// Recurse // Recurse
e := t.key(inner) e := t.key(inner, nestedNameLevel)
if e != nil { if e != nil {
return list, e return list, e
} }

@ -16,6 +16,7 @@ limitations under the License.
package strvals package strvals
import ( import (
"fmt"
"testing" "testing"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@ -754,3 +755,64 @@ func TestToYAML(t *testing.T) {
t.Errorf("Expected %q, got %q", expect, o) t.Errorf("Expected %q, got %q", expect, o)
} }
} }
func TestParseSetNestedLevels(t *testing.T) {
var keyMultipleNestedLevels string
for i := 1; i <= MaxNestedNameLevel+2; i++ {
tmpStr := fmt.Sprintf("name%d", i)
if i <= MaxNestedNameLevel+1 {
tmpStr = tmpStr + "."
}
keyMultipleNestedLevels += tmpStr
}
tests := []struct {
str string
expect map[string]interface{}
err bool
errStr string
}{
{
"outer.middle.inner=value",
map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}},
false,
"",
},
{
str: keyMultipleNestedLevels + "=value",
err: true,
errStr: fmt.Sprintf("value name nested level is greater than maximum supported nested level of %d",
MaxNestedNameLevel),
},
}
for _, tt := range tests {
got, err := Parse(tt.str)
if err != nil {
if tt.err {
if tt.errStr != "" {
if err.Error() != tt.errStr {
t.Errorf("Expected error: %s. Got error: %s", tt.errStr, err.Error())
}
}
continue
}
t.Fatalf("%s: %s", tt.str, err)
}
if tt.err {
t.Errorf("%s: Expected error. Got nil", tt.str)
}
y1, err := yaml.Marshal(tt.expect)
if err != nil {
t.Fatal(err)
}
y2, err := yaml.Marshal(got)
if err != nil {
t.Fatalf("Error serializing parsed value: %s", err)
}
if string(y1) != string(y2) {
t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2)
}
}
}

@ -37,7 +37,7 @@ generate_cover_data() {
} }
push_to_coveralls() { push_to_coveralls() {
goveralls -coverprofile="${profile}" -service=circle-ci goveralls -coverprofile="${profile}" -service=github
} }
generate_cover_data generate_cover_data

@ -29,6 +29,7 @@ HAS_CURL="$(type "curl" &> /dev/null && echo true || echo false)"
HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)" HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)"
HAS_OPENSSL="$(type "openssl" &> /dev/null && echo true || echo false)" HAS_OPENSSL="$(type "openssl" &> /dev/null && echo true || echo false)"
HAS_GPG="$(type "gpg" &> /dev/null && echo true || echo false)" HAS_GPG="$(type "gpg" &> /dev/null && echo true || echo false)"
HAS_GIT="$(type "git" &> /dev/null && echo true || echo false)"
# initArch discovers the architecture for this system. # initArch discovers the architecture for this system.
initArch() { initArch() {
@ -97,6 +98,10 @@ verifySupported() {
exit 1 exit 1
fi fi
fi fi
if [ "${HAS_GIT}" != "true" ]; then
echo "[WARNING] Could not find git. It is required for plugin installation."
fi
} }
# checkDesiredVersion checks if the desired version is available. # checkDesiredVersion checks if the desired version is available.

Loading…
Cancel
Save