Repository: helm/helm
Pull request #8753 (2020-09-17 17:24:55)

handle case where dependency name collisions break dependency resolution
Signed-off-by: Matt Butcher <matt.butcher@microsoft.com>

<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/master/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:

This fixes a bug in dependency name scanning that led to falls errors

**Special notes for your reviewer**:

**If applicable**:
- [ ] this PR contains documentation
- [x] this PR contains unit tests
- [x] this PR has been tested for backwards compatibility



Patch:
diff --git a/cmd/helm/root.go b/cmd/helm/root.go
index 91542bb7e..82c87bd7d 100644
--- a/cmd/helm/root.go
+++ b/cmd/helm/root.go
@@ -204,8 +204,5 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string
 	// Find and add plugins
 	loadPlugins(cmd, out)
 
-	// Check permissions on critical files
-	checkPerms(out)
-
 	return cmd, nil
 }
diff --git a/cmd/helm/root_unix.go b/cmd/helm/root_unix.go
deleted file mode 100644
index 210842b35..000000000
--- a/cmd/helm/root_unix.go
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-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 main
-
-import (
-	"fmt"
-	"io"
-	"os"
-	"os/user"
-	"path/filepath"
-)
-
-func checkPerms(out io.Writer) {
-	// This function MUST NOT FAIL, as it is just a check for a common permissions problem.
-	// If for some reason the function hits a stopping condition, it may panic. But only if
-	// we can be sure that it is panicing because Helm cannot proceed.
-
-	kc := settings.KubeConfig
-	if kc == "" {
-		kc = os.Getenv("KUBECONFIG")
-	}
-	if kc == "" {
-		u, err := user.Current()
-		if err != nil {
-			// No idea where to find KubeConfig, so return silently. Many helm commands
-			// can proceed happily without a KUBECONFIG, so this is not a fatal error.
-			return
-		}
-		kc = filepath.Join(u.HomeDir, ".kube", "config")
-	}
-	fi, err := os.Stat(kc)
-	if err != nil {
-		// DO NOT error if no KubeConfig is found. Not all commands require one.
-		return
-	}
-
-	perm := fi.Mode().Perm()
-	if perm&0040 > 0 {
-		fmt.Fprintf(out, "WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: %s\n", kc)
-	}
-	if perm&0004 > 0 {
-		fmt.Fprintf(out, "WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: %s\n", kc)
-	}
-}
diff --git a/cmd/helm/root_windows.go b/cmd/helm/root_windows.go
deleted file mode 100644
index 243780d40..000000000
--- a/cmd/helm/root_windows.go
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
-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 main
-
-import "io"
-
-func checkPerms(out io.Writer) {
-	// Not yet implemented on Windows. If you know how to do a comprehensive perms
-	// check on Windows, contributions welcomed!
-}
diff --git a/pkg/action/action.go b/pkg/action/action.go
index 79bb4f638..071db709b 100644
--- a/pkg/action/action.go
+++ b/pkg/action/action.go
@@ -58,15 +58,14 @@ var (
 	errMissingRelease = errors.New("no release provided")
 	// errInvalidRevision indicates that an invalid release revision number was provided.
 	errInvalidRevision = errors.New("invalid release revision")
+	// errInvalidName indicates that an invalid release name was provided
+	errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53")
 	// errPending indicates that another instance of Helm is already applying an operation on a release.
 	errPending = errors.New("another operation (install/upgrade/rollback) is in progress")
 )
 
 // ValidName is a regular expression for resource names.
 //
-// DEPRECATED: This will be removed in Helm 4, and is no longer used here. See
-// pkg/chartutil.ValidateName for the replacement.
-//
 // According to the Kubernetes help text, the regular expression it uses is:
 //
 //	[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
