Repository: helm/helm (go)

Issue:
Need support for --insecure-skip-tls-verify
<!-- If you need help or think you have found a bug, please help us with your issue by entering the following information (otherwise you can delete this text): -->

Output of `helm version`: 2.13

Output of `kubectl version`: 1.13

Cloud Provider/Platform (AKS, GKE, Minikube etc.): 


Need support for `--insecure-skip-tls-verify`

E.g.,

`helm repo update --insecure-skip-tls-verify`



Pull request:
Add --insecure-skip-tls-verify for repositories
Fixes #5434

Patch:
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/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/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/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/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/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)
+}


Test patch:
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/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_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/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)
+	}
+}