Skip to content

Commit

Permalink
Allow hcl format from stdin (#3288)
Browse files Browse the repository at this point in the history
* Allow formating from stdin

Resolves #2926

* Update cli/commands/hclfmt/action_test.go

Co-authored-by: Yousif Akbar <[email protected]>

* Change os.TempDir() to t.TempDir()

* Fix lint error

* Try fix lint error

* Fix lint issues

* Refactor

* Fix clone function

* Add integration test

* Simplify test, change stdout to opts.Writer

* Fix strict linter issues

---------

Co-authored-by: Yousif Akbar <[email protected]>
  • Loading branch information
alikhil and yhakbar authored Nov 12, 2024
1 parent 1be1f53 commit 748f855
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 4 deletions.
45 changes: 45 additions & 0 deletions cli/commands/hclfmt/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
package hclfmt

import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
Expand All @@ -27,6 +29,15 @@ import (
func Run(opts *options.TerragruntOptions) error {
workingDir := opts.WorkingDir
targetFile := opts.HclFile
stdIn := opts.HclFromStdin

if stdIn {
if targetFile != "" {
return errors.Errorf("both stdin and path flags are specified")
}

return formatFromStdin(opts)
}

// handle when option specifies a particular file
if targetFile != "" {
Expand Down Expand Up @@ -80,6 +91,40 @@ 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 fmt.Errorf("error reading from stdin: %w", err)
}

if err = checkErrors(opts.Logger, opts.DisableLogColors, contents, "stdin"); err != nil {
opts.Logger.Errorf("Error parsing hcl from stdin")

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

newContents := hclwrite.Format(contents)

buf := bufio.NewWriter(opts.Writer)

if _, err = buf.Write(newContents); err != nil {
opts.Logger.Errorf("Failed to write to stdout")

return fmt.Errorf("failed to write to stdout: %w", err)
}

if err = buf.Flush(); err != nil {
opts.Logger.Errorf("Failed to flush to stdout")

return fmt.Errorf("failed to flush to stdout: %w", 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 {
Expand Down
35 changes: 35 additions & 0 deletions cli/commands/hclfmt/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/fixtures/hclfmt-stdin/terragrunt.hcl")
defer func() { os.Stdin = realStdin }()
require.NoError(t, err)

expected, err := os.ReadFile("../../../test/fixtures/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()

Expand Down
13 changes: 10 additions & 3 deletions cli/commands/hclfmt/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,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 {
Expand All @@ -33,6 +34,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.",
},
}
}

Expand Down
12 changes: 11 additions & 1 deletion docs/_docs/04_reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -771,6 +772,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)
Expand Down Expand Up @@ -1158,7 +1160,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`<br/>
**Environment Variable**: `TERRAGRUNT_HCLFMT_STDIN` (set to `true`)<br/>
**Commands**:

- [hclfmt](#hclfmt)

When passed in, run `hclfmt` only on hcl passed to `stdin`, result is printed to `stdout`.

### terragrunt-hclvalidate-json

Expand Down
4 changes: 4 additions & 0 deletions options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,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

Expand Down Expand Up @@ -589,6 +592,7 @@ func (opts *TerragruntOptions) Clone(terragruntConfigPath string) (*TerragruntOp
RunTerragrunt: opts.RunTerragrunt,
AwsProviderPatchOverrides: opts.AwsProviderPatchOverrides,
HclFile: opts.HclFile,
HclFromStdin: opts.HclFromStdin,
JSONOut: opts.JSONOut,
JSONLogFormat: opts.JSONLogFormat,
Check: opts.Check,
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/hclfmt-stdin/expected.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
inputs = {
# comments
foo = "bar"
bar = "baz"

inputs = "disjoint"
disjoint = true

listInput = [
"foo",
"bar",
]
}
13 changes: 13 additions & 0 deletions test/fixtures/hclfmt-stdin/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
inputs = {
# comments
foo = "bar"
bar="baz"

inputs = "disjoint"
disjoint = true

listInput = [
"foo",
"bar",
]
}
19 changes: 19 additions & 0 deletions test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const (
testFixtureGraphDependencies = "fixtures/graph-dependencies"
testFixtureHclfmtDiff = "fixtures/hclfmt-diff"
testFixtureHclvalidate = "fixtures/hclvalidate"
testFixtureHclfmtStdin = "fixtures/hclfmt-stdin"
testFixtureIamRolesMultipleModules = "fixtures/read-config/iam_roles_multiple_modules"
testFixtureIncludeParent = "fixtures/include-parent"
testFixtureInfoError = "fixtures/terragrunt-info-error"
Expand Down Expand Up @@ -3322,6 +3323,24 @@ func TestHclFmtDiff(t *testing.T) {
assert.Contains(t, output, string(expectedDiff))
}

func TestHclFmtStdin(t *testing.T) {
t.Parallel()

cleanupTerraformFolder(t, testFixtureHclfmtStdin)
tmpEnvPath := copyEnvironment(t, testFixtureHclfmtStdin)
rootPath := util.JoinPath(tmpEnvPath, testFixtureHclfmtStdin)

os.Stdin, _ = os.Open(util.JoinPath(rootPath, "terragrunt.hcl"))

stdout, _, err := runTerragruntCommandWithOutput(t, "terragrunt hclfmt --terragrunt-hclfmt-stdin")
require.NoError(t, err)

expectedDiff, err := os.ReadFile(util.JoinPath(rootPath, "expected.hcl"))
require.NoError(t, err)

assert.Contains(t, stdout, string(expectedDiff))
}

func TestDownloadSourceWithRef(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 748f855

Please sign in to comment.