Repository: helm/helm
Pull request #6728 (2019-10-21 19:42:55)

fix(sympath): walk symbolic links one once
Symbolic links will now only be walked once.

The tests reflect this: node `c` is marked on its own as a regular file, then it is marked a second time through node `d`. Previously, `c` would be marked 3 times: once on its own, and twice from the symbolic link on the resolved path and source path.

In practice, this would mean charts that included a symbolic link would include that symbolic link twice in the package.

Simple test to reproduce this error:

```
$ helm create foo
$ echo "hello!" > hello.txt
$ ln -s $PWD/hello.txt foo/README.txt
$ helm package foo
$ tar ztf foo-0.1.0.tgz
foo/Chart.yaml
foo/values.yaml
foo/templates/NOTES.txt
foo/templates/_helpers.tpl
foo/templates/deployment.yaml
foo/templates/ingress.yaml
foo/templates/service.yaml
foo/templates/serviceaccount.yaml
foo/templates/tests/test-connection.yaml
foo/.helmignore
foo/home/bacongobbler/code/go/src/helm.sh/helm/hello.txt
foo/README.txt
```

With this fix:

```
$ tar ztf foo-0.1.0.tgz
foo/Chart.yaml
foo/values.yaml
foo/templates/NOTES.txt
foo/templates/_helpers.tpl
foo/templates/deployment.yaml
foo/templates/ingress.yaml
foo/templates/service.yaml
foo/templates/serviceaccount.yaml
foo/templates/tests/test-connection.yaml
foo/.helmignore
foo/README.txt
$ cat foo/README.txt
hello!
```

Signed-off-by: Matthew Fisher <matt.fisher@microsoft.com>


Patch:
diff --git a/.circleci/deploy.sh b/.circleci/deploy.sh
index bbb08e6f0..f6881eadb 100755
--- a/.circleci/deploy.sh
+++ b/.circleci/deploy.sh
@@ -28,6 +28,8 @@ if [[ -n "${CIRCLE_TAG:-}" ]]; then
   VERSION="${CIRCLE_TAG}"
 elif [[ "${CIRCLE_BRANCH:-}" == "master" ]]; then
   VERSION="canary"
+elif [[ "${CIRCLE_BRANCH:-}" == "dev-v3" ]]; then
+  VERSION="dev-v3"
 else
   echo "Skipping deploy step; this is neither a releasable branch or a tag"
   exit
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 63780365e..c9e7298fd 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,7 +12,7 @@ us a chance to try to fix the issue before it is exploited in the wild.
 
 ## Sign Your Work
 
