Repository: helm/helm (go)

Issue:
helm lint stuck with malformed template object
Hi

<!-- 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.10.0", GitCommit:"ce66412a723e4d89555dc67217607c6579ffcb21", GitTreeState:"clean", GoVersion:"go1.19.1"}

With this template file (notice the space between the first and the second curly brackets): 
`{ {- $relname := .Release.Name -}}`

helm lint does not respond whereas helm template throws an error (which is the normal behaviour) :
> Error: YAML parse error on onboarding-ms/templates/secret.yaml: error converting YAML to JSON: yaml: did not find expected node content
Use --debug flag to render out invalid YAML`



Pull request:
bugfix: helm lint infinite loop
Signed-off-by: wujunwei <wjw3323@live.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/main/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**:
Fixes #11391
**Special notes for your reviewer**:

**If applicable**:
- [x] this PR contains unit tests
- [x] this PR has been tested for backwards compatibility


Patch:
diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go
index 61425f92e..e6a389153 100644
--- a/pkg/lint/rules/template.go
+++ b/pkg/lint/rules/template.go
@@ -141,10 +141,11 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace
 					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 YAML linting fails here, it will always fail in the next block as well, so we should return here.
+				// fix https://github.com/helm/helm/issues/11391
+				if !linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err)) {
+					return
+				}
 				if yamlStruct != nil {
 					// NOTE: set to warnings to allow users to support out-of-date kubernetes
 					// Refs https://github.com/helm/helm/issues/8596


Test patch:
diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go
index 0e5d42391..236ad9121 100644
--- a/pkg/lint/lint_test.go
+++ b/pkg/lint/lint_test.go
@@ -19,6 +19,7 @@ package lint
 import (
 	"strings"
 	"testing"
+	"time"
 
 	"helm.sh/helm/v3/pkg/chartutil"
 	"helm.sh/helm/v3/pkg/lint/support"
@@ -34,6 +35,7 @@ const badValuesFileDir = "rules/testdata/badvaluesfile"
 const badYamlFileDir = "rules/testdata/albatross"
 const goodChartDir = "rules/testdata/goodone"
 const subChartValuesDir = "rules/testdata/withsubchart"
+const malformedTemplate = "rules/testdata/malformed-template"
 
 func TestBadChart(t *testing.T) {
 	m := All(badChartDir, values, namespace, strict).Messages
@@ -151,3 +153,26 @@ func TestSubChartValuesChart(t *testing.T) {
 		}
 	}
 }
+
+// lint stuck with malformed template object
+// See https://github.com/helm/helm/issues/11391
+func TestMalformedTemplate(t *testing.T) {
+	c := time.After(3 * time.Second)
+	ch := make(chan int, 1)
+	var m []support.Message
+	go func() {
+		m = All(malformedTemplate, values, namespace, strict).Messages
+		ch <- 1
+	}()
+	select {
+	case <-c:
+		t.Fatalf("lint malformed template timeout")
+	case <-ch:
+		if len(m) != 1 {
+			t.Fatalf("All didn't fail with expected errors, got %#v", m)
+		}
+		if !strings.Contains(m[0].Err.Error(), "invalid character '{'") {
+			t.Errorf("All didn't have the error for invalid character '{'")
+		}
+	}
+}
diff --git a/pkg/lint/rules/testdata/malformed-template/.helmignore b/pkg/lint/rules/testdata/malformed-template/.helmignore
new file mode 100644
index 000000000..0e8a0eb36
--- /dev/null
+++ b/pkg/lint/rules/testdata/malformed-template/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/pkg/lint/rules/testdata/malformed-template/Chart.yaml b/pkg/lint/rules/testdata/malformed-template/Chart.yaml
new file mode 100644
index 000000000..11b2c71c2
--- /dev/null
+++ b/pkg/lint/rules/testdata/malformed-template/Chart.yaml
@@ -0,0 +1,25 @@
+apiVersion: v2
+name: test
+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.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+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. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.16.0"
+icon: https://riverrun.io
\ No newline at end of file
diff --git a/pkg/lint/rules/testdata/malformed-template/templates/bad.yaml b/pkg/lint/rules/testdata/malformed-template/templates/bad.yaml
new file mode 100644
index 000000000..213198fda
--- /dev/null
+++ b/pkg/lint/rules/testdata/malformed-template/templates/bad.yaml
@@ -0,0 +1,1 @@
+{ {- $relname := .Release.Name -}}
diff --git a/pkg/lint/rules/testdata/malformed-template/values.yaml b/pkg/lint/rules/testdata/malformed-template/values.yaml
new file mode 100644
index 000000000..1cc3182ea
--- /dev/null
+++ b/pkg/lint/rules/testdata/malformed-template/values.yaml
@@ -0,0 +1,82 @@
+# Default values for test.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: nginx
+  pullPolicy: IfNotPresent
+  # Overrides the image tag whose default is the chart appVersion.
+  tag: ""
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: true
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set and create is true, a name is generated using the fullname template
+  name: ""
+
+podAnnotations: {}
+
+podSecurityContext: {}
+  # fsGroup: 2000
+
+securityContext: {}
+  # capabilities:
+  #   drop:
+  #   - ALL
+  # readOnlyRootFilesystem: true
+  # runAsNonRoot: true
+  # runAsUser: 1000
+
+service:
+  type: ClusterIP
+  port: 80
+
+ingress:
+  enabled: false
+  className: ""
+  annotations: {}
+    # kubernetes.io/ingress.class: nginx
+    # kubernetes.io/tls-acme: "true"
+  hosts:
+    - host: chart-example.local
+      paths:
+        - path: /
+          pathType: ImplementationSpecific
+  tls: []
+  #  - secretName: chart-example-tls
+  #    hosts:
+  #      - chart-example.local
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+autoscaling:
+  enabled: false
+  minReplicas: 1
+  maxReplicas: 100
+  targetCPUUtilizationPercentage: 80
+  # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}