Merge branch 'master' into feat/fishCompletion

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

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

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

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

@ -198,7 +198,7 @@ func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
func manuallyProcessArgs(args []string) ([]string, []string) { func manuallyProcessArgs(args []string) ([]string, []string) {
known := []string{} known := []string{}
unknown := []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 { knownArg := func(a string) bool {
for _, pre := range kvargs { for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") { if strings.HasPrefix(a, pre+"=") {

@ -20,7 +20,6 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"testing" "testing"
"helm.sh/helm/v3/pkg/repo/repotest" "helm.sh/helm/v3/pkg/repo/repotest"
@ -37,6 +36,10 @@ func TestPullCmd(t *testing.T) {
t.Fatal(err) 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. // all flags will get "-d outdir" appended.
tests := []struct { tests := []struct {
name string name string
@ -49,6 +52,7 @@ func TestPullCmd(t *testing.T) {
expectFile string expectFile string
expectDir bool expectDir bool
expectVerify bool expectVerify bool
expectSha string
}{ }{
{ {
name: "Basic chart fetch", name: "Basic chart fetch",
@ -77,6 +81,7 @@ func TestPullCmd(t *testing.T) {
args: "test/signtest --verify --keyring testdata/helm-test-key.pub", args: "test/signtest --verify --keyring testdata/helm-test-key.pub",
expectFile: "./signtest-0.1.0.tgz", expectFile: "./signtest-0.1.0.tgz",
expectVerify: true, expectVerify: true,
expectSha: "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55",
}, },
{ {
name: "Fetch and fail verify", name: "Fetch and fail verify",
@ -110,6 +115,7 @@ func TestPullCmd(t *testing.T) {
expectFile: "./signtest2", expectFile: "./signtest2",
expectDir: true, expectDir: true,
expectVerify: true, expectVerify: true,
expectSha: "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55",
}, },
{ {
name: "Chart fetch using repo URL", name: "Chart fetch using repo URL",
@ -171,13 +177,11 @@ func TestPullCmd(t *testing.T) {
} }
if tt.expectVerify { if tt.expectVerify {
pointerAddressPattern := "0[xX][A-Fa-f0-9]+" outString := helmTestKeyOut + tt.expectSha + "\n"
sha256Pattern := "[A-Fa-f0-9]{64}" if out != outString {
verificationRegex := regexp.MustCompile( t.Errorf("%q: expected verification output %q, got %q", tt.name, outString, out)
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)
} }
} }
ef := filepath.Join(outdir, tt.expectFile) ef := filepath.Join(outdir, tt.expectFile)

@ -43,9 +43,10 @@ type repoAddOptions struct {
password string password string
noUpdate bool noUpdate bool
certFile string certFile string
keyFile string keyFile string
caFile string caFile string
insecureSkipTLSverify bool
repoFile string repoFile string
repoCache 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.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.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.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 return cmd
} }
@ -113,13 +115,14 @@ func (o *repoAddOptions) run(out io.Writer) error {
} }
c := repo.Entry{ c := repo.Entry{
Name: o.name, Name: o.name,
URL: o.url, URL: o.url,
Username: o.username, Username: o.username,
Password: o.password, Password: o.password,
CertFile: o.certFile, CertFile: o.certFile,
KeyFile: o.keyFile, KeyFile: o.keyFile,
CAFile: o.caFile, CAFile: o.caFile,
InsecureSkipTLSverify: o.insecureSkipTLSverify,
} }
r, err := repo.NewChartRepository(&c, getter.All(settings)) r, err := repo.NewChartRepository(&c, getter.All(settings))

@ -26,7 +26,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"helm.sh/helm/v3/cmd/helm/require"
"helm.sh/helm/v3/internal/completion" "helm.sh/helm/v3/internal/completion"
"helm.sh/helm/v3/internal/experimental/registry" "helm.sh/helm/v3/internal/experimental/registry"
"helm.sh/helm/v3/pkg/action" "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.", Short: "The Helm package manager for Kubernetes.",
Long: globalUsage, Long: globalUsage,
SilenceUsage: true, SilenceUsage: true,
Args: require.NoArgs,
BashCompletionFunction: completion.GetBashCustomFunction(), BashCompletionFunction: completion.GetBashCustomFunction(),
} }
flags := cmd.PersistentFlags() 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.APIVersions = chartutil.VersionSet(extraAPIs)
client.IncludeCRDs = includeCrds client.IncludeCRDs = includeCrds
rel, err := runInstall(args, client, valueOpts, out) 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 return err
} }
var manifests bytes.Buffer // We ignore a potential error here because, when the --debug flag was specified,
fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest)) // 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 { if !client.DisableHooks {
for _, m := range rel.Hooks { for _, m := range rel.Hooks {
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest) 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 // if we have a list of files to render, then check that each of the
// provided files exists in the chart. // provided files exists in the chart.
if len(showFiles) > 0 { if len(showFiles) > 0 {
splitManifests := releaseutil.SplitManifests(manifests.String()) splitManifests := releaseutil.SplitManifests(manifests.String())
manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)") manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)")
var manifestsToRender []string var manifestsToRender []string
for _, f := range showFiles { for _, f := range showFiles {
missing := true missing := true
for _, manifest := range splitManifests { for _, manifest := range splitManifests {
submatch := manifestNameRegex.FindStringSubmatch(manifest) submatch := manifestNameRegex.FindStringSubmatch(manifest)
if len(submatch) == 0 { if len(submatch) == 0 {
continue 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] if missing {
// manifest.Name is rendered using linux-style filepath separators on Windows as return fmt.Errorf("could not find template %s in chart", f)
// 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 { for _, m := range manifestsToRender {
return fmt.Errorf("could not find template %s in chart", f) 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. // don't accidentally get the expected result.
repeat: 10, 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) 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. 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 package main
import ( import (
"fmt"
"io" "io"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -44,7 +45,14 @@ func newVerifyCmd(out io.Writer) *cobra.Command {
Long: verifyDesc, Long: verifyDesc,
Args: require.ExactArgs(1), Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { 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", name: "verify validates a properly signed chart",
cmd: "verify testdata/testcharts/signtest-0.1.0.tgz --keyring testdata/helm-test-key.pub", 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, wantError: false,
}, },
} }

@ -29,6 +29,7 @@ require (
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/xeipuuv/gojsonschema v1.1.0 github.com/xeipuuv/gojsonschema v1.1.0
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d 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/api v0.17.3
k8s.io/apiextensions-apiserver v0.17.3 k8s.io/apiextensions-apiserver v0.17.3
k8s.io/apimachinery v0.17.3 k8s.io/apimachinery v0.17.3

@ -462,7 +462,7 @@ func CompDebug(msg string) {
if debug { if debug {
// Must print to stderr for this not to be read by the completion script. // 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 not already printed by the call to CompDebug().
if !debug { if !debug {
// Must print to stderr for this not to be read by the completion script. // 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 // non-existent ref
ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost)) ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost))
suite.Nil(err) suite.Nil(err)
ch, err := suite.RegistryClient.LoadChart(ref) _, err = suite.RegistryClient.LoadChart(ref)
suite.NotNil(err) suite.NotNil(err)
// existing ref // existing ref
ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost)) ref, err = ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost))
suite.Nil(err) suite.Nil(err)
ch, err = suite.RegistryClient.LoadChart(ref) ch, err := suite.RegistryClient.LoadChart(ref)
suite.Nil(err) suite.Nil(err)
suite.Equal("testchart", ch.Metadata.Name) suite.Equal("testchart", ch.Metadata.Name)
suite.Equal("1.2.3", ch.Metadata.Version) suite.Equal("1.2.3", ch.Metadata.Version)

@ -101,7 +101,11 @@ func (p *Pull) Run(chartRef string) (string, error) {
} }
if p.Verify { 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. // After verification, untar the chart into the requested directory.

@ -17,6 +17,9 @@ limitations under the License.
package action package action
import ( import (
"fmt"
"strings"
"helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v3/pkg/downloader"
) )
@ -25,6 +28,7 @@ import (
// It provides the implementation of 'helm verify'. // It provides the implementation of 'helm verify'.
type Verify struct { type Verify struct {
Keyring string Keyring string
Out string
} }
// NewVerify creates a new Verify object with the given configuration. // NewVerify creates a new Verify object with the given configuration.
@ -34,6 +38,22 @@ func NewVerify() *Verify {
// Run executes 'helm verify'. // Run executes 'helm verify'.
func (v *Verify) Run(chartfile string) error { func (v *Verify) Run(chartfile string) error {
_, err := downloader.VerifyChart(chartfile, v.Keyring) var out strings.Builder
return err 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) 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 // recursively call self to process sub dependencies
for _, t := range cd { for _, t := range cd {
@ -181,6 +189,9 @@ Loop:
return err return err
} }
} }
// set the correct dependencies in metadata
c.Metadata.Dependencies = nil
c.Metadata.Dependencies = append(c.Metadata.Dependencies, cdMetadata...)
c.SetDependencies(cd...) c.SetDependencies(cd...)
return nil 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) { func TestGetAliasDependency(t *testing.T) {
c := loadChart(t, "testdata/frobnitz") c := loadChart(t, "testdata/frobnitz")
req := c.Metadata.Dependencies req := c.Metadata.Dependencies

@ -39,19 +39,6 @@ func TestExpand(t *testing.T) {
t.Fatal(err) 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") expectedChartPath := filepath.Join(dest, "frobnitz")
fi, err := os.Stat(expectedChartPath) fi, err := os.Stat(expectedChartPath)
if err != nil { if err != nil {
@ -81,8 +68,14 @@ func TestExpand(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if fi.Size() != expect.Size() { // os.Stat can return different values for directories, based on the OS
t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) // 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if fi.Size() != expect.Size() { // os.Stat can return different values for directories, based on the OS
t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) // 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 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 // Save values.yaml
for _, f := range c.Raw { for _, f := range c.Raw {
if f.Name == ValuesfileName { if f.Name == ValuesfileName {

@ -49,6 +49,9 @@ func TestSave(t *testing.T) {
Name: "ahab", Name: "ahab",
Version: "1.2.3", Version: "1.2.3",
}, },
Lock: &chart.Lock{
Digest: "testdigest",
},
Files: []*chart.File{ Files: []*chart.File{
{Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, {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" { if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" {
t.Fatal("Files data did not match") 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) { if !bytes.Equal(c.Schema, c2.Schema) {
indentation := 4 indentation := 4
@ -87,6 +93,22 @@ func TestSave(t *testing.T) {
if _, err := Save(&chartWithInvalidJSON, dest); err == nil { if _, err := Save(&chartWithInvalidJSON, dest); err == nil {
t.Fatalf("Invalid JSON was not caught while saving chart") 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 KubeConfig string
// KubeContext is the name of the kubeconfig context. // KubeContext is the name of the kubeconfig context.
KubeContext string 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 indicates whether or not Helm is running in Debug mode.
Debug bool Debug bool
// RegistryConfig is the path to the registry config file. // RegistryConfig is the path to the registry config file.
@ -63,6 +67,8 @@ func New() *EnvSettings {
env := EnvSettings{ env := EnvSettings{
namespace: os.Getenv("HELM_NAMESPACE"), namespace: os.Getenv("HELM_NAMESPACE"),
KubeContext: os.Getenv("HELM_KUBECONTEXT"), KubeContext: os.Getenv("HELM_KUBECONTEXT"),
KubeToken: os.Getenv("HELM_KUBETOKEN"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry.json")), RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry.json")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), 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.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.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.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.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.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") 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_REPOSITORY_CONFIG": s.RepositoryConfig,
"HELM_NAMESPACE": s.Namespace(), "HELM_NAMESPACE": s.Namespace(),
"HELM_KUBECONTEXT": s.KubeContext, "HELM_KUBECONTEXT": s.KubeContext,
"HELM_KUBETOKEN": s.KubeToken,
"HELM_KUBEAPISERVER": s.KubeAPIServer,
} }
if s.KubeConfig != "" { if s.KubeConfig != "" {
@ -124,7 +134,15 @@ func (s *EnvSettings) Namespace() string {
//RESTClientGetter gets the kubeconfig from EnvSettings //RESTClientGetter gets the kubeconfig from EnvSettings
func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter {
s.configOnce.Do(func() { 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 return s.config
} }

@ -28,13 +28,14 @@ import (
// //
// Getters may or may not ignore these parameters as they are passed in. // Getters may or may not ignore these parameters as they are passed in.
type options struct { type options struct {
url string url string
certFile string certFile string
keyFile string keyFile string
caFile string caFile string
username string insecureSkipVerifyTLS bool
password string username string
userAgent string password string
userAgent string
} }
// Option allows specifying various settings configurable by the user for overriding the defaults // 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. // WithTLSClientConfig sets the client auth with the provided credentials.
func WithTLSClientConfig(certFile, keyFile, caFile string) Option { func WithTLSClientConfig(certFile, keyFile, caFile string) Option {
return func(opts *options) { return func(opts *options) {

@ -17,6 +17,7 @@ package getter
import ( import (
"bytes" "bytes"
"crypto/tls"
"io" "io"
"net/http" "net/http"
@ -111,5 +112,19 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
return client, nil 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 return http.DefaultClient, nil
} }

@ -44,12 +44,14 @@ func TestHTTPGetter(t *testing.T) {
cd := "../../testdata" cd := "../../testdata"
join := filepath.Join join := filepath.Join
ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem") ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem")
insecure := false
// Test with options // Test with options
g, err = NewHTTPGetter( g, err = NewHTTPGetter(
WithBasicAuth("I", "Am"), WithBasicAuth("I", "Am"),
WithUserAgent("Groot"), WithUserAgent("Groot"),
WithTLSClientConfig(pub, priv, ca), WithTLSClientConfig(pub, priv, ca),
WithInsecureSkipVerifyTLS(insecure),
) )
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -83,6 +85,29 @@ func TestHTTPGetter(t *testing.T) {
if hg.opts.caFile != ca { if hg.opts.caFile != ca {
t.Errorf("Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile) 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) { func TestDownload(t *testing.T) {
@ -191,3 +216,35 @@ func TestDownloadTLS(t *testing.T) {
t.Error(err) 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" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -50,6 +51,8 @@ import (
// ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found.
var ErrNoObjectsVisited = errors.New("no objects visited") var ErrNoObjectsVisited = errors.New("no objects visited")
var metadataAccessor = meta.NewAccessor()
// Client represents a client capable of communicating with the Kubernetes API. // Client represents a client capable of communicating with the Kubernetes API.
type Client struct { type Client struct {
Factory Factory Factory Factory
@ -210,6 +213,19 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err
for _, info := range original.Difference(target) { for _, info := range original.Difference(target) {
c.Log("Deleting %q in %s...", info.Name, info.Namespace) 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) res.Deleted = append(res.Deleted, info)
if err := deleteResource(info); err != nil { if err := deleteResource(info); err != nil {
if apierrors.IsNotFound(err) { if apierrors.IsNotFound(err) {

@ -147,6 +147,8 @@ func TestUpdate(t *testing.T) {
return newResponse(200, &listB.Items[1]) return newResponse(200, &listB.Items[1])
case p == "/namespaces/default/pods/squid" && m == "DELETE": case p == "/namespaces/default/pods/squid" && m == "DELETE":
return newResponse(200, &listB.Items[1]) return newResponse(200, &listB.Items[1])
case p == "/namespaces/default/pods/squid" && m == "GET":
return newResponse(200, &listB.Items[2])
default: default:
t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path)
return nil, nil return nil, nil
@ -184,6 +186,7 @@ func TestUpdate(t *testing.T) {
"/namespaces/default/pods/otter:GET", "/namespaces/default/pods/otter:GET",
"/namespaces/default/pods/dolphin:GET", "/namespaces/default/pods/dolphin:GET",
"/namespaces/default/pods:POST", "/namespaces/default/pods:POST",
"/namespaces/default/pods/squid:GET",
"/namespaces/default/pods/squid:DELETE", "/namespaces/default/pods/squid:DELETE",
} }
if len(expectedActions) != len(actions) { 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 // key will be raised as well
err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct) err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct)
validYaml := linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) // If YAML linting fails, we sill progress. So we don't capture the returned state
// on this linter run.
if !validYaml { linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err))
continue
}
} }
} }

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