-The sign-off is a simple line at the end of the explanation for a commit. All
+The sign-off is a simple line at the end of the explanation for a commit. All 
 commits needs to be signed. Your signature certifies that you wrote the patch or
 otherwise have the right to contribute the material. The rules are pretty simple,
 if you can certify the below (from [developercertificate.org](https://developercertificate.org/)):
@@ -111,7 +111,7 @@ A milestone (and hence release) is considered done when all outstanding issues/P
 
 ## Semantic Versioning
 
-Helm maintains a strong commitment to backward compatibility. All of our changes to protocols and formats are backward compatible from one major release to the next. No features, flags, or commands are removed or substantially modified (unless we need to fix a security issue).
+Helm maintains a strong commitment to backward compatibility. All of our changes to protocols and formats are backward compatible from Helm 3.0 until Helm 4.0. No features, flags, or commands are removed or substantially modified (other than bug fixes).
 
 We also try very hard to not change publicly accessible Go library definitions inside of the `pkg/` directory of our source code.
 
@@ -123,16 +123,6 @@ For a quick summary of our backward compatibility guidelines for releases betwee
 - Chart repository functionality MUST be backward compatible
 - Go libraries inside of `pkg/` SHOULD remain backward compatible, though code inside of `cmd/` and `internal/` may be changed from release to release without notice.
 
-## Support Contract for Helm 2
-
-With Helm 2's current release schedule, we want to take into account any migration issues for users due to the upcoming holiday shopping season and tax season. We also want to clarify what actions may occur after the support contract ends for Helm 2, so that users will not be surprised or caught off guard.
-
-After Helm 2.15.0 is released, Helm 2 will go into "maintenance mode". We will continue to accept bug fixes and fix any security issues that arise, but no new features will be accepted for Helm 2. All feature development will be moved over to Helm 3.
-
-6 months after Helm 3.0.0's public release, Helm 2 will stop accepting bug fixes. Only security issues will be accepted.
-
-12 months after Helm 3.0.0's public release, support for Helm 2 will formally end. Download links for the Helm 2 client through Google Cloud Storage, the Docker image for Tiller stored in Google Container Registry, and the Google Cloud buckets for the stable and incubator chart repositories may no longer work at any point. Client downloads through `get.helm.sh` will continue to work, and we will distribute a Tiller image that will be made available at an alternative location which can be updated with `helm init --tiller-image`.
-
 ## Issues
 
 Issues are used as the primary method for tracking anything to do with the Helm project.
@@ -214,7 +204,7 @@ Like any good open source project, we use Pull Requests (PRs) to track code chan
     - Once a review has the `awaiting review` label, maintainers will review them as schedule permits.
     The maintainer who takes the issue should self-request a review.
     - Any PR with the `size/large` label requires 2 review approvals from maintainers before it can be
-    merged. Those with `size/medium` or `size/small` are per the judgement of the maintainers.
+    merged. Those with `size/medium` or `size/small` are per the judgement of the maintainers. 
 4. Reviewing/Discussion
     - All reviews will be completed using Github review tool.
     - A "Comment" review should be used when there are questions about the code that should be
diff --git a/cmd/helm/install.go b/cmd/helm/install.go
index 6c6920daa..e29fbbda1 100644
--- a/cmd/helm/install.go
+++ b/cmd/helm/install.go
@@ -81,15 +81,15 @@ file MUST pass all verification steps.
 
 There are five different ways you can express the chart you want to install:
 
-1. By chart reference: helm install mymaria example/mariadb
-2. By path to a packaged chart: helm install mynginx ./nginx-1.2.3.tgz
-3. By path to an unpacked chart directory: helm install mynginx ./nginx
-4. By absolute URL: helm install mynginx https://example.com/charts/nginx-1.2.3.tgz
-5. By chart reference and repo url: helm install --repo https://example.com/charts/ mynginx nginx
+1. By chart reference: helm install example/mariadb
+2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz
+3. By path to an unpacked chart directory: helm install ./nginx
+4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz
+5. By chart reference and repo url: helm install --repo https://example.com/charts/ nginx
 
 CHART REFERENCES
 
-A chart reference is a convenient way of referencing a chart in a chart repository.
+A chart reference is a convenient way of reference a chart in a chart repository.
 
 When you use a chart reference with a repo prefix ('example/mariadb'), Helm will look in the local
 configuration for a chart repository named 'example', and will then look for a
diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go
index 1c84ad8d6..a28bb82a1 100644
--- a/cmd/helm/repo_add.go
+++ b/cmd/helm/repo_add.go
@@ -23,7 +23,6 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"strings"
 	"time"
 
 	"github.com/gofrs/flock"
@@ -86,8 +85,8 @@ func (o *repoAddOptions) run(out io.Writer) error {
 		return err
 	}
 
-	// Acquire a file lock for process synchronization
-	fileLock := flock.New(strings.Replace(o.repoFile, filepath.Ext(o.repoFile), ".lock", 1))
+	// Lock the repository file for concurrent goroutines or processes synchronization
+	fileLock := flock.New(o.repoFile)
 	lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 	defer cancel()
 	locked, err := fileLock.TryLockContext(lockCtx, time.Second)
diff --git a/internal/sympath/walk.go b/internal/sympath/walk.go
index 196a6f489..4c5bc0950 100644
--- a/internal/sympath/walk.go
+++ b/internal/sympath/walk.go
@@ -73,9 +73,10 @@ func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
 		if info, err = os.Lstat(resolved); err != nil {
 			return err
 		}
-		if err := symwalk(resolved, info, walkFn); err != nil && err != filepath.SkipDir {
+		if err := symwalk(path, info, walkFn); err != nil && err != filepath.SkipDir {
 			return err
 		}
+		return nil
 	}
 
 	if err := walkFn(path, info, nil); err != nil {
diff --git a/scripts/get b/scripts/get
index 3f645f807..e8dd25d99 100755
--- a/scripts/get
+++ b/scripts/get
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 
-# Copyright The Helm Authors.
+# Copyright 2016 The Kubernetes Authors All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -18,9 +18,7 @@
 # the package manager for Go: https://github.com/Masterminds/glide.sh/blob/master/get
 
 PROJECT_NAME="helm"
-TILLER_NAME="tiller"
 
-: ${USE_SUDO:="true"}
 : ${HELM_INSTALL_DIR:="/usr/local/bin"}
 
 # initArch discovers the architecture for this system.
@@ -29,7 +27,7 @@ initArch() {
   case $ARCH in
     armv5*) ARCH="armv5";;
     armv6*) ARCH="armv6";;
-    armv7*) ARCH="arm";;
+    armv7*) ARCH="armv7";;
     aarch64) ARCH="arm64";;
     x86) ARCH="386";;
     x86_64) ARCH="amd64";;