@@ -295,7 +294,7 @@ func (c *Configuration) Now() time.Time {
 }
 
 func (c *Configuration) releaseContent(name string, version int) (*release.Release, error) {
-	if err := chartutil.ValidateReleaseName(name); err != nil {
+	if err := validateReleaseName(name); err != nil {
 		return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name)
 	}
 
diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go
index 4a4b8ebad..4c80d0159 100644
--- a/pkg/action/dependency.go
+++ b/pkg/action/dependency.go
@@ -21,6 +21,7 @@ import (
 	"io"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/Masterminds/semver/v3"
 	"github.com/gosuri/uitable"
@@ -61,6 +62,7 @@ func (d *Dependency) List(chartpath string, out io.Writer) error {
 	return nil
 }
 
+// dependecyStatus returns a string describing the status of a dependency viz a viz the parent chart.
 func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, parent *chart.Chart) string {
 	filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*")
 
@@ -75,35 +77,40 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p
 	case err != nil:
 		return "bad pattern"
 	case len(archives) > 1:
-		return "too many matches"
-	case len(archives) == 1:
-		archive := archives[0]
-		if _, err := os.Stat(archive); err == nil {
-			c, err := loader.Load(archive)
-			if err != nil {
-				return "corrupt"
+		// See if the second part is a SemVer
+		found := []string{}
+		for _, arc := range archives {
+			// we need to trip the prefix dirs and the extension off.
+			filename = strings.TrimSuffix(filepath.Base(arc), ".tgz")
+			maybeVersion := strings.TrimPrefix(filename, fmt.Sprintf("%s-", dep.Name))
+
+			if _, err := semver.StrictNewVersion(maybeVersion); err == nil {
+				// If the version parsed without an error, it is possibly a valid
+				// version.
+				found = append(found, arc)
 			}
-			if c.Name() != dep.Name {
-				return "misnamed"
+		}
+
+		if l := len(found); l == 1 {
+			// If we get here, we do the same thing as in len(archives) == 1.
+			if r := statArchiveForStatus(found[0], dep); r != "" {
+				return r
 			}
 
-			if c.Metadata.Version != dep.Version {
-				constraint, err := semver.NewConstraint(dep.Version)
-				if err != nil {
-					return "invalid version"
-				}
+			// Fall through and look for directories
+		} else if l > 1 {
+			return "too many matches"
+		}
 
-				v, err := semver.NewVersion(c.Metadata.Version)
-				if err != nil {
-					return "invalid version"
-				}
+		// The sanest thing to do here is to fall through and see if we have any directory
+		// matches.
 
-				if !constraint.Check(v) {
-					return "wrong version"
-				}
-			}
-			return "ok"
+	case len(archives) == 1:
+		archive := archives[0]
+		if r := statArchiveForStatus(archive, dep); r != "" {
+			return r
 		}
+
 	}
 	// End unnecessary code.
 
@@ -137,6 +144,40 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p
 	return "unpacked"
 }
 
+// stat an archive and return a message if the stat is successful
+//
+// This is a refactor of the code originally in dependencyStatus. It is here to
+// support legacy behavior, and should be removed in Helm 4.
+func statArchiveForStatus(archive string, dep *chart.Dependency) string {
+	if _, err := os.Stat(archive); err == nil {
+		c, err := loader.Load(archive)
+		if err != nil {
+			return "corrupt"
+		}
+		if c.Name() != dep.Name {
+			return "misnamed"
+		}
+
+		if c.Metadata.Version != dep.Version {
+			constraint, err := semver.NewConstraint(dep.Version)
+			if err != nil {
+				return "invalid version"
+			}
+
+			v, err := semver.NewVersion(c.Metadata.Version)
+			if err != nil {
+				return "invalid version"
+			}
+
+			if !constraint.Check(v) {
+				return "wrong version"
+			}
+		}
+		return "ok"
+	}
+	return ""
+}
+
 // printDependencies prints all of the dependencies in the yaml file.
 func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) {
 	table := uitable.New()
diff --git a/pkg/action/history.go b/pkg/action/history.go
index f4043609c..a592745e9 100644
--- a/pkg/action/history.go
+++ b/pkg/action/history.go
@@ -19,7 +19,6 @@ package action
 import (
 	"github.com/pkg/errors"
 
-	"helm.sh/helm/v3/pkg/chartutil"
 	"helm.sh/helm/v3/pkg/release"
 )
 
@@ -46,7 +45,7 @@ func (h *History) Run(name string) ([]*release.Release, error) {
 		return nil, err
 	}
 
-	if err := chartutil.ValidateReleaseName(name); err != nil {
+	if err := validateReleaseName(name); err != nil {
 		return nil, errors.Errorf("release name is invalid: %s", name)
 	}
 
diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go
index 542acefae..8773b6271 100644
--- a/pkg/action/rollback.go
+++ b/pkg/action/rollback.go
@@ -24,7 +24,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"helm.sh/helm/v3/pkg/chartutil"
 	"helm.sh/helm/v3/pkg/release"
 	helmtime "helm.sh/helm/v3/pkg/time"
 )
@@ -91,7 +90,7 @@ func (r *Rollback) Run(name string) error {
 // prepareRollback finds the previous release and prepares a new release object with
 // the previous release's configuration
 func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) {
-	if err := chartutil.ValidateReleaseName(name); err != nil {
+	if err := validateReleaseName(name); err != nil {
 		return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name)
 	}
 
diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go
index c466c6ee2..a51a283d6 100644
--- a/pkg/action/uninstall.go
+++ b/pkg/action/uninstall.go
@@ -22,7 +22,6 @@ import (
 
 	"github.com/pkg/errors"
 
-	"helm.sh/helm/v3/pkg/chartutil"
 	"helm.sh/helm/v3/pkg/release"
 	"helm.sh/helm/v3/pkg/releaseutil"
 	helmtime "helm.sh/helm/v3/pkg/time"
@@ -63,7 +62,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
 		return &release.UninstallReleaseResponse{Release: r}, nil
 	}
 
-	if err := chartutil.ValidateReleaseName(name); err != nil {
+	if err := validateReleaseName(name); err != nil {
 		return nil, errors.Errorf("uninstall: Release name is invalid: %s", name)
 	}
 
diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go
index c439af79d..b707e7e69 100644
--- a/pkg/action/upgrade.go
+++ b/pkg/action/upgrade.go
@@ -115,7 +115,7 @@ func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface
 	// the user doesn't have to specify both
 	u.Wait = u.Wait || u.Atomic
 
-	if err := chartutil.ValidateReleaseName(name); err != nil {
+	if err := validateReleaseName(name); err != nil {
 		return nil, errors.Errorf("release name is invalid: %s", name)
 	}
 	u.cfg.Log("preparing upgrade for %s", name)
@@ -142,6 +142,19 @@ func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface
 	return res, nil
 }
 
+func validateReleaseName(releaseName string) error {
+	if releaseName == "" {
+		return errMissingRelease
+	}
+
+	// Check length first, since that is a less expensive operation.
+	if len(releaseName) > releaseNameMaxLen || !ValidName.MatchString(releaseName) {
+		return errInvalidName
+	}
+
+	return nil
+}
+
 // prepareUpgrade builds an upgraded release for an upgrade operation.
 func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) {
 	if chart == nil {
diff --git a/pkg/chartutil/validate_name.go b/pkg/chartutil/validate_name.go
deleted file mode 100644
index 22132c80e..000000000
--- a/pkg/chartutil/validate_name.go
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
-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 chartutil
-
-import (
-	"regexp"
-
-	"github.com/pkg/errors"
-)
-
-// validName is a regular expression for resource names.
-//
-// According to the Kubernetes help text, the regular expression it uses is:
-//
-//	[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
-//
-// This follows the above regular expression (but requires a full string match, not partial).
-//
-// The Kubernetes documentation is here, though it is not entirely correct:
-// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
-var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
-
-var (
-	// errMissingName indicates that a release (name) was not provided.
-	errMissingName = errors.New("no name provided")
-
-	// errInvalidName indicates that an invalid release name was provided
-	errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53")
-
-	// errInvalidKubernetesName indicates that the name does not meet the Kubernetes
-	// restrictions on metadata names.
-	errInvalidKubernetesName = errors.New("invalid metadata name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 253")
-)
-
-const (
-	// maxNameLen is the maximum length Helm allows for a release name
-	maxReleaseNameLen = 53
-	// maxMetadataNameLen is the maximum length Kubernetes allows for any name.
-	maxMetadataNameLen = 253
-)
-
-// ValidateReleaseName performs checks for an entry for a Helm release name
-//
-// For Helm to allow a name, it must be below a certain character count (53) and also match
-// a reguar expression.
-//
-// According to the Kubernetes help text, the regular expression it uses is:
-//
-//	[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
-//
-// This follows the above regular expression (but requires a full string match, not partial).
-//
-// The Kubernetes documentation is here, though it is not entirely correct:
-// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
-func ValidateReleaseName(name string) error {
-	// This case is preserved for backwards compatibility
-	if name == "" {
-		return errMissingName
-
-	}
-	if len(name) > maxReleaseNameLen || !validName.MatchString(name) {
-		return errInvalidName
-	}
-	return nil
-}
-
-// ValidateMetadataName validates the name field of a Kubernetes metadata object.
-//
-// Empty strings, strings longer than 253 chars, or strings that don't match the regexp
-// will fail.
-//
-// According to the Kubernetes help text, the regular expression it uses is:
-//
-//	[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
-//
-// This follows the above regular expression (but requires a full string match, not partial).
-//
-// The Kubernetes documentation is here, though it is not entirely correct:
-// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
-func ValidateMetadataName(name string) error {
-	if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) {
-		return errInvalidKubernetesName
-	}
-	return nil
-}
diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go
index 5de0819c4..73d645264 100644
--- a/pkg/lint/rules/template.go
+++ b/pkg/lint/rules/template.go
@@ -40,6 +40,14 @@ var (
 	releaseTimeSearch = regexp.MustCompile(`\.Release\.Time`)
 )
 
+// validName is a regular expression for names.
+//
+// This is different than action.ValidName. It conforms to the regular expression
+// `kubectl` says it uses, plus it disallows empty names.
+//
+// For details, see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)
+
 // Templates lints the templates in the Linter.
 func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) {
 	fpath := "templates/"
@@ -191,10 +199,10 @@ func validateMetadataName(obj *K8sYamlStruct) error {
 	}
 	// This will return an error if the characters do not abide by the standard OR if the
 	// name is left empty.
-	if err := chartutil.ValidateMetadataName(obj.Metadata.Name); err != nil {
-		return errors.Wrapf(err, "object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name)
+	if validName.MatchString(obj.Metadata.Name) {
+		return nil
 	}
-	return nil
+	return fmt.Errorf("object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name)
 }
 
 func validateNoCRDHooks(manifest []byte) error {


Test patch:
diff --git a/cmd/helm/root_unix_test.go b/cmd/helm/root_unix_test.go
deleted file mode 100644
index 73f18ec28..000000000
--- a/cmd/helm/root_unix_test.go
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-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 main
-
-import (
-	"bytes"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-	"testing"
-)
-
-func TestCheckPerms(t *testing.T) {
-	tdir, err := ioutil.TempDir("", "helmtest")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer os.RemoveAll(tdir)
-	tfile := filepath.Join(tdir, "testconfig")
-	fh, err := os.OpenFile(tfile, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0440)
-	if err != nil {
-		t.Errorf("Failed to create temp file: %s", err)
-	}
-
-	tconfig := settings.KubeConfig
-	settings.KubeConfig = tfile
-	defer func() { settings.KubeConfig = tconfig }()
-
-	var b bytes.Buffer
-	checkPerms(&b)
-	expectPrefix := "WARNING: Kubernetes configuration file is group-readable. This is insecure. Location:"
-	if !strings.HasPrefix(b.String(), expectPrefix) {
-		t.Errorf("Expected to get a warning for group perms. Got %q", b.String())
-	}
-
-	if err := fh.Chmod(0404); err != nil {
-		t.Errorf("Could not change mode on file: %s", err)
-	}
-	b.Reset()
-	checkPerms(&b)
-	expectPrefix = "WARNING: Kubernetes configuration file is world-readable. This is insecure. Location:"
-	if !strings.HasPrefix(b.String(), expectPrefix) {
-		t.Errorf("Expected to get a warning for world perms. Got %q", b.String())
-	}
-
-}
diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go
index fedf260fb..c05b4403d 100644
--- a/pkg/action/action_test.go
+++ b/pkg/action/action_test.go
@@ -319,3 +319,40 @@ func TestGetVersionSet(t *testing.T) {
 		t.Error("Non-existent version is reported found.")
 	}
 }
+
+// TestValidName is a regression test for ValidName
+//
+// Kubernetes has strict naming conventions for resource names. This test represents
+// those conventions.
+//
+// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+//
+// NOTE: At the time of this writing, the docs above say that names cannot begin with
+// digits. However, `kubectl`'s regular expression explicit allows this, and
+// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits.
+func TestValidName(t *testing.T) {
+	names := map[string]bool{
+		"":                          false,
+		"foo":                       true,
+		"foo.bar1234baz.seventyone": true,
+		"FOO":                       false,
+		"123baz":                    true,
+		"foo.BAR.baz":               false,
+		"one-two":                   true,
+		"-two":                      false,
+		"one_two":                   false,
+		"a..b":                      false,
+		"%^&#$%*@^*@&#^":            false,
+		"example:com":               false,
+		"example%%com":              false,
+	}
+	for input, expectPass := range names {
+		if ValidName.MatchString(input) != expectPass {
+			st := "fail"
+			if expectPass {
+				st = "succeed"
+			}
+			t.Errorf("Expected %q to %s", input, st)
+		}
+	}
+}
diff --git a/pkg/action/dependency_test.go b/pkg/action/dependency_test.go
index 4f3cb69a5..b5032a377 100644
--- a/pkg/action/dependency_test.go
+++ b/pkg/action/dependency_test.go
@@ -18,9 +18,16 @@ package action
 
 import (
 	"bytes"
+	"io/ioutil"
+	"os"
+	"path/filepath"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
+
 	"helm.sh/helm/v3/internal/test"
+	"helm.sh/helm/v3/pkg/chart"
+	"helm.sh/helm/v3/pkg/chartutil"
 )
 
 func TestList(t *testing.T) {
@@ -56,3 +63,99 @@ func TestList(t *testing.T) {
 		test.AssertGoldenBytes(t, buf.Bytes(), tcase.golden)
 	}
 }
+
+// TestDependencyStatus_Dashes is a regression test to make sure that dashes in
+// chart names do not cause resolution problems.
+func TestDependencyStatus_Dashes(t *testing.T) {
+	// Make a temp dir
+	dir, err := ioutil.TempDir("", "helmtest-")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	chartpath := filepath.Join(dir, "charts")
+	if err := os.MkdirAll(chartpath, 0700); err != nil {
+		t.Fatal(err)
+	}
+
+	// Add some fake charts
+	first := buildChart(withName("first-chart"))
+	_, err = chartutil.Save(first, chartpath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	second := buildChart(withName("first-chart-second-chart"))
+	_, err = chartutil.Save(second, chartpath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	dep := &chart.Dependency{
+		Name:    "first-chart",
+		Version: "0.1.0",
+	}
+
+	// Now try to get the deps
+	stat := NewDependency().dependencyStatus(dir, dep, first)
+	if stat != "ok" {
+		t.Errorf("Unexpected status: %q", stat)
+	}
+}
+
+func TestStatArchiveForStatus(t *testing.T) {
+	// Make a temp dir
+	dir, err := ioutil.TempDir("", "helmtest-")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	chartpath := filepath.Join(dir, "charts")
+	if err := os.MkdirAll(chartpath, 0700); err != nil {
+		t.Fatal(err)
+	}
+
+	// unsaved chart
+	lilith := buildChart(withName("lilith"))
+
+	// dep referring to chart
+	dep := &chart.Dependency{
+		Name:    "lilith",
+		Version: "1.2.3",
+	}
+
+	is := assert.New(t)
+
+	lilithpath := filepath.Join(chartpath, "lilith-1.2.3.tgz")
+	is.Empty(statArchiveForStatus(lilithpath, dep))
+
+	// save the chart (version 0.1.0, because that is the default)
+	where, err := chartutil.Save(lilith, chartpath)
+	is.NoError(err)
+
+	// Should get "wrong version" because we asked for 1.2.3 and got 0.1.0
+	is.Equal("wrong version", statArchiveForStatus(where, dep))
+
+	// Break version on dep
+	dep = &chart.Dependency{
+		Name:    "lilith",
+		Version: "1.2.3.4.5",
+	}
+	is.Equal("invalid version", statArchiveForStatus(where, dep))
+
+	// Break the name
+	dep = &chart.Dependency{
+		Name:    "lilith2",
+		Version: "1.2.3",
+	}
+	is.Equal("misnamed", statArchiveForStatus(where, dep))
+
+	// Now create the right version
+	dep = &chart.Dependency{
+		Name:    "lilith",
+		Version: "0.1.0",
+	}
+	is.Equal("ok", statArchiveForStatus(where, dep))
+}
diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go
index 2f6f5cfce..795c3c747 100644
--- a/pkg/action/release_testing.go
+++ b/pkg/action/release_testing.go
@@ -25,7 +25,6 @@ import (
 	"github.com/pkg/errors"
 	v1 "k8s.io/api/core/v1"
 
-	"helm.sh/helm/v3/pkg/chartutil"
 	"helm.sh/helm/v3/pkg/release"
 )
 
@@ -52,7 +51,7 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
 		return nil, err
 	}
 
-	if err := chartutil.ValidateReleaseName(name); err != nil {
+	if err := validateReleaseName(name); err != nil {
 		return nil, errors.Errorf("releaseTest: Release name is invalid: %s", name)
 	}
 
diff --git a/pkg/chartutil/validate_name_test.go b/pkg/chartutil/validate_name_test.go
deleted file mode 100644
index 5f0792f94..000000000
--- a/pkg/chartutil/validate_name_test.go
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
-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 chartutil
-
-import "testing"
-
-// TestValidateName is a regression test for ValidateName
-//
-// Kubernetes has strict naming conventions for resource names. This test represents
-// those conventions.
-//
-// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
-//
-// NOTE: At the time of this writing, the docs above say that names cannot begin with
-// digits. However, `kubectl`'s regular expression explicit allows this, and
-// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits.
-func TestValidateReleaseName(t *testing.T) {
-	names := map[string]bool{
-		"":                          false,
-		"foo":                       true,
-		"foo.bar1234baz.seventyone": true,
-		"FOO":                       false,
-		"123baz":                    true,
-		"foo.BAR.baz":               false,
-		"one-two":                   true,
-		"-two":                      false,
-		"one_two":                   false,
-		"a..b":                      false,
-		"%^&#$%*@^*@&#^":            false,
-		"example:com":               false,
-		"example%%com":              false,
-		"a1111111111111111111111111111111111111111111111111111111111z": false,
-	}
-	for input, expectPass := range names {
-		if err := ValidateReleaseName(input); (err == nil) != expectPass {
-			st := "fail"
-			if expectPass {
-				st = "succeed"
-			}
-			t.Errorf("Expected %q to %s", input, st)
-		}
-	}
-}
-
-func TestValidateMetadataName(t *testing.T) {
-	names := map[string]bool{
-		"":                          false,
-		"foo":                       true,
-		"foo.bar1234baz.seventyone": true,
-		"FOO":                       false,
-		"123baz":                    true,
-		"foo.BAR.baz":               false,
-		"one-two":                   true,
-		"-two":                      false,
-		"one_two":                   false,
-		"a..b":                      false,
-		"%^&#$%*@^*@&#^":            false,
-		"example:com":               false,
-		"example%%com":              false,
-		"a1111111111111111111111111111111111111111111111111111111111z": true,
-		"a1111111111111111111111111111111111111111111111111111111111z" +
-			"a1111111111111111111111111111111111111111111111111111111111z" +
-			"a1111111111111111111111111111111111111111111111111111111111z" +
-			"a1111111111111111111111111111111111111111111111111111111111z" +
-			"a1111111111111111111111111111111111111111111111111111111111z" +
-			"a1111111111111111111111111111111111111111111111111111111111z": false,
-	}
-	for input, expectPass := range names {
-		if err := ValidateMetadataName(input); (err == nil) != expectPass {
-			st := "fail"
-			if expectPass {
-				st = "succeed"
-			}
-			t.Errorf("Expected %q to %s", input, st)
-		}
-	}
-}