mirror of
https://github.com/helm/helm.git
synced 2026-08-05 23:51:03 +00:00
Merge branch 'main' into feat/reset-then-reuse-flag
Signed-off-by: Joe Julian <me@joejulian.name>
This commit is contained in:
@@ -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,40 +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
|
||||
|
||||
jobs:
|
||||
build:
|
||||
working_directory: ~/helm.sh/helm
|
||||
docker:
|
||||
- image: circleci/golang:1.16
|
||||
|
||||
auth:
|
||||
username: $DOCKER_USER
|
||||
password: $DOCKER_PASS
|
||||
|
||||
environment:
|
||||
GOCACHE: "/tmp/go/cache"
|
||||
GOLANGCI_LINT_VERSION: "1.36.0"
|
||||
- image: cimg/go:1.18
|
||||
|
||||
steps:
|
||||
- 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
|
||||
- deploy:
|
||||
name: deploy
|
||||
command: .circleci/deploy.sh
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build:
|
||||
jobs:
|
||||
- build:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
|
||||
@@ -1,49 +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/ stretch 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"
|
||||
az storage blob upload-batch -s _dist/ -d "$AZURE_STORAGE_CONTAINER_NAME" --pattern 'helm-*' --connection-string "$AZURE_STORAGE_CONNECTION_STRING"
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -4,4 +4,8 @@ updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "daily"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
36
.github/workflows/build-test.yml
vendored
Normal file
36
.github/workflows/build-test.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: build-test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'release-**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # pin@4.0.1
|
||||
with:
|
||||
go-version: '1.20'
|
||||
- name: Install golangci-lint
|
||||
run: |
|
||||
curl -sSLO https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
|
||||
shasum -a 256 golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz | grep "^$GOLANGCI_LINT_SHA256 " > /dev/null
|
||||
tar -xf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.tar.gz
|
||||
sudo mv golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64/golangci-lint /usr/local/bin/golangci-lint
|
||||
rm -rf golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64*
|
||||
env:
|
||||
GOLANGCI_LINT_VERSION: '1.51.2'
|
||||
GOLANGCI_LINT_SHA256: '4de479eb9d9bc29da51aec1834e7c255b333723d38dbd56781c68e5dddc6a90b'
|
||||
- name: Test style
|
||||
run: make test-style
|
||||
- name: Run unit tests
|
||||
run: make test-coverage
|
||||
- name: Test build
|
||||
run: make build
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -35,11 +35,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # pinv2.21.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # pinv2.21.0
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -64,4 +64,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # pinv2.21.0
|
||||
|
||||
75
.github/workflows/release.yml
vendored
Normal file
75
.github/workflows/release.yml
vendored
Normal file
@@ -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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # pin@4.0.1
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Run unit tests
|
||||
run: make test-coverage
|
||||
|
||||
- name: Build Helm Binaries
|
||||
run: |
|
||||
make build-cross
|
||||
make dist checksum VERSION="${{ github.ref_name }}"
|
||||
|
||||
- name: 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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # pin@4.0.1
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Run unit tests
|
||||
run: make test-coverage
|
||||
|
||||
- name: Build Helm Binaries
|
||||
run: |
|
||||
make build-cross
|
||||
make dist checksum VERSION="canary"
|
||||
|
||||
- name: Upload Binaries
|
||||
uses: bacongobbler/azure-blob-storage-upload@50f7d898b7697e864130ea04c303ca38b5751c50 # pin@3.0.0
|
||||
with:
|
||||
source_dir: _dist
|
||||
container_name: ${{ secrets.AZURE_STORAGE_CONTAINER_NAME }}
|
||||
connection_string: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
|
||||
extra_args: '--pattern helm-*'
|
||||
# WARNING: this will overwrite existing blobs in your blob storage
|
||||
overwrite: 'true'
|
||||
2
.github/workflows/stale-issue-bot.yaml
vendored
2
.github/workflows/stale-issue-bot.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
with:
|
||||
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.'
|
||||
exempt-issue-labels: 'keep open,v4.x'
|
||||
exempt-issue-labels: 'keep open,v4.x,in progress'
|
||||
days-before-stale: 90
|
||||
days-before-close: 30
|
||||
operations-per-run: 100
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
run:
|
||||
timeout: 2m
|
||||
timeout: 10m
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- deadcode
|
||||
- dupl
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- nakedret
|
||||
- structcheck
|
||||
- revive
|
||||
- unused
|
||||
- varcheck
|
||||
- staticcheck
|
||||
|
||||
linters-settings:
|
||||
|
||||
27
Makefile
27
Makefile
@@ -18,12 +18,13 @@ ACCEPTANCE_DIR:=../acceptance-testing
|
||||
ACCEPTANCE_RUN_TESTS=.
|
||||
|
||||
# go option
|
||||
PKG := ./...
|
||||
TAGS :=
|
||||
TESTS := .
|
||||
TESTFLAGS :=
|
||||
LDFLAGS := -w -s
|
||||
GOFLAGS :=
|
||||
PKG := ./...
|
||||
TAGS :=
|
||||
TESTS := .
|
||||
TESTFLAGS :=
|
||||
LDFLAGS := -w -s
|
||||
GOFLAGS :=
|
||||
CGO_ENABLED ?= 0
|
||||
|
||||
# Rebuild the binary if any of these files change
|
||||
SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum
|
||||
@@ -77,7 +78,7 @@ all: build
|
||||
build: $(BINDIR)/$(BINNAME)
|
||||
|
||||
$(BINDIR)/$(BINNAME): $(SRC)
|
||||
GO111MODULE=on go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
|
||||
GO111MODULE=on CGO_ENABLED=$(CGO_ENABLED) go build $(GOFLAGS) -trimpath -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o '$(BINDIR)'/$(BINNAME) ./cmd/helm
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# install
|
||||
@@ -113,7 +114,7 @@ test-coverage:
|
||||
|
||||
.PHONY: test-style
|
||||
test-style:
|
||||
GO111MODULE=on golangci-lint run --timeout 5m0s
|
||||
GO111MODULE=on golangci-lint run
|
||||
@scripts/validate-license.sh
|
||||
|
||||
.PHONY: test-acceptance
|
||||
@@ -149,15 +150,15 @@ gen-test-golden: test-unit
|
||||
# ------------------------------------------------------------------------------
|
||||
# dependencies
|
||||
|
||||
# If go get is run from inside the project directory it will add the dependencies
|
||||
# to the go.mod file. To avoid that we change to a directory without a go.mod file
|
||||
# when downloading the following dependencies
|
||||
# If go install is run from inside the project directory it will add the
|
||||
# dependencies to the go.mod file. To avoid that we change to a directory
|
||||
# without a go.mod file when downloading the following dependencies
|
||||
|
||||
$(GOX):
|
||||
(cd /; GO111MODULE=on go get -u github.com/mitchellh/gox)
|
||||
(cd /; GO111MODULE=on go install github.com/mitchellh/gox@latest)
|
||||
|
||||
$(GOIMPORTS):
|
||||
(cd /; GO111MODULE=on go get -u golang.org/x/tools/cmd/goimports)
|
||||
(cd /; GO111MODULE=on go install golang.org/x/tools/cmd/goimports@latest)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# release
|
||||
|
||||
19
OWNERS
19
OWNERS
@@ -1,21 +1,28 @@
|
||||
maintainers:
|
||||
- adamreese
|
||||
- bacongobbler
|
||||
- fibonacci1729
|
||||
- hickeyma
|
||||
- joejulian
|
||||
- jdolitsky
|
||||
- marckhouzam
|
||||
- mattfarina
|
||||
- prydonius
|
||||
- SlickNik
|
||||
- sabre1041
|
||||
- scottrigby
|
||||
- technosophos
|
||||
- viglesiasce
|
||||
triage:
|
||||
- yxxhero
|
||||
- zonggen
|
||||
- gjenkins8
|
||||
emeritus:
|
||||
- adamreese
|
||||
- bacongobbler
|
||||
- fibonacci1729
|
||||
- jascott1
|
||||
- michelleN
|
||||
- migmartri
|
||||
- nebril
|
||||
- prydonius
|
||||
- rimusz
|
||||
- seh
|
||||
- SlickNik
|
||||
- thomastaylor312
|
||||
- vaikas-google
|
||||
- viglesiasce
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Helm
|
||||
|
||||
[](https://circleci.com/gh/helm/helm)
|
||||
[](https://github.com/helm/helm/actions?workflow=release)
|
||||
[](https://goreportcard.com/report/github.com/helm/helm)
|
||||
[](https://pkg.go.dev/helm.sh/helm/v3)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/3131)
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartHelp = `
|
||||
This command consists of multiple subcommands to work with the chart cache.
|
||||
|
||||
The subcommands can be used to push, pull, tag, list, or remove Helm charts.
|
||||
`
|
||||
|
||||
func newChartCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "chart",
|
||||
Short: "push, pull, tag, or remove Helm charts",
|
||||
Long: chartHelp,
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
PersistentPreRunE: checkOCIFeatureGate(),
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newChartListCmd(cfg, out),
|
||||
newChartExportCmd(cfg, out),
|
||||
newChartPullCmd(cfg, out),
|
||||
newChartPushCmd(cfg, out),
|
||||
newChartRemoveCmd(cfg, out),
|
||||
newChartSaveCmd(cfg, out),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartExportDesc = `
|
||||
Export a chart stored in local registry cache.
|
||||
|
||||
This will create a new directory with the name of
|
||||
the chart, in a format that developers can modify
|
||||
and check into source control if desired.
|
||||
`
|
||||
|
||||
func newChartExportCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
client := action.NewChartExport(cfg)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "export [ref]",
|
||||
Short: "export a chart to directory",
|
||||
Long: chartExportDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ref := args[0]
|
||||
return client.Run(out, ref)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartListDesc = `
|
||||
List all charts in the local registry cache.
|
||||
|
||||
Charts are sorted by ref name, alphabetically.
|
||||
`
|
||||
|
||||
func newChartListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "list all saved charts",
|
||||
Long: chartListDesc,
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return action.NewChartList(cfg).Run(out)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartPullDesc = `
|
||||
Download a chart from a remote registry.
|
||||
|
||||
This will store the chart in the local registry cache to be used later.
|
||||
`
|
||||
|
||||
func newChartPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "pull [ref]",
|
||||
Short: "pull a chart from remote",
|
||||
Long: chartPullDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ref := args[0]
|
||||
return action.NewChartPull(cfg).Run(out, ref)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartPushDesc = `
|
||||
Upload a chart to a remote registry.
|
||||
|
||||
Note: the ref must already exist in the local registry cache.
|
||||
|
||||
Must first run "helm chart save" or "helm chart pull".
|
||||
`
|
||||
|
||||
func newChartPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "push [ref]",
|
||||
Short: "push a chart to remote",
|
||||
Long: chartPushDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ref := args[0]
|
||||
return action.NewChartPush(cfg).Run(out, ref)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
const chartRemoveDesc = `
|
||||
Remove a chart from the local registry cache.
|
||||
|
||||
Note: the chart content will still exist in the cache,
|
||||
but it will no longer appear in "helm chart list".
|
||||
|
||||
To remove all unlinked content, please run "helm chart prune". (TODO)
|
||||
`
|
||||
|
||||
func newChartRemoveCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "remove [ref]",
|
||||
Aliases: []string{"rm"},
|
||||
Short: "remove a chart",
|
||||
Long: chartRemoveDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ref := args[0]
|
||||
return action.NewChartRemove(cfg).Run(out, ref)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
const chartSaveDesc = `
|
||||
Store a copy of chart in local registry cache.
|
||||
|
||||
Note: modifying the chart after this operation will
|
||||
not change the item as it exists in the cache.
|
||||
`
|
||||
|
||||
func newChartSaveCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "save [path] [ref]",
|
||||
Short: "save a chart directory",
|
||||
Long: chartSaveDesc,
|
||||
Args: require.MinimumNArgs(2),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
path := args[0]
|
||||
ref := args[1]
|
||||
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch, err := loader.Load(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return action.NewChartSave(cfg).Run(out, ch, ref)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -33,37 +33,55 @@ const bashCompDesc = `
|
||||
Generate the autocompletion script for Helm for the bash shell.
|
||||
|
||||
To load completions in your current shell session:
|
||||
$ source <(helm completion bash)
|
||||
|
||||
source <(helm completion bash)
|
||||
|
||||
To load completions for every new session, execute once:
|
||||
Linux:
|
||||
$ helm completion bash > /etc/bash_completion.d/helm
|
||||
MacOS:
|
||||
$ helm completion bash > /usr/local/etc/bash_completion.d/helm
|
||||
- Linux:
|
||||
|
||||
helm completion bash > /etc/bash_completion.d/helm
|
||||
|
||||
- MacOS:
|
||||
|
||||
helm completion bash > /usr/local/etc/bash_completion.d/helm
|
||||
`
|
||||
|
||||
const zshCompDesc = `
|
||||
Generate the autocompletion script for Helm for the zsh shell.
|
||||
|
||||
To load completions in your current shell session:
|
||||
$ source <(helm completion zsh)
|
||||
|
||||
source <(helm completion zsh)
|
||||
|
||||
To load completions for every new session, execute once:
|
||||
$ helm completion zsh > "${fpath[1]}/_helm"
|
||||
|
||||
helm completion zsh > "${fpath[1]}/_helm"
|
||||
`
|
||||
|
||||
const fishCompDesc = `
|
||||
Generate the autocompletion script for Helm for the fish shell.
|
||||
|
||||
To load completions in your current shell session:
|
||||
$ helm completion fish | source
|
||||
|
||||
helm completion fish | source
|
||||
|
||||
To load completions for every new session, execute once:
|
||||
$ helm completion fish > ~/.config/fish/completions/helm.fish
|
||||
|
||||
helm completion fish > ~/.config/fish/completions/helm.fish
|
||||
|
||||
You will need to start a new shell for this setup to take effect.
|
||||
`
|
||||
|
||||
const powershellCompDesc = `
|
||||
Generate the autocompletion script for powershell.
|
||||
|
||||
To load completions in your current shell session:
|
||||
PS C:\> helm completion powershell | Out-String | Invoke-Expression
|
||||
|
||||
To load completions for every new session, add the output of the above command
|
||||
to your powershell profile.
|
||||
`
|
||||
|
||||
const (
|
||||
noDescFlagName = "no-descriptions"
|
||||
noDescFlagText = "disable completion descriptions"
|
||||
@@ -80,16 +98,16 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
|
||||
}
|
||||
|
||||
bash := &cobra.Command{
|
||||
Use: "bash",
|
||||
Short: "generate autocompletion script for bash",
|
||||
Long: bashCompDesc,
|
||||
Args: require.NoArgs,
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgsFunction: noCompletions,
|
||||
Use: "bash",
|
||||
Short: "generate autocompletion script for bash",
|
||||
Long: bashCompDesc,
|
||||
Args: require.NoArgs,
|
||||
ValidArgsFunction: noCompletions,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runCompletionBash(out, cmd)
|
||||
},
|
||||
}
|
||||
bash.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
|
||||
|
||||
zsh := &cobra.Command{
|
||||
Use: "zsh",
|
||||
@@ -115,13 +133,25 @@ func newCompletionCmd(out io.Writer) *cobra.Command {
|
||||
}
|
||||
fish.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
|
||||
|
||||
cmd.AddCommand(bash, zsh, fish)
|
||||
powershell := &cobra.Command{
|
||||
Use: "powershell",
|
||||
Short: "generate autocompletion script for powershell",
|
||||
Long: powershellCompDesc,
|
||||
Args: require.NoArgs,
|
||||
ValidArgsFunction: noCompletions,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runCompletionPowershell(out, cmd)
|
||||
},
|
||||
}
|
||||
powershell.Flags().BoolVar(&disableCompDescriptions, noDescFlagName, false, noDescFlagText)
|
||||
|
||||
cmd.AddCommand(bash, zsh, fish, powershell)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCompletionBash(out io.Writer, cmd *cobra.Command) error {
|
||||
err := cmd.Root().GenBashCompletion(out)
|
||||
err := cmd.Root().GenBashCompletionV2(out, !disableCompDescriptions)
|
||||
|
||||
// In case the user renamed the helm binary (e.g., to be able to run
|
||||
// both helm2 and helm3), we hook the new binary name to the completion function
|
||||
@@ -172,6 +202,13 @@ func runCompletionFish(out io.Writer, cmd *cobra.Command) error {
|
||||
return cmd.Root().GenFishCompletion(out, !disableCompDescriptions)
|
||||
}
|
||||
|
||||
func runCompletionPowershell(out io.Writer, cmd *cobra.Command) error {
|
||||
if disableCompDescriptions {
|
||||
return cmd.Root().GenPowerShellCompletion(out)
|
||||
}
|
||||
return cmd.Root().GenPowerShellCompletionWithDesc(out)
|
||||
}
|
||||
|
||||
// Function to disable file completion
|
||||
func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
|
||||
@@ -47,10 +47,10 @@ func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) {
|
||||
}
|
||||
if !strings.Contains(out, "ShellCompDirectiveNoFileComp") != shouldBePerformed {
|
||||
if shouldBePerformed {
|
||||
t.Error(fmt.Sprintf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName))
|
||||
t.Errorf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)
|
||||
} else {
|
||||
|
||||
t.Error(fmt.Sprintf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName))
|
||||
t.Errorf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName)
|
||||
}
|
||||
t.Log(out)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -77,7 +76,7 @@ func TestCreateStarterCmd(t *testing.T) {
|
||||
t.Logf("Created %s", dest)
|
||||
}
|
||||
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
|
||||
if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil {
|
||||
if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
|
||||
t.Fatalf("Could not write template: %s", err)
|
||||
}
|
||||
|
||||
@@ -140,7 +139,7 @@ func TestCreateStarterAbsoluteCmd(t *testing.T) {
|
||||
t.Logf("Created %s", dest)
|
||||
}
|
||||
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
|
||||
if err := ioutil.WriteFile(tplpath, []byte("test"), 0644); err != nil {
|
||||
if err := os.WriteFile(tplpath, []byte("test"), 0644); err != nil {
|
||||
t.Fatalf("Could not write template: %s", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,6 @@ func newDependencyCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
|
||||
func newDependencyListCmd(out io.Writer) *cobra.Command {
|
||||
client := action.NewDependency()
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list CHART",
|
||||
Aliases: []string{"ls"},
|
||||
@@ -115,5 +114,9 @@ func newDependencyListCmd(out io.Writer) *cobra.Command {
|
||||
return client.List(chartpath, out)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
|
||||
f.UintVar(&client.ColumnWidth, "max-col-width", 80, "maximum column width for output table")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -45,16 +45,11 @@ func TestDependencyBuildCmd(t *testing.T) {
|
||||
|
||||
ociChartName := "oci-depending-chart"
|
||||
c := createTestingMetadataForOCI(ociChartName, ociSrv.RegistryURL)
|
||||
if err := chartutil.SaveDir(c, ociSrv.Dir); err != nil {
|
||||
if _, err := chartutil.Save(c, ociSrv.Dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ociSrv.Run(t, repotest.WithDependingChart(c))
|
||||
|
||||
err = os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
|
||||
if err != nil {
|
||||
t.Fatal("failed to set environment variable enabling OCI support")
|
||||
}
|
||||
|
||||
dir := func(p ...string) string {
|
||||
return filepath.Join(append([]string{srv.Root()}, p...)...)
|
||||
}
|
||||
@@ -136,6 +131,9 @@ func TestDependencyBuildCmd(t *testing.T) {
|
||||
}
|
||||
|
||||
// OCI dependencies
|
||||
if err := chartutil.SaveDir(c, dir()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd = fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json",
|
||||
dir(ociChartName),
|
||||
dir("repositories.yaml"),
|
||||
|
||||
@@ -17,7 +17,6 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -47,16 +46,11 @@ func TestDependencyUpdateCmd(t *testing.T) {
|
||||
|
||||
ociChartName := "oci-depending-chart"
|
||||
c := createTestingMetadataForOCI(ociChartName, ociSrv.RegistryURL)
|
||||
if err := chartutil.SaveDir(c, ociSrv.Dir); err != nil {
|
||||
if _, err := chartutil.Save(c, ociSrv.Dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ociSrv.Run(t, repotest.WithDependingChart(c))
|
||||
|
||||
err = os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
|
||||
if err != nil {
|
||||
t.Fatal("failed to set environment variable enabling OCI support")
|
||||
}
|
||||
|
||||
if err := srv.LinkIndices(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -134,6 +128,9 @@ func TestDependencyUpdateCmd(t *testing.T) {
|
||||
}
|
||||
|
||||
// test for OCI charts
|
||||
if err := chartutil.SaveDir(c, dir()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json",
|
||||
dir(ociChartName),
|
||||
dir("repositories.yaml"),
|
||||
@@ -188,7 +185,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
|
||||
}
|
||||
|
||||
// Make sure charts dir still has dependencies
|
||||
files, err := ioutil.ReadDir(filepath.Join(dir(chartname), "charts"))
|
||||
files, err := os.ReadDir(filepath.Join(dir(chartname), "charts"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/cobra/doc"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
)
|
||||
@@ -69,14 +71,7 @@ func newDocsCmd(out io.Writer) *cobra.Command {
|
||||
f.BoolVar(&o.generateHeaders, "generate-headers", false, "generate standard headers for markdown files")
|
||||
|
||||
cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
types := []string{"bash", "man", "markdown"}
|
||||
var comps []string
|
||||
for _, t := range types {
|
||||
if strings.HasPrefix(t, toComplete) {
|
||||
comps = append(comps, t)
|
||||
}
|
||||
}
|
||||
return comps, cobra.ShellCompDirectiveNoFileComp
|
||||
return []string{"bash", "man", "markdown"}, cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
|
||||
return cmd
|
||||
@@ -91,7 +86,7 @@ func (o *docsOptions) run(out io.Writer) error {
|
||||
hdrFunc := func(filename string) string {
|
||||
base := filepath.Base(filename)
|
||||
name := strings.TrimSuffix(base, path.Ext(base))
|
||||
title := strings.Title(strings.Replace(name, "_", " ", -1))
|
||||
title := cases.Title(language.Und, cases.NoLower).String(strings.Replace(name, "_", " ", -1))
|
||||
return fmt.Sprintf("---\ntitle: \"%s\"\n---\n\n", title)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@ func TestDocsTypeFlagCompletion(t *testing.T) {
|
||||
cmd: "__complete docs --type ''",
|
||||
golden: "output/docs-type-comp.txt",
|
||||
}, {
|
||||
name: "completion for docs --type",
|
||||
name: "completion for docs --type, no filter",
|
||||
cmd: "__complete docs --type mar",
|
||||
golden: "output/docs-type-filtered-comp.txt",
|
||||
golden: "output/docs-type-comp.txt",
|
||||
}}
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
||||
@@ -36,14 +36,19 @@ import (
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
)
|
||||
|
||||
const outputFlag = "output"
|
||||
const postRenderFlag = "post-renderer"
|
||||
const (
|
||||
outputFlag = "output"
|
||||
postRenderFlag = "post-renderer"
|
||||
postRenderArgsFlag = "post-renderer-args"
|
||||
)
|
||||
|
||||
func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) {
|
||||
f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL (can specify multiple)")
|
||||
f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
||||
f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
|
||||
f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
|
||||
f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)")
|
||||
f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line")
|
||||
}
|
||||
|
||||
func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
|
||||
@@ -56,7 +61,9 @@ func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) {
|
||||
f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
|
||||
f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file")
|
||||
f.BoolVar(&c.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download")
|
||||
f.BoolVar(&c.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download")
|
||||
f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
|
||||
f.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
|
||||
}
|
||||
|
||||
// bindOutputFlag will add the output flag to the given command and bind the
|
||||
@@ -68,9 +75,7 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
|
||||
err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
var formatNames []string
|
||||
for format, desc := range output.FormatsWithDesc() {
|
||||
if strings.HasPrefix(format, toComplete) {
|
||||
formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc))
|
||||
}
|
||||
formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc))
|
||||
}
|
||||
|
||||
// Sort the results to get a deterministic order for the tests
|
||||
@@ -111,33 +116,85 @@ func (o *outputValue) Set(s string) error {
|
||||
}
|
||||
|
||||
func bindPostRenderFlag(cmd *cobra.Command, varRef *postrender.PostRenderer) {
|
||||
cmd.Flags().Var(&postRenderer{varRef}, postRenderFlag, "the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path")
|
||||
p := &postRendererOptions{varRef, "", []string{}}
|
||||
cmd.Flags().Var(&postRendererString{p}, postRenderFlag, "the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path")
|
||||
cmd.Flags().Var(&postRendererArgsSlice{p}, postRenderArgsFlag, "an argument to the post-renderer (can specify multiple)")
|
||||
}
|
||||
|
||||
type postRenderer struct {
|
||||
renderer *postrender.PostRenderer
|
||||
type postRendererOptions struct {
|
||||
renderer *postrender.PostRenderer
|
||||
binaryPath string
|
||||
args []string
|
||||
}
|
||||
|
||||
func (p postRenderer) String() string {
|
||||
return "exec"
|
||||
type postRendererString struct {
|
||||
options *postRendererOptions
|
||||
}
|
||||
|
||||
func (p postRenderer) Type() string {
|
||||
return "postrenderer"
|
||||
func (p *postRendererString) String() string {
|
||||
return p.options.binaryPath
|
||||
}
|
||||
|
||||
func (p postRenderer) Set(s string) error {
|
||||
if s == "" {
|
||||
func (p *postRendererString) Type() string {
|
||||
return "postRendererString"
|
||||
}
|
||||
|
||||
func (p *postRendererString) Set(val string) error {
|
||||
if val == "" {
|
||||
return nil
|
||||
}
|
||||
pr, err := postrender.NewExec(s)
|
||||
p.options.binaryPath = val
|
||||
pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*p.renderer = pr
|
||||
*p.options.renderer = pr
|
||||
return nil
|
||||
}
|
||||
|
||||
type postRendererArgsSlice struct {
|
||||
options *postRendererOptions
|
||||
}
|
||||
|
||||
func (p *postRendererArgsSlice) String() string {
|
||||
return "[" + strings.Join(p.options.args, ",") + "]"
|
||||
}
|
||||
|
||||
func (p *postRendererArgsSlice) Type() string {
|
||||
return "postRendererArgsSlice"
|
||||
}
|
||||
|
||||
func (p *postRendererArgsSlice) Set(val string) error {
|
||||
|
||||
// a post-renderer defined by a user may accept empty arguments
|
||||
p.options.args = append(p.options.args, val)
|
||||
|
||||
if p.options.binaryPath == "" {
|
||||
return nil
|
||||
}
|
||||
// overwrite if already create PostRenderer by `post-renderer` flags
|
||||
pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*p.options.renderer = pr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postRendererArgsSlice) Append(val string) error {
|
||||
p.options.args = append(p.options.args, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postRendererArgsSlice) Replace(val []string) error {
|
||||
p.options.args = val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *postRendererArgsSlice) GetSlice() []string {
|
||||
return p.options.args
|
||||
}
|
||||
|
||||
func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
chartInfo := strings.Split(chartRef, "/")
|
||||
if len(chartInfo) != 2 {
|
||||
@@ -152,24 +209,21 @@ func compVersionFlag(chartRef string, toComplete string) ([]string, cobra.ShellC
|
||||
var versions []string
|
||||
if indexFile, err := repo.LoadIndexFile(path); err == nil {
|
||||
for _, details := range indexFile.Entries[chartName] {
|
||||
version := details.Metadata.Version
|
||||
if strings.HasPrefix(version, toComplete) {
|
||||
appVersion := details.Metadata.AppVersion
|
||||
appVersionDesc := ""
|
||||
if appVersion != "" {
|
||||
appVersionDesc = fmt.Sprintf("App: %s, ", appVersion)
|
||||
}
|
||||
created := details.Created.Format("January 2, 2006")
|
||||
createdDesc := ""
|
||||
if created != "" {
|
||||
createdDesc = fmt.Sprintf("Created: %s ", created)
|
||||
}
|
||||
deprecated := ""
|
||||
if details.Metadata.Deprecated {
|
||||
deprecated = "(deprecated)"
|
||||
}
|
||||
versions = append(versions, fmt.Sprintf("%s\t%s%s%s", version, appVersionDesc, createdDesc, deprecated))
|
||||
appVersion := details.Metadata.AppVersion
|
||||
appVersionDesc := ""
|
||||
if appVersion != "" {
|
||||
appVersionDesc = fmt.Sprintf("App: %s, ", appVersion)
|
||||
}
|
||||
created := details.Created.Format("January 2, 2006")
|
||||
createdDesc := ""
|
||||
if created != "" {
|
||||
createdDesc = fmt.Sprintf("Created: %s ", created)
|
||||
}
|
||||
deprecated := ""
|
||||
if details.Metadata.Deprecated {
|
||||
deprecated = "(deprecated)"
|
||||
}
|
||||
versions = append(versions, fmt.Sprintf("%s\t%s%s%s", details.Metadata.Version, appVersionDesc, createdDesc, deprecated))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,13 @@ func outputFlagCompletionTest(t *testing.T, cmdName string) {
|
||||
rels: releasesMockWithStatus(&release.Info{
|
||||
Status: release.StatusDeployed,
|
||||
}),
|
||||
}, {
|
||||
name: "completion for output flag, no filter",
|
||||
cmd: fmt.Sprintf("__complete %s --output jso", cmdName),
|
||||
golden: "output/output-comp.txt",
|
||||
rels: releasesMockWithStatus(&release.Info{
|
||||
Status: release.StatusDeployed,
|
||||
}),
|
||||
}}
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ get extended information about the release, including:
|
||||
- The generated manifest file
|
||||
- The notes provided by the chart of the release
|
||||
- The hooks associated with the release
|
||||
- The metadata of the release
|
||||
`
|
||||
|
||||
func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
@@ -48,6 +49,7 @@ func newGetCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
cmd.AddCommand(newGetManifestCmd(cfg, out))
|
||||
cmd.AddCommand(newGetHooksCmd(cfg, out))
|
||||
cmd.AddCommand(newGetNotesCmd(cfg, out))
|
||||
cmd.AddCommand(newGetMetadataCmd(cfg, out))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return tpl(template, data, out)
|
||||
}
|
||||
|
||||
return output.Table.Write(out, &statusPrinter{res, true, false})
|
||||
return output.Table.Write(out, &statusPrinter{res, true, false, false})
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
94
cmd/helm/get_metadata.go
Normal file
94
cmd/helm/get_metadata.go
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/cli/output"
|
||||
)
|
||||
|
||||
type metadataWriter struct {
|
||||
metadata *action.Metadata
|
||||
}
|
||||
|
||||
func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
var outfmt output.Format
|
||||
client := action.NewGetMetadata(cfg)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "metadata RELEASE_NAME",
|
||||
Short: "This command fetches metadata for a given release",
|
||||
Args: require.ExactArgs(1),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return compListReleases(toComplete, args, cfg)
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
releaseMetadata, err := client.Run(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return outfmt.Write(out, &metadataWriter{releaseMetadata})
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.IntVar(&client.Version, "revision", 0, "specify release revision")
|
||||
err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == 1 {
|
||||
return compListRevisions(toComplete, cfg, args[0])
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
bindOutputFlag(cmd, &outfmt)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (w metadataWriter) WriteTable(out io.Writer) error {
|
||||
_, _ = fmt.Fprint(out, fmt.Sprintf("NAME: %v\n", w.metadata.Name))
|
||||
_, _ = fmt.Fprint(out, fmt.Sprintf("CHART: %v\n", w.metadata.Chart))
|
||||
_, _ = fmt.Fprint(out, fmt.Sprintf("VERSION: %v\n", w.metadata.Version))
|
||||
_, _ = fmt.Fprint(out, fmt.Sprintf("APP_VERSION: %v\n", w.metadata.AppVersion))
|
||||
_, _ = fmt.Fprint(out, fmt.Sprintf("NAMESPACE: %v\n", w.metadata.Namespace))
|
||||
_, _ = fmt.Fprint(out, fmt.Sprintf("REVISION: %v\n", w.metadata.Revision))
|
||||
_, _ = fmt.Fprint(out, fmt.Sprintf("STATUS: %v\n", w.metadata.Status))
|
||||
_, _ = fmt.Fprint(out, fmt.Sprintf("DEPLOYED_AT: %v\n", w.metadata.DeployedAt))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w metadataWriter) WriteJSON(out io.Writer) error {
|
||||
return output.EncodeJSON(out, w.metadata)
|
||||
}
|
||||
|
||||
func (w metadataWriter) WriteYAML(out io.Writer) error {
|
||||
return output.EncodeYAML(out, w.metadata)
|
||||
}
|
||||
66
cmd/helm/get_metadata_test.go
Normal file
66
cmd/helm/get_metadata_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
)
|
||||
|
||||
func TestGetMetadataCmd(t *testing.T) {
|
||||
tests := []cmdTestCase{{
|
||||
name: "get metadata with a release",
|
||||
cmd: "get metadata thomas-guide",
|
||||
golden: "output/get-metadata.txt",
|
||||
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
|
||||
}, {
|
||||
name: "get metadata requires release name arg",
|
||||
cmd: "get metadata",
|
||||
golden: "output/get-metadata-args.txt",
|
||||
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
|
||||
wantError: true,
|
||||
}, {
|
||||
name: "get metadata to json",
|
||||
cmd: "get metadata thomas-guide --output json",
|
||||
golden: "output/get-metadata.json",
|
||||
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
|
||||
}, {
|
||||
name: "get metadata to yaml",
|
||||
cmd: "get metadata thomas-guide --output yaml",
|
||||
golden: "output/get-metadata.yaml",
|
||||
rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "thomas-guide"})},
|
||||
}}
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
||||
func TestGetMetadataCompletion(t *testing.T) {
|
||||
checkReleaseCompletion(t, "get metadata", false)
|
||||
}
|
||||
|
||||
func TestGetMetadataRevisionCompletion(t *testing.T) {
|
||||
revisionFlagCompletionTest(t, "get metadata")
|
||||
}
|
||||
|
||||
func TestGetMetadataOutputCompletion(t *testing.T) {
|
||||
outputFlagCompletionTest(t, "get metadata")
|
||||
}
|
||||
|
||||
func TestGetMetadataFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "get metadata", false)
|
||||
checkFileCompletion(t, "get metadata myrelease", false)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ package main // import "helm.sh/helm/v3/cmd/helm"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -31,15 +31,12 @@ import (
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
"helm.sh/helm/v3/pkg/gates"
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
kubefake "helm.sh/helm/v3/pkg/kube/fake"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
)
|
||||
|
||||
// FeatureGateOCI is the feature gate for checking if `helm chart` and `helm registry` commands should work
|
||||
const FeatureGateOCI = gates.Gate("HELM_EXPERIMENTAL_OCI")
|
||||
|
||||
var settings = cli.New()
|
||||
|
||||
func init() {
|
||||
@@ -59,6 +56,12 @@ func warning(format string, v ...interface{}) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Setting the name of the app for managedFields in the Kubernetes client.
|
||||
// It is set here to the full name of "helm" so that renaming of helm to
|
||||
// another name (e.g., helm2 or helm3) does not change the name of the
|
||||
// manager as picked up by the automated name detection.
|
||||
kube.ManagedFieldsManager = "helm"
|
||||
|
||||
actionConfig := new(action.Configuration)
|
||||
cmd, err := newRootCmd(actionConfig, os.Stdout, os.Args[1:])
|
||||
if err != nil {
|
||||
@@ -88,15 +91,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func checkOCIFeatureGate() func(_ *cobra.Command, _ []string) error {
|
||||
return func(_ *cobra.Command, _ []string) error {
|
||||
if !FeatureGateOCI.IsEnabled() {
|
||||
return FeatureGateOCI.Error()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// This function loads releases into the memory storage if the
|
||||
// environment variable is properly set.
|
||||
func loadReleasesInMemory(actionConfig *action.Configuration) {
|
||||
@@ -112,10 +106,10 @@ func loadReleasesInMemory(actionConfig *action.Configuration) {
|
||||
return
|
||||
}
|
||||
|
||||
actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard}
|
||||
actionConfig.KubeClient = &kubefake.PrintingKubeClient{Out: io.Discard}
|
||||
|
||||
for _, path := range filePaths {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to read memory driver data", err)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
@@ -60,8 +60,11 @@ func runTestCmd(t *testing.T, tests []cmdTestCase) {
|
||||
}
|
||||
t.Logf("running cmd (attempt %d): %s", i+1, tt.cmd)
|
||||
_, out, err := executeActionCommandC(storage, tt.cmd)
|
||||
if (err != nil) != tt.wantError {
|
||||
t.Errorf("expected error, got '%v'", err)
|
||||
if tt.wantError && err == nil {
|
||||
t.Errorf("expected error, got success with the following output:\n%s", out)
|
||||
}
|
||||
if !tt.wantError && err != nil {
|
||||
t.Errorf("expected no error, got: '%v'", err)
|
||||
}
|
||||
if tt.golden != "" {
|
||||
test.AssertGoldenString(t, out, tt.golden)
|
||||
@@ -71,27 +74,6 @@ func runTestCmd(t *testing.T, tests []cmdTestCase) {
|
||||
}
|
||||
}
|
||||
|
||||
func runTestActionCmd(t *testing.T, tests []cmdTestCase) {
|
||||
t.Helper()
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer resetEnv()()
|
||||
|
||||
store := storageFixture()
|
||||
for _, rel := range tt.rels {
|
||||
store.Create(rel)
|
||||
}
|
||||
_, out, err := executeActionCommandC(store, tt.cmd)
|
||||
if (err != nil) != tt.wantError {
|
||||
t.Errorf("expected error, got '%v'", err)
|
||||
}
|
||||
if tt.golden != "" {
|
||||
test.AssertGoldenString(t, out, tt.golden)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func storageFixture() *storage.Storage {
|
||||
return storage.Init(driver.NewMemory())
|
||||
}
|
||||
@@ -110,7 +92,7 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string)
|
||||
|
||||
actionConfig := &action.Configuration{
|
||||
Releases: store,
|
||||
KubeClient: &kubefake.PrintingKubeClient{Out: ioutil.Discard},
|
||||
KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard},
|
||||
Capabilities: chartutil.DefaultCapabilities,
|
||||
Log: func(format string, v ...interface{}) {},
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
@@ -191,12 +190,9 @@ func compListRevisions(toComplete string, cfg *action.Configuration, releaseName
|
||||
var revisions []string
|
||||
if hist, err := client.Run(releaseName); err == nil {
|
||||
for _, release := range hist {
|
||||
version := strconv.Itoa(release.Version)
|
||||
if strings.HasPrefix(version, toComplete) {
|
||||
appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion)
|
||||
chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version)
|
||||
revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", version, appVersion, chartDesc))
|
||||
}
|
||||
appVersion := fmt.Sprintf("App: %s", release.Chart.Metadata.AppVersion)
|
||||
chartDesc := fmt.Sprintf("Chart: %s-%s", release.Chart.Metadata.Name, release.Chart.Metadata.Version)
|
||||
revisions = append(revisions, fmt.Sprintf("%s\t%s, %s", strconv.Itoa(release.Version), appVersion, chartDesc))
|
||||
}
|
||||
return revisions, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
@@ -95,6 +95,11 @@ func revisionFlagCompletionTest(t *testing.T, cmdName string) {
|
||||
cmd: fmt.Sprintf("__complete %s musketeers --revision ''", cmdName),
|
||||
rels: releases,
|
||||
golden: "output/revision-comp.txt",
|
||||
}, {
|
||||
name: "completion for revision flag, no filter",
|
||||
cmd: fmt.Sprintf("__complete %s musketeers --revision 1", cmdName),
|
||||
rels: releases,
|
||||
golden: "output/revision-comp.txt",
|
||||
}, {
|
||||
name: "completion for revision flag with too few args",
|
||||
cmd: fmt.Sprintf("__complete %s --revision ''", cmdName),
|
||||
|
||||
@@ -17,8 +17,13 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -44,9 +49,10 @@ a path to an unpacked chart directory or a URL.
|
||||
|
||||
To override values in a chart, use either the '--values' flag and pass in a file
|
||||
or use the '--set' flag and pass configuration from the command line, to force
|
||||
a string value use '--set-string'. In case a value is large and therefore
|
||||
you want not to use neither '--values' nor '--set', use '--set-file' to read the
|
||||
single large value from file.
|
||||
a string value use '--set-string'. You can use '--set-file' to set individual
|
||||
values from a file when the value itself is too long for the command line
|
||||
or is dynamically generated. You can also use '--set-json' to set json values
|
||||
(scalars/objects/arrays) from the command line.
|
||||
|
||||
$ helm install -f myvalues.yaml myredis ./redis
|
||||
|
||||
@@ -62,6 +68,11 @@ or
|
||||
|
||||
$ helm install --set-file my_script=dothings.sh myredis ./redis
|
||||
|
||||
or
|
||||
|
||||
$ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis
|
||||
|
||||
|
||||
You can specify the '--values'/'-f' flag multiple times. The priority will be given to the
|
||||
last (right-most) file specified. For example, if both myvalues.yaml and override.yaml
|
||||
contained a key called 'Test', the value set in override.yaml would take precedence:
|
||||
@@ -74,6 +85,13 @@ set for a key called 'foo', the 'newbar' value would take precedence:
|
||||
|
||||
$ helm install --set foo=bar --set foo=newbar myredis ./redis
|
||||
|
||||
Similarly, in the following example 'foo' is set to '["four"]':
|
||||
|
||||
$ helm install --set-json='foo=["one", "two", "three"]' --set-json='foo=["four"]' myredis ./redis
|
||||
|
||||
And in the following example, 'foo' is set to '{"key1":"value1","key2":"bar"}':
|
||||
|
||||
$ helm install --set-json='foo={"key1":"value1","key2":"value2"}' --set-json='foo.key2="bar"' myredis ./redis
|
||||
|
||||
To check the generated manifests of a release without installing the chart,
|
||||
the '--debug' and '--dry-run' flags can be combined.
|
||||
@@ -81,13 +99,14 @@ the '--debug' and '--dry-run' flags can be combined.
|
||||
If --verify is set, the chart MUST have a provenance file, and the provenance
|
||||
file MUST pass all verification steps.
|
||||
|
||||
There are five different ways you can express the chart you want to install:
|
||||
There are six different ways you can express the chart you want to install:
|
||||
|
||||
1. By chart reference: helm install mymaria example/mariadb
|
||||
2. By path to a packaged chart: helm install mynginx ./nginx-1.2.3.tgz
|
||||
3. By path to an unpacked chart directory: helm install mynginx ./nginx
|
||||
4. By absolute URL: helm install mynginx https://example.com/charts/nginx-1.2.3.tgz
|
||||
5. By chart reference and repo url: helm install --repo https://example.com/charts/ mynginx nginx
|
||||
6. By OCI registries: helm install mynginx --version 1.2.3 oci://example.com/charts/nginx
|
||||
|
||||
CHART REFERENCES
|
||||
|
||||
@@ -117,12 +136,25 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return compInstall(args, toComplete, client)
|
||||
},
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
|
||||
client.InsecureSkipTLSverify, client.PlainHTTP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("missing registry client: %w", err)
|
||||
}
|
||||
client.SetRegistryClient(registryClient)
|
||||
|
||||
// This is for the case where "" is specifically passed in as a
|
||||
// value. When there is no value passed in NoOptDefVal will be used
|
||||
// and it is set to client. See addInstallFlags.
|
||||
if client.DryRunOption == "" {
|
||||
client.DryRunOption = "none"
|
||||
}
|
||||
rel, err := runInstall(args, client, valueOpts, out)
|
||||
if err != nil {
|
||||
return err
|
||||
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})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -135,7 +167,14 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
|
||||
func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) {
|
||||
f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present")
|
||||
f.BoolVar(&client.DryRun, "dry-run", false, "simulate an install")
|
||||
// --dry-run options with expected outcome:
|
||||
// - Not set means no dry run and server is contacted.
|
||||
// - Set with no value, a value of client, or a value of true and the server is not contacted
|
||||
// - Set with a value of false, none, or false and the server is contacted
|
||||
// The true/false part is meant to reflect some legacy behavior while none is equal to "".
|
||||
f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.")
|
||||
f.Lookup("dry-run").NoOptDefVal = "client"
|
||||
f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy")
|
||||
f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install")
|
||||
f.BoolVar(&client.Replace, "replace", false, "re-use the given name, only if that name is a deleted release which remains in the history. This is unsafe in production")
|
||||
f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
|
||||
@@ -145,11 +184,12 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
|
||||
f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release")
|
||||
f.StringVar(&client.Description, "description", "", "add a custom description")
|
||||
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
|
||||
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "run helm dependency update before installing the chart")
|
||||
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
|
||||
f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema")
|
||||
f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used")
|
||||
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")
|
||||
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
|
||||
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")
|
||||
addValueOptionsFlags(f, valueOpts)
|
||||
addChartPathOptionsFlags(f, &client.ChartPathOptions)
|
||||
|
||||
@@ -214,6 +254,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
|
||||
// As of Helm 2.4.0, this is treated as a stopping condition:
|
||||
// https://github.com/helm/helm/issues/2209
|
||||
if err := action.CheckDependencies(chartRequested, req); err != nil {
|
||||
err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies")
|
||||
if client.DependencyUpdate {
|
||||
man := &downloader.Manager{
|
||||
Out: out,
|
||||
@@ -224,6 +265,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
|
||||
RepositoryConfig: settings.RepositoryConfig,
|
||||
RepositoryCache: settings.RepositoryCache,
|
||||
Debug: settings.Debug,
|
||||
RegistryClient: client.GetRegistryClient(),
|
||||
}
|
||||
if err := man.Update(); err != nil {
|
||||
return nil, err
|
||||
@@ -239,7 +281,28 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
|
||||
}
|
||||
|
||||
client.Namespace = settings.Namespace()
|
||||
return client.Run(chartRequested, vals)
|
||||
|
||||
// Validate DryRunOption member is one of the allowed values
|
||||
if err := validateDryRunOptionFlag(client.DryRunOption); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create context and prepare the handle of SIGTERM
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
// Set up channel on which to send signal notifications.
|
||||
// We must use a buffered channel or risk missing the signal
|
||||
// if we're not ready to receive when the signal is sent.
|
||||
cSignal := make(chan os.Signal, 2)
|
||||
signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-cSignal
|
||||
fmt.Fprintf(out, "Release %s has been cancelled.\n", args[0])
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return client.RunWithContext(ctx, chartRequested, vals)
|
||||
}
|
||||
|
||||
// checkIfInstallable validates if a chart can be installed
|
||||
@@ -264,3 +327,19 @@ func compInstall(args []string, toComplete string, client *action.Install) ([]st
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func validateDryRunOptionFlag(dryRunOptionFlagValue string) error {
|
||||
// Validate dry-run flag value with a set of allowed value
|
||||
allowedDryRunValues := []string{"false", "true", "none", "client", "server"}
|
||||
isAllowed := false
|
||||
for _, v := range allowedDryRunValues {
|
||||
if dryRunOptionFlagValue == v {
|
||||
isAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isAllowed {
|
||||
return errors.New("Invalid dry-run flag. Flag must one of the following: false, true, none, client, server")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,10 +18,39 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/v3/pkg/repo/repotest"
|
||||
)
|
||||
|
||||
func TestInstall(t *testing.T) {
|
||||
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer srv.Stop()
|
||||
|
||||
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok || username != "username" || password != "password" {
|
||||
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
|
||||
}
|
||||
}))
|
||||
|
||||
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r)
|
||||
}))
|
||||
defer srv2.Close()
|
||||
|
||||
if err := srv.LinkIndices(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
repoFile := filepath.Join(srv.Root(), "repositories.yaml")
|
||||
|
||||
tests := []cmdTestCase{
|
||||
// Install, base case
|
||||
{
|
||||
@@ -94,7 +123,7 @@ func TestInstall(t *testing.T) {
|
||||
// Install, using the name-template
|
||||
{
|
||||
name: "install with name-template",
|
||||
cmd: "install testdata/testcharts/empty --name-template '{{upper \"foobar\"}}'",
|
||||
cmd: "install testdata/testcharts/empty --name-template '{{ \"foobar\"}}'",
|
||||
golden: "output/install-name-template.txt",
|
||||
},
|
||||
// Install, perform chart verification along the way.
|
||||
@@ -140,7 +169,7 @@ func TestInstall(t *testing.T) {
|
||||
name: "install library chart",
|
||||
cmd: "install libchart testdata/testcharts/lib-chart",
|
||||
wantError: true,
|
||||
golden: "output/template-lib-chart.txt",
|
||||
golden: "output/install-lib-chart.txt",
|
||||
},
|
||||
// Install, chart with bad type
|
||||
{
|
||||
@@ -207,9 +236,25 @@ func TestInstall(t *testing.T) {
|
||||
name: "install chart with only crds",
|
||||
cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default",
|
||||
},
|
||||
// Verify the user/pass works
|
||||
{
|
||||
name: "basic install with credentials",
|
||||
cmd: "install aeneas reqtest --namespace default --repo " + srv.URL() + " --username username --password password",
|
||||
golden: "output/install.txt",
|
||||
},
|
||||
{
|
||||
name: "basic install with credentials",
|
||||
cmd: "install aeneas reqtest --namespace default --repo " + srv2.URL + " --username username --password password --pass-credentials",
|
||||
golden: "output/install.txt",
|
||||
},
|
||||
{
|
||||
name: "basic install with credentials and no repo",
|
||||
cmd: fmt.Sprintf("install aeneas test/reqtest --username username --password password --repository-config %s --repository-cache %s", repoFile, srv.Root()),
|
||||
golden: "output/install.txt",
|
||||
},
|
||||
}
|
||||
|
||||
runTestActionCmd(t, tests)
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
||||
func TestInstallOutputCompletion(t *testing.T) {
|
||||
@@ -230,6 +275,10 @@ func TestInstallVersionCompletion(t *testing.T) {
|
||||
name: "completion for install version flag with generate-name",
|
||||
cmd: fmt.Sprintf("%s __complete install --generate-name testing/alpine --version ''", repoSetup),
|
||||
golden: "output/version-comp.txt",
|
||||
}, {
|
||||
name: "completion for install version flag, no filter",
|
||||
cmd: fmt.Sprintf("%s __complete install releasename testing/alpine --version 0.3", repoSetup),
|
||||
golden: "output/version-comp.txt",
|
||||
}, {
|
||||
name: "completion for install version flag too few args",
|
||||
cmd: fmt.Sprintf("%s __complete install testing/alpine --version ''", repoSetup),
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/cli/values"
|
||||
"helm.sh/helm/v3/pkg/getter"
|
||||
"helm.sh/helm/v3/pkg/lint/support"
|
||||
)
|
||||
|
||||
var longLintHelp = `
|
||||
@@ -76,12 +77,23 @@ func newLintCmd(out io.Writer) *cobra.Command {
|
||||
|
||||
var message strings.Builder
|
||||
failed := 0
|
||||
errorsOrWarnings := 0
|
||||
|
||||
for _, path := range paths {
|
||||
fmt.Fprintf(&message, "==> Linting %s\n", path)
|
||||
|
||||
result := client.Run([]string{path}, vals)
|
||||
|
||||
// If there is no errors/warnings and quiet flag is set
|
||||
// go to the next chart
|
||||
hasWarningsOrErrors := action.HasWarningsOrErrors(result)
|
||||
if hasWarningsOrErrors {
|
||||
errorsOrWarnings++
|
||||
}
|
||||
if client.Quiet && !hasWarningsOrErrors {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintf(&message, "==> Linting %s\n", path)
|
||||
|
||||
// All the Errors that are generated by a chart
|
||||
// that failed a lint will be included in the
|
||||
// results.Messages so we only need to print
|
||||
@@ -93,7 +105,9 @@ func newLintCmd(out io.Writer) *cobra.Command {
|
||||
}
|
||||
|
||||
for _, msg := range result.Messages {
|
||||
fmt.Fprintf(&message, "%s\n", msg)
|
||||
if !client.Quiet || msg.Severity > support.InfoSev {
|
||||
fmt.Fprintf(&message, "%s\n", msg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(result.Errors) != 0 {
|
||||
@@ -112,7 +126,9 @@ func newLintCmd(out io.Writer) *cobra.Command {
|
||||
if failed > 0 {
|
||||
return errors.New(summary)
|
||||
}
|
||||
fmt.Fprintln(out, summary)
|
||||
if !client.Quiet || errorsOrWarnings > 0 {
|
||||
fmt.Fprintln(out, summary)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -120,6 +136,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
|
||||
f := cmd.Flags()
|
||||
f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings")
|
||||
f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts")
|
||||
f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors")
|
||||
addValueOptionsFlags(f, valueOpts)
|
||||
|
||||
return cmd
|
||||
|
||||
@@ -37,6 +37,32 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) {
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
||||
func TestLintCmdWithQuietFlag(t *testing.T) {
|
||||
testChart1 := "testdata/testcharts/alpine"
|
||||
testChart2 := "testdata/testcharts/chart-bad-requirements"
|
||||
tests := []cmdTestCase{{
|
||||
name: "lint good chart using --quiet flag",
|
||||
cmd: fmt.Sprintf("lint --quiet %s", testChart1),
|
||||
golden: "output/lint-quiet.txt",
|
||||
}, {
|
||||
name: "lint two charts, one with error using --quiet flag",
|
||||
cmd: fmt.Sprintf("lint --quiet %s %s", testChart1, testChart2),
|
||||
golden: "output/lint-quiet-with-error.txt",
|
||||
wantError: true,
|
||||
}, {
|
||||
name: "lint chart with warning using --quiet flag",
|
||||
cmd: "lint --quiet testdata/testcharts/chart-with-only-crds",
|
||||
golden: "output/lint-quiet-with-warning.txt",
|
||||
}, {
|
||||
name: "lint non-existent chart using --quiet flag",
|
||||
cmd: "lint --quiet thischartdoesntexist/",
|
||||
golden: "",
|
||||
wantError: true,
|
||||
}}
|
||||
runTestCmd(t, tests)
|
||||
|
||||
}
|
||||
|
||||
func TestLintFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "lint", true)
|
||||
checkFileCompletion(t, "lint mypath", true) // Multiple paths can be given
|
||||
|
||||
@@ -83,8 +83,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
}
|
||||
|
||||
if client.Short {
|
||||
|
||||
names := make([]string, 0)
|
||||
names := make([]string, 0, len(results))
|
||||
for _, res := range results {
|
||||
names = append(names, res.Name)
|
||||
}
|
||||
@@ -103,17 +102,16 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
fmt.Fprintln(out, res.Name)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat))
|
||||
}
|
||||
}
|
||||
|
||||
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat))
|
||||
return outfmt.Write(out, newReleaseListWriter(results, client.TimeFormat, client.NoHeaders))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format")
|
||||
f.BoolVarP(&client.NoHeaders, "no-headers", "", false, "don't print headers when using the default output format")
|
||||
f.StringVar(&client.TimeFormat, "time-format", "", `format time using golang time formatter. Example: --time-format "2006-01-02 15:04:05Z0700"`)
|
||||
f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date")
|
||||
f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order")
|
||||
@@ -145,10 +143,11 @@ type releaseElement struct {
|
||||
}
|
||||
|
||||
type releaseListWriter struct {
|
||||
releases []releaseElement
|
||||
releases []releaseElement
|
||||
noHeaders bool
|
||||
}
|
||||
|
||||
func newReleaseListWriter(releases []*release.Release, timeFormat string) *releaseListWriter {
|
||||
func newReleaseListWriter(releases []*release.Release, timeFormat string, noHeaders bool) *releaseListWriter {
|
||||
// Initialize the array so no results returns an empty array instead of null
|
||||
elements := make([]releaseElement, 0, len(releases))
|
||||
for _, r := range releases {
|
||||
@@ -157,8 +156,8 @@ func newReleaseListWriter(releases []*release.Release, timeFormat string) *relea
|
||||
Namespace: r.Namespace,
|
||||
Revision: strconv.Itoa(r.Version),
|
||||
Status: r.Info.Status.String(),
|
||||
Chart: fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version),
|
||||
AppVersion: r.Chart.Metadata.AppVersion,
|
||||
Chart: formatChartname(r.Chart),
|
||||
AppVersion: formatAppVersion(r.Chart),
|
||||
}
|
||||
|
||||
t := "-"
|
||||
@@ -173,12 +172,14 @@ func newReleaseListWriter(releases []*release.Release, timeFormat string) *relea
|
||||
|
||||
elements = append(elements, element)
|
||||
}
|
||||
return &releaseListWriter{elements}
|
||||
return &releaseListWriter{elements, noHeaders}
|
||||
}
|
||||
|
||||
func (r *releaseListWriter) WriteTable(out io.Writer) error {
|
||||
table := uitable.New()
|
||||
table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION")
|
||||
if !r.noHeaders {
|
||||
table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION")
|
||||
}
|
||||
for _, r := range r.releases {
|
||||
table.AddRow(r.Name, r.Namespace, r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion)
|
||||
}
|
||||
@@ -224,7 +225,14 @@ func compListReleases(toComplete string, ignoredReleaseNames []string, cfg *acti
|
||||
client := action.NewList(cfg)
|
||||
client.All = true
|
||||
client.Limit = 0
|
||||
client.Filter = fmt.Sprintf("^%s", toComplete)
|
||||
// Do not filter so as to get the entire list of releases.
|
||||
// This will allow zsh and fish to match completion choices
|
||||
// on other criteria then prefix. For example:
|
||||
// helm status ingress<TAB>
|
||||
// can match
|
||||
// helm status nginx-ingress
|
||||
//
|
||||
// client.Filter = fmt.Sprintf("^%s", toComplete)
|
||||
|
||||
client.SetStateMask()
|
||||
releases, err := client.Run()
|
||||
|
||||
@@ -148,6 +148,11 @@ func TestListCmd(t *testing.T) {
|
||||
cmd: "list",
|
||||
golden: "output/list.txt",
|
||||
rels: releaseFixture,
|
||||
}, {
|
||||
name: "list without headers",
|
||||
cmd: "list --no-headers",
|
||||
golden: "output/list-no-headers.txt",
|
||||
rels: releaseFixture,
|
||||
}, {
|
||||
name: "list all releases",
|
||||
cmd: "list --all",
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -154,7 +153,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
|
||||
func manuallyProcessArgs(args []string) ([]string, []string) {
|
||||
known := []string{}
|
||||
unknown := []string{}
|
||||
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config"}
|
||||
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config", "--insecure-skip-tls-verify", "--tls-server-name"}
|
||||
knownArg := func(a string) bool {
|
||||
for _, pre := range kvargs {
|
||||
if strings.HasPrefix(a, pre+"=") {
|
||||
@@ -311,9 +310,9 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug
|
||||
// loadFile takes a yaml file at the given path, parses it and returns a pluginCommand object
|
||||
func loadFile(path string) (*pluginCommand, error) {
|
||||
cmds := new(pluginCommand)
|
||||
b, err := ioutil.ReadFile(path)
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return cmds, errors.New(fmt.Sprintf("File (%s) not provided by plugin. No plugin auto-completion possible.", path))
|
||||
return cmds, fmt.Errorf("file (%s) not provided by plugin. No plugin auto-completion possible", path)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(b, cmds)
|
||||
|
||||
@@ -19,7 +19,6 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -48,7 +47,7 @@ If '--keyring' is not specified, Helm usually defaults to the public keyring
|
||||
unless your environment is otherwise configured.
|
||||
`
|
||||
|
||||
func newPackageCmd(out io.Writer) *cobra.Command {
|
||||
func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
client := action.NewPackage()
|
||||
valueOpts := &values.Options{}
|
||||
|
||||
@@ -87,11 +86,12 @@ func newPackageCmd(out io.Writer) *cobra.Command {
|
||||
|
||||
if client.DependencyUpdate {
|
||||
downloadManager := &downloader.Manager{
|
||||
Out: ioutil.Discard,
|
||||
Out: io.Discard,
|
||||
ChartPath: path,
|
||||
Keyring: client.Keyring,
|
||||
Getters: p,
|
||||
Debug: settings.Debug,
|
||||
RegistryClient: cfg.RegistryClient,
|
||||
RepositoryConfig: settings.RepositoryConfig,
|
||||
RepositoryCache: settings.RepositoryCache,
|
||||
}
|
||||
|
||||
@@ -16,14 +16,13 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/internal/test/ensure"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
@@ -118,15 +117,12 @@ func TestPackage(t *testing.T) {
|
||||
if err := os.MkdirAll("toot", 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
c := newPackageCmd(&buf)
|
||||
|
||||
// This is an unfortunate byproduct of the tmpdir
|
||||
if v, ok := tt.flags["keyring"]; ok && len(v) > 0 {
|
||||
tt.flags["keyring"] = filepath.Join(origDir, v)
|
||||
}
|
||||
|
||||
setFlags(c, tt.flags)
|
||||
re := regexp.MustCompile(tt.expect)
|
||||
|
||||
adjustedArgs := make([]string, len(tt.args))
|
||||
@@ -134,7 +130,16 @@ func TestPackage(t *testing.T) {
|
||||
adjustedArgs[i] = filepath.Join(origDir, f)
|
||||
}
|
||||
|
||||
err := c.RunE(c, adjustedArgs)
|
||||
cmd := []string{"package"}
|
||||
if len(adjustedArgs) > 0 {
|
||||
cmd = append(cmd, adjustedArgs...)
|
||||
}
|
||||
for k, v := range tt.flags {
|
||||
if v != "0" {
|
||||
cmd = append(cmd, fmt.Sprintf("--%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
_, _, err = executeActionCommand(strings.Join(cmd, " "))
|
||||
if err != nil {
|
||||
if tt.err && re.MatchString(err.Error()) {
|
||||
return
|
||||
@@ -142,10 +147,6 @@ func TestPackage(t *testing.T) {
|
||||
t.Fatalf("%q: expected error %q, got %q", tt.name, tt.expect, err)
|
||||
}
|
||||
|
||||
if !re.Match(buf.Bytes()) {
|
||||
t.Errorf("%q: expected output %q, got %q", tt.name, tt.expect, buf.String())
|
||||
}
|
||||
|
||||
if len(tt.hasfile) > 0 {
|
||||
if fi, err := os.Stat(tt.hasfile); err != nil {
|
||||
t.Errorf("%q: expected file %q, got err %q", tt.name, tt.hasfile, err)
|
||||
@@ -168,26 +169,21 @@ func TestPackage(t *testing.T) {
|
||||
func TestSetAppVersion(t *testing.T) {
|
||||
var ch *chart.Chart
|
||||
expectedAppVersion := "app-version-foo"
|
||||
|
||||
chartToPackage := "testdata/testcharts/alpine"
|
||||
dir := ensure.TempDir(t)
|
||||
|
||||
c := newPackageCmd(&bytes.Buffer{})
|
||||
flags := map[string]string{
|
||||
"destination": dir,
|
||||
"app-version": expectedAppVersion,
|
||||
cmd := fmt.Sprintf("package %s --destination=%s --app-version=%s", chartToPackage, dir, expectedAppVersion)
|
||||
_, output, err := executeActionCommand(cmd)
|
||||
if err != nil {
|
||||
t.Logf("Output: %s", output)
|
||||
t.Fatal(err)
|
||||
}
|
||||
setFlags(c, flags)
|
||||
if err := c.RunE(c, []string{"testdata/testcharts/alpine"}); err != nil {
|
||||
t.Errorf("unexpected error %q", err)
|
||||
}
|
||||
|
||||
chartPath := filepath.Join(dir, "alpine-0.1.0.tgz")
|
||||
if fi, err := os.Stat(chartPath); err != nil {
|
||||
t.Errorf("expected file %q, got err %q", chartPath, err)
|
||||
} else if fi.Size() == 0 {
|
||||
t.Errorf("file %q has zero bytes.", chartPath)
|
||||
}
|
||||
ch, err := loader.Load(chartPath)
|
||||
ch, err = loader.Load(chartPath)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error loading packaged chart: %v", err)
|
||||
}
|
||||
@@ -196,13 +192,6 @@ func TestSetAppVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func setFlags(cmd *cobra.Command, flags map[string]string) {
|
||||
dest := cmd.Flags()
|
||||
for f, v := range flags {
|
||||
dest.Set(f, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackageFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "package", true)
|
||||
checkFileCompletion(t, "package mypath", true) // Multiple paths can be given
|
||||
|
||||
@@ -18,7 +18,6 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -82,9 +81,7 @@ func compListPlugins(toComplete string, ignoredPluginNames []string) []string {
|
||||
if err == nil && len(plugins) > 0 {
|
||||
filteredPlugins := filterPlugins(plugins, ignoredPluginNames)
|
||||
for _, p := range filteredPlugins {
|
||||
if strings.HasPrefix(p.Metadata.Name, toComplete) {
|
||||
pNames = append(pNames, fmt.Sprintf("%s\t%s", p.Metadata.Name, p.Metadata.Usage))
|
||||
}
|
||||
pNames = append(pNames, fmt.Sprintf("%s\t%s", p.Metadata.Name, p.Metadata.Usage))
|
||||
}
|
||||
}
|
||||
return pNames
|
||||
|
||||
@@ -277,11 +277,6 @@ func TestPluginDynamicCompletion(t *testing.T) {
|
||||
cmd: "__complete echo -n mynamespace ''",
|
||||
golden: "output/plugin_echo_no_directive.txt",
|
||||
rels: []*release.Release{},
|
||||
}, {
|
||||
name: "completion for plugin bad directive",
|
||||
cmd: "__complete echo ''",
|
||||
golden: "output/plugin_echo_bad_directive.txt",
|
||||
rels: []*release.Release{},
|
||||
}}
|
||||
for _, test := range tests {
|
||||
settings.PluginsDirectory = "testdata/helmhome/helm/plugins"
|
||||
@@ -312,6 +307,11 @@ func TestPluginCmdsCompletion(t *testing.T) {
|
||||
cmd: "__complete plugin update ''",
|
||||
golden: "output/plugin_list_comp.txt",
|
||||
rels: []*release.Release{},
|
||||
}, {
|
||||
name: "completion for plugin update, no filter",
|
||||
cmd: "__complete plugin update full",
|
||||
golden: "output/plugin_list_comp.txt",
|
||||
rels: []*release.Release{},
|
||||
}, {
|
||||
name: "completion for plugin update repetition",
|
||||
cmd: "__complete plugin update args ''",
|
||||
@@ -322,6 +322,11 @@ func TestPluginCmdsCompletion(t *testing.T) {
|
||||
cmd: "__complete plugin uninstall ''",
|
||||
golden: "output/plugin_list_comp.txt",
|
||||
rels: []*release.Release{},
|
||||
}, {
|
||||
name: "completion for plugin uninstall, no filter",
|
||||
cmd: "__complete plugin uninstall full",
|
||||
golden: "output/plugin_list_comp.txt",
|
||||
rels: []*release.Release{},
|
||||
}, {
|
||||
name: "completion for plugin uninstall repetition",
|
||||
cmd: "__complete plugin uninstall args ''",
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -65,11 +64,12 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
client.Version = ">0.0.0-0"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(args[0], "oci://") {
|
||||
if !FeatureGateOCI.IsEnabled() {
|
||||
return FeatureGateOCI.Error()
|
||||
}
|
||||
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
|
||||
client.InsecureSkipTLSverify, client.PlainHTTP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("missing registry client: %w", err)
|
||||
}
|
||||
client.SetRegistryClient(registryClient)
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
output, err := client.Run(args[i])
|
||||
@@ -87,7 +87,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
f.BoolVar(&client.Untar, "untar", false, "if set to true, will untar the chart after downloading it")
|
||||
f.BoolVar(&client.VerifyLater, "prov", false, "fetch the provenance file, but don't perform verification")
|
||||
f.StringVar(&client.UntarDir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded")
|
||||
f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this")
|
||||
f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and untardir are specified, untardir is appended to this")
|
||||
addChartPathOptionsFlags(f, &client.ChartPathOptions)
|
||||
|
||||
err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
|
||||
@@ -18,6 +18,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -32,7 +34,6 @@ func TestPullCmd(t *testing.T) {
|
||||
}
|
||||
defer srv.Stop()
|
||||
|
||||
os.Setenv("HELM_EXPERIMENTAL_OCI", "1")
|
||||
ociSrv, err := repotest.NewOCIServer(t, srv.Root())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -250,6 +251,115 @@ func TestPullCmd(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullWithCredentialsCmd(t *testing.T) {
|
||||
srv, err := repotest.NewTempServerWithCleanup(t, "testdata/testcharts/*.tgz*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer srv.Stop()
|
||||
|
||||
srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok || username != "username" || password != "password" {
|
||||
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
|
||||
}
|
||||
}))
|
||||
|
||||
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r)
|
||||
}))
|
||||
defer srv2.Close()
|
||||
|
||||
if err := srv.LinkIndices(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// all flags will get "-d outdir" appended.
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
existFile string
|
||||
existDir string
|
||||
wantError bool
|
||||
wantErrorMsg string
|
||||
expectFile string
|
||||
expectDir bool
|
||||
}{
|
||||
{
|
||||
name: "Chart fetch using repo URL",
|
||||
expectFile: "./signtest-0.1.0.tgz",
|
||||
args: "signtest --repo " + srv.URL() + " --username username --password password",
|
||||
},
|
||||
{
|
||||
name: "Fail fetching non-existent chart on repo URL",
|
||||
args: "someChart --repo " + srv.URL() + " --username username --password password",
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "Specific version chart fetch using repo URL",
|
||||
expectFile: "./signtest-0.1.0.tgz",
|
||||
args: "signtest --version=0.1.0 --repo " + srv.URL() + " --username username --password password",
|
||||
},
|
||||
{
|
||||
name: "Specific version chart fetch using repo URL",
|
||||
args: "signtest --version=0.2.0 --repo " + srv.URL() + " --username username --password password",
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "Chart located on different domain with credentials passed",
|
||||
args: "reqtest --repo " + srv2.URL + " --username username --password password --pass-credentials",
|
||||
expectFile: "./reqtest-0.1.0.tgz",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
outdir := srv.Root()
|
||||
cmd := fmt.Sprintf("pull %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s",
|
||||
tt.args,
|
||||
outdir,
|
||||
filepath.Join(outdir, "repositories.yaml"),
|
||||
outdir,
|
||||
filepath.Join(outdir, "config.json"),
|
||||
)
|
||||
// Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182
|
||||
if tt.existFile != "" {
|
||||
file := filepath.Join(outdir, tt.existFile)
|
||||
_, err := os.Create(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if tt.existDir != "" {
|
||||
file := filepath.Join(outdir, tt.existDir)
|
||||
err := os.Mkdir(file, 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
_, _, err := executeActionCommand(cmd)
|
||||
if err != nil {
|
||||
if tt.wantError {
|
||||
if tt.wantErrorMsg != "" && tt.wantErrorMsg == err.Error() {
|
||||
t.Fatalf("Actual error %s, not equal to expected error %s", err, tt.wantErrorMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Fatalf("%q reported error: %s", tt.name, err)
|
||||
}
|
||||
|
||||
ef := filepath.Join(outdir, tt.expectFile)
|
||||
fi, err := os.Stat(ef)
|
||||
if err != nil {
|
||||
t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err)
|
||||
}
|
||||
if fi.IsDir() != tt.expectDir {
|
||||
t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullVersionCompletion(t *testing.T) {
|
||||
repoFile := "testdata/helmhome/helm/repositories.yaml"
|
||||
repoCache := "testdata/helmhome/helm/repository"
|
||||
@@ -260,6 +370,10 @@ func TestPullVersionCompletion(t *testing.T) {
|
||||
name: "completion for pull version flag",
|
||||
cmd: fmt.Sprintf("%s __complete pull testing/alpine --version ''", repoSetup),
|
||||
golden: "output/version-comp.txt",
|
||||
}, {
|
||||
name: "completion for pull version flag, no filter",
|
||||
cmd: fmt.Sprintf("%s __complete pull testing/alpine --version 0.3", repoSetup),
|
||||
golden: "output/version-comp.txt",
|
||||
}, {
|
||||
name: "completion for pull version flag too few args",
|
||||
cmd: fmt.Sprintf("%s __complete pull --version ''", repoSetup),
|
||||
|
||||
101
cmd/helm/push.go
Normal file
101
cmd/helm/push.go
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/pusher"
|
||||
)
|
||||
|
||||
const pushDesc = `
|
||||
Upload a chart to a registry.
|
||||
|
||||
If the chart has an associated provenance file,
|
||||
it will also be uploaded.
|
||||
`
|
||||
|
||||
type registryPushOptions struct {
|
||||
certFile string
|
||||
keyFile string
|
||||
caFile string
|
||||
insecureSkipTLSverify bool
|
||||
plainHTTP bool
|
||||
}
|
||||
|
||||
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
o := ®istryPushOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "push [chart] [remote]",
|
||||
Short: "push a chart to remote",
|
||||
Long: pushDesc,
|
||||
Args: require.MinimumNArgs(2),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) == 0 {
|
||||
// Do file completion for the chart file to push
|
||||
return nil, cobra.ShellCompDirectiveDefault
|
||||
}
|
||||
if len(args) == 1 {
|
||||
providers := []pusher.Provider(pusher.All(settings))
|
||||
var comps []string
|
||||
for _, p := range providers {
|
||||
for _, scheme := range p.Schemes {
|
||||
comps = append(comps, fmt.Sprintf("%s://", scheme))
|
||||
}
|
||||
}
|
||||
return comps, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
registryClient, err := newRegistryClient(o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify, o.plainHTTP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("missing registry client: %w", err)
|
||||
}
|
||||
cfg.RegistryClient = registryClient
|
||||
chartRef := args[0]
|
||||
remote := args[1]
|
||||
client := action.NewPushWithOpts(action.WithPushConfig(cfg),
|
||||
action.WithTLSClientConfig(o.certFile, o.keyFile, o.caFile),
|
||||
action.WithInsecureSkipTLSVerify(o.insecureSkipTLSverify),
|
||||
action.WithPlainHTTP(o.plainHTTP),
|
||||
action.WithPushOptWriter(out))
|
||||
client.Settings = settings
|
||||
output, err := client.Run(chartRef, remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(out, output)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file")
|
||||
f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file")
|
||||
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
|
||||
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart upload")
|
||||
f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -14,16 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConstants(t *testing.T) {
|
||||
knownMediaTypes := KnownMediaTypes()
|
||||
assert.Contains(t, knownMediaTypes, HelmChartConfigMediaType)
|
||||
assert.Contains(t, knownMediaTypes, HelmChartContentLayerMediaType)
|
||||
func TestPushFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "push", true)
|
||||
checkFileCompletion(t, "push package.tgz", false)
|
||||
checkFileCompletion(t, "push package.tgz oci://localhost:5000", false)
|
||||
}
|
||||
@@ -29,11 +29,9 @@ This command consists of multiple subcommands to interact with registries.
|
||||
|
||||
func newRegistryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "registry",
|
||||
Short: "login to or logout from a registry",
|
||||
Long: registryHelp,
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
PersistentPreRunE: checkOCIFeatureGate(),
|
||||
Use: "registry",
|
||||
Short: "login to or logout from a registry",
|
||||
Long: registryHelp,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newRegistryLoginCmd(cfg, out),
|
||||
|
||||
@@ -21,11 +21,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/moby/term"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
@@ -36,45 +35,61 @@ const registryLoginDesc = `
|
||||
Authenticate to a remote registry.
|
||||
`
|
||||
|
||||
type registryLoginOptions struct {
|
||||
username string
|
||||
password string
|
||||
passwordFromStdinOpt bool
|
||||
certFile string
|
||||
keyFile string
|
||||
caFile string
|
||||
insecure bool
|
||||
}
|
||||
|
||||
func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
var usernameOpt, passwordOpt string
|
||||
var passwordFromStdinOpt, insecureOpt bool
|
||||
o := ®istryLoginOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "login [host]",
|
||||
Short: "login to a registry",
|
||||
Long: registryLoginDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
Use: "login [host]",
|
||||
Short: "login to a registry",
|
||||
Long: registryLoginDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
ValidArgsFunction: noCompletions,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
hostname := args[0]
|
||||
|
||||
username, password, err := getUsernamePassword(usernameOpt, passwordOpt, passwordFromStdinOpt)
|
||||
username, password, err := getUsernamePassword(o.username, o.password, o.passwordFromStdinOpt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return action.NewRegistryLogin(cfg).Run(out, hostname, username, password, insecureOpt)
|
||||
return action.NewRegistryLogin(cfg).Run(out, hostname, username, password,
|
||||
action.WithCertFile(o.certFile),
|
||||
action.WithKeyFile(o.keyFile),
|
||||
action.WithCAFile(o.caFile),
|
||||
action.WithInsecure(o.insecure))
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.StringVarP(&usernameOpt, "username", "u", "", "registry username")
|
||||
f.StringVarP(&passwordOpt, "password", "p", "", "registry password or identity token")
|
||||
f.BoolVarP(&passwordFromStdinOpt, "password-stdin", "", false, "read password or identity token from stdin")
|
||||
f.BoolVarP(&insecureOpt, "insecure", "", false, "allow connections to TLS registry without certs")
|
||||
f.StringVarP(&o.username, "username", "u", "", "registry username")
|
||||
f.StringVarP(&o.password, "password", "p", "", "registry password or identity token")
|
||||
f.BoolVarP(&o.passwordFromStdinOpt, "password-stdin", "", false, "read password or identity token from stdin")
|
||||
f.BoolVarP(&o.insecure, "insecure", "", false, "allow connections to TLS registry without certs")
|
||||
f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file")
|
||||
f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file")
|
||||
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Adapted from https://github.com/deislabs/oras
|
||||
// Adapted from https://github.com/oras-project/oras
|
||||
func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) {
|
||||
var err error
|
||||
username := usernameOpt
|
||||
password := passwordOpt
|
||||
|
||||
if passwordFromStdinOpt {
|
||||
passwordFromStdin, err := ioutil.ReadAll(os.Stdin)
|
||||
passwordFromStdin, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@@ -110,7 +125,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd
|
||||
return username, password, nil
|
||||
}
|
||||
|
||||
// Copied/adapted from https://github.com/deislabs/oras
|
||||
// Copied/adapted from https://github.com/oras-project/oras
|
||||
func readLine(prompt string, silent bool) (string, error) {
|
||||
fmt.Print(prompt)
|
||||
if silent {
|
||||
|
||||
@@ -14,15 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/deislabs/oras/pkg/auth"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type (
|
||||
// Authorizer handles registry auth operations
|
||||
Authorizer struct {
|
||||
auth.Client
|
||||
}
|
||||
)
|
||||
func TestRegistryLoginFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "registry login", false)
|
||||
}
|
||||
@@ -31,11 +31,11 @@ Remove credentials stored for a remote registry.
|
||||
|
||||
func newRegistryLogoutCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "logout [host]",
|
||||
Short: "logout from a registry",
|
||||
Long: registryLogoutDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
Hidden: !FeatureGateOCI.IsEnabled(),
|
||||
Use: "logout [host]",
|
||||
Short: "logout from a registry",
|
||||
Long: registryLogoutDesc,
|
||||
Args: require.MinimumNArgs(1),
|
||||
ValidArgsFunction: noCompletions,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
hostname := args[0]
|
||||
return action.NewRegistryLogout(cfg).Run(out, hostname)
|
||||
|
||||
@@ -14,15 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package registry // import "helm.sh/helm/v3/internal/experimental/registry"
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/remotes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type (
|
||||
// Resolver provides remotes based on a locator
|
||||
Resolver struct {
|
||||
remotes.Resolver
|
||||
}
|
||||
)
|
||||
func TestRegistryLogoutFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "registry logout", false)
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
|
||||
return runErr
|
||||
}
|
||||
|
||||
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false}); err != nil {
|
||||
if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -48,6 +47,8 @@ type repoAddOptions struct {
|
||||
url string
|
||||
username string
|
||||
password string
|
||||
passwordFromStdinOpt bool
|
||||
passCredentialsAll bool
|
||||
forceUpdate bool
|
||||
allowDeprecatedRepos bool
|
||||
|
||||
@@ -84,6 +85,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&o.username, "username", "", "chart repository username")
|
||||
f.StringVar(&o.password, "password", "", "chart repository password")
|
||||
f.BoolVarP(&o.passwordFromStdinOpt, "password-stdin", "", false, "read chart repository password from stdin")
|
||||
f.BoolVar(&o.forceUpdate, "force-update", false, "replace (overwrite) the repo if it already exists")
|
||||
f.BoolVar(&o.deprecatedNoUpdate, "no-update", false, "Ignored. Formerly, it would disabled forced updates. It is deprecated by force-update.")
|
||||
f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
|
||||
@@ -91,6 +93,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
|
||||
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
|
||||
f.BoolVar(&o.insecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the repository")
|
||||
f.BoolVar(&o.allowDeprecatedRepos, "allow-deprecated-repos", false, "by default, this command will not allow adding official repos that have been permanently deleted. This disables that behavior")
|
||||
f.BoolVar(&o.passCredentialsAll, "pass-credentials", false, "pass credentials to all domains")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -112,7 +115,14 @@ func (o *repoAddOptions) run(out io.Writer) error {
|
||||
}
|
||||
|
||||
// Acquire a file lock for process synchronization
|
||||
fileLock := flock.New(strings.Replace(o.repoFile, filepath.Ext(o.repoFile), ".lock", 1))
|
||||
repoFileExt := filepath.Ext(o.repoFile)
|
||||
var lockPath string
|
||||
if len(repoFileExt) > 0 && len(repoFileExt) < len(o.repoFile) {
|
||||
lockPath = strings.TrimSuffix(o.repoFile, repoFileExt) + ".lock"
|
||||
} else {
|
||||
lockPath = o.repoFile + ".lock"
|
||||
}
|
||||
fileLock := flock.New(lockPath)
|
||||
lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
locked, err := fileLock.TryLockContext(lockCtx, time.Second)
|
||||
@@ -123,7 +133,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(o.repoFile)
|
||||
b, err := os.ReadFile(o.repoFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
@@ -134,14 +144,24 @@ func (o *repoAddOptions) run(out io.Writer) error {
|
||||
}
|
||||
|
||||
if o.username != "" && o.password == "" {
|
||||
fd := int(os.Stdin.Fd())
|
||||
fmt.Fprint(out, "Password: ")
|
||||
password, err := term.ReadPassword(fd)
|
||||
fmt.Fprintln(out)
|
||||
if err != nil {
|
||||
return err
|
||||
if o.passwordFromStdinOpt {
|
||||
passwordFromStdin, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
password := strings.TrimSuffix(string(passwordFromStdin), "\n")
|
||||
password = strings.TrimSuffix(password, "\r")
|
||||
o.password = password
|
||||
} else {
|
||||
fd := int(os.Stdin.Fd())
|
||||
fmt.Fprint(out, "Password: ")
|
||||
password, err := term.ReadPassword(fd)
|
||||
fmt.Fprintln(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.password = string(password)
|
||||
}
|
||||
o.password = string(password)
|
||||
}
|
||||
|
||||
c := repo.Entry{
|
||||
@@ -149,12 +169,18 @@ func (o *repoAddOptions) run(out io.Writer) error {
|
||||
URL: o.url,
|
||||
Username: o.username,
|
||||
Password: o.password,
|
||||
PassCredentialsAll: o.passCredentialsAll,
|
||||
CertFile: o.certFile,
|
||||
KeyFile: o.keyFile,
|
||||
CAFile: o.caFile,
|
||||
InsecureSkipTLSverify: o.insecureSkipTLSverify,
|
||||
}
|
||||
|
||||
// Check if the repo name is legal
|
||||
if strings.Contains(o.name, "/") {
|
||||
return errors.Errorf("repository name (%s) contains '/', please specify a different name without '/'", o.name)
|
||||
}
|
||||
|
||||
// If the repo exists do one of two things:
|
||||
// 1. If the configuration for the name is the same continue without error
|
||||
// 2. When the config is different require --force-update
|
||||
@@ -186,7 +212,7 @@ func (o *repoAddOptions) run(out io.Writer) error {
|
||||
|
||||
f.Update(&c)
|
||||
|
||||
if err := f.WriteFile(o.repoFile, 0644); err != nil {
|
||||
if err := f.WriteFile(o.repoFile, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(out, "%q has been added to your repositories\n", o.name)
|
||||
|
||||
@@ -18,9 +18,10 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@@ -47,7 +48,11 @@ func TestRepoAddCmd(t *testing.T) {
|
||||
}
|
||||
defer srv2.Stop()
|
||||
|
||||
tmpdir := ensure.TempDir(t)
|
||||
tmpdir := filepath.Join(ensure.TempDir(t), "path-component.yaml/data")
|
||||
err = os.MkdirAll(tmpdir, 0777)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repoFile := filepath.Join(tmpdir, "repositories.yaml")
|
||||
|
||||
tests := []cmdTestCase{
|
||||
@@ -97,7 +102,7 @@ func TestRepoAdd(t *testing.T) {
|
||||
}
|
||||
os.Setenv(xdg.CacheHomeEnvVar, rootDir)
|
||||
|
||||
if err := o.run(ioutil.Discard); err != nil {
|
||||
if err := o.run(io.Discard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -121,15 +126,48 @@ func TestRepoAdd(t *testing.T) {
|
||||
|
||||
o.forceUpdate = true
|
||||
|
||||
if err := o.run(ioutil.Discard); err != nil {
|
||||
if err := o.run(io.Discard); err != nil {
|
||||
t.Errorf("Repository was not updated: %s", err)
|
||||
}
|
||||
|
||||
if err := o.run(ioutil.Discard); err != nil {
|
||||
if err := o.run(io.Discard); err != nil {
|
||||
t.Errorf("Duplicate repository name was added")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoAddCheckLegalName(t *testing.T) {
|
||||
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ts.Stop()
|
||||
defer resetEnv()()
|
||||
|
||||
const testRepoName = "test-hub/test-name"
|
||||
|
||||
rootDir := ensure.TempDir(t)
|
||||
repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
|
||||
|
||||
o := &repoAddOptions{
|
||||
name: testRepoName,
|
||||
url: ts.URL(),
|
||||
forceUpdate: false,
|
||||
deprecatedNoUpdate: true,
|
||||
repoFile: repoFile,
|
||||
}
|
||||
os.Setenv(xdg.CacheHomeEnvVar, rootDir)
|
||||
|
||||
wantErrorMsg := fmt.Sprintf("repository name (%s) contains '/', please specify a different name without '/'", testRepoName)
|
||||
|
||||
if err := o.run(io.Discard); err != nil {
|
||||
if wantErrorMsg != err.Error() {
|
||||
t.Fatalf("Actual error %s, not equal to expected error %s", err, wantErrorMsg)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("expect reported an error.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoAddConcurrentGoRoutines(t *testing.T) {
|
||||
const testName = "test-name"
|
||||
repoFile := filepath.Join(ensure.TempDir(t), "repositories.yaml")
|
||||
@@ -142,6 +180,18 @@ func TestRepoAddConcurrentDirNotExist(t *testing.T) {
|
||||
repoAddConcurrent(t, testName, repoFile)
|
||||
}
|
||||
|
||||
func TestRepoAddConcurrentNoFileExtension(t *testing.T) {
|
||||
const testName = "test-name-3"
|
||||
repoFile := filepath.Join(ensure.TempDir(t), "repositories")
|
||||
repoAddConcurrent(t, testName, repoFile)
|
||||
}
|
||||
|
||||
func TestRepoAddConcurrentHiddenFile(t *testing.T) {
|
||||
const testName = "test-name-4"
|
||||
repoFile := filepath.Join(ensure.TempDir(t), ".repositories")
|
||||
repoAddConcurrent(t, testName, repoFile)
|
||||
}
|
||||
|
||||
func repoAddConcurrent(t *testing.T, testName, repoFile string) {
|
||||
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
|
||||
if err != nil {
|
||||
@@ -161,14 +211,14 @@ func repoAddConcurrent(t *testing.T, testName, repoFile string) {
|
||||
forceUpdate: false,
|
||||
repoFile: repoFile,
|
||||
}
|
||||
if err := o.run(ioutil.Discard); err != nil {
|
||||
if err := o.run(io.Discard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}(fmt.Sprintf("%s-%d", testName, i))
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
b, err := ioutil.ReadFile(repoFile)
|
||||
b, err := os.ReadFile(repoFile)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -192,3 +242,33 @@ func TestRepoAddFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "repo add reponame", false)
|
||||
checkFileCompletion(t, "repo add reponame https://example.com", false)
|
||||
}
|
||||
|
||||
func TestRepoAddWithPasswordFromStdin(t *testing.T) {
|
||||
srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/testserver/*.*")
|
||||
defer srv.Stop()
|
||||
|
||||
defer resetEnv()()
|
||||
|
||||
in, err := os.Open("testdata/password")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error, got '%v'", err)
|
||||
}
|
||||
|
||||
tmpdir := ensure.TempDir(t)
|
||||
repoFile := filepath.Join(tmpdir, "repositories.yaml")
|
||||
|
||||
store := storageFixture()
|
||||
|
||||
const testName = "test-name"
|
||||
const username = "username"
|
||||
cmd := fmt.Sprintf("repo add %s %s --repository-config %s --repository-cache %s --username %s --password-stdin", testName, srv.URL(), repoFile, tmpdir, username)
|
||||
var result string
|
||||
_, result, err = executeActionCommandStdinC(store, in, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error, got '%v'", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(result, fmt.Sprintf("\"%s\" has been added to your repositories", testName)) {
|
||||
t.Errorf("Repo was not successfully added. Output: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/pkg/errors"
|
||||
@@ -38,8 +38,8 @@ func newRepoListCmd(out io.Writer) *cobra.Command {
|
||||
Args: require.NoArgs,
|
||||
ValidArgsFunction: noCompletions,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
f, err := repo.LoadFile(settings.RepositoryConfig)
|
||||
if isNotExist(err) || (len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML)) {
|
||||
f, _ := repo.LoadFile(settings.RepositoryConfig)
|
||||
if len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML) {
|
||||
return errors.New("no repositories to show")
|
||||
}
|
||||
|
||||
@@ -130,9 +130,7 @@ func compListRepos(prefix string, ignoredRepoNames []string) []string {
|
||||
if err == nil && len(f.Repositories) > 0 {
|
||||
filteredRepos := filterRepos(f.Repositories, ignoredRepoNames)
|
||||
for _, repo := range filteredRepos {
|
||||
if strings.HasPrefix(repo.Name, prefix) {
|
||||
rNames = append(rNames, repo.Name)
|
||||
}
|
||||
rNames = append(rNames, fmt.Sprintf("%s\t%s", repo.Name, repo.URL))
|
||||
}
|
||||
}
|
||||
return rNames
|
||||
|
||||
@@ -67,7 +67,7 @@ func (o *repoRemoveOptions) run(out io.Writer) error {
|
||||
if !r.Remove(name) {
|
||||
return errors.Errorf("no repo named %q found", name)
|
||||
}
|
||||
if err := r.WriteFile(o.repoFile, 0644); err != nil {
|
||||
if err := r.WriteFile(o.repoFile, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -161,6 +162,55 @@ func testCacheFiles(t *testing.T, cacheIndexFile string, cacheChartsFile string,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoRemoveCompletion(t *testing.T) {
|
||||
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ts.Stop()
|
||||
|
||||
rootDir := ensure.TempDir(t)
|
||||
repoFile := filepath.Join(rootDir, "repositories.yaml")
|
||||
repoCache := filepath.Join(rootDir, "cache/")
|
||||
|
||||
var testRepoNames = []string{"foo", "bar", "baz"}
|
||||
|
||||
// Add test repos
|
||||
for _, repoName := range testRepoNames {
|
||||
o := &repoAddOptions{
|
||||
name: repoName,
|
||||
url: ts.URL(),
|
||||
repoFile: repoFile,
|
||||
}
|
||||
|
||||
if err := o.run(os.Stderr); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
repoSetup := fmt.Sprintf("--repository-config %s --repository-cache %s", repoFile, repoCache)
|
||||
|
||||
// In the following tests, we turn off descriptions for completions by using __completeNoDesc.
|
||||
// We have to do this because the description will contain the port used by the webserver,
|
||||
// and that port changes each time we run the test.
|
||||
tests := []cmdTestCase{{
|
||||
name: "completion for repo remove",
|
||||
cmd: fmt.Sprintf("%s __completeNoDesc repo remove ''", repoSetup),
|
||||
golden: "output/repo_list_comp.txt",
|
||||
}, {
|
||||
name: "completion for repo remove, no filter",
|
||||
cmd: fmt.Sprintf("%s __completeNoDesc repo remove fo", repoSetup),
|
||||
golden: "output/repo_list_comp.txt",
|
||||
}, {
|
||||
name: "completion for repo remove repetition",
|
||||
cmd: fmt.Sprintf("%s __completeNoDesc repo remove foo ''", repoSetup),
|
||||
golden: "output/repo_repeat_comp.txt",
|
||||
}}
|
||||
for _, test := range tests {
|
||||
runTestCmd(t, []cmdTestCase{test})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoRemoveFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "repo remove", false)
|
||||
checkFileCompletion(t, "repo remove repo1", false)
|
||||
|
||||
@@ -32,32 +32,48 @@ import (
|
||||
const updateDesc = `
|
||||
Update gets the latest information about charts from the respective chart repositories.
|
||||
Information is cached locally, where it is used by commands like 'helm search'.
|
||||
|
||||
You can optionally specify a list of repositories you want to update.
|
||||
$ helm repo update <repo_name> ...
|
||||
To update all the repositories, use 'helm repo update'.
|
||||
`
|
||||
|
||||
var errNoRepositories = errors.New("no repositories found. You must add one before updating")
|
||||
|
||||
type repoUpdateOptions struct {
|
||||
update func([]*repo.ChartRepository, io.Writer)
|
||||
repoFile string
|
||||
repoCache string
|
||||
update func([]*repo.ChartRepository, io.Writer, bool) error
|
||||
repoFile string
|
||||
repoCache string
|
||||
names []string
|
||||
failOnRepoUpdateFail bool
|
||||
}
|
||||
|
||||
func newRepoUpdateCmd(out io.Writer) *cobra.Command {
|
||||
o := &repoUpdateOptions{update: updateCharts}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "update",
|
||||
Aliases: []string{"up"},
|
||||
Short: "update information of available charts locally from chart repositories",
|
||||
Long: updateDesc,
|
||||
Args: require.NoArgs,
|
||||
ValidArgsFunction: noCompletions,
|
||||
Use: "update [REPO1 [REPO2 ...]]",
|
||||
Aliases: []string{"up"},
|
||||
Short: "update information of available charts locally from chart repositories",
|
||||
Long: updateDesc,
|
||||
Args: require.MinimumNArgs(0),
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
o.repoFile = settings.RepositoryConfig
|
||||
o.repoCache = settings.RepositoryCache
|
||||
o.names = args
|
||||
return o.run(out)
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
|
||||
// Adding this flag for Helm 3 as stop gap functionality for https://github.com/helm/helm/issues/10016.
|
||||
// This should be deprecated in Helm 4 by update to the behaviour of `helm repo update` command.
|
||||
f.BoolVar(&o.failOnRepoUpdateFail, "fail-on-repo-update-fail", false, "update fails if any of the repository updates fail")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -73,35 +89,79 @@ func (o *repoUpdateOptions) run(out io.Writer) error {
|
||||
}
|
||||
|
||||
var repos []*repo.ChartRepository
|
||||
for _, cfg := range f.Repositories {
|
||||
r, err := repo.NewChartRepository(cfg, getter.All(settings))
|
||||
if err != nil {
|
||||
updateAllRepos := len(o.names) == 0
|
||||
|
||||
if !updateAllRepos {
|
||||
// Fail early if the user specified an invalid repo to update
|
||||
if err := checkRequestedRepos(o.names, f.Repositories); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.repoCache != "" {
|
||||
r.CachePath = o.repoCache
|
||||
}
|
||||
repos = append(repos, r)
|
||||
}
|
||||
|
||||
o.update(repos, out)
|
||||
return nil
|
||||
for _, cfg := range f.Repositories {
|
||||
if updateAllRepos || isRepoRequested(cfg.Name, o.names) {
|
||||
r, err := repo.NewChartRepository(cfg, getter.All(settings))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.repoCache != "" {
|
||||
r.CachePath = o.repoCache
|
||||
}
|
||||
repos = append(repos, r)
|
||||
}
|
||||
}
|
||||
|
||||
return o.update(repos, out, o.failOnRepoUpdateFail)
|
||||
}
|
||||
|
||||
func updateCharts(repos []*repo.ChartRepository, out io.Writer) {
|
||||
func updateCharts(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error {
|
||||
fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...")
|
||||
var wg sync.WaitGroup
|
||||
var repoFailList []string
|
||||
for _, re := range repos {
|
||||
wg.Add(1)
|
||||
go func(re *repo.ChartRepository) {
|
||||
defer wg.Done()
|
||||
if _, err := re.DownloadIndexFile(); err != nil {
|
||||
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
|
||||
repoFailList = append(repoFailList, re.Config.URL)
|
||||
} else {
|
||||
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name)
|
||||
}
|
||||
}(re)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(repoFailList) > 0 && failOnRepoUpdateFail {
|
||||
return fmt.Errorf("Failed to update the following repositories: %s",
|
||||
repoFailList)
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈")
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRequestedRepos(requestedRepos []string, validRepos []*repo.Entry) error {
|
||||
for _, requestedRepo := range requestedRepos {
|
||||
found := false
|
||||
for _, repo := range validRepos {
|
||||
if requestedRepo == repo.Name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return errors.Errorf("no repositories found matching '%s'. Nothing will be updated", requestedRepo)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isRepoRequested(repoName string, requestedRepos []string) bool {
|
||||
for _, requestedRepo := range requestedRepos {
|
||||
if repoName == requestedRepo {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -35,10 +34,11 @@ func TestUpdateCmd(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
// Instead of using the HTTP updater, we provide our own for this test.
|
||||
// The TestUpdateCharts test verifies the HTTP behavior independently.
|
||||
updater := func(repos []*repo.ChartRepository, out io.Writer) {
|
||||
updater := func(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error {
|
||||
for _, re := range repos {
|
||||
fmt.Fprintln(out, re.Config.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
o := &repoUpdateOptions{
|
||||
update: updater,
|
||||
@@ -48,8 +48,56 @@ func TestUpdateCmd(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got := out.String(); !strings.Contains(got, "charts") {
|
||||
t.Errorf("Expected 'charts' got %q", got)
|
||||
if got := out.String(); !strings.Contains(got, "charts") ||
|
||||
!strings.Contains(got, "firstexample") ||
|
||||
!strings.Contains(got, "secondexample") {
|
||||
t.Errorf("Expected 'charts', 'firstexample' and 'secondexample' but got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateCmdMultiple(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
// Instead of using the HTTP updater, we provide our own for this test.
|
||||
// The TestUpdateCharts test verifies the HTTP behavior independently.
|
||||
updater := func(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error {
|
||||
for _, re := range repos {
|
||||
fmt.Fprintln(out, re.Config.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
o := &repoUpdateOptions{
|
||||
update: updater,
|
||||
repoFile: "testdata/repositories.yaml",
|
||||
names: []string{"firstexample", "charts"},
|
||||
}
|
||||
if err := o.run(&out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got := out.String(); !strings.Contains(got, "charts") ||
|
||||
!strings.Contains(got, "firstexample") ||
|
||||
strings.Contains(got, "secondexample") {
|
||||
t.Errorf("Expected 'charts' and 'firstexample' but not 'secondexample' but got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateCmdInvalid(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
// Instead of using the HTTP updater, we provide our own for this test.
|
||||
// The TestUpdateCharts test verifies the HTTP behavior independently.
|
||||
updater := func(repos []*repo.ChartRepository, out io.Writer, failOnRepoUpdateFail bool) error {
|
||||
for _, re := range repos {
|
||||
fmt.Fprintln(out, re.Config.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
o := &repoUpdateOptions{
|
||||
update: updater,
|
||||
repoFile: "testdata/repositories.yaml",
|
||||
names: []string{"firstexample", "invalid"},
|
||||
}
|
||||
if err := o.run(&out); err == nil {
|
||||
t.Fatal("expected error but did not get one")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +118,7 @@ func TestUpdateCustomCacheCmd(t *testing.T) {
|
||||
repoFile: filepath.Join(ts.Root(), "repositories.yaml"),
|
||||
repoCache: cachePath,
|
||||
}
|
||||
b := ioutil.Discard
|
||||
b := io.Discard
|
||||
if err := o.run(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -98,7 +146,7 @@ func TestUpdateCharts(t *testing.T) {
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
updateCharts([]*repo.ChartRepository{r}, b)
|
||||
updateCharts([]*repo.ChartRepository{r}, b, false)
|
||||
|
||||
got := b.String()
|
||||
if strings.Contains(got, "Unable to get an update") {
|
||||
@@ -111,4 +159,81 @@ func TestUpdateCharts(t *testing.T) {
|
||||
|
||||
func TestRepoUpdateFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "repo update", false)
|
||||
checkFileCompletion(t, "repo update repo1", false)
|
||||
}
|
||||
|
||||
func TestUpdateChartsFail(t *testing.T) {
|
||||
defer resetEnv()()
|
||||
defer ensure.HelmHome(t)()
|
||||
|
||||
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ts.Stop()
|
||||
|
||||
var invalidURL = ts.URL() + "55"
|
||||
r, err := repo.NewChartRepository(&repo.Entry{
|
||||
Name: "charts",
|
||||
URL: invalidURL,
|
||||
}, getter.All(settings))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
if err := updateCharts([]*repo.ChartRepository{r}, b, false); err != nil {
|
||||
t.Error("Repo update should not return error if update of repository fails")
|
||||
}
|
||||
|
||||
got := b.String()
|
||||
if !strings.Contains(got, "Unable to get an update") {
|
||||
t.Errorf("Repo should have failed update but instead got: %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "Update Complete.") {
|
||||
t.Error("Update was not successful")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateChartsFailWithError(t *testing.T) {
|
||||
defer resetEnv()()
|
||||
defer ensure.HelmHome(t)()
|
||||
|
||||
ts, err := repotest.NewTempServerWithCleanup(t, "testdata/testserver/*.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ts.Stop()
|
||||
|
||||
var invalidURL = ts.URL() + "55"
|
||||
r, err := repo.NewChartRepository(&repo.Entry{
|
||||
Name: "charts",
|
||||
URL: invalidURL,
|
||||
}, getter.All(settings))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
err = updateCharts([]*repo.ChartRepository{r}, b, true)
|
||||
if err == nil {
|
||||
t.Error("Repo update should return error because update of repository fails and 'fail-on-repo-update-fail' flag set")
|
||||
return
|
||||
}
|
||||
var expectedErr = "Failed to update the following repositories"
|
||||
var receivedErr = err.Error()
|
||||
if !strings.Contains(receivedErr, expectedErr) {
|
||||
t.Errorf("Expected error (%s) but got (%s) instead", expectedErr, receivedErr)
|
||||
}
|
||||
if !strings.Contains(receivedErr, invalidURL) {
|
||||
t.Errorf("Expected invalid URL (%s) in error message but got (%s) instead", invalidURL, receivedErr)
|
||||
}
|
||||
|
||||
got := b.String()
|
||||
if !strings.Contains(got, "Unable to get an update") {
|
||||
t.Errorf("Repo should have failed update but instead got: %q", got)
|
||||
}
|
||||
if strings.Contains(got, "Update Complete.") {
|
||||
t.Error("Update was not successful and should return error message because 'fail-on-repo-update-fail' flag set")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package require
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -71,7 +71,7 @@ func runTestCases(t *testing.T, testCases []testCase) {
|
||||
Args: tc.validateFunc,
|
||||
}
|
||||
cmd.SetArgs(tc.args)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetOutput(io.Discard)
|
||||
|
||||
err := cmd.Execute()
|
||||
if tc.wantError == "" {
|
||||
|
||||
115
cmd/helm/root.go
115
cmd/helm/root.go
@@ -29,8 +29,8 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"helm.sh/helm/v3/internal/experimental/registry"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/registry"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
)
|
||||
|
||||
@@ -45,28 +45,31 @@ Common actions for Helm:
|
||||
|
||||
Environment variables:
|
||||
|
||||
| Name | Description |
|
||||
|------------------------------------|-----------------------------------------------------------------------------------|
|
||||
| $HELM_CACHE_HOME | set an alternative location for storing cached files. |
|
||||
| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. |
|
||||
| $HELM_DATA_HOME | set an alternative location for storing Helm data. |
|
||||
| $HELM_DEBUG | indicate whether or not Helm is running in Debug mode |
|
||||
| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, postgres |
|
||||
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. |
|
||||
| $HELM_MAX_HISTORY | set the maximum number of helm release history. |
|
||||
| $HELM_NAMESPACE | set the namespace used for the helm operations. |
|
||||
| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. |
|
||||
| $HELM_PLUGINS | set the path to the plugins directory |
|
||||
| $HELM_REGISTRY_CONFIG | set the path to the registry config file. |
|
||||
| $HELM_REPOSITORY_CACHE | set the path to the repository cache directory |
|
||||
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
|
||||
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
|
||||
| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication |
|
||||
| $HELM_KUBECAFILE | set the Kubernetes certificate authority file. |
|
||||
| $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. |
|
||||
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. |
|
||||
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
|
||||
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
|
||||
| Name | Description |
|
||||
|------------------------------------|---------------------------------------------------------------------------------------------------|
|
||||
| $HELM_CACHE_HOME | set an alternative location for storing cached files. |
|
||||
| $HELM_CONFIG_HOME | set an alternative location for storing Helm configuration. |
|
||||
| $HELM_DATA_HOME | set an alternative location for storing Helm data. |
|
||||
| $HELM_DEBUG | indicate whether or not Helm is running in Debug mode |
|
||||
| $HELM_DRIVER | set the backend storage driver. Values are: configmap, secret, memory, sql. |
|
||||
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use. |
|
||||
| $HELM_MAX_HISTORY | set the maximum number of helm release history. |
|
||||
| $HELM_NAMESPACE | set the namespace used for the helm operations. |
|
||||
| $HELM_NO_PLUGINS | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. |
|
||||
| $HELM_PLUGINS | set the path to the plugins directory |
|
||||
| $HELM_REGISTRY_CONFIG | set the path to the registry config file. |
|
||||
| $HELM_REPOSITORY_CACHE | set the path to the repository cache directory |
|
||||
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
|
||||
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
|
||||
| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication |
|
||||
| $HELM_KUBECAFILE | set the Kubernetes certificate authority file. |
|
||||
| $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. |
|
||||
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. |
|
||||
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
|
||||
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
|
||||
| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure) |
|
||||
| $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate |
|
||||
| $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable)|
|
||||
|
||||
Helm stores cache, configuration, and data based on the following configuration order:
|
||||
|
||||
@@ -106,9 +109,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
|
||||
nsNames := []string{}
|
||||
if namespaces, err := client.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{TimeoutSeconds: &to}); err == nil {
|
||||
for _, ns := range namespaces.Items {
|
||||
if strings.HasPrefix(ns.Name, toComplete) {
|
||||
nsNames = append(nsNames, ns.Name)
|
||||
}
|
||||
nsNames = append(nsNames, ns.Name)
|
||||
}
|
||||
return nsNames, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
@@ -133,9 +134,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
|
||||
&clientcmd.ConfigOverrides{}).RawConfig(); err == nil {
|
||||
comps := []string{}
|
||||
for name, context := range config.Contexts {
|
||||
if strings.HasPrefix(name, toComplete) {
|
||||
comps = append(comps, fmt.Sprintf("%s\t%s", name, context.Cluster))
|
||||
}
|
||||
comps = append(comps, fmt.Sprintf("%s\t%s", name, context.Cluster))
|
||||
}
|
||||
return comps, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
@@ -153,11 +152,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
|
||||
flags.ParseErrorsWhitelist.UnknownFlags = true
|
||||
flags.Parse(args)
|
||||
|
||||
registryClient, err := registry.NewClient(
|
||||
registry.ClientOptDebug(settings.Debug),
|
||||
registry.ClientOptWriter(out),
|
||||
registry.ClientOptCredentialsFile(settings.RegistryConfig),
|
||||
)
|
||||
registryClient, err := newDefaultRegistryClient(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -169,9 +164,9 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
|
||||
newCreateCmd(out),
|
||||
newDependencyCmd(actionConfig, out),
|
||||
newPullCmd(actionConfig, out),
|
||||
newShowCmd(out),
|
||||
newShowCmd(actionConfig, out),
|
||||
newLintCmd(out),
|
||||
newPackageCmd(out),
|
||||
newPackageCmd(actionConfig, out),
|
||||
newRepoCmd(out),
|
||||
newSearchCmd(out),
|
||||
newVerifyCmd(out),
|
||||
@@ -197,10 +192,9 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
|
||||
newDocsCmd(out),
|
||||
)
|
||||
|
||||
// Add *experimental* subcommands
|
||||
cmd.AddCommand(
|
||||
newRegistryCmd(actionConfig, out),
|
||||
newChartCmd(actionConfig, out),
|
||||
newPushCmd(actionConfig, out),
|
||||
)
|
||||
|
||||
// Find and add plugins
|
||||
@@ -262,3 +256,48 @@ func checkForExpiredRepos(repofile string) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func newRegistryClient(certFile, keyFile, caFile string, insecureSkipTLSverify, plainHTTP bool) (*registry.Client, error) {
|
||||
if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSverify {
|
||||
registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSverify)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return registryClient, nil
|
||||
}
|
||||
registryClient, err := newDefaultRegistryClient(plainHTTP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return registryClient, nil
|
||||
}
|
||||
|
||||
func newDefaultRegistryClient(plainHTTP bool) (*registry.Client, error) {
|
||||
opts := []registry.ClientOption{
|
||||
registry.ClientOptDebug(settings.Debug),
|
||||
registry.ClientOptEnableCache(true),
|
||||
registry.ClientOptWriter(os.Stderr),
|
||||
registry.ClientOptCredentialsFile(settings.RegistryConfig),
|
||||
}
|
||||
if plainHTTP {
|
||||
opts = append(opts, registry.ClientOptPlainHTTP())
|
||||
}
|
||||
|
||||
// Create a new registry client
|
||||
registryClient, err := registry.NewClient(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return registryClient, nil
|
||||
}
|
||||
|
||||
func newRegistryClientWithTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*registry.Client, error) {
|
||||
// Create a new registry client
|
||||
registryClient, err := registry.NewRegistryClientWithTLS(os.Stderr, certFile, keyFile, caFile, insecureSkipTLSverify,
|
||||
settings.RegistryConfig, settings.Debug,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return registryClient, nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !windows
|
||||
//go:build !windows
|
||||
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !windows
|
||||
//go:build !windows
|
||||
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
@@ -21,7 +21,6 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -49,11 +48,7 @@ func checkPermsStderr() (string, error) {
|
||||
}
|
||||
|
||||
func TestCheckPerms(t *testing.T) {
|
||||
tdir, err := ioutil.TempDir("", "helmtest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tdir)
|
||||
tdir := t.TempDir()
|
||||
tfile := filepath.Join(tdir, "testconfig")
|
||||
fh, err := os.OpenFile(tfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0440)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*Package search provides client-side repository searching.
|
||||
/*
|
||||
Package search provides client-side repository searching.
|
||||
|
||||
This supports building an in-memory search index based on the contents of
|
||||
multiple repositories, and then using string matching or regular expressions
|
||||
@@ -146,11 +147,10 @@ func (i *Index) SearchLiteral(term string, threshold int) []*Result {
|
||||
term = strings.ToLower(term)
|
||||
buf := []*Result{}
|
||||
for k, v := range i.lines {
|
||||
lk := strings.ToLower(k)
|
||||
lv := strings.ToLower(v)
|
||||
res := strings.Index(lv, term)
|
||||
if score := i.calcScore(res, lv); res != -1 && score < threshold {
|
||||
parts := strings.Split(lk, verSep) // Remove version, if it is there.
|
||||
parts := strings.Split(k, verSep) // Remove version, if it is there.
|
||||
buf = append(buf, &Result{Name: parts[0], Score: score, Chart: i.charts[k]})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,11 +105,11 @@ func loadTestIndex(t *testing.T, all bool) *Index {
|
||||
i := NewIndex()
|
||||
i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all)
|
||||
i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{
|
||||
"pinta": {
|
||||
"Pinta": {
|
||||
{
|
||||
URLs: []string{"http://example.com/charts/pinta-2.0.0.tgz"},
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "pinta",
|
||||
Name: "Pinta",
|
||||
Version: "2.0.0",
|
||||
Description: "Two ship, version two",
|
||||
},
|
||||
@@ -170,14 +170,14 @@ func TestSearchByName(t *testing.T) {
|
||||
query: "pinta",
|
||||
expect: []*Result{
|
||||
{Name: "testing/pinta"},
|
||||
{Name: "ztesting/pinta"},
|
||||
{Name: "ztesting/Pinta"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "repo-specific search for one result",
|
||||
query: "ztesting/pinta",
|
||||
expect: []*Result{
|
||||
{Name: "ztesting/pinta"},
|
||||
{Name: "ztesting/Pinta"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -199,7 +199,15 @@ func TestSearchByName(t *testing.T) {
|
||||
query: "two",
|
||||
expect: []*Result{
|
||||
{Name: "testing/pinta"},
|
||||
{Name: "ztesting/pinta"},
|
||||
{Name: "ztesting/Pinta"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "search mixedCase and result should be mixedCase too",
|
||||
query: "pinta",
|
||||
expect: []*Result{
|
||||
{Name: "testing/pinta"},
|
||||
{Name: "ztesting/Pinta"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -207,7 +215,7 @@ func TestSearchByName(t *testing.T) {
|
||||
query: "TWO",
|
||||
expect: []*Result{
|
||||
{Name: "testing/pinta"},
|
||||
{Name: "ztesting/pinta"},
|
||||
{Name: "ztesting/Pinta"},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -53,6 +53,7 @@ type searchHubOptions struct {
|
||||
searchEndpoint string
|
||||
maxColWidth uint
|
||||
outputFormat output.Format
|
||||
listRepoURL bool
|
||||
}
|
||||
|
||||
func newSearchHubCmd(out io.Writer) *cobra.Command {
|
||||
@@ -70,6 +71,8 @@ func newSearchHubCmd(out io.Writer) *cobra.Command {
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&o.searchEndpoint, "endpoint", "https://hub.helm.sh", "Hub instance to query for charts")
|
||||
f.UintVar(&o.maxColWidth, "max-col-width", 50, "maximum column width for output table")
|
||||
f.BoolVar(&o.listRepoURL, "list-repo-url", false, "print charts repository URL")
|
||||
|
||||
bindOutputFlag(cmd, &o.outputFormat)
|
||||
|
||||
return cmd
|
||||
@@ -88,22 +91,29 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error {
|
||||
return fmt.Errorf("unable to perform search against %q", o.searchEndpoint)
|
||||
}
|
||||
|
||||
return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth))
|
||||
return o.outputFormat.Write(out, newHubSearchWriter(results, o.searchEndpoint, o.maxColWidth, o.listRepoURL))
|
||||
}
|
||||
|
||||
type hubChartRepo struct {
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type hubChartElement struct {
|
||||
URL string `json:"url"`
|
||||
Version string `json:"version"`
|
||||
AppVersion string `json:"app_version"`
|
||||
Description string `json:"description"`
|
||||
URL string `json:"url"`
|
||||
Version string `json:"version"`
|
||||
AppVersion string `json:"app_version"`
|
||||
Description string `json:"description"`
|
||||
Repository hubChartRepo `json:"repository"`
|
||||
}
|
||||
|
||||
type hubSearchWriter struct {
|
||||
elements []hubChartElement
|
||||
columnWidth uint
|
||||
listRepoURL bool
|
||||
}
|
||||
|
||||
func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint) *hubSearchWriter {
|
||||
func newHubSearchWriter(results []monocular.SearchResult, endpoint string, columnWidth uint, listRepoURL bool) *hubSearchWriter {
|
||||
var elements []hubChartElement
|
||||
for _, r := range results {
|
||||
// Backwards compatibility for Monocular
|
||||
@@ -114,9 +124,9 @@ func newHubSearchWriter(results []monocular.SearchResult, endpoint string, colum
|
||||
url = r.ArtifactHub.PackageURL
|
||||
}
|
||||
|
||||
elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description})
|
||||
elements = append(elements, hubChartElement{url, r.Relationships.LatestChartVersion.Data.Version, r.Relationships.LatestChartVersion.Data.AppVersion, r.Attributes.Description, hubChartRepo{URL: r.Attributes.Repo.URL, Name: r.Attributes.Repo.Name}})
|
||||
}
|
||||
return &hubSearchWriter{elements, columnWidth}
|
||||
return &hubSearchWriter{elements, columnWidth, listRepoURL}
|
||||
}
|
||||
|
||||
func (h *hubSearchWriter) WriteTable(out io.Writer) error {
|
||||
@@ -129,9 +139,19 @@ func (h *hubSearchWriter) WriteTable(out io.Writer) error {
|
||||
}
|
||||
table := uitable.New()
|
||||
table.MaxColWidth = h.columnWidth
|
||||
table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION")
|
||||
|
||||
if h.listRepoURL {
|
||||
table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION", "REPO URL")
|
||||
} else {
|
||||
table.AddRow("URL", "CHART VERSION", "APP VERSION", "DESCRIPTION")
|
||||
}
|
||||
|
||||
for _, r := range h.elements {
|
||||
table.AddRow(r.URL, r.Version, r.AppVersion, r.Description)
|
||||
if h.listRepoURL {
|
||||
table.AddRow(r.URL, r.Version, r.AppVersion, r.Description, r.Repository.URL)
|
||||
} else {
|
||||
table.AddRow(r.URL, r.Version, r.AppVersion, r.Description)
|
||||
}
|
||||
}
|
||||
return output.EncodeTable(out, table)
|
||||
}
|
||||
@@ -149,7 +169,7 @@ func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) er
|
||||
chartList := make([]hubChartElement, 0, len(h.elements))
|
||||
|
||||
for _, r := range h.elements {
|
||||
chartList = append(chartList, hubChartElement{r.URL, r.Version, r.AppVersion, r.Description})
|
||||
chartList = append(chartList, hubChartElement{r.URL, r.Version, r.AppVersion, r.Description, r.Repository})
|
||||
}
|
||||
|
||||
switch format {
|
||||
|
||||
@@ -33,6 +33,8 @@ func TestSearchHubCmd(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
// The expected output has the URL to the mocked search service in it
|
||||
// Trailing spaces are necessary to preserve in "expected" as the uitable package adds
|
||||
// them during printing.
|
||||
var expected = fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION
|
||||
%s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend
|
||||
%s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend
|
||||
@@ -51,6 +53,36 @@ func TestSearchHubCmd(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchHubListRepoCmd(t *testing.T) {
|
||||
|
||||
// Setup a mock search service
|
||||
var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}`
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, searchResult)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
// The expected output has the URL to the mocked search service in it
|
||||
// Trailing spaces are necessary to preserve in "expected" as the uitable package adds
|
||||
// them during printing.
|
||||
var expected = fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION REPO URL
|
||||
%s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.helm.sh/stable
|
||||
%s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.bitnami.com
|
||||
`, ts.URL, ts.URL)
|
||||
|
||||
testcmd := "search hub --list-repo-url --endpoint " + ts.URL + " maria"
|
||||
storage := storageFixture()
|
||||
_, out, err := executeActionCommandC(storage, testcmd)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error, %s", err)
|
||||
}
|
||||
if out != expected {
|
||||
t.Error("expected and actual output did not match")
|
||||
t.Log(out)
|
||||
t.Log(expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchHubOutputCompletion(t *testing.T) {
|
||||
outputFlagCompletionTest(t, "search hub")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -258,7 +258,7 @@ func compListChartsOfRepo(repoName string, prefix string) []string {
|
||||
var charts []string
|
||||
|
||||
path := filepath.Join(settings.RepositoryCache, helmpath.CacheChartsFile(repoName))
|
||||
content, err := ioutil.ReadFile(path)
|
||||
content, err := os.ReadFile(path)
|
||||
if err == nil {
|
||||
scanner := bufio.NewScanner(bytes.NewReader(content))
|
||||
for scanner.Scan() {
|
||||
@@ -301,23 +301,31 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
|
||||
|
||||
// First check completions for repos
|
||||
repos := compListRepos("", nil)
|
||||
for _, repo := range repos {
|
||||
for _, repoInfo := range repos {
|
||||
// Split name from description
|
||||
repoInfo := strings.Split(repoInfo, "\t")
|
||||
repo := repoInfo[0]
|
||||
repoDesc := ""
|
||||
if len(repoInfo) > 1 {
|
||||
repoDesc = repoInfo[1]
|
||||
}
|
||||
repoWithSlash := fmt.Sprintf("%s/", repo)
|
||||
if strings.HasPrefix(toComplete, repoWithSlash) {
|
||||
// Must complete with charts within the specified repo
|
||||
completions = append(completions, compListChartsOfRepo(repo, toComplete)...)
|
||||
// Must complete with charts within the specified repo.
|
||||
// Don't filter on toComplete to allow for shell fuzzy matching
|
||||
completions = append(completions, compListChartsOfRepo(repo, "")...)
|
||||
noSpace = false
|
||||
break
|
||||
} else if strings.HasPrefix(repo, toComplete) {
|
||||
// Must complete the repo name
|
||||
completions = append(completions, repoWithSlash)
|
||||
// Must complete the repo name with the slash, followed by the description
|
||||
completions = append(completions, fmt.Sprintf("%s\t%s", repoWithSlash, repoDesc))
|
||||
noSpace = true
|
||||
}
|
||||
}
|
||||
cobra.CompDebugln(fmt.Sprintf("Completions after repos: %v", completions), settings.Debug)
|
||||
|
||||
// Now handle completions for url prefixes
|
||||
for _, url := range []string{"https://", "http://", "file://"} {
|
||||
for _, url := range []string{"oci://\tChart OCI prefix", "https://\tChart URL prefix", "http://\tChart URL prefix", "file://\tChart local URL prefix"} {
|
||||
if strings.HasPrefix(toComplete, url) {
|
||||
// The user already put in the full url prefix; we don't have
|
||||
// anything to add, but make sure the shell does not default
|
||||
@@ -340,7 +348,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
|
||||
// listing the entire content of the current directory which will
|
||||
// be too many choices for the user to find the real repos)
|
||||
if includeFiles && len(completions) > 0 && len(toComplete) > 0 {
|
||||
if files, err := ioutil.ReadDir("."); err == nil {
|
||||
if files, err := os.ReadDir("."); err == nil {
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(file.Name(), toComplete) {
|
||||
// We are completing a file prefix
|
||||
@@ -354,7 +362,7 @@ func compListCharts(toComplete string, includeFiles bool) ([]string, cobra.Shell
|
||||
// If the user didn't provide any input to completion,
|
||||
// we provide a hint that a path can also be used
|
||||
if includeFiles && len(toComplete) == 0 {
|
||||
completions = append(completions, "./", "/")
|
||||
completions = append(completions, "./\tRelative path prefix to local chart", "/\tAbsolute path prefix to local chart")
|
||||
}
|
||||
cobra.CompDebugln(fmt.Sprintf("Completions after checking empty input: %v", completions), settings.Debug)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ This command consists of multiple subcommands to display information about a cha
|
||||
|
||||
const showAllDesc = `
|
||||
This command inspects a chart (directory, file, or URL) and displays all its content
|
||||
(values.yaml, Charts.yaml, README)
|
||||
(values.yaml, Chart.yaml, README)
|
||||
`
|
||||
|
||||
const showValuesDesc = `
|
||||
@@ -43,7 +43,7 @@ of the values.yaml file
|
||||
|
||||
const showChartDesc = `
|
||||
This command inspects a chart (directory, file, or URL) and displays the contents
|
||||
of the Charts.yaml file
|
||||
of the Chart.yaml file
|
||||
`
|
||||
|
||||
const readmeChartDesc = `
|
||||
@@ -51,8 +51,13 @@ This command inspects a chart (directory, file, or URL) and displays the content
|
||||
of the README file
|
||||
`
|
||||
|
||||
func newShowCmd(out io.Writer) *cobra.Command {
|
||||
client := action.NewShow(action.ShowAll)
|
||||
const showCRDsDesc = `
|
||||
This command inspects a chart (directory, file, or URL) and displays the contents
|
||||
of the CustomResourceDefinition files
|
||||
`
|
||||
|
||||
func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
client := action.NewShowWithConfig(action.ShowAll, cfg)
|
||||
|
||||
showCommand := &cobra.Command{
|
||||
Use: "show",
|
||||
@@ -79,6 +84,10 @@ func newShowCmd(out io.Writer) *cobra.Command {
|
||||
ValidArgsFunction: validArgsFunc,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
client.OutputFormat = action.ShowAll
|
||||
err := addRegistryClient(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output, err := runShow(args, client)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -96,6 +105,10 @@ func newShowCmd(out io.Writer) *cobra.Command {
|
||||
ValidArgsFunction: validArgsFunc,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
client.OutputFormat = action.ShowValues
|
||||
err := addRegistryClient(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output, err := runShow(args, client)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -113,6 +126,10 @@ func newShowCmd(out io.Writer) *cobra.Command {
|
||||
ValidArgsFunction: validArgsFunc,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
client.OutputFormat = action.ShowChart
|
||||
err := addRegistryClient(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output, err := runShow(args, client)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -130,6 +147,10 @@ func newShowCmd(out io.Writer) *cobra.Command {
|
||||
ValidArgsFunction: validArgsFunc,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
client.OutputFormat = action.ShowReadme
|
||||
err := addRegistryClient(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output, err := runShow(args, client)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -139,7 +160,28 @@ func newShowCmd(out io.Writer) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmds := []*cobra.Command{all, readmeSubCmd, valuesSubCmd, chartSubCmd}
|
||||
crdsSubCmd := &cobra.Command{
|
||||
Use: "crds [CHART]",
|
||||
Short: "show the chart's CRDs",
|
||||
Long: showCRDsDesc,
|
||||
Args: require.ExactArgs(1),
|
||||
ValidArgsFunction: validArgsFunc,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
client.OutputFormat = action.ShowCRDs
|
||||
err := addRegistryClient(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output, err := runShow(args, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(out, output)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmds := []*cobra.Command{all, readmeSubCmd, valuesSubCmd, chartSubCmd, crdsSubCmd}
|
||||
for _, subCmd := range cmds {
|
||||
addShowFlags(subCmd, client)
|
||||
showCommand.AddCommand(subCmd)
|
||||
@@ -182,3 +224,13 @@ func runShow(args []string, client *action.Show) (string, error) {
|
||||
}
|
||||
return client.Run(cp)
|
||||
}
|
||||
|
||||
func addRegistryClient(client *action.Show) error {
|
||||
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
|
||||
client.InsecureSkipTLSverify, client.PlainHTTP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("missing registry client: %w", err)
|
||||
}
|
||||
client.SetRegistryClient(registryClient)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -47,14 +47,14 @@ func TestShowPreReleaseChart(t *testing.T) {
|
||||
name: "show pre-release chart",
|
||||
args: "test/pre-release-chart",
|
||||
fail: true,
|
||||
expectedErr: "failed to download \"test/pre-release-chart\"",
|
||||
expectedErr: "chart \"pre-release-chart\" matching not found in test index. (try 'helm repo update'): no chart version found for pre-release-chart-",
|
||||
},
|
||||
{
|
||||
name: "show pre-release chart",
|
||||
args: "test/pre-release-chart",
|
||||
fail: true,
|
||||
flags: "--version 1.0.0",
|
||||
expectedErr: "failed to download \"test/pre-release-chart\" at version \"1.0.0\"",
|
||||
expectedErr: "chart \"pre-release-chart\" matching 1.0.0 not found in test index. (try 'helm repo update'): no chart version found for pre-release-chart-1.0.0",
|
||||
},
|
||||
{
|
||||
name: "show pre-release chart with 'devel' flag",
|
||||
@@ -98,6 +98,10 @@ func TestShowVersionCompletion(t *testing.T) {
|
||||
name: "completion for show version flag",
|
||||
cmd: fmt.Sprintf("%s __complete show chart testing/alpine --version ''", repoSetup),
|
||||
golden: "output/version-comp.txt",
|
||||
}, {
|
||||
name: "completion for show version flag, no filter",
|
||||
cmd: fmt.Sprintf("%s __complete show chart testing/alpine --version 0.3", repoSetup),
|
||||
golden: "output/version-comp.txt",
|
||||
}, {
|
||||
name: "completion for show version flag too few args",
|
||||
cmd: fmt.Sprintf("%s __complete show chart --version ''", repoSetup),
|
||||
@@ -145,3 +149,7 @@ func TestShowReadmeFileCompletion(t *testing.T) {
|
||||
func TestShowValuesFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "show values", true)
|
||||
}
|
||||
|
||||
func TestShowCRDsFileCompletion(t *testing.T) {
|
||||
checkFileCompletion(t, "show crds", true)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -25,6 +26,8 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/kubectl/pkg/cmd/get"
|
||||
|
||||
"helm.sh/helm/v3/cmd/helm/require"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"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)
|
||||
- revision of the release
|
||||
- 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
|
||||
- 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)
|
||||
},
|
||||
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])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -70,7 +80,7 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
// strip chart metadata from the output
|
||||
rel.Chart = nil
|
||||
|
||||
return outfmt.Write(out, &statusPrinter{rel, false, client.ShowDescription})
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -99,6 +111,7 @@ type statusPrinter struct {
|
||||
release *release.Release
|
||||
debug bool
|
||||
showDescription bool
|
||||
showResources bool
|
||||
}
|
||||
|
||||
func (s statusPrinter) WriteJSON(out io.Writer) error {
|
||||
@@ -113,27 +126,57 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
|
||||
if s.release == nil {
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(out, "NAME: %s\n", s.release.Name)
|
||||
_, _ = fmt.Fprintf(out, "NAME: %s\n", s.release.Name)
|
||||
if !s.release.Info.LastDeployed.IsZero() {
|
||||
fmt.Fprintf(out, "LAST DEPLOYED: %s\n", s.release.Info.LastDeployed.Format(time.ANSIC))
|
||||
_, _ = fmt.Fprintf(out, "LAST DEPLOYED: %s\n", s.release.Info.LastDeployed.Format(time.ANSIC))
|
||||
}
|
||||
fmt.Fprintf(out, "NAMESPACE: %s\n", s.release.Namespace)
|
||||
fmt.Fprintf(out, "STATUS: %s\n", s.release.Info.Status.String())
|
||||
fmt.Fprintf(out, "REVISION: %d\n", s.release.Version)
|
||||
_, _ = fmt.Fprintf(out, "CHART: %s\n", s.release.Chart.Metadata.Name)
|
||||
_, _ = fmt.Fprintf(out, "NAMESPACE: %s\n", s.release.Namespace)
|
||||
_, _ = fmt.Fprintf(out, "STATUS: %s\n", s.release.Info.Status.String())
|
||||
_, _ = fmt.Fprintf(out, "REVISION: %d\n", s.release.Version)
|
||||
_, _ = fmt.Fprintf(out, "VERSION: %s\n", s.release.Chart.Metadata.Version)
|
||||
_, _ = fmt.Fprintf(out, "APP_VERSION: %s\n", s.release.Chart.Metadata.AppVersion)
|
||||
if s.showDescription {
|
||||
fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description)
|
||||
_, _ = fmt.Fprintf(out, "DESCRIPTION: %s\n", s.release.Info.Description)
|
||||
}
|
||||
|
||||
if s.showResources && s.release.Info.Resources != nil && len(s.release.Info.Resources) > 0 {
|
||||
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)
|
||||
if tests, ok := executions[release.HookTest]; !ok || len(tests) == 0 {
|
||||
fmt.Fprintln(out, "TEST SUITE: None")
|
||||
_, _ = fmt.Fprintln(out, "TEST SUITE: None")
|
||||
} else {
|
||||
for _, h := range tests {
|
||||
// Don't print anything if hook has not been initiated
|
||||
if h.LastRun.StartedAt.IsZero() {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(out, "TEST SUITE: %s\n%s\n%s\n%s\n",
|
||||
_, _ = fmt.Fprintf(out, "TEST SUITE: %s\n%s\n%s\n%s\n",
|
||||
h.Name,
|
||||
fmt.Sprintf("Last Started: %s", h.LastRun.StartedAt.Format(time.ANSIC)),
|
||||
fmt.Sprintf("Last Completed: %s", h.LastRun.CompletedAt.Format(time.ANSIC)),
|
||||
@@ -143,38 +186,38 @@ func (s statusPrinter) WriteTable(out io.Writer) error {
|
||||
}
|
||||
|
||||
if s.debug {
|
||||
fmt.Fprintln(out, "USER-SUPPLIED VALUES:")
|
||||
_, _ = fmt.Fprintln(out, "USER-SUPPLIED VALUES:")
|
||||
err := output.EncodeYAML(out, s.release.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Print an extra newline
|
||||
fmt.Fprintln(out)
|
||||
_, _ = fmt.Fprintln(out)
|
||||
|
||||
cfg, err := chartutil.CoalesceValues(s.release.Chart, s.release.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, "COMPUTED VALUES:")
|
||||
_, _ = fmt.Fprintln(out, "COMPUTED VALUES:")
|
||||
err = output.EncodeYAML(out, cfg.AsMap())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Print an extra newline
|
||||
fmt.Fprintln(out)
|
||||
_, _ = fmt.Fprintln(out)
|
||||
}
|
||||
|
||||
if strings.EqualFold(s.release.Info.Description, "Dry run complete") || s.debug {
|
||||
fmt.Fprintln(out, "HOOKS:")
|
||||
_, _ = fmt.Fprintln(out, "HOOKS:")
|
||||
for _, h := range s.release.Hooks {
|
||||
fmt.Fprintf(out, "---\n# Source: %s\n%s\n", h.Path, h.Manifest)
|
||||
_, _ = fmt.Fprintf(out, "---\n# Source: %s\n%s\n", h.Path, h.Manifest)
|
||||
}
|
||||
fmt.Fprintf(out, "MANIFEST:\n%s\n", s.release.Manifest)
|
||||
_, _ = fmt.Fprintf(out, "MANIFEST:\n%s\n", s.release.Manifest)
|
||||
}
|
||||
|
||||
if len(s.release.Info.Notes) > 0 {
|
||||
fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(s.release.Info.Notes))
|
||||
_, _ = fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(s.release.Info.Notes))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -68,6 +68,24 @@ func TestStatusCmd(t *testing.T) {
|
||||
Status: release.StatusDeployed,
|
||||
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",
|
||||
cmd: "status flummoxed-chickadee",
|
||||
|
||||
@@ -52,6 +52,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
var skipTests bool
|
||||
client := action.NewInstall(cfg)
|
||||
valueOpts := &values.Options{}
|
||||
var kubeVersion string
|
||||
var extraAPIs []string
|
||||
var showFiles []string
|
||||
|
||||
@@ -64,8 +65,29 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
return compInstall(args, toComplete, client)
|
||||
},
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
if kubeVersion != "" {
|
||||
parsedKubeVersion, err := chartutil.ParseKubeVersion(kubeVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err)
|
||||
}
|
||||
client.KubeVersion = parsedKubeVersion
|
||||
}
|
||||
|
||||
registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile,
|
||||
client.InsecureSkipTLSverify, client.PlainHTTP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("missing registry client: %w", err)
|
||||
}
|
||||
client.SetRegistryClient(registryClient)
|
||||
|
||||
// This is for the case where "" is specifically passed in as a
|
||||
// value. When there is no value passed in NoOptDefVal will be used
|
||||
// and it is set to client. See addInstallFlags.
|
||||
if client.DryRunOption == "" {
|
||||
client.DryRunOption = "true"
|
||||
}
|
||||
client.DryRun = true
|
||||
client.ReleaseName = "RELEASE-NAME"
|
||||
client.ReleaseName = "release-name"
|
||||
client.Replace = true // Skip the name check
|
||||
client.ClientOnly = !validate
|
||||
client.APIVersions = chartutil.VersionSet(extraAPIs)
|
||||
@@ -97,11 +119,15 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
if client.UseReleaseName {
|
||||
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])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileWritten[m.Path] = true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -171,7 +197,8 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
f.BoolVar(&includeCrds, "include-crds", false, "include CRDs in the 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.StringArrayVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
|
||||
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion")
|
||||
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.")
|
||||
bindPostRenderFlag(cmd, &client.PostRenderer)
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import (
|
||||
var chartPath = "testdata/testcharts/subchart"
|
||||
|
||||
func TestTemplateCmd(t *testing.T) {
|
||||
deletevalchart := "testdata/testcharts/issue-9027"
|
||||
|
||||
tests := []cmdTestCase{
|
||||
{
|
||||
name: "check name",
|
||||
@@ -43,7 +45,7 @@ func TestTemplateCmd(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "check name template",
|
||||
cmd: fmt.Sprintf(`template '%s' --name-template='foobar-{{ b64enc "abc" }}-baz'`, chartPath),
|
||||
cmd: fmt.Sprintf(`template '%s' --name-template='foobar-{{ b64enc "abc" | lower }}-baz'`, chartPath),
|
||||
golden: "output/template-name-template.txt",
|
||||
},
|
||||
{
|
||||
@@ -62,7 +64,7 @@ func TestTemplateCmd(t *testing.T) {
|
||||
name: "check chart bad type",
|
||||
cmd: fmt.Sprintf("template '%s'", "testdata/testcharts/chart-bad-type"),
|
||||
wantError: true,
|
||||
golden: "output/install-chart-bad-type.txt",
|
||||
golden: "output/template-chart-bad-type.txt",
|
||||
},
|
||||
{
|
||||
name: "check chart with dependency which is an app chart acting as a library chart",
|
||||
@@ -74,6 +76,11 @@ func TestTemplateCmd(t *testing.T) {
|
||||
cmd: fmt.Sprintf("template '%s'", "testdata/testcharts/chart-with-template-lib-archive-dep"),
|
||||
golden: "output/template-chart-with-template-lib-archive-dep.txt",
|
||||
},
|
||||
{
|
||||
name: "check kube version",
|
||||
cmd: fmt.Sprintf("template --kube-version 1.16.0 '%s'", chartPath),
|
||||
golden: "output/template-with-kube-version.txt",
|
||||
},
|
||||
{
|
||||
name: "check kube api versions",
|
||||
cmd: fmt.Sprintf("template --api-versions helm.k8s.io/test '%s'", chartPath),
|
||||
@@ -126,6 +133,34 @@ func TestTemplateCmd(t *testing.T) {
|
||||
cmd: fmt.Sprintf(`template '%s' --skip-tests`, chartPath),
|
||||
golden: "output/template-skip-tests.txt",
|
||||
},
|
||||
{
|
||||
// This test case is to ensure the case where specified dependencies
|
||||
// in the Chart.yaml and those where the Chart.yaml don't have them
|
||||
// specified are the same.
|
||||
name: "ensure nil/null values pass to subcharts delete values",
|
||||
cmd: fmt.Sprintf("template '%s'", deletevalchart),
|
||||
golden: "output/issue-9027.txt",
|
||||
},
|
||||
{
|
||||
// Ensure that imported values take precedence over parent chart values
|
||||
name: "template with imported subchart values ensuring import",
|
||||
cmd: fmt.Sprintf("template '%s' --set configmap.enabled=true --set subchartb.enabled=true", chartPath),
|
||||
golden: "output/template-subchart-cm.txt",
|
||||
},
|
||||
{
|
||||
// Ensure that user input values take precedence over imported
|
||||
// values from sub-charts.
|
||||
name: "template with imported subchart values set with --set",
|
||||
cmd: fmt.Sprintf("template '%s' --set configmap.enabled=true --set subchartb.enabled=true --set configmap.value=baz", chartPath),
|
||||
golden: "output/template-subchart-cm-set.txt",
|
||||
},
|
||||
{
|
||||
// Ensure that user input values take precedence over imported
|
||||
// values from sub-charts when passed by file
|
||||
name: "template with imported subchart values set with --set",
|
||||
cmd: fmt.Sprintf("template '%s' -f %s/extra_values.yaml", chartPath, chartPath),
|
||||
golden: "output/template-subchart-cm-set-file.txt",
|
||||
},
|
||||
}
|
||||
runTestCmd(t, tests)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ echo "Args received: ${@}"
|
||||
|
||||
# Final printout is the optional completion directive of the form :<directive>
|
||||
if [ "$HELM_NAMESPACE" = "default" ]; then
|
||||
# Output an invalid directive, which should be ignored
|
||||
echo ":2222"
|
||||
echo ":0"
|
||||
# else
|
||||
# Don't include the directive, to test it is really optional
|
||||
fi
|
||||
|
||||
3
cmd/helm/testdata/output/env-comp.txt
vendored
3
cmd/helm/testdata/output/env-comp.txt
vendored
@@ -1,4 +1,5 @@
|
||||
HELM_BIN
|
||||
HELM_BURST_LIMIT
|
||||
HELM_CACHE_HOME
|
||||
HELM_CONFIG_HOME
|
||||
HELM_DATA_HOME
|
||||
@@ -8,6 +9,8 @@ HELM_KUBEASGROUPS
|
||||
HELM_KUBEASUSER
|
||||
HELM_KUBECAFILE
|
||||
HELM_KUBECONTEXT
|
||||
HELM_KUBEINSECURE_SKIP_TLS_VERIFY
|
||||
HELM_KUBETLS_SERVER_NAME
|
||||
HELM_KUBETOKEN
|
||||
HELM_MAX_HISTORY
|
||||
HELM_NAMESPACE
|
||||
|
||||
3
cmd/helm/testdata/output/get-metadata-args.txt
vendored
Normal file
3
cmd/helm/testdata/output/get-metadata-args.txt
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Error: "helm get metadata" requires 1 argument
|
||||
|
||||
Usage: helm get metadata RELEASE_NAME [flags]
|
||||
1
cmd/helm/testdata/output/get-metadata.json
vendored
Normal file
1
cmd/helm/testdata/output/get-metadata.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"name":"thomas-guide","chart":"foo","version":"0.1.0-beta.1","appVersion":"1.0","namespace":"default","revision":1,"status":"deployed","deployedAt":"1977-09-02T22:04:05Z"}
|
||||
8
cmd/helm/testdata/output/get-metadata.txt
vendored
Normal file
8
cmd/helm/testdata/output/get-metadata.txt
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
NAME: thomas-guide
|
||||
CHART: foo
|
||||
VERSION: 0.1.0-beta.1
|
||||
APP_VERSION: 1.0
|
||||
NAMESPACE: default
|
||||
REVISION: 1
|
||||
STATUS: deployed
|
||||
DEPLOYED_AT: 1977-09-02T22:04:05Z
|
||||
8
cmd/helm/testdata/output/get-metadata.yaml
vendored
Normal file
8
cmd/helm/testdata/output/get-metadata.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
appVersion: "1.0"
|
||||
chart: foo
|
||||
deployedAt: "1977-09-02T22:04:05Z"
|
||||
name: thomas-guide
|
||||
namespace: default
|
||||
revision: 1
|
||||
status: deployed
|
||||
version: 0.1.0-beta.1
|
||||
3
cmd/helm/testdata/output/get-release.txt
vendored
3
cmd/helm/testdata/output/get-release.txt
vendored
@@ -1,8 +1,11 @@
|
||||
NAME: thomas-guide
|
||||
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
|
||||
CHART: foo
|
||||
NAMESPACE: default
|
||||
STATUS: deployed
|
||||
REVISION: 1
|
||||
VERSION: 0.1.0-beta.1
|
||||
APP_VERSION: 1.0
|
||||
TEST SUITE: None
|
||||
USER-SUPPLIED VALUES:
|
||||
name: value
|
||||
|
||||
@@ -1 +1 @@
|
||||
Error: validation: chart.metadata.type must be application or library
|
||||
Error: INSTALLATION FAILED: validation: chart.metadata.type must be application or library
|
||||
|
||||
1
cmd/helm/testdata/output/install-lib-chart.txt
vendored
Normal file
1
cmd/helm/testdata/output/install-lib-chart.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Error: INSTALLATION FAILED: validation: chart.metadata.type must be application or library
|
||||
@@ -1,4 +1,4 @@
|
||||
NAME: FOOBAR
|
||||
NAME: foobar
|
||||
LAST DEPLOYED: Fri Sep 2 22:04:05 1977
|
||||
NAMESPACE: default
|
||||
STATUS: deployed
|
||||
|
||||
32
cmd/helm/testdata/output/issue-9027.txt
vendored
Normal file
32
cmd/helm/testdata/output/issue-9027.txt
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# Source: issue-9027/charts/subchart/templates/values.yaml
|
||||
global:
|
||||
hash:
|
||||
key3: 13
|
||||
key4: 4
|
||||
key5: 5
|
||||
key6: 6
|
||||
hash:
|
||||
key3: 13
|
||||
key4: 4
|
||||
key5: 5
|
||||
key6: 6
|
||||
---
|
||||
# Source: issue-9027/templates/values.yaml
|
||||
global:
|
||||
hash:
|
||||
key1: null
|
||||
key2: null
|
||||
key3: 13
|
||||
subchart:
|
||||
global:
|
||||
hash:
|
||||
key3: 13
|
||||
key4: 4
|
||||
key5: 5
|
||||
key6: 6
|
||||
hash:
|
||||
key3: 13
|
||||
key4: 4
|
||||
key5: 5
|
||||
key6: 6
|
||||
@@ -1,6 +1,6 @@
|
||||
==> Linting testdata/testcharts/chart-with-bad-subcharts
|
||||
[INFO] Chart.yaml: icon is recommended
|
||||
[WARNING] templates/: directory not found
|
||||
[ERROR] templates/: error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
|
||||
[ERROR] : unable to load chart
|
||||
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
[ERROR] Chart.yaml: apiVersion is required. The value must be either "v1" or "v2"
|
||||
[ERROR] Chart.yaml: version is required
|
||||
[INFO] Chart.yaml: icon is recommended
|
||||
[WARNING] templates/: directory not found
|
||||
[ERROR] templates/: validation: chart.metadata.name is required
|
||||
[ERROR] : unable to load chart
|
||||
validation: chart.metadata.name is required
|
||||
|
||||
==> Linting testdata/testcharts/chart-with-bad-subcharts/charts/good-subchart
|
||||
[INFO] Chart.yaml: icon is recommended
|
||||
[WARNING] templates/: directory not found
|
||||
|
||||
Error: 3 chart(s) linted, 2 chart(s) failed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
==> Linting testdata/testcharts/chart-with-bad-subcharts
|
||||
[INFO] Chart.yaml: icon is recommended
|
||||
[WARNING] templates/: directory not found
|
||||
[ERROR] templates/: error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
|
||||
[ERROR] : unable to load chart
|
||||
error unpacking bad-subchart in chart-with-bad-subcharts: validation: chart.metadata.name is required
|
||||
|
||||
|
||||
8
cmd/helm/testdata/output/lint-quiet-with-error.txt
vendored
Normal file
8
cmd/helm/testdata/output/lint-quiet-with-error.txt
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
==> Linting testdata/testcharts/chart-bad-requirements
|
||||
[ERROR] Chart.yaml: unable to parse YAML
|
||||
error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
|
||||
[ERROR] templates/: cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
|
||||
[ERROR] : unable to load chart
|
||||
cannot load Chart.yaml: error converting YAML to JSON: yaml: line 6: did not find expected '-' indicator
|
||||
|
||||
Error: 2 chart(s) linted, 1 chart(s) failed
|
||||
0
cmd/helm/testdata/output/lint-quiet-with-warning.txt
vendored
Normal file
0
cmd/helm/testdata/output/lint-quiet-with-warning.txt
vendored
Normal file
0
cmd/helm/testdata/output/lint-quiet.txt
vendored
Normal file
0
cmd/helm/testdata/output/lint-quiet.txt
vendored
Normal file
4
cmd/helm/testdata/output/list-no-headers.txt
vendored
Normal file
4
cmd/helm/testdata/output/list-no-headers.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
hummingbird default 1 2016-01-16 00:00:03 +0000 UTC deployed chickadee-1.0.0 0.0.1
|
||||
iguana default 2 2016-01-16 00:00:04 +0000 UTC deployed chickadee-1.0.0 0.0.1
|
||||
rocket default 1 2016-01-16 00:00:02 +0000 UTC failed chickadee-1.0.0 0.0.1
|
||||
starlord default 2 2016-01-16 00:00:01 +0000 UTC deployed chickadee-1.0.0 0.0.1
|
||||
@@ -1,6 +0,0 @@
|
||||
echo plugin.complete was called
|
||||
Namespace: default
|
||||
Num args received: 1
|
||||
Args received:
|
||||
:0
|
||||
Completion ended with directive: ShellCompDirectiveDefault
|
||||
5
cmd/helm/testdata/output/repo_list_comp.txt
vendored
Normal file
5
cmd/helm/testdata/output/repo_list_comp.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
:4
|
||||
Completion ended with directive: ShellCompDirectiveNoFileComp
|
||||
@@ -1,3 +1,4 @@
|
||||
markdown
|
||||
bar
|
||||
baz
|
||||
:4
|
||||
Completion ended with directive: ShellCompDirectiveNoFileComp
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user