Skip to content

Commit b3106a6

Browse files
committed
chore: discovery helpers
1 parent 971c091 commit b3106a6

6 files changed

Lines changed: 43 additions & 14 deletions

File tree

internal/discovery/helpers.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package discovery
22

33
import (
4+
iofs "io/fs"
45
"os"
56
"path/filepath"
67
"slices"
@@ -290,8 +291,14 @@ func stackDependencyPaths(fs vfs.FS, depPaths []string, c component.Component) (
290291
expanded := make([]string, 0, len(depPaths))
291292

292293
for _, depPath := range depPaths {
293-
// Only expand directories: dependency paths can also point at non-default-named config files (e.g. another-name.hcl), which the stack-file parser would otherwise reject as "not a directory".
294-
if info, statErr := fs.Stat(depPath); statErr != nil || !info.IsDir() {
294+
info, statErr := fs.Stat(depPath)
295+
// Real I/O errors (permission denied, etc.) must surface so a malformed DAG isn't silently produced; only ENOENT is treated as "keep the raw path".
296+
if statErr != nil && !errors.Is(statErr, iofs.ErrNotExist) {
297+
return nil, NewStackDependencyExpansionError(depPath, statErr)
298+
}
299+
300+
// Dependency paths can also point at non-default-named config files (e.g. another-name.hcl), which the stack-file parser would otherwise reject as "not a directory".
301+
if statErr != nil || !info.IsDir() {
295302
expanded = append(expanded, depPath)
296303
continue
297304
}

internal/discovery/stack_dependency_expansion_error_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package discovery_test
22

33
import (
4+
"errors"
45
"testing"
56

67
"github.com/gruntwork-io/terragrunt/internal/discovery"
@@ -36,3 +37,25 @@ func TestStackDependencyExpansionError_Unwrap(t *testing.T) {
3637
// errors.Is must reach the leaf via Unwrap chain.
3738
require.ErrorIs(t, wrapped, innerErr)
3839
}
40+
41+
// Locks the full unwrap chain: discovery wrapper -> MalformedDependencyError -> underlying sentinel.
42+
func TestStackDependencyExpansionError_UnwrapNestedSentinel(t *testing.T) {
43+
t.Parallel()
44+
45+
sentinel := errors.New("hcl diag root cause")
46+
47+
innerErr := hclparse.MalformedDependencyError{
48+
Err: sentinel,
49+
FilePath: "/some/path/terragrunt.autoinclude.hcl",
50+
Name: "vpc",
51+
Reason: "config_path: <hcl diag>",
52+
}
53+
54+
wrapped := discovery.NewStackDependencyExpansionError("/path/to/dep", innerErr)
55+
56+
require.ErrorIs(t, wrapped, sentinel)
57+
58+
var malformed hclparse.MalformedDependencyError
59+
require.ErrorAs(t, wrapped, &malformed)
60+
require.ErrorIs(t, malformed, sentinel)
61+
}

internal/hclparse/autoinclude_test.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package hclparse_test
22

33
import (
4-
"errors"
54
"os"
65
"path/filepath"
76
"strconv"
@@ -418,23 +417,19 @@ func TestAutoIncludeDependencyPaths_FileParseErrorOnSyntaxError(t *testing.T) {
418417
require.ErrorAs(t, err, &fpe)
419418
}
420419

421-
func TestAutoIncludeDependencyPaths_UnexpectedBodyTypeOnJSON(t *testing.T) {
420+
func TestAutoIncludeDependencyPaths_FileParseErrorOnJSON(t *testing.T) {
422421
t.Parallel()
423422

424423
fs := vfs.NewMemMapFS()
425424
jsonBody := `{"dependency": {"vpc": {"config_path": "../vpc"}}}`
426-
require.NoError(t, vfs.WriteFile(fs, filepath.Join("/test", hclparse.AutoIncludeFile+".json"), []byte(jsonBody), 0644))
427425
require.NoError(t, vfs.WriteFile(fs, filepath.Join("/test", hclparse.AutoIncludeFile), []byte(jsonBody), 0644))
428426

429427
paths, err := hclparse.AutoIncludeDependencyPaths(fs, "/test")
430428
require.Error(t, err)
431429
assert.Nil(t, paths)
432430

433431
var fpe hclparse.FileParseError
434-
435-
var ube hclparse.UnexpectedBodyTypeError
436-
437-
assert.True(t, errors.As(err, &fpe) || errors.As(err, &ube), "expected FileParseError or UnexpectedBodyTypeError, got %T: %v", err, err)
432+
require.ErrorAs(t, err, &fpe)
438433
}
439434

440435
// parseHCLBody is a test helper that parses an HCL string and returns the body.

internal/hclparse/stack_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,10 @@ func TestParseStackFileFromPath_StackDirIsFileReturnsError(t *testing.T) {
353353
result, err := hclparse.ParseStackFileFromPath(vfs.NewOSFS(), filePath)
354354
require.Error(t, err)
355355
assert.Nil(t, result)
356+
357+
var readErr hclparse.FileReadError
358+
require.ErrorAs(t, err, &readErr)
359+
assert.Equal(t, filepath.Join(filePath, "terragrunt.stack.hcl"), readErr.FilePath)
356360
}
357361

358362
func TestParseStackFileFromPath_Symlink(t *testing.T) {
@@ -403,7 +407,7 @@ unit "vpc" {
403407
paths, err := hclparse.UnitPathsFromStackDir(vfs.NewOSFS(), symlinkDir)
404408
require.NoError(t, err)
405409
require.Len(t, paths, 1)
406-
// Path should resolve to the real directory, not the symlink target.
410+
// Path should resolve to the real directory, not the symlink path.
407411
assert.Contains(t, paths[0], "real-stack")
408412
assert.NotContains(t, paths[0], "symlinked-stack")
409413
}

pkg/config/stack_autoinclude_internal_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestStackConfigHasAutoInclude(t *testing.T) {
3737
}
3838
}
3939

40-
// JSON-format remain bodies aren't *hclsyntax.Body and must return false: autoinclude blocks are only supported in native HCL.
40+
// Non-*hclsyntax.Body remain bodies (e.g. hcl.EmptyBody, JSON-format bodies) must return false: autoinclude blocks are only inspected in native HCL syntax.
4141
func TestHasAutoIncludeInBody_NonSyntaxBodyReturnsFalse(t *testing.T) {
4242
t.Parallel()
4343

test/integration_stack_dependencies_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ func TestStackDepsAutoIncludeLoudFailOnParserLimit(t *testing.T) {
496496
stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t,
497497
"terragrunt stack generate --experiment stack-dependencies --working-dir "+rootPath)
498498

499-
require.Error(t, err, "stack generate must fail loudly when autoinclude is declared and the two-pass parser cannot decode the stack file")
499+
require.Error(t, err, "stack generate must return an error when autoinclude is declared and the two-pass parser cannot decode the stack file")
500500
assert.Contains(t, err.Error()+"\n"+stdout+stderr, "failed to parse autoinclude block")
501501
require.NoFileExists(t, filepath.Join(rootPath, inthclparse.StackDir, "subnet", inthclparse.AutoIncludeFile))
502502
}
@@ -538,7 +538,7 @@ func TestStackDepsAutoIncludeLoudFailViaInclude(t *testing.T) {
538538
stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t,
539539
"terragrunt stack generate --experiment stack-dependencies --working-dir "+rootPath)
540540

541-
require.Error(t, err, "stack generate must fail loudly when autoinclude is declared in an included stack file")
541+
require.Error(t, err, "stack generate must return an error when autoinclude is declared in an included stack file")
542542
assert.Contains(t, err.Error()+"\n"+stdout+stderr, "failed to parse autoinclude block")
543543
require.NoFileExists(t, filepath.Join(rootPath, inthclparse.StackDir, "subnet", inthclparse.AutoIncludeFile))
544544
}
@@ -554,7 +554,7 @@ func TestStackDepsAutoIncludeLoudFailViaDynamicInclude(t *testing.T) {
554554
stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t,
555555
"terragrunt stack generate --experiment stack-dependencies --working-dir "+rootPath)
556556

557-
require.Error(t, err, "stack generate must fail loudly when autoinclude is reachable via an expression-based include path")
557+
require.Error(t, err, "stack generate must return an error when autoinclude is reachable via an expression-based include path")
558558
assert.Contains(t, err.Error()+"\n"+stdout+stderr, "failed to parse autoinclude block")
559559
require.NoFileExists(t, filepath.Join(rootPath, inthclparse.StackDir, "subnet", inthclparse.AutoIncludeFile))
560560
}

0 commit comments

Comments
 (0)