Repository: helm/helm (go)

Issue:
Linting fails in 3.3.0 when a template file results in only a comment.
<!-- 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`:
version.BuildInfo{Version:"v3.3.0", GitCommit:"8a4aeec08d67a7b84472007529e8097ec3742105", GitTreeState:"dirty", GoVersion:"go1.14.7"}

An error occurs when linting a chart that results in a template file containing only a comment:
```` 
# comment on first line
{{- if false }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: mychart-configmap
data:
  myvalue: "Hello World"
{{- end }}
````

the resulting lint throws an error:
````
[ERROR] templates/configmap-false.yaml: object name does not conform to Kubernetes naming requirements: ""
````

Essentially an empty file with only a comment triggers this issue.

```` 
# comment on first line
{{- if true }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: mychart-configmap
data:
  myvalue: "Hello World"
{{- end }}
````

results in a successful lint:
````
1 chart(s) linted, 0 chart(s) failed
````


Pull request:
lint: lint all documents in a multi-doc yaml file
Signed-off-by: Nandor Kracser <bonifaido@gmail.com>

**What this PR does / why we need it**:
The current linting code handled YAML files incorrectly, so I tried my best to fix multiple issues around that:
- multiple documents, only the first document got parsed and linted
- YAML files without actual documents, just containing a `---` marker
- empty YAML files with comments

There might be other issues that I have fixed accidentally.

Related: #8621

**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 fced43743..4a0bc5bbd 100644
--- a/cmd/helm/root.go
+++ b/cmd/helm/root.go
@@ -252,12 +252,10 @@ func checkForExpiredRepos(repofile string) {
 		if url := r.URL; strings.Contains(url, exp.old) {
 			fmt.Fprintf(
 				os.Stderr,
-				"WARNING: %q is deprecated for %q and will be deleted Nov. 13, 2020.\nWARNING: You should switch to %q via:\nWARNING: helm repo add %q %q --force-update\n",
+				"WARNING: %q is deprecated for %q and will be deleted Nov. 13, 2020.\nWARNING: You should switch to %q\n",
 				exp.old,
 				exp.name,
 				exp.new,
-				exp.name,
-				exp.new,
 			)
 		}
 	}
diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go
index 3e4e0ebd1..0bb9f8671 100644
--- a/pkg/lint/rules/template.go
+++ b/pkg/lint/rules/template.go
@@ -20,6 +20,7 @@ import (
 	"bufio"
 	"bytes"
 	"fmt"
+	"io"
 	"os"
 	"path"
 	"path/filepath"
@@ -27,7 +28,7 @@ import (
 	"strings"
 
 	"github.com/pkg/errors"
-	"sigs.k8s.io/yaml"
+	"k8s.io/apimachinery/pkg/util/yaml"
 
 	"helm.sh/helm/v3/pkg/chart/loader"
 	"helm.sh/helm/v3/pkg/chartutil"
@@ -117,20 +118,30 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
 		renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)]
 		if strings.TrimSpace(renderedContent) != "" {
 			linter.RunLinterRule(support.WarningSev, fpath, validateTopIndentLevel(renderedContent))
-			var yamlStruct K8sYamlStruct
-			// Even though K8sYamlStruct only defines a few fields, an error in any other
-			// key will be raised as well
-			err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct)
 
-			if (K8sYamlStruct{}) == yamlStruct {
-				continue
+			decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(renderedContent), 4096)
+
+			// Lint all resources if the file contains multiple documents separated by ---
+			for {
+				// Even though K8sYamlStruct only defines a few fields, an error in any other
+				// key will be raised as well
+				var yamlStruct *K8sYamlStruct
+
+				err := decoder.Decode(&yamlStruct)
+				if err == io.EOF {
+					break
+				}
+
+				// If YAML linting fails, we sill progress. So we don't capture the returned state
+				// on this linter run.
+				linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err))
+
+				if yamlStruct != nil {
+					linter.RunLinterRule(support.ErrorSev, fpath, validateMetadataName(yamlStruct))
+					linter.RunLinterRule(support.ErrorSev, fpath, validateNoDeprecations(yamlStruct))
+					linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent))
+				}
 			}
-			// If YAML linting fails, we sill progress. So we don't capture the returned state
-			// on this linter run.
-			linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err))
-			linter.RunLinterRule(support.ErrorSev, fpath, validateMetadataName(&yamlStruct))
-			linter.RunLinterRule(support.ErrorSev, fpath, validateNoDeprecations(&yamlStruct))
-			linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(&yamlStruct, renderedContent))
 		}
 	}
 }


Test patch:
diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go
index 50cd562aa..eb076a1bf 100644
--- a/pkg/lint/rules/template_test.go
+++ b/pkg/lint/rules/template_test.go
@@ -107,6 +107,20 @@ func TestV3Fail(t *testing.T) {
 	}
 }
 
+func TestMultiTemplateFail(t *testing.T) {
+	linter := support.Linter{ChartDir: "./testdata/multi-template-fail"}
+	Templates(&linter, values, namespace, strict)
+	res := linter.Messages
+
+	if len(res) != 1 {
+		t.Fatalf("Expected 1 error, got %d, %v", len(res), res)
+	}
+
+	if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") {
+		t.Errorf("Unexpected error: %s", res[0].Err)
+	}
+}
+
 func TestValidateMetadataName(t *testing.T) {
 	names := map[string]bool{
 		"":                          false,
diff --git a/pkg/lint/rules/testdata/multi-template-fail/Chart.yaml b/pkg/lint/rules/testdata/multi-template-fail/Chart.yaml
new file mode 100644
index 000000000..f022d5ad9
--- /dev/null
+++ b/pkg/lint/rules/testdata/multi-template-fail/Chart.yaml
@@ -0,0 +1,21 @@
+apiVersion: v2
+name: multi-template-fail
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application.
+appVersion: 1.16.0
diff --git a/pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml b/pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml
new file mode 100644
index 000000000..835be07be
--- /dev/null
+++ b/pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: game-config
+data:
+  game.properties: cheat
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: -this:name-is-not_valid$
+data:
+  game.properties: empty