@@ -52,7 +50,7 @@ initOS() {
 runAsRoot() {
   local CMD="$*"
 
-  if [ $EUID -ne 0 -a $USE_SUDO = "true" ]; then
+  if [ $EUID -ne 0 ]; then
     CMD="sudo $CMD"
   fi
 
@@ -77,16 +75,16 @@ verifySupported() {
 
 # checkDesiredVersion checks if the desired version is available.
 checkDesiredVersion() {
-  if [ "x$DESIRED_VERSION" == "x" ]; then
-    # Get tag from release URL
-    local latest_release_url="https://github.com/helm/helm/releases/latest"
-    if type "curl" > /dev/null; then
-      TAG=$(curl -Ls -o /dev/null -w %{url_effective} $latest_release_url | grep -oE "[^/]+$" )
-    elif type "wget" > /dev/null; then
-      TAG=$(wget $latest_release_url --server-response -O /dev/null 2>&1 | awk '/^  Location: /{DEST=$2} END{ print DEST}' | grep -oE "[^/]+$")
-    fi
-  else
-    TAG=$DESIRED_VERSION
+  # Use the GitHub releases webpage for the project to find the desired version for this project.
+  local release_url="https://github.com/helm/helm/releases/${DESIRED_VERSION:-latest}"
+  if type "curl" > /dev/null; then
+    TAG=$(curl -SsL $release_url | awk '/\/tag\//' | grep -v no-underline | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}')
+  elif type "wget" > /dev/null; then
+    TAG=$(wget -q -O - $release_url | awk '/\/tag\//' | grep -v no-underline | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}')
+  fi
+  if [ "x$TAG" == "x" ]; then
+    echo "Cannot determine ${DESIRED_VERSION} tag."
+    exit 1
   fi
 }
 
@@ -94,7 +92,7 @@ checkDesiredVersion() {
 # if it needs to be changed.
 checkHelmInstalledVersion() {
   if [[ -f "${HELM_INSTALL_DIR}/${PROJECT_NAME}" ]]; then
-    local version=$("${HELM_INSTALL_DIR}/${PROJECT_NAME}" version -c | grep '^Client' | cut -d'"' -f2)
+    local version=$(helm version | grep '^Client' | cut -d'"' -f2)
     if [[ "$version" == "$TAG" ]]; then
       echo "Helm ${version} is already ${DESIRED_VERSION:-latest}"
       return 0
@@ -143,16 +141,8 @@ installFile() {
   mkdir -p "$HELM_TMP"
   tar xf "$HELM_TMP_FILE" -C "$HELM_TMP"
   HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/$PROJECT_NAME"
-  TILLER_TMP_BIN="$HELM_TMP/$OS-$ARCH/$TILLER_NAME"
-  echo "Preparing to install $PROJECT_NAME and $TILLER_NAME into ${HELM_INSTALL_DIR}"
+  echo "Preparing to install into ${HELM_INSTALL_DIR}"
   runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR"
-  echo "$PROJECT_NAME installed into $HELM_INSTALL_DIR/$PROJECT_NAME"
-  if [ -x "$TILLER_TMP_BIN" ]; then
-    runAsRoot cp "$TILLER_TMP_BIN" "$HELM_INSTALL_DIR"
-    echo "$TILLER_NAME installed into $HELM_INSTALL_DIR/$TILLER_NAME"
-  else
-    echo "info: $TILLER_NAME binary was not found in this release; skipping $TILLER_NAME installation"
-  fi
 }
 
 # fail_trap is executed if an error occurs.
@@ -174,6 +164,7 @@ fail_trap() {
 # testVersion tests the installed client to make sure it is working.
 testVersion() {
   set +e
+  echo "$PROJECT_NAME installed into $HELM_INSTALL_DIR/$PROJECT_NAME"
   HELM="$(which $PROJECT_NAME)"
   if [ "$?" = "1" ]; then
     echo "$PROJECT_NAME not found. Is $HELM_INSTALL_DIR on your "'$PATH?'
@@ -189,7 +180,6 @@ help () {
   echo -e "\t[--help|-h ] ->> prints this help"
   echo -e "\t[--version|-v <desired_version>] . When not defined it defaults to latest"
   echo -e "\te.g. --version v2.4.0  or -v latest"
-  echo -e "\t[--no-sudo]  ->> install without sudo"
 }
 
 # cleanup temporary files to avoid https://github.com/helm/helm/issues/2977
@@ -219,9 +209,6 @@ while [[ $# -gt 0 ]]; do
            exit 0
        fi
        ;;
-    '--no-sudo')
-       USE_SUDO="false"
-       ;;
     '--help'|-h)
        help
        exit 0


Test patch:
diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go
index 7190ec736..a49816305 100644
--- a/cmd/helm/release_testing.go
+++ b/cmd/helm/release_testing.go
@@ -17,7 +17,6 @@ limitations under the License.
 package main
 
 import (
-	"fmt"
 	"io"
 	"time"
 
@@ -37,8 +36,7 @@ The tests to be run are defined in the chart that was installed.
 
 func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
 	client := action.NewReleaseTesting(cfg)
-	var outfmt = output.Table
-	var outputLogs bool
+	var outfmt output.Format
 
 	cmd := &cobra.Command{
 		Use:   "test [RELEASE]",
@@ -46,34 +44,17 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
 		Long:  releaseTestHelp,
 		Args:  require.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			client.Namespace = settings.Namespace()
-			rel, runErr := client.Run(args[0])
-			// We only return an error if we weren't even able to get the
-			// release, otherwise we keep going so we can print status and logs
-			// if requested
-			if runErr != nil && rel == nil {
-				return runErr
-			}
-
-			if err := outfmt.Write(out, &statusPrinter{rel, settings.Debug}); err != nil {
+			rel, err := client.Run(args[0])
+			if err != nil {
 				return err
 			}
 
-			if outputLogs {
-				// Print a newline to stdout to separate the output
-				fmt.Fprintln(out)
-				if err := client.GetPodLogs(out, rel); err != nil {
-					return err
-				}
-			}
-
-			return runErr
+			return outfmt.Write(out, &statusPrinter{rel, settings.Debug})
 		},
 	}
 
 	f := cmd.Flags()
 	f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
-	f.BoolVar(&outputLogs, "logs", false, "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup)")
 
 	return cmd
 }
diff --git a/internal/sympath/walk_test.go b/internal/sympath/walk_test.go
index c9364e91a..25f737134 100644
--- a/internal/sympath/walk_test.go
+++ b/internal/sympath/walk_test.go
@@ -27,36 +27,45 @@ import (
 )
 
 type Node struct {
-	name    string
-	entries []*Node // nil if the entry is a file
-	mark    int
+	name          string
+	entries       []*Node // nil if the entry is a file
+	marks         int
+	expectedMarks int
+	symLinkedTo   string
 }
 
 var tree = &Node{
 	"testdata",
 	[]*Node{
-		{"a", nil, 0},
-		{"b", []*Node{}, 0},
-		{"c", nil, 0},
+		{"a", nil, 0, 1, ""},
+		{"b", []*Node{}, 0, 1, ""},
+		{"c", nil, 0, 2, ""},
+		{"d", nil, 0, 0, "c"},
 		{
-			"d",
+			"e",
 			[]*Node{
-				{"x", nil, 0},
-				{"y", []*Node{}, 0},
+				{"x", nil, 0, 1, ""},
+				{"y", []*Node{}, 0, 1, ""},
 				{
 					"z",
 					[]*Node{
-						{"u", nil, 0},
-						{"v", nil, 0},
-						{"w", nil, 0},
+						{"u", nil, 0, 1, ""},
+						{"v", nil, 0, 1, ""},
+						{"w", nil, 0, 1, ""},
 					},
 					0,
+					1,
+					"",
 				},
 			},
 			0,
+			1,
+			"",
 		},
 	},
 	0,
+	1,
+	"",
 }
 
 func walkTree(n *Node, path string, f func(path string, n *Node)) {
@@ -69,24 +78,32 @@ func walkTree(n *Node, path string, f func(path string, n *Node)) {
 func makeTree(t *testing.T) {
 	walkTree(tree, tree.name, func(path string, n *Node) {
 		if n.entries == nil {
-			fd, err := os.Create(path)
-			if err != nil {
-				t.Errorf("makeTree: %v", err)
-				return
+			if n.symLinkedTo != "" {
+				if err := os.Symlink(n.symLinkedTo, path); err != nil {
+					t.Fatalf("makeTree: %v", err)
+				}
+			} else {
+				fd, err := os.Create(path)
+				if err != nil {
+					t.Fatalf("makeTree: %v", err)
+					return
+				}
+				fd.Close()
 			}
-			fd.Close()
 		} else {
-			os.Mkdir(path, 0770)
+			if err := os.Mkdir(path, 0770); err != nil {
+				t.Fatalf("makeTree: %v", err)
+			}
 		}
 	})
 }
 
 func checkMarks(t *testing.T, report bool) {
 	walkTree(tree, tree.name, func(path string, n *Node) {
-		if n.mark != 1 && report {
-			t.Errorf("node %s mark = %d; expected 1", path, n.mark)
+		if n.marks != n.expectedMarks && report {
+			t.Errorf("node %s mark = %d; expected %d", path, n.marks, n.expectedMarks)
 		}
-		n.mark = 0
+		n.marks = 0
 	})
 }
 
@@ -104,7 +121,7 @@ func mark(info os.FileInfo, err error, errors *[]error, clear bool) error {
 	name := info.Name()
 	walkTree(tree, tree.name, func(path string, n *Node) {
 		if n.name == name {
-			n.mark++
+			n.marks++
 		}
 	})
 	return nil
diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go
index b7a1da757..fbd36bef8 100644
--- a/pkg/action/release_testing.go
+++ b/pkg/action/release_testing.go
@@ -17,12 +17,9 @@ limitations under the License.
 package action
 
 import (
-	"fmt"
-	"io"
 	"time"
 
 	"github.com/pkg/errors"
-	v1 "k8s.io/api/core/v1"
 
 	"helm.sh/helm/v3/pkg/release"
 )
@@ -33,8 +30,6 @@ import (
 type ReleaseTesting struct {
 	cfg     *Configuration
 	Timeout time.Duration
-	// Used for fetching logs from test pods
-	Namespace string
 }
 
 // NewReleaseTesting creates a new ReleaseTesting object with the given configuration.
@@ -67,33 +62,3 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
 
 	return rel, r.cfg.Releases.Update(rel)
 }
-
-// GetPodLogs will write the logs for all test pods in the given release into
-// the given writer. These can be immediately output to the user or captured for
-// other uses
-func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
-	client, err := r.cfg.KubernetesClientSet()
-	if err != nil {
-		return errors.Wrap(err, "unable to get kubernetes client to fetch pod logs")
-	}
-
-	for _, h := range rel.Hooks {
-		for _, e := range h.Events {
-			if e == release.HookTest {
-				req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{})
-				logReader, err := req.Stream()
-				if err != nil {
-					return errors.Wrapf(err, "unable to get pod logs for %s", h.Name)
-				}
-
-				fmt.Fprintf(out, "POD LOGS: %s\n", h.Name)
-				_, err = io.Copy(out, logReader)
-				fmt.Fprintln(out)
-				if err != nil {
-					return errors.Wrapf(err, "unable to write pod logs for %s", h.Name)
-				}
-			}
-		}
-	}
-	return nil
-}