Skip to content

Commit 2bdc9b4

Browse files
authored
fix: Fixing hcl fmt with --stdin combined with --check and/or --diff (#6726)
* fix: Fixing `hcl fmt` with `--stdin` combined with `--check` and/or `--diff` * chore: Fix-up due to dynamic anchors
1 parent f5c1733 commit 2bdc9b4

5 files changed

Lines changed: 115 additions & 15 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+
#### `hcl fmt --stdin` honors `--check` and `--diff`
7+
8+
`terragrunt hcl fmt --stdin` ignored `--check` and `--diff`. It printed the reformatted HCL and exited 0 whether or not the input needed formatting.
9+
10+
`--check` now exits with status code 1 when the input needs formatting, and `--diff` prints a unified diff labeled `old/stdin` and `new/stdin`. Neither flag prints the formatted content, so getting that content back means running `--stdin` without them.

docs/src/data/flags/hcl-fmt-stdin.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ Example:
1313
```bash
1414
echo 'locals { foo="bar" }' | terragrunt hcl fmt --stdin
1515
```
16+
17+
With [`--check`](/reference/cli/commands/hcl/fmt#check) or [`--diff`](/reference/cli/commands/hcl/fmt#diff), Terragrunt does not print the formatted content. `--check` exits with status code 1 when the input needs formatting, and `--diff` prints a unified diff labeled `old/stdin` and `new/stdin`.

internal/cli/commands/hcl/format/errors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package format
22

33
import "fmt"
44

5-
// FileNeedsFormattingError is an error that is returned when a file needs formatting.
5+
// FileNeedsFormattingError is returned when HCL input needs formatting.
66
type FileNeedsFormattingError struct {
77
Path string
88
}

internal/cli/commands/hcl/format/format.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func Run(ctx context.Context, l log.Logger, v *venv.Venv, opts *options.Terragru
5050
return errors.New("both stdin and path flags are specified")
5151
}
5252

53-
return formatFromStdin(l, v)
53+
return formatFromStdin(l, v, opts)
5454
}
5555

5656
if targetFile != "" {
@@ -195,22 +195,42 @@ func RunForFiles(
195195
return errors.Join(errs...)
196196
}
197197

198-
func formatFromStdin(l log.Logger, v *venv.Venv) error {
198+
func formatFromStdin(l log.Logger, v *venv.Venv, opts *options.TerragruntOptions) error {
199+
const stdinPath = "stdin"
200+
199201
contents, err := io.ReadAll(v.Stdin)
200202
if err != nil {
201203
l.Errorf("Error reading from stdin: %s", err)
202204

203205
return fmt.Errorf("error reading from stdin: %w", err)
204206
}
205207

206-
if err = checkErrors(l, v, contents, "stdin"); err != nil {
208+
if err = checkErrors(l, v, contents, stdinPath); err != nil {
207209
l.Errorf("Error parsing hcl from stdin")
208210

209211
return fmt.Errorf("error parsing hcl from stdin: %w", err)
210212
}
211213

212214
newContents := hclwrite.Format(contents)
213215

216+
needsFormatting := !bytes.Equal(newContents, contents)
217+
218+
if opts.Diff && needsFormatting {
219+
if _, err := v.Writers.Writer.Write(bytesDiff(contents, newContents, stdinPath)); err != nil {
220+
l.Errorf("Failed to print diff for stdin")
221+
222+
return err
223+
}
224+
}
225+
226+
if opts.Check && needsFormatting {
227+
return &FileNeedsFormattingError{Path: stdinPath}
228+
}
229+
230+
if opts.Diff || opts.Check {
231+
return nil
232+
}
233+
214234
buf := bufio.NewWriter(v.Writers.Writer)
215235

216236
if _, err = buf.Write(newContents); err != nil {

internal/cli/commands/hcl/format/format_test.go

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -286,22 +286,90 @@ func TestHCLFmtStdin(t *testing.T) {
286286
unformatted := onDisk(t, "../../../../../test/fixtures/hclfmt-stdin/terragrunt.hcl")
287287
expected := onDisk(t, "../../../../../test/fixtures/hclfmt-stdin/expected.hcl")
288288

289-
tgOptions, err := options.NewTerragruntOptionsForTest("")
290-
require.NoError(t, err)
289+
tests := map[string]struct {
290+
input string
291+
wantStdout string
292+
wantDiffLines []string
293+
check bool
294+
diff bool
295+
wantNeedsFormatting bool
296+
}{
297+
"formatted content goes to stdout": {
298+
input: unformatted,
299+
wantStdout: expected,
300+
},
301+
"check reports unformatted input": {
302+
input: unformatted,
303+
check: true,
304+
wantNeedsFormatting: true,
305+
},
306+
"check accepts formatted input": {
307+
input: expected,
308+
check: true,
309+
},
310+
"diff shows what would change": {
311+
input: unformatted,
312+
diff: true,
313+
wantDiffLines: []string{"--- old/stdin", "+++ new/stdin", "+ foo = \"bar\""},
314+
},
315+
"diff is empty for formatted input": {
316+
input: expected,
317+
diff: true,
318+
},
319+
"check and diff both apply": {
320+
input: unformatted,
321+
check: true,
322+
diff: true,
323+
wantDiffLines: []string{"--- old/stdin", "+++ new/stdin"},
324+
wantNeedsFormatting: true,
325+
},
326+
}
291327

292-
var formatted bytes.Buffer
328+
for name, tc := range tests {
329+
t.Run(name, func(t *testing.T) {
330+
t.Parallel()
293331

294-
// format hcl from stdin
295-
tgOptions.HclFromStdin = true
332+
tgOptions, err := options.NewTerragruntOptionsForTest("")
333+
require.NoError(t, err)
296334

297-
v := venvtest.New().
298-
WithStdin(strings.NewReader(unformatted)).
299-
WithWriter(&formatted)
335+
tgOptions.HclFromStdin = true
336+
tgOptions.Check = tc.check
337+
tgOptions.Diff = tc.diff
300338

301-
err = format.Run(t.Context(), logger.CreateLogger(), v, tgOptions)
302-
require.NoError(t, err)
339+
var out bytes.Buffer
340+
341+
v := venvtest.New().
342+
WithStdin(strings.NewReader(tc.input)).
343+
WithWriter(&out)
344+
345+
err = format.Run(t.Context(), logger.CreateLogger(), v, tgOptions)
346+
347+
stdout := out.String()
303348

304-
assert.Equal(t, expected, formatted.String())
349+
for _, want := range tc.wantDiffLines {
350+
assert.Contains(t, stdout, want)
351+
}
352+
353+
if len(tc.wantDiffLines) > 0 {
354+
assert.NotContains(t, stdout, expected, "--diff prints the diff instead of the formatted content")
355+
}
356+
357+
if len(tc.wantDiffLines) == 0 {
358+
assert.Equal(t, tc.wantStdout, stdout)
359+
}
360+
361+
if !tc.wantNeedsFormatting {
362+
require.NoError(t, err)
363+
364+
return
365+
}
366+
367+
var needsFormatting *format.FileNeedsFormattingError
368+
369+
require.ErrorAs(t, err, &needsFormatting)
370+
assert.Equal(t, "stdin", needsFormatting.Path)
371+
})
372+
}
305373
}
306374

307375
func TestHCLFmtHeredoc(t *testing.T) {

0 commit comments

Comments
 (0)