diff --git a/CHANGELOG.md b/CHANGELOG.md index f26ae7ae..6343e2e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## UNRELEASED IMPROVEMENTS: +* cli: Improved error message when pack lacks `.nomad.tpl` files to clearly explain naming requirements, show template naming convention, and list found template files [[GH-510](https://github.com/hashicorp/nomad-pack/pull/831)] * cli: Add registry now honors default main/master branch [[GH-843](https://github.com/hashicorp/nomad-pack/pull/843)] ## 0.4.2 (March 16, 2026) diff --git a/internal/cli/helpers.go b/internal/cli/helpers.go index 45328955..8f3162e2 100644 --- a/internal/cli/helpers.go +++ b/internal/cli/helpers.go @@ -6,6 +6,7 @@ package cli import ( "fmt" "os" + "path/filepath" "strings" "github.com/hashicorp/nomad/api" @@ -557,3 +558,27 @@ func limit(s string, length int) string { return s[:length] } + +// addNoParentTemplatesContext adds error details for missing parent templates +// to an existing error context. It lists any .tpl files discovered and provides +// naming guidance. +func addNoParentTemplatesContext(errorContext *errors.UIErrorContext, packPath string) { + errorContext.Add(errors.UIContextErrorDetail, "No parent templates (*.nomad.tpl files) were found in the pack") + errorContext.Add(errors.UIContextErrorSuggestion, "Parent templates must end with .nomad.tpl (e.g., app.nomad.tpl). Helper templates should start with _ (e.g., _helpers.tpl)") + + // list found template files + templatesPath := filepath.Join(packPath, "templates") + if entries, err := os.ReadDir(templatesPath); err == nil { + var templateFiles []string + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".tpl") { + templateFiles = append(templateFiles, entry.Name()) + } + } + if len(templateFiles) > 0 { + errorContext.Add("Found Templates: ", strings.Join(templateFiles, ", ")) + } else { + errorContext.Add("Found Templates: ", "none") + } + } +} diff --git a/internal/cli/helpers_test.go b/internal/cli/helpers_test.go index 63047e13..40740eae 100644 --- a/internal/cli/helpers_test.go +++ b/internal/cli/helpers_test.go @@ -7,15 +7,20 @@ import ( "encoding/json" "os" "path" + "path/filepath" + "strings" "testing" "github.com/posener/complete" "github.com/shoenig/test/must" "github.com/hashicorp/nomad-pack/internal/pkg/caching" + "github.com/hashicorp/nomad-pack/internal/pkg/errors" "github.com/hashicorp/nomad-pack/internal/pkg/helper/filesystem" "github.com/hashicorp/nomad-pack/internal/pkg/logging" "github.com/hashicorp/nomad-pack/internal/pkg/testfixture" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestExtractFlagValue(t *testing.T) { @@ -131,3 +136,35 @@ func assertPredictorResults(t *testing.T, got, expected []string) { must.Eq(t, len(expected), len(got)) must.SliceContainsAll(t, expected, got) } + +func TestAddNoParentTemplatesContext(t *testing.T) { + // create a temporary test directory with template files + tmpDir := t.TempDir() + templatesDir := filepath.Join(tmpDir, "templates") + err := os.MkdirAll(templatesDir, 0755) + require.NoError(t, err) + + //create test template files + testFiles := []string{"test.tpl", "_helpers.tpl", "config.tpl"} + for _, file := range testFiles { + err := os.WriteFile(filepath.Join(templatesDir, file), []byte("test"), 0644) + require.NoError(t, err) + } + + // build error context + ctx := errors.NewUIErrorContext() + addNoParentTemplatesContext(ctx, tmpDir) + require.NotNil(t, ctx) + + // get all context entries + entries := ctx.GetAll() + require.NotEmpty(t, entries) + + //verify expected content + contextStr := strings.Join(entries, " ") + assert.Contains(t, contextStr, "No parent templates") + assert.Contains(t, contextStr, "*.nomad.tpl") + assert.Contains(t, contextStr, "test.tpl") + assert.Contains(t, contextStr, "_helpers.tpl") + assert.Contains(t, contextStr, "config.tpl") +} diff --git a/internal/cli/plan.go b/internal/cli/plan.go index 4095145e..ad258e9b 100644 --- a/internal/cli/plan.go +++ b/internal/cli/plan.go @@ -69,10 +69,9 @@ func (c *PlanCommand) Run(args []string) int { return c.exitCodeError } - // Commands that render templates are required to render at least one - // parent template. if r.LenParentRenders() < 1 { - c.ui.ErrorWithContext(errors.ErrNoTemplatesRendered, "no templates rendered", errorContext.GetAll()...) + addNoParentTemplatesContext(errorContext, c.packConfig.Path) + c.ui.ErrorWithContext(errors.ErrNoTemplatesRendered, "no parent templates found", errorContext.GetAll()...) return c.exitCodeError } diff --git a/internal/cli/run.go b/internal/cli/run.go index 029001dc..91bf1193 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -80,6 +80,12 @@ func (c *RunCommand) run() int { return 255 } + if r.LenParentRenders() < 1 { + addNoParentTemplatesContext(errorContext, c.packConfig.Path) + c.ui.ErrorWithContext(errors.ErrNoTemplatesRendered, "no parent templates found", errorContext.GetAll()...) + return 1 + } + renderedParents := r.ParentRenders() renderedDeps := r.DependentRenders()