diff --git a/cli/commands/hclfmt/action.go b/cli/commands/hclfmt/action.go index 58888b194..b5eee59d3 100644 --- a/cli/commands/hclfmt/action.go +++ b/cli/commands/hclfmt/action.go @@ -4,8 +4,10 @@ package hclfmt import ( + "bufio" "bytes" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -28,6 +30,16 @@ import ( func Run(opts *options.TerragruntOptions) error { workingDir := opts.WorkingDir targetFile := opts.HclFile + stdIn := opts.HclFromStdin + + if stdIn { + if targetFile != "" { + opts.Logger.Debugf("Both stdin and path flags are specified") + return errors.Errorf("both stdin and path flags are specified") + } + + return formatFromStdin(opts) + } // handle when option specifies a particular file if targetFile != "" { @@ -81,6 +93,39 @@ func Run(opts *options.TerragruntOptions) error { return formatErrors.ErrorOrNil() } +func formatFromStdin(opts *options.TerragruntOptions) error { + contents, err := io.ReadAll(os.Stdin) + + if err != nil { + opts.Logger.Errorf("Error reading from stdin: %s", err) + return err + } + + err = checkErrors(opts.Logger, opts.DisableLogColors, contents, "stdin") + if err != nil { + opts.Logger.Errorf("Error parsing hcl from stdin") + return err + } + + newContents := hclwrite.Format(contents) + + buf := bufio.NewWriter(os.Stdout) + _, err = buf.Write(newContents) + + if err != nil { + opts.Logger.Errorf("Failed to write to stdout") + return err + } + + err = buf.Flush() + if err != nil { + opts.Logger.Errorf("Failed to flush to stdout") + return err + } + + return nil +} + // formatTgHCL uses the hcl2 library to format the hcl file. This will attempt to parse the HCL file first to // ensure that there are no syntax errors, before attempting to format it. func formatTgHCL(opts *options.TerragruntOptions, tgHclFile string) error { @@ -125,7 +170,7 @@ func formatTgHCL(opts *options.TerragruntOptions, tgHclFile string) error { } if opts.Check && fileUpdated { - return fmt.Errorf("Invalid file format %s", tgHclFile) + return fmt.Errorf("invalid file format %s", tgHclFile) } if fileUpdated { diff --git a/cli/commands/hclfmt/action_test.go b/cli/commands/hclfmt/action_test.go index 2cc085113..6fd58cbb7 100644 --- a/cli/commands/hclfmt/action_test.go +++ b/cli/commands/hclfmt/action_test.go @@ -263,6 +263,41 @@ func TestHCLFmtFile(t *testing.T) { } } +func TestHCLFmtStdin(t *testing.T) { + t.Parallel() + + realStdin := os.Stdin + realStdout := os.Stdout + + tempStdoutFile, err := os.CreateTemp(t.TempDir(), "stdout.hcl") + defer func() { + _ = tempStdoutFile.Close() + }() + require.NoError(t, err) + + os.Stdout = tempStdoutFile + defer func() { os.Stdout = realStdout }() + + os.Stdin, err = os.Open("../../../test/fixture-hclfmt-stdin/terragrunt.hcl") + defer func() { os.Stdin = realStdin }() + require.NoError(t, err) + + expected, err := os.ReadFile("../../../test/fixture-hclfmt-stdin/expected.hcl") + require.NoError(t, err) + + tgOptions, err := options.NewTerragruntOptionsForTest("") + require.NoError(t, err) + + // format hcl from stdin + tgOptions.HclFromStdin = true + err = hclfmt.Run(tgOptions) + require.NoError(t, err) + + formatted, err := os.ReadFile(tempStdoutFile.Name()) + require.NoError(t, err) + assert.Equal(t, expected, formatted) +} + func TestHCLFmtHeredoc(t *testing.T) { t.Parallel() diff --git a/cli/commands/hclfmt/command.go b/cli/commands/hclfmt/command.go index bdda06c81..8585f182b 100644 --- a/cli/commands/hclfmt/command.go +++ b/cli/commands/hclfmt/command.go @@ -8,9 +8,10 @@ import ( const ( CommandName = "hclfmt" - FlagNameTerragruntHCLFmt = "terragrunt-hclfmt-file" - FlagNameTerragruntCheck = "terragrunt-check" - FlagNameTerragruntDiff = "terragrunt-diff" + FlagNameTerragruntHCLFmt = "terragrunt-hclfmt-file" + FlagNameTerragruntCheck = "terragrunt-check" + FlagNameTerragruntDiff = "terragrunt-diff" + FlagNameTerragruntHCLFmtStdin = "terragrunt-hclfmt-stdin" ) func NewFlags(opts *options.TerragruntOptions) cli.Flags { @@ -32,6 +33,12 @@ func NewFlags(opts *options.TerragruntOptions) cli.Flags { EnvVar: "TERRAGRUNT_DIFF", Usage: "Print diff between original and modified file versions when running with 'hclfmt'.", }, + &cli.BoolFlag{ + Name: FlagNameTerragruntHCLFmtStdin, + Destination: &opts.HclFromStdin, + EnvVar: "TERRAGRUNT_HCLFMT_STDIN", + Usage: "Format HCL from stdin and print result to stdout.", + }, } } diff --git a/docs/_docs/04_reference/cli-options.md b/docs/_docs/04_reference/cli-options.md index 462b71563..7556db615 100644 --- a/docs/_docs/04_reference/cli-options.md +++ b/docs/_docs/04_reference/cli-options.md @@ -65,6 +65,7 @@ This page documents the CLI commands and options available with Terragrunt: - [terragrunt-check](#terragrunt-check) - [terragrunt-diff](#terragrunt-diff) - [terragrunt-hclfmt-file](#terragrunt-hclfmt-file) + - [terragrunt-hclfmt-stdin](#terragrunt-hclfmt-stdin) - [terragrunt-hclvalidate-json](#terragrunt-hclvalidate-json) - [terragrunt-hclvalidate-show-config-path](#terragrunt-hclvalidate-show-config-path) - [terragrunt-override-attr](#terragrunt-override-attr) @@ -769,6 +770,7 @@ prefix `--terragrunt-` (e.g., `--terragrunt-config`). The currently available op - [terragrunt-check](#terragrunt-check) - [terragrunt-diff](#terragrunt-diff) - [terragrunt-hclfmt-file](#terragrunt-hclfmt-file) + - [terragrunt-hclfmt-stdin](#terragrunt-hclfmt-stdin) - [terragrunt-hclvalidate-json](#terragrunt-hclvalidate-json) - [terragrunt-hclvalidate-show-config-path](#terragrunt-hclvalidate-show-config-path) - [terragrunt-override-attr](#terragrunt-override-attr) @@ -1149,7 +1151,15 @@ When passed in, running `hclfmt` will print diff between original and modified f - [hclfmt](#hclfmt) -When passed in, run `hclfmt` only on specified hcl file. +### terragrunt-hclfmt-stdin + +**CLI Arg**: `--terragrunt-hclfmt-stdin`
+**Environment Variable**: `TERRAGRUNT_HCLFMT_STDIN` (set to `true`)
+**Commands**: + +- [hclfmt](#hclfmt) + +When passed in, run `hclfmt` only on hcl passed to `stdin`, result is printed to `stdout`. ### terragrunt-hclvalidate-json diff --git a/options/options.go b/options/options.go index 439ee5a43..827247988 100644 --- a/options/options.go +++ b/options/options.go @@ -235,6 +235,9 @@ type TerragruntOptions struct { // The file which hclfmt should be specifically run on HclFile string + // If True then HCL from StdIn must should be formatted. + HclFromStdin bool + // The file path that terragrunt should use when rendering the terragrunt.hcl config as json. JSONOut string diff --git a/test/fixture-hclfmt-stdin/expected.hcl b/test/fixture-hclfmt-stdin/expected.hcl new file mode 100644 index 000000000..a9d035a9e --- /dev/null +++ b/test/fixture-hclfmt-stdin/expected.hcl @@ -0,0 +1,13 @@ +inputs = { + # comments + foo = "bar" + bar = "baz" + + inputs = "disjoint" + disjoint = true + + listInput = [ + "foo", + "bar", + ] +} diff --git a/test/fixture-hclfmt-stdin/terragrunt.hcl b/test/fixture-hclfmt-stdin/terragrunt.hcl new file mode 100644 index 000000000..e7727b806 --- /dev/null +++ b/test/fixture-hclfmt-stdin/terragrunt.hcl @@ -0,0 +1,13 @@ +inputs = { +# comments + foo = "bar" + bar="baz" + + inputs = "disjoint" + disjoint = true + + listInput = [ +"foo", +"bar", +] +}