diff --git a/.golangci.yml b/.golangci.yml index 2c3b6234d..491e648a1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,6 +17,7 @@ linters: - structcheck - unused - varcheck + - staticcheck linters-settings: gofmt: diff --git a/ADOPTERS.md b/ADOPTERS.md index a72f51e09..46b42b8a0 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -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) diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index bc0d1852b..fe39a5741 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -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 { diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index 358679302..e56feab40 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -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+"=") { diff --git a/cmd/helm/pull_test.go b/cmd/helm/pull_test.go index 1aca66100..d4661f928 100644 --- a/cmd/helm/pull_test.go +++ b/cmd/helm/pull_test.go @@ -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.) \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) diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index e6afce3d5..3d36fd0ed 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -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)) diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 3c6a0d18e..3ebea3bae 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -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() diff --git a/cmd/helm/root_test.go b/cmd/helm/root_test.go index df592a96d..e1fa1fc27 100644 --- a/cmd/helm/root_test.go +++ b/cmd/helm/root_test.go @@ -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) + } +} diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 91a398429..bd14cde1d 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -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 }, } diff --git a/cmd/helm/template_test.go b/cmd/helm/template_test.go index dc7987d01..3fd139fad 100644 --- a/cmd/helm/template_test.go +++ b/cmd/helm/template_test.go @@ -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) } diff --git a/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt b/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt new file mode 100644 index 000000000..c1f51185c --- /dev/null +++ b/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt @@ -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 ':' diff --git a/cmd/helm/testdata/output/template-with-invalid-yaml.txt b/cmd/helm/testdata/output/template-with-invalid-yaml.txt new file mode 100644 index 000000000..687227b90 --- /dev/null +++ b/cmd/helm/testdata/output/template-with-invalid-yaml.txt @@ -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 diff --git a/cmd/helm/testdata/testcharts/alpine/README.md b/cmd/helm/testdata/testcharts/alpine/README.md index fcf7ee017..05d39dbbc 100644 --- a/cmd/helm/testdata/testcharts/alpine/README.md +++ b/cmd/helm/testdata/testcharts/alpine/README.md @@ -1,4 +1,4 @@ -#Alpine: A simple Helm chart +# Alpine: A simple Helm chart Run a single pod of Alpine Linux. diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml new file mode 100644 index 000000000..29b477b06 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/Chart.yaml @@ -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 diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md new file mode 100644 index 000000000..fcf7ee017 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/README.md @@ -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`. diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml new file mode 100644 index 000000000..697cb50fe --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml @@ -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 diff --git a/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml new file mode 100644 index 000000000..807e12aea --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-template-with-invalid-yaml/values.yaml @@ -0,0 +1 @@ +Name: my-alpine diff --git a/cmd/helm/verify.go b/cmd/helm/verify.go index d3ae517c9..f26fb377f 100644 --- a/cmd/helm/verify.go +++ b/cmd/helm/verify.go @@ -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 }, } diff --git a/cmd/helm/verify_test.go b/cmd/helm/verify_test.go index a70051ff6..ccbcb3cf2 100644 --- a/cmd/helm/verify_test.go +++ b/cmd/helm/verify_test.go @@ -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.) \nUsing Key With Fingerprint: 5E615389B53CA37F0EE60BD3843BBF981FC18762\nChart Hash Verified: sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\n", wantError: false, }, } diff --git a/go.mod b/go.mod index 7ba7a5542..4e3bcf9a1 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/internal/completion/complete.go b/internal/completion/complete.go index 0de786844..d8a354797 100644 --- a/internal/completion/complete.go +++ b/internal/completion/complete.go @@ -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) } } diff --git a/internal/experimental/registry/client_test.go b/internal/experimental/registry/client_test.go index 33799f5fa..6e9d5db36 100644 --- a/internal/experimental/registry/client_test.go +++ b/internal/experimental/registry/client_test.go @@ -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) diff --git a/pkg/action/pull.go b/pkg/action/pull.go index 4ff5f5c3e..ee20bbe83 100644 --- a/pkg/action/pull.go +++ b/pkg/action/pull.go @@ -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. diff --git a/pkg/action/verify.go b/pkg/action/verify.go index c66b14b47..f36239496 100644 --- a/pkg/action/verify.go +++ b/pkg/action/verify.go @@ -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 } diff --git a/pkg/chartutil/dependencies.go b/pkg/chartutil/dependencies.go index 521e2abc4..d2e7d6dc9 100644 --- a/pkg/chartutil/dependencies.go +++ b/pkg/chartutil/dependencies.go @@ -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 diff --git a/pkg/chartutil/dependencies_test.go b/pkg/chartutil/dependencies_test.go index ecd632540..342d7fe87 100644 --- a/pkg/chartutil/dependencies_test.go +++ b/pkg/chartutil/dependencies_test.go @@ -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 diff --git a/pkg/chartutil/expand_test.go b/pkg/chartutil/expand_test.go index 0eb35aedb..9a85e3247 100644 --- a/pkg/chartutil/expand_test.go +++ b/pkg/chartutil/expand_test.go @@ -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()) + } } } } diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go index be0dfdc24..a2c6a9225 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chartutil/save.go @@ -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 { diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go index f367d42eb..306c13cee 100644 --- a/pkg/chartutil/save_test.go +++ b/pkg/chartutil/save_test.go @@ -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") + } }) } } diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock new file mode 100644 index 000000000..b2f17fb39 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock @@ -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" diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml new file mode 100644 index 000000000..24b26d9e5 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml @@ -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 \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz new file mode 100644 index 000000000..d28e1621c Binary files /dev/null and b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz differ diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz new file mode 100644 index 000000000..a0c5aa84b Binary files /dev/null and b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz differ diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml new file mode 100644 index 000000000..80a52f538 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: dev +version: v0.1.0 +appVersion: v0.1.0 \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml new file mode 100644 index 000000000..38f03484d --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml @@ -0,0 +1,9 @@ +# Dev values parent-chart +nameOverride: parent-chart-dev +exports: + data: + resources: + autoscaler: + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml new file mode 100644 index 000000000..bda4be458 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: prod +version: v0.1.0 +appVersion: v0.1.0 \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml new file mode 100644 index 000000000..10cc756b2 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml @@ -0,0 +1,9 @@ +# Prod values parent-chart +nameOverride: parent-chart-prod +exports: + data: + resources: + autoscaler: + minReplicas: 2 + maxReplicas: 5 + targetCPUUtilizationPercentage: 90 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml new file mode 100644 index 000000000..976e5a8f1 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml @@ -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 }} \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml new file mode 100644 index 000000000..b812f0a33 --- /dev/null +++ b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml @@ -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 \ No newline at end of file diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 5f947aec7..e279331b0 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -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 } diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index 68638c2ca..4ccc74834 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -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) { diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 5b476ff2d..695a87743 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -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 } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index b20085574..a1288bf47 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -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) + } + +} diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 31dabcc5d..b761c6d12 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -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) { diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 9e7581d00..aa081423c 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -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) { diff --git a/pkg/kube/resource_policy.go b/pkg/kube/resource_policy.go new file mode 100644 index 000000000..5f391eb50 --- /dev/null +++ b/pkg/kube/resource_policy.go @@ -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" diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 5c6cd7336..3d388f81b 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -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)) } } diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index 38b6b8fb0..c2c366a1e 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -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) +} diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index 2aa38f284..a36cee1be 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -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) + } +} diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go index 3ede92bcd..0a27839cc 100644 --- a/pkg/storage/driver/records_test.go +++ b/pkg/storage/driver/records_test.go @@ -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) + } + } +} diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go index 892482e5b..e4420704d 100644 --- a/pkg/storage/driver/secrets_test.go +++ b/pkg/storage/driver/secrets_test.go @@ -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) + } +}