Merge branch 'master' into feat/fishCompletion

pull/7690/head
Marc Khouzam 6 years ago
commit e38d3e8b8f

@ -17,6 +17,7 @@ linters:
- structcheck
- unused
- varcheck
- staticcheck
linters-settings:
gofmt:

@ -6,8 +6,8 @@
# Organizations Using Helm
- [Blood Orange](https://bloodorange.io)
- [Microsoft](https://microsoft.com)
- [IBM](https://www.ibm.com)
- [Microsoft](https://microsoft.com)
- [Qovery](https://www.qovery.com/)
- [Samsung SDS](https://www.samsungsds.com/)
- [Ville de Montreal](https://montreal.ca)

@ -106,7 +106,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
fmt.Fprint(&message, "\n")
}
fmt.Fprintf(out, message.String())
fmt.Fprint(out, message.String())
summary := fmt.Sprintf("%d chart(s) linted, %d chart(s) failed", len(paths), failed)
if failed > 0 {

@ -198,7 +198,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
func manuallyProcessArgs(args []string) ([]string, []string) {
known := []string{}
unknown := []string{}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--registry-config", "--repository-cache", "--repository-config"}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--registry-config", "--repository-cache", "--repository-config"}
knownArg := func(a string) bool {
for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") {

@ -20,7 +20,6 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"testing"
"helm.sh/helm/v3/pkg/repo/repotest"
@ -37,6 +36,10 @@ func TestPullCmd(t *testing.T) {
t.Fatal(err)
}
helmTestKeyOut := "Signed by: Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>\n" +
"Using Key With Fingerprint: 5E615389B53CA37F0EE60BD3843BBF981FC18762\n" +
"Chart Hash Verified: "
// all flags will get "-d outdir" appended.
tests := []struct {
name string
@ -49,6 +52,7 @@ func TestPullCmd(t *testing.T) {
expectFile string
expectDir bool
expectVerify bool
expectSha string
}{
{
name: "Basic chart fetch",
@ -77,6 +81,7 @@ func TestPullCmd(t *testing.T) {
args: "test/signtest --verify --keyring testdata/helm-test-key.pub",
expectFile: "./signtest-0.1.0.tgz",
expectVerify: true,
expectSha: "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55",
},
{
name: "Fetch and fail verify",
@ -110,6 +115,7 @@ func TestPullCmd(t *testing.T) {
expectFile: "./signtest2",
expectDir: true,
expectVerify: true,
expectSha: "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55",
},
{
name: "Chart fetch using repo URL",
@ -171,13 +177,11 @@ func TestPullCmd(t *testing.T) {
}
if tt.expectVerify {
pointerAddressPattern := "0[xX][A-Fa-f0-9]+"
sha256Pattern := "[A-Fa-f0-9]{64}"
verificationRegex := regexp.MustCompile(
fmt.Sprintf("Verification: &{%s sha256:%s signtest-0.1.0.tgz}\n", pointerAddressPattern, sha256Pattern))
if !verificationRegex.MatchString(out) {
t.Errorf("%q: expected match for regex %s, got %s", tt.name, verificationRegex, out)
outString := helmTestKeyOut + tt.expectSha + "\n"
if out != outString {
t.Errorf("%q: expected verification output %q, got %q", tt.name, outString, out)
}
}
ef := filepath.Join(outdir, tt.expectFile)

@ -43,9 +43,10 @@ type repoAddOptions struct {
password string
noUpdate bool
certFile string
keyFile string
caFile string
certFile string
keyFile string
caFile string
insecureSkipTLSverify bool
repoFile string
repoCache string
@ -75,6 +76,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
f.StringVar(&o.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&o.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")
return cmd
}
@ -113,13 +115,14 @@ func (o *repoAddOptions) run(out io.Writer) error {
}
c := repo.Entry{
Name: o.name,
URL: o.url,
Username: o.username,
Password: o.password,
CertFile: o.certFile,
KeyFile: o.keyFile,
CAFile: o.caFile,
Name: o.name,
URL: o.url,
Username: o.username,
Password: o.password,
CertFile: o.certFile,
KeyFile: o.keyFile,
CAFile: o.caFile,
InsecureSkipTLSverify: o.insecureSkipTLSverify,
}
r, err := repo.NewChartRepository(&c, getter.All(settings))

@ -26,7 +26,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/action"
@ -77,7 +76,6 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
Short: "The Helm package manager for Kubernetes.",
Long: globalUsage,
SilenceUsage: true,
Args: require.NoArgs,
BashCompletionFunction: completion.GetBashCustomFunction(),
}
flags := cmd.PersistentFlags()

@ -95,3 +95,11 @@ func TestRootCmd(t *testing.T) {
})
}
}
func TestUnknownSubCmd(t *testing.T) {
_, _, err := executeActionCommand("foobar")
if err == nil || err.Error() != `unknown command "foobar" for "helm"` {
t.Errorf("Expect unknown command error, got %q", err)
}
}

@ -63,57 +63,65 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.APIVersions = chartutil.VersionSet(extraAPIs)
client.IncludeCRDs = includeCrds
rel, err := runInstall(args, client, valueOpts, out)
if err != nil {
if err != nil && !settings.Debug {
if rel != nil {
return fmt.Errorf("%w\n\nUse --debug flag to render out invalid YAML", err)
}
return err
}
var manifests bytes.Buffer
fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest))
// We ignore a potential error here because, when the --debug flag was specified,
// we always want to print the YAML, even if it is not valid. The error is still returned afterwards.
if rel != nil {
var manifests bytes.Buffer
fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest))
if !client.DisableHooks {
for _, m := range rel.Hooks {
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest)
if !client.DisableHooks {
for _, m := range rel.Hooks {
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest)
}
}
}
// if we have a list of files to render, then check that each of the
// provided files exists in the chart.
if len(showFiles) > 0 {
splitManifests := releaseutil.SplitManifests(manifests.String())
manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)")
var manifestsToRender []string
for _, f := range showFiles {
missing := true
for _, manifest := range splitManifests {
submatch := manifestNameRegex.FindStringSubmatch(manifest)
if len(submatch) == 0 {
continue
// if we have a list of files to render, then check that each of the
// provided files exists in the chart.
if len(showFiles) > 0 {
splitManifests := releaseutil.SplitManifests(manifests.String())
manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)")
var manifestsToRender []string
for _, f := range showFiles {
missing := true
for _, manifest := range splitManifests {
submatch := manifestNameRegex.FindStringSubmatch(manifest)
if len(submatch) == 0 {
continue
}
manifestName := submatch[1]
// manifest.Name is rendered using linux-style filepath separators on Windows as
// well as macOS/linux.
manifestPathSplit := strings.Split(manifestName, "/")
manifestPath := filepath.Join(manifestPathSplit...)
// if the filepath provided matches a manifest path in the
// chart, render that manifest
if f == manifestPath {
manifestsToRender = append(manifestsToRender, manifest)
missing = false
}
}
manifestName := submatch[1]
// manifest.Name is rendered using linux-style filepath separators on Windows as
// well as macOS/linux.
manifestPathSplit := strings.Split(manifestName, "/")
manifestPath := filepath.Join(manifestPathSplit...)
// if the filepath provided matches a manifest path in the
// chart, render that manifest
if f == manifestPath {
manifestsToRender = append(manifestsToRender, manifest)
missing = false
if missing {
return fmt.Errorf("could not find template %s in chart", f)
}
}
if missing {
return fmt.Errorf("could not find template %s in chart", f)
for _, m := range manifestsToRender {
fmt.Fprintf(out, "---\n%s\n", m)
}
} else {
fmt.Fprintf(out, "%s", manifests.String())
}
for _, m := range manifestsToRender {
fmt.Fprintf(out, "---\n%s\n", m)
}
} else {
fmt.Fprintf(out, "%s", manifests.String())
}
return nil
return err
},
}

@ -102,6 +102,18 @@ func TestTemplateCmd(t *testing.T) {
// don't accidentally get the expected result.
repeat: 10,
},
{
name: "chart with template with invalid yaml",
cmd: fmt.Sprintf("template '%s'", "testdata/testcharts/chart-with-template-with-invalid-yaml"),
wantError: true,
golden: "output/template-with-invalid-yaml.txt",
},
{
name: "chart with template with invalid yaml (--debug)",
cmd: fmt.Sprintf("template '%s' --debug", "testdata/testcharts/chart-with-template-with-invalid-yaml"),
wantError: true,
golden: "output/template-with-invalid-yaml-debug.txt",
},
}
runTestCmd(t, tests)
}

@ -0,0 +1,13 @@
---
# Source: chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: "RELEASE-NAME-my-alpine"
spec:
containers:
- name: waiter
image: "alpine:3.9"
command: ["/bin/sleep","9000"]
invalid
Error: YAML parse error on chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml: error converting YAML to JSON: yaml: line 11: could not find expected ':'

@ -0,0 +1,3 @@
Error: YAML parse error on chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml: error converting YAML to JSON: yaml: line 11: could not find expected ':'
Use --debug flag to render out invalid YAML

@ -1,4 +1,4 @@
#Alpine: A simple Helm chart
# Alpine: A simple Helm chart
Run a single pod of Alpine Linux.

@ -0,0 +1,8 @@
apiVersion: v1
description: Deploy a basic Alpine Linux pod
home: https://helm.sh/helm
name: chart-with-template-with-invalid-yaml
sources:
- https://github.com/helm/helm
version: 0.1.0
type: application

@ -0,0 +1,13 @@
#Alpine: A simple Helm chart
Run a single pod of Alpine Linux.
This example was generated using the command `helm create alpine`.
The `templates/` directory contains a very simple pod resource with a
couple of parameters.
The `values.yaml` file contains the default values for the
`alpine-pod.yaml` template.
You can install this example using `helm install ./alpine`.

@ -0,0 +1,10 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{.Release.Name}}-{{.Values.Name}}"
spec:
containers:
- name: waiter
image: "alpine:3.9"
command: ["/bin/sleep","9000"]
invalid

@ -16,6 +16,7 @@ limitations under the License.
package main
import (
"fmt"
"io"
"github.com/spf13/cobra"
@ -44,7 +45,14 @@ func newVerifyCmd(out io.Writer) *cobra.Command {
Long: verifyDesc,
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return client.Run(args[0])
err := client.Run(args[0])
if err != nil {
return err
}
fmt.Fprint(out, client.Out)
return nil
},
}

@ -65,7 +65,7 @@ func TestVerifyCmd(t *testing.T) {
{
name: "verify validates a properly signed chart",
cmd: "verify testdata/testcharts/signtest-0.1.0.tgz --keyring testdata/helm-test-key.pub",
expect: "",
expect: "Signed by: Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>\nUsing Key With Fingerprint: 5E615389B53CA37F0EE60BD3843BBF981FC18762\nChart Hash Verified: sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\n",
wantError: false,
},
}

@ -29,6 +29,7 @@ require (
github.com/stretchr/testify v1.4.0
github.com/xeipuuv/gojsonschema v1.1.0
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc // indirect
k8s.io/api v0.17.3
k8s.io/apiextensions-apiserver v0.17.3
k8s.io/apimachinery v0.17.3

@ -462,7 +462,7 @@ func CompDebug(msg string) {
if debug {
// Must print to stderr for this not to be read by the completion script.
fmt.Fprintf(os.Stderr, msg)
fmt.Fprintln(os.Stderr, msg)
}
}
@ -483,7 +483,7 @@ func CompError(msg string) {
// If not already printed by the call to CompDebug().
if !debug {
// Must print to stderr for this not to be read by the completion script.
fmt.Fprintf(os.Stderr, msg)
fmt.Fprintln(os.Stderr, msg)
}
}

@ -162,13 +162,13 @@ func (suite *RegistryClientTestSuite) Test_2_LoadChart() {
// non-existent ref
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
suite.Nil(err)
ch, err := suite.RegistryClient.LoadChart(ref)
_, err = suite.RegistryClient.LoadChart(ref)
suite.NotNil(err)
// existing ref
ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
suite.Nil(err)
ch, err = suite.RegistryClient.LoadChart(ref)
ch, err := suite.RegistryClient.LoadChart(ref)
suite.Nil(err)
suite.Equal("testchart", ch.Metadata.Name)
suite.Equal("1.2.3", ch.Metadata.Version)

@ -101,7 +101,11 @@ func (p *Pull) Run(chartRef string) (string, error) {
}
if p.Verify {
fmt.Fprintf(&out, "Verification: %v\n", v)
for name := range v.SignedBy.Identities {
fmt.Fprintf(&out, "Signed by: %v\n", name)
}
fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", v.SignedBy.PrimaryKey.Fingerprint)
fmt.Fprintf(&out, "Chart Hash Verified: %s\n", v.FileHash)
}
// After verification, untar the chart into the requested directory.

@ -17,6 +17,9 @@ limitations under the License.
package action
import (
"fmt"
"strings"
"helm.sh/helm/v3/pkg/downloader"
)
@ -25,6 +28,7 @@ import (
// It provides the implementation of 'helm verify'.
type Verify struct {
Keyring string
Out string
}
// NewVerify creates a new Verify object with the given configuration.
@ -34,6 +38,22 @@ func NewVerify() *Verify {
// Run executes 'helm verify'.
func (v *Verify) Run(chartfile string) error {
_, err := downloader.VerifyChart(chartfile, v.Keyring)
return err
var out strings.Builder
p, err := downloader.VerifyChart(chartfile, v.Keyring)
if err != nil {
return err
}
for name := range p.SignedBy.Identities {
fmt.Fprintf(&out, "Signed by: %v\n", name)
}
fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", p.SignedBy.PrimaryKey.Fingerprint)
fmt.Fprintf(&out, "Chart Hash Verified: %s\n", p.FileHash)
// TODO(mattfarina): The output is set as a property rather than returned
// to maintain the Go API. In Helm v4 this function should return the out
// and the property on the struct can be removed.
v.Out = out.String()
return nil
}

@ -173,6 +173,14 @@ Loop:
cd = append(cd, n)
}
}
// don't keep disabled charts in metadata
cdMetadata := []*chart.Dependency{}
copy(cdMetadata, c.Metadata.Dependencies[:0])
for _, n := range c.Metadata.Dependencies {
if _, ok := rm[n.Name]; !ok {
cdMetadata = append(cdMetadata, n)
}
}
// recursively call self to process sub dependencies
for _, t := range cd {
@ -181,6 +189,9 @@ Loop:
return err
}
}
// set the correct dependencies in metadata
c.Metadata.Dependencies = nil
c.Metadata.Dependencies = append(c.Metadata.Dependencies, cdMetadata...)
c.SetDependencies(cd...)
return nil

@ -239,6 +239,36 @@ func TestProcessDependencyImportValues(t *testing.T) {
}
}
func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) {
c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart")
nameOverride := "parent-chart-prod"
if err := processDependencyImportValues(c); err != nil {
t.Fatalf("processing import values dependencies %v", err)
}
if len(c.Dependencies()) != 2 {
t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies()))
}
if err := processDependencyEnabled(c, c.Values, ""); err != nil {
t.Fatalf("expected no errors but got %q", err)
}
if len(c.Dependencies()) != 1 {
t.Fatal("expected no changes in dependencies")
}
if len(c.Metadata.Dependencies) != 1 {
t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies))
}
prodDependencyValues := c.Dependencies()[0].Values
if prodDependencyValues["nameOverride"] != nameOverride {
t.Fatalf("dependency chart name should be %s but got %s", nameOverride, prodDependencyValues["nameOverride"])
}
}
func TestGetAliasDependency(t *testing.T) {
c := loadChart(t, "testdata/frobnitz")
req := c.Metadata.Dependencies

@ -39,19 +39,6 @@ func TestExpand(t *testing.T) {
t.Fatal(err)
}
files, err := ioutil.ReadDir(dest)
if err != nil {
t.Fatalf("error reading output directory %s: %s", dest, err)
}
if len(files) != 1 {
t.Fatalf("expected a single chart directory in output directory %s", dest)
}
if !files[0].IsDir() {
t.Fatalf("expected a chart directory in output directory %s", dest)
}
expectedChartPath := filepath.Join(dest, "frobnitz")
fi, err := os.Stat(expectedChartPath)
if err != nil {
@ -81,8 +68,14 @@ func TestExpand(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if fi.Size() != expect.Size() {
t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size())
// os.Stat can return different values for directories, based on the OS
// for Linux, for example, os.Stat alwaty returns the size of the directory
// (value-4096) regardless of the size of the contents of the directory
mode := expect.Mode()
if !mode.IsDir() {
if fi.Size() != expect.Size() {
t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size())
}
}
}
}
@ -127,8 +120,14 @@ func TestExpandFile(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if fi.Size() != expect.Size() {
t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size())
// os.Stat can return different values for directories, based on the OS
// for Linux, for example, os.Stat alwaty returns the size of the directory
// (value-4096) regardless of the size of the contents of the directory
mode := expect.Mode()
if !mode.IsDir() {
if fi.Size() != expect.Size() {
t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size())
}
}
}
}

