diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index e876df6c0..dae591f3b 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "maps" "math" "reflect" @@ -88,8 +89,15 @@ func funcMap() template.FuncMap { "tpl": func(string, any) any { return "not implemented" }, "required": func(string, any) (any, error) { return "not implemented", nil }, // Provide a placeholder for the "lookup" function, which requires a kubernetes - // connection. - "lookup": func(string, string, string, string) (map[string]any, error) { + // connection. Emits a debug log so `helm template --debug` surfaces the fact + // that a chart's `lookup` calls are being silently short-circuited. + "lookup": func(apiVersion, kind, namespace, name string) (map[string]any, error) { + slog.Debug("lookup: no Kubernetes client available, returning empty result", + slog.String("apiVersion", apiVersion), + slog.String("kind", kind), + slog.String("namespace", namespace), + slog.String("name", name), + ) return map[string]any{}, nil }, } diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go index 03ed64153..c7e736ec0 100644 --- a/pkg/engine/funcs_test.go +++ b/pkg/engine/funcs_test.go @@ -17,6 +17,8 @@ limitations under the License. package engine import ( + "bytes" + "log/slog" "math" "strings" "testing" @@ -195,6 +197,30 @@ keyInElement1 = "valueInElement1"`, } } +// TestPlaceholderLookupEmitsDebugLog verifies that the offline `lookup` +// placeholder (used by `helm template` when there is no cluster connection) +// emits a debug log identifying the requested resource, so users running +// `helm template --debug` can see that `lookup` was silently short-circuited. +func TestPlaceholderLookupEmitsDebugLog(t *testing.T) { + var buf bytes.Buffer + handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) + prev := slog.Default() + slog.SetDefault(slog.New(handler)) + t.Cleanup(func() { slog.SetDefault(prev) }) + + tpl := `{{ lookup "v1" "ConfigMap" "some-ns" "some-name" }}` + var out strings.Builder + require.NoError(t, template.Must(template.New("test").Funcs(funcMap()).Parse(tpl)).Execute(&out, nil)) + assert.Equal(t, "map[]", out.String()) + + logged := buf.String() + assert.Contains(t, logged, "lookup: no Kubernetes client available") + assert.Contains(t, logged, `apiVersion=v1`) + assert.Contains(t, logged, `kind=ConfigMap`) + assert.Contains(t, logged, `namespace=some-ns`) + assert.Contains(t, logged, `name=some-name`) +} + func TestDurationHelpers(t *testing.T) { tests := []struct { name string