Skip to content

Commit 2324e63

Browse files
committed
fix: Fixing --queue-construct-as resulting in empty tokenization
1 parent 87661fe commit 2324e63

4 files changed

Lines changed: 101 additions & 0 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
version: "v1.1.4"
3+
category: "bug-fixes"
4+
---
5+
6+
#### `find` and `list` reject a `--queue-construct-as` value that holds no command
7+
8+
A value made only of shell punctuation, such as `terragrunt find --queue-construct-as=';'`, ended the run with a crash report. A value that quotes an empty command, such as `--queue-construct-as='""'`, was accepted even though it names no command.
9+
10+
`find` and `list` now exit with an error that repeats the value you passed and shows what [`--queue-construct-as`](/reference/cli/commands/list#queue-construct-as) expects instead.

internal/discovery/constructor.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ func NewForDiscoveryCommand(l log.Logger, fsys vfs.FS, opts *DiscoveryCommandOpt
8484
return nil, err
8585
}
8686

87+
// The parser stops at a shell operator like ';' or '|' without reporting an error, so
88+
// a value that leads with one yields no words. A quoted empty string yields one empty word.
89+
if len(args) == 0 || args[0] == "" {
90+
return nil, NewEmptyQueueConstructAsError(opts.QueueConstructAs)
91+
}
92+
8793
cmd := args[0]
8894
if len(args) > 1 {
8995
args = args[1:]
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package discovery_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/gruntwork-io/terragrunt/internal/discovery"
7+
"github.com/gruntwork-io/terragrunt/test/helpers/logger"
8+
"github.com/gruntwork-io/terragrunt/test/helpers/venvtest"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestNewForDiscoveryCommand_QueueConstructAs(t *testing.T) {
13+
t.Parallel()
14+
15+
newForDiscoveryCommand := func(t *testing.T, queueConstructAs string) (*discovery.Discovery, error) {
16+
t.Helper()
17+
18+
v := venvtest.New()
19+
20+
return discovery.NewForDiscoveryCommand(logger.CreateLogger(), v.FS, &discovery.DiscoveryCommandOptions{
21+
WorkingDir: "/repo",
22+
QueueConstructAs: queueConstructAs,
23+
})
24+
}
25+
26+
testCases := []struct {
27+
name string
28+
queueConstructAs string
29+
}{
30+
{name: "semicolon", queueConstructAs: ";"},
31+
{name: "pipe", queueConstructAs: "|"},
32+
{name: "logical and", queueConstructAs: "&&"},
33+
{name: "redirect", queueConstructAs: ">"},
34+
{name: "stderr redirect", queueConstructAs: "2>&1"},
35+
{name: "whitespace", queueConstructAs: " "},
36+
{name: "tab", queueConstructAs: "\t"},
37+
{name: "command after separator", queueConstructAs: "; plan"},
38+
{name: "double quoted empty string", queueConstructAs: `""`},
39+
{name: "single quoted empty string", queueConstructAs: "''"},
40+
}
41+
42+
for _, tc := range testCases {
43+
t.Run(tc.name, func(t *testing.T) {
44+
t.Parallel()
45+
46+
_, err := newForDiscoveryCommand(t, tc.queueConstructAs)
47+
require.ErrorAs(t, err, &discovery.EmptyQueueConstructAsError{})
48+
})
49+
}
50+
51+
t.Run("command with arguments", func(t *testing.T) {
52+
t.Parallel()
53+
54+
d, err := newForDiscoveryCommand(t, "apply -destroy")
55+
require.NoError(t, err)
56+
require.NotNil(t, d)
57+
})
58+
59+
t.Run("unbalanced quote", func(t *testing.T) {
60+
t.Parallel()
61+
62+
_, err := newForDiscoveryCommand(t, "plan '")
63+
require.Error(t, err)
64+
require.NotErrorAs(t, err, &discovery.EmptyQueueConstructAsError{})
65+
})
66+
}

internal/discovery/errors.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,22 @@ func (e DiscoveryBoundaryScopeError) Error() string {
201201
func NewDiscoveryBoundaryScopeError(boundary, workingDir string) error {
202202
return DiscoveryBoundaryScopeError{Boundary: boundary, WorkingDir: workingDir}
203203
}
204+
205+
// EmptyQueueConstructAsError represents an error that occurs when the value
206+
// given for the --queue-construct-as flag contains no command.
207+
type EmptyQueueConstructAsError struct {
208+
Value string
209+
}
210+
211+
func (e EmptyQueueConstructAsError) Error() string {
212+
return fmt.Sprintf(
213+
"The --queue-construct-as value %q contains no command. "+
214+
"Pass the command to construct the queue as, like 'plan' or 'apply -destroy'.",
215+
e.Value,
216+
)
217+
}
218+
219+
// NewEmptyQueueConstructAsError creates a new [EmptyQueueConstructAsError] for the given flag value.
220+
func NewEmptyQueueConstructAsError(value string) error {
221+
return EmptyQueueConstructAsError{Value: value}
222+
}

0 commit comments

Comments
 (0)