@ -161,6 +161,20 @@ func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error {
return err
}
// Save Chart.lock
// TODO: remove the APIVersion check when APIVersionV1 is not used anymore
if c.Metadata.APIVersion == chart.APIVersionV2 {
if c.Lock != nil {
ldata, err := yaml.Marshal(c.Lock)
if err != nil {
return err
}
if err := writeToTar(out, filepath.Join(base, "Chart.lock"), ldata); err != nil {
return err
}
}
}
// Save values.yaml
for _, f := range c.Raw {
if f.Name == ValuesfileName {

@ -49,6 +49,9 @@ func TestSave(t *testing.T) {
Name: "ahab",
Version: "1.2.3",
},
Lock: &chart.Lock{
Digest: "testdigest",
},
Files: []*chart.File{
{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")},
},
@ -77,6 +80,9 @@ func TestSave(t *testing.T) {
if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" {
t.Fatal("Files data did not match")
}
if c2.Lock != nil {
t.Fatal("Expected v1 chart archive not to contain Chart.lock file")
}
if !bytes.Equal(c.Schema, c2.Schema) {
indentation := 4
@ -87,6 +93,22 @@ func TestSave(t *testing.T) {
if _, err := Save(&chartWithInvalidJSON, dest); err == nil {
t.Fatalf("Invalid JSON was not caught while saving chart")
}
c.Metadata.APIVersion = chart.APIVersionV2
where, err = Save(c, dest)
if err != nil {
t.Fatalf("Failed to save: %s", err)
}
c2, err = loader.LoadFile(where)
if err != nil {
t.Fatal(err)
}
if c2.Lock == nil {
t.Fatal("Expected v2 chart archive to containe a Chart.lock file")
}
if c2.Lock.Digest != c.Lock.Digest {
t.Fatal("Chart.lock data did not match")
}
})
}
}

@ -0,0 +1,9 @@
dependencies:
- name: dev
repository: file://envs/dev
version: v0.1.0
- name: prod
repository: file://envs/prod
version: v0.1.0
digest: sha256:9403fc24f6cf9d6055820126cf7633b4bd1fed3c77e4880c674059f536346182
generated: "2020-02-03T10:38:51.180474+01:00"

@ -0,0 +1,22 @@
apiVersion: v2
name: parent-chart
version: v0.1.0
appVersion: v0.1.0
dependencies:
- name: dev
repository: "file://envs/dev"
version: ">= 0.0.1"
condition: dev.enabled,global.dev.enabled
tags:
- dev
import-values:
- data
- name: prod
repository: "file://envs/prod"
version: ">= 0.0.1"
condition: prod.enabled,global.prod.enabled
tags:
- prod
import-values:
- data

@ -0,0 +1,4 @@
apiVersion: v2
name: dev
version: v0.1.0
appVersion: v0.1.0

@ -0,0 +1,9 @@
# Dev values parent-chart
nameOverride: parent-chart-dev
exports:
data:
resources:
autoscaler:
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 80

@ -0,0 +1,4 @@
apiVersion: v2
name: prod
version: v0.1.0
appVersion: v0.1.0

@ -0,0 +1,9 @@
# Prod values parent-chart
nameOverride: parent-chart-prod
exports:
data:
resources:
autoscaler:
minReplicas: 2
maxReplicas: 5
targetCPUUtilizationPercentage: 90

@ -0,0 +1,16 @@
###################################################################################################
# parent-chart horizontal pod autoscaler
###################################################################################################
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: {{ .Release.Name }}-autoscaler
namespace: {{ .Release.Namespace }}
spec:
scaleTargetRef:
apiVersion: apps/v1beta1
kind: Deployment
name: {{ .Release.Name }}
minReplicas: {{ required "A valid .Values.resources.autoscaler.minReplicas entry required!" .Values.resources.autoscaler.minReplicas }}
maxReplicas: {{ required "A valid .Values.resources.autoscaler.maxReplicas entry required!" .Values.resources.autoscaler.maxReplicas }}
targetCPUUtilizationPercentage: {{ required "A valid .Values.resources.autoscaler.targetCPUUtilizationPercentage!" .Values.resources.autoscaler.targetCPUUtilizationPercentage }}

@ -0,0 +1,10 @@
# Default values for parent-chart.
nameOverride: parent-chart
tags:
dev: false
prod: true
resources:
autoscaler:
minReplicas: 0
maxReplicas: 0
targetCPUUtilizationPercentage: 99

@ -46,6 +46,10 @@ type EnvSettings struct {
KubeConfig string
// KubeContext is the name of the kubeconfig context.
KubeContext string
// Bearer KubeToken used for authentication
KubeToken string
// Kubernetes API Server Endpoint for authentication
KubeAPIServer string
// Debug indicates whether or not Helm is running in Debug mode.
Debug bool
// RegistryConfig is the path to the registry config file.
@ -63,6 +67,8 @@ func New() *EnvSettings {
env := EnvSettings{
namespace: os.Getenv("HELM_NAMESPACE"),
KubeContext: os.Getenv("HELM_KUBECONTEXT"),
KubeToken: os.Getenv("HELM_KUBETOKEN"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry.json")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
@ -77,6 +83,8 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.StringVarP(&s.namespace, "namespace", "n", s.namespace, "namespace scope for this request")
fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file")
fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use")
fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication")
fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server")
fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output")
fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file")
fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs")
@ -100,6 +108,8 @@ func (s *EnvSettings) EnvVars() map[string]string {
"HELM_REPOSITORY_CONFIG": s.RepositoryConfig,
"HELM_NAMESPACE": s.Namespace(),
"HELM_KUBECONTEXT": s.KubeContext,
"HELM_KUBETOKEN": s.KubeToken,
"HELM_KUBEAPISERVER": s.KubeAPIServer,
}
if s.KubeConfig != "" {
@ -124,7 +134,15 @@ func (s *EnvSettings) Namespace() string {
//RESTClientGetter gets the kubeconfig from EnvSettings
func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter {
s.configOnce.Do(func() {
s.config = kube.GetConfig(s.KubeConfig, s.KubeContext, s.namespace)
clientConfig := kube.GetConfig(s.KubeConfig, s.KubeContext, s.namespace)
if s.KubeToken != "" {
clientConfig.BearerToken = &s.KubeToken
}
if s.KubeAPIServer != "" {
clientConfig.APIServer = &s.KubeAPIServer
}
s.config = clientConfig
})
return s.config
}

@ -28,13 +28,14 @@ import (
//
// Getters may or may not ignore these parameters as they are passed in.
type options struct {
url string
certFile string
keyFile string
caFile string
username string
password string
userAgent string
url string
certFile string
keyFile string
caFile string
insecureSkipVerifyTLS bool
username string
password string
userAgent string
}
// Option allows specifying various settings configurable by the user for overriding the defaults
@ -64,6 +65,13 @@ func WithUserAgent(userAgent string) Option {
}
}
// WithInsecureSkipVerifyTLS determines if a TLS Certificate will be checked
func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) Option {
return func(opts *options) {
opts.insecureSkipVerifyTLS = insecureSkipVerifyTLS
}
}
// WithTLSClientConfig sets the client auth with the provided credentials.
func WithTLSClientConfig(certFile, keyFile, caFile string) Option {
return func(opts *options) {

@ -17,6 +17,7 @@ package getter
import (
"bytes"
"crypto/tls"
"io"
"net/http"
@ -111,5 +112,19 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
return client, nil
}
if g.opts.insecureSkipVerifyTLS {
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
Proxy: http.ProxyFromEnvironment,
},
}
return client, nil
}
return http.DefaultClient, nil
}

@ -44,12 +44,14 @@ func TestHTTPGetter(t *testing.T) {
cd := "../../testdata"
join := filepath.Join
ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem")
insecure := false
// Test with options
g, err = NewHTTPGetter(
WithBasicAuth("I", "Am"),
WithUserAgent("Groot"),
WithTLSClientConfig(pub, priv, ca),
WithInsecureSkipVerifyTLS(insecure),
)
if err != nil {
t.Fatal(err)
@ -83,6 +85,29 @@ func TestHTTPGetter(t *testing.T) {
if hg.opts.caFile != ca {
t.Errorf("Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile)
}
if hg.opts.insecureSkipVerifyTLS != insecure {
t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", false, hg.opts.insecureSkipVerifyTLS)
}
// Test if setting insecureSkipVerifyTLS is being passed to the ops
insecure = true
g, err = NewHTTPGetter(
WithInsecureSkipVerifyTLS(insecure),
)
if err != nil {
t.Fatal(err)
}
hg, ok = g.(*HTTPGetter)
if !ok {
t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter")
}
if hg.opts.insecureSkipVerifyTLS != insecure {
t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS)
}
}
func TestDownload(t *testing.T) {
@ -191,3 +216,35 @@ func TestDownloadTLS(t *testing.T) {
t.Error(err)
}
}
func TestDownloadInsecureSkipTLSVerify(t *testing.T) {
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
defer ts.Close()
u, _ := url.ParseRequestURI(ts.URL)
// Ensure the default behaviour did not change
g, err := NewHTTPGetter(
WithURL(u.String()),
)
if err != nil {
t.Error(err)
}
if _, err := g.Get(u.String()); err == nil {
t.Errorf("Expected Getter to throw an error, got %s", err)
}
// Test certificate check skip
g, err = NewHTTPGetter(
WithURL(u.String()),
WithInsecureSkipVerifyTLS(true),
)
if err != nil {
t.Error(err)
}
if _, err = g.Get(u.String()); err != nil {
t.Error(err)
}
}

@ -33,6 +33,7 @@ import (
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime"
@ -50,6 +51,8 @@ import (
// ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found.
var ErrNoObjectsVisited = errors.New("no objects visited")
var metadataAccessor = meta.NewAccessor()
// Client represents a client capable of communicating with the Kubernetes API.
type Client struct {
Factory Factory
@ -210,6 +213,19 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
for _, info := range original.Difference(target) {
c.Log("Deleting %q in %s...", info.Name, info.Namespace)
if err := info.Get(); err != nil {
c.Log("Unable to get obj %q, err: %s", info.Name, err)
}
annotations, err := metadataAccessor.Annotations(info.Object)
if err != nil {
c.Log("Unable to get annotations on %q, err: %s", info.Name, err)
}
if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy {
c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy)
continue
}
res.Deleted = append(res.Deleted, info)
if err := deleteResource(info); err != nil {
if apierrors.IsNotFound(err) {

@ -147,6 +147,8 @@ func TestUpdate(t *testing.T) {
return newResponse(200, &listB.Items[1])
case p == "/namespaces/default/pods/squid" && m == "DELETE":
return newResponse(200, &listB.Items[1])
case p == "/namespaces/default/pods/squid" && m == "GET":
return newResponse(200, &listB.Items[2])
default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil
@ -184,6 +186,7 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods/otter:GET",
"/namespaces/default/pods/dolphin:GET",
"/namespaces/default/pods:POST",
"/namespaces/default/pods/squid:GET",
"/namespaces/default/pods/squid:DELETE",
}
if len(expectedActions) != len(actions) {

@ -0,0 +1,26 @@
/*
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 kube // import "helm.sh/helm/v3/pkg/kube"
// ResourcePolicyAnno is the annotation name for a resource policy
const ResourcePolicyAnno = "helm.sh/resource-policy"
// KeepPolicy is the resource policy type for keep
//
// This resource policy type allows resources to skip being deleted
// during an uninstallRelease action.
const KeepPolicy = "keep"

@ -116,11 +116,9 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
// key will be raised as well
err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct)
validYaml := linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err))
if !validYaml {
continue
}
// If YAML linting fails, we sill progress. So we don't capture the returned state
// on this linter run.
linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err))
}
}

@ -19,8 +19,10 @@ package repo // import "helm.sh/helm/v3/pkg/repo"
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/url"
"os"
"path"
@ -38,13 +40,14 @@ import (
// Entry represents a collection of parameters for chart repository
type Entry struct {
Name string `json:"name"`
URL string `json:"url"`
Username string `json:"username"`
Password string `json:"password"`
CertFile string `json:"certFile"`
KeyFile string `json:"keyFile"`
CAFile string `json:"caFile"`
Name string `json:"name"`
URL string `json:"url"`
Username string `json:"username"`
Password string `json:"password"`
CertFile string `json:"certFile"`
KeyFile string `json:"keyFile"`
CAFile string `json:"caFile"`
InsecureSkipTLSverify bool `json:"insecure_skip_tls_verify"`
}
// ChartRepository represents a chart repository
@ -121,6 +124,7 @@ func (r *ChartRepository) DownloadIndexFile() (string, error) {
// TODO add user-agent
resp, err := r.Client.Get(indexURL,
getter.WithURL(r.Config.URL),
getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify),
getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile),
getter.WithBasicAuth(r.Config.Username, r.Config.Password),
)
@ -271,3 +275,11 @@ func ResolveReferenceURL(baseURL, refURL string) (string, error) {
parsedBaseURL.Path = strings.TrimSuffix(parsedBaseURL.Path, "/") + "/"
return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil
}
func (e *Entry) String() string {
buf, err := json.Marshal(e)
if err != nil {
log.Panic(err)
}
return string(buf)
}

@ -184,3 +184,28 @@ func TestConfigMapUpdate(t *testing.T) {
t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String())
}
}
func TestConfigMapDelete(t *testing.T) {
vers := 1
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)
// perform the delete
rls, err := cfgmaps.Delete(key)
if err != nil {
t.Fatalf("Failed to delete release with key %q: %s", key, err)
}
if !reflect.DeepEqual(rel, rls) {
t.Errorf("Expected {%v}, got {%v}", rel, rls)
}
// fetch the deleted release
_, err = cfgmaps.Get(key)
if !reflect.DeepEqual(ErrReleaseNotFound, err) {
t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err)
}
}

@ -204,3 +204,37 @@ func TestRecordsExists(t *testing.T) {
}
}
}
func TestRecordsReplace(t *testing.T) {
rs := records([]*record{
newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
})
var tests = []struct {
desc string
key string
rec *record
expected *record
}{
{
"replace with existing key",
"rls-a.v2",
newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", rspb.StatusSuperseded)),
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)),
},
{
"replace with non existing key",
"rls-a.v4",
newRecord("rls-a.v4", releaseStub("rls-a", 4, "default", rspb.StatusDeployed)),
nil,
},
}
for _, tt := range tests {
got := rs.Replace(tt.key, tt.rec)
if !reflect.DeepEqual(tt.expected, got) {
t.Fatalf("Expected %v, got %v", tt.expected, got)
}
}
}

@ -184,3 +184,28 @@ func TestSecretUpdate(t *testing.T) {
t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String())
}
}
func TestSecretDelete(t *testing.T) {
vers := 1
name := "smug-pigeon"
namespace := "default"
key := testKey(name, vers)
rel := releaseStub(name, vers, namespace, rspb.StatusDeployed)
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)
// perform the delete
rls, err := secrets.Delete(key)
if err != nil {
t.Fatalf("Failed to delete release with key %q: %s", key, err)
}
if !reflect.DeepEqual(rel, rls) {
t.Errorf("Expected {%v}, got {%v}", rel, rls)
}
// fetch the deleted release
_, err = secrets.Get(key)
if !reflect.DeepEqual(ErrReleaseNotFound, err) {
t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err)
}
}

Loading…
Cancel
Save