diff --git a/pkg/engine/contract_render_test.go b/pkg/engine/contract_render_test.go index f77cb5c..ec4b7ec 100644 --- a/pkg/engine/contract_render_test.go +++ b/pkg/engine/contract_render_test.go @@ -56,6 +56,33 @@ func TestContract_Render_NoTemplateFilesError(t *testing.T) { } } +// Contract: the empty-TemplateFiles guard fires BEFORE any chart +// rendering. When no template is requested, talm must surface the +// actionable "templates are not set" error without first rendering the +// chart — otherwise a render-time failure (here a `fail` directive, +// online a live `lookup` against an unreachable node) masks the real +// problem with a misleading error. The chart body below errors if it is +// ever executed; the guard must short-circuit before that happens. +func TestContract_Render_NoTemplateFilesShortCircuitsBeforeRender(t *testing.T) { + chartRoot := createTestChart(t, "tc", "config.yaml", + `{{ fail "chart body must not render when no templates are requested" }}`) + _, err := Render(context.Background(), nil, Options{ + Offline: true, + Root: chartRoot, + // TemplateFiles intentionally empty + }) + if err == nil { + t.Fatal("expected error for empty TemplateFiles") + } + msg := err.Error() + if strings.Contains(msg, "chart body must not render") { + t.Errorf("chart was rendered before the no-templates guard fired; the guard must run first. got: %s", msg) + } + if !strings.Contains(msg, "templates are not set") { + t.Errorf("expected the 'templates are not set' guard error, got: %s", msg) + } +} + // Contract: Render with a TemplateFiles entry that does not exist in // the chart surfaces an error naming the missing template. Operators // hit this when they typo a path or when a template file is renamed diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index de9480c..e575a5a 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -1602,6 +1602,17 @@ func Render(ctx context.Context, c *client.Client, opts Options) ([]byte, error) helmEngine.LookupFunc = newLookupFunction(ctx, c, cmdName, opts.TalosEndpoints) } + // Require at least one template before loading and rendering the chart. + // Rendering it first would be wasted (the output is discarded when no + // template is selected) and, worse, the render fires live lookups whose + // "connection refused" then masks the real problem: the operator forgot + // --file / --template. Fail fast with the actionable error instead. This + // runs after the online multi-node guard, which is a cheaper precondition + // with no network I/O. + if len(opts.TemplateFiles) == 0 { + return nil, errors.New("templates are not set for the command: please use `--file` or `--template` flag") + } + chartPath, err := os.Getwd() if err != nil { return nil, errors.Wrap(err, "resolving working directory") @@ -1633,10 +1644,6 @@ func Render(ctx context.Context, c *client.Client, opts Options) ([]byte, error) return nil, errors.Wrap(err, "rendering chart") } - if len(opts.TemplateFiles) == 0 { - return nil, errors.New("templates are not set for the command: please use `--file` or `--template` flag") - } - configPatches := []string{} for _, templateFile := range opts.TemplateFiles {