Repository: helm/helm (go)

Issue:
Helm function 'fail' should not fail when doing 'helm lint'
When executing `helm lint` directives such as this fail the linting:

```yaml
failure: {{ fail "this is a failure!" }}
```

The same problem was already fixed for the `required` function: https://github.com/helm/helm/pull/4221
An issue had already been written on `failure` but was closed due to inactivity: https://github.com/helm/helm/issues/3026

In order to lint charts using `fail` this needs to be fixed.

Reference: https://helm.sh/docs/chart_template_guide/function_list/#fail



Pull request:
Fail message is now the same as the required message.
closes #8973 Helm function 'fail' should not fail when doing 'helm lint'

Signed-off-by: Marcus Speight <marcus.speight@hotmail.co.uk>

**What this PR does / why we need it**:
The fail function output does not match the required function output.

e.g.
`{{ required "This is an error" .notExist }}` produces `Error: execution error at (example/templates/required.yaml:1:3): This is an error`
`{{ fail "This is an error" }}` produces `Error: template: example/templates/fail.yaml:1:3: executing "example/templates/service.yaml" at <fail "This is an error">: error calling fail: This is an error`

This PR makes the fail message identical to the required message.

**Special notes for your reviewer**:

**If applicable**:
- [ ] this PR contains documentation
- [x] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility


Patch:
diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go
index 155d50a38..4da64f5cc 100644
--- a/pkg/engine/engine.go
+++ b/pkg/engine/engine.go
@@ -173,6 +173,16 @@ func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]render
 		return val, nil
 	}
 
+	// Override sprig fail function for linting and wrapping message
+	funcMap["fail"] = func(msg string) (string, error) {
+		if e.LintMode {
+			// Don't fail when linting
+			log.Printf("[INFO] Fail: %s", msg)
+			return "", nil
+		}
+		return "", errors.New(warnWrap(msg))
+	}
+
 	// If we are not linting and have a cluster connection, provide a Kubernetes-backed
 	// implementation.
 	if !e.LintMode && e.config != nil {


Test patch:
diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go
index 87e84c48b..39f895681 100644
--- a/pkg/engine/engine_test.go
+++ b/pkg/engine/engine_test.go
@@ -286,6 +286,35 @@ func TestExecErrors(t *testing.T) {
 	}
 }
 
+func TestFailErrors(t *testing.T) {
+	vals := chartutil.Values{"Values": map[string]interface{}{}}
+
+	failtpl := `All your base are belong to us{{ fail "This is an error" }}`
+	tplsFailed := map[string]renderable{
+		"failtpl": {tpl: failtpl, vals: vals},
+	}
+	_, err := new(Engine).render(tplsFailed)
+	if err == nil {
+		t.Fatalf("Expected failures while rendering: %s", err)
+	}
+	expected := `execution error at (failtpl:1:33): This is an error`
+	if err.Error() != expected {
+		t.Errorf("Expected '%s', got %q", expected, err.Error())
+	}
+
+	var e Engine
+	e.LintMode = true
+	out, err := e.render(tplsFailed)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expectStr := "All your base are belong to us"
+	if gotStr := out["failtpl"]; gotStr != expectStr {
+		t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out)
+	}
+}
+
 func TestAllTemplates(t *testing.T) {
 	ch1 := &chart.Chart{
 		Metadata: &chart.Metadata{Name: "ch1"},