Skip to content

Commit

Permalink
Sorting output and fixing minor bugs in hclvalidate command (#3309)
Browse files Browse the repository at this point in the history
* feat: sorting hclvalidate output

* chore: improving updating of unknown values

* fix: go-fmt

* fix: updating of unknown values

* fix: tests

* fix: tests

* chore: rename env var
  • Loading branch information
levkohimins authored Aug 1, 2024
1 parent 117a299 commit 2b40a24
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 87 deletions.
12 changes: 10 additions & 2 deletions cli/commands/hclvalidate/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package hclvalidate

import (
"context"
"sort"

"github.com/gruntwork-io/terragrunt/config"
"github.com/gruntwork-io/terragrunt/config/hclparse"
Expand Down Expand Up @@ -42,6 +43,13 @@ func Run(ctx context.Context, opts *Options) (er error) {
stackErr := stack.Run(ctx, opts.TerragruntOptions)

if len(diags) > 0 {
sort.Slice(diags, func(i, j int) bool {
if diags[i].Range != nil && diags[j].Range != nil && diags[i].Range.Filename > diags[j].Range.Filename {
return false
}
return true
})

if err := writeDiagnostics(opts, diags); err != nil {
return err
}
Expand All @@ -58,8 +66,8 @@ func writeDiagnostics(opts *Options, diags diagnostic.Diagnostics) error {

writer := view.NewWriter(opts.Writer, render)

if opts.InvalidConfigPath {
return writer.InvalidConfigPath(diags)
if opts.ShowConfigPath {
return writer.ShowConfigPath(diags)
}

return writer.Diagnostics(diags)
Expand Down
10 changes: 5 additions & 5 deletions cli/commands/hclvalidate/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
const (
CommandName = "hclvalidate"

InvalidFlagName = "terragrunt-hclvalidate-invalid"
InvalidEnvVarName = "TERRAGRUNT_HCLVALIDATE_INVALID"
ShowConfigPathFlagName = "terragrunt-hclvalidate-show-config-path"
ShowConfigPathEnvVarName = "TERRAGRUNT_HCLVALIDATE_SHOW_CONFIG_PATH"

JSONOutputFlagName = "terragrunt-hclvalidate-json"
JSONOutputEnvVarName = "TERRAGRUNT_HCLVALIDATE_JSON"
Expand All @@ -21,10 +21,10 @@ const (
func NewFlags(opts *Options) cli.Flags {
return cli.Flags{
&cli.BoolFlag{
Name: InvalidFlagName,
EnvVar: InvalidEnvVarName,
Name: ShowConfigPathFlagName,
EnvVar: ShowConfigPathEnvVarName,
Usage: "Show a list of files with invalid configuration.",
Destination: &opts.InvalidConfigPath,
Destination: &opts.ShowConfigPath,
},
&cli.BoolFlag{
Name: JSONOutputFlagName,
Expand Down
4 changes: 2 additions & 2 deletions cli/commands/hclvalidate/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import "github.com/gruntwork-io/terragrunt/options"
type Options struct {
*options.TerragruntOptions

InvalidConfigPath bool
JSONOutput bool
ShowConfigPath bool
JSONOutput bool
}

func NewOptions(general *options.TerragruntOptions) *Options {
Expand Down
10 changes: 2 additions & 8 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -899,11 +899,11 @@ func decodeAsTerragruntConfigFile(ctx *ParsingContext, file *hclparse.File, eval
}

if terragruntConfig.Inputs != nil {
inputs, err := updateUnknownCtyValValues(terragruntConfig.Inputs)
inputs, err := updateUnknownCtyValValues(*terragruntConfig.Inputs)
if err != nil {
return nil, err
}
terragruntConfig.Inputs = inputs
terragruntConfig.Inputs = &inputs
}

return &terragruntConfig, nil
Expand Down Expand Up @@ -1158,12 +1158,6 @@ func convertToTerragruntConfig(ctx *ParsingContext, configPath string, terragrun
}

if ctx.Locals != nil && *ctx.Locals != cty.NilVal {
locals, err := updateUnknownCtyValValues(ctx.Locals)
if err != nil {
return nil, err
}
ctx.Locals = locals

localsParsed, err := parseCtyValueToMap(*ctx.Locals)
if err != nil {
return nil, err
Expand Down
5 changes: 4 additions & 1 deletion config/config_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,13 +589,16 @@ func TestResolveCliArgsInterpolationConfigString(t *testing.T) {
assert.True(t, containsFoo)

fooSlice := toStringSlice(t, foo)

assert.EqualValues(t, testCase.expectedFooInput, fooSlice, "For string '%s' include %v and options %v", testCase.str, testCase.include, testCase.terragruntOptions)
})
}
}

func toStringSlice(t *testing.T, value interface{}) []string {
if value == nil {
return nil
}

asInterfaceSlice, isInterfaceSlice := value.([]interface{})
require.True(t, isInterfaceSlice)

Expand Down
6 changes: 0 additions & 6 deletions config/config_partial.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,6 @@ func PartialParseConfig(ctx *ParsingContext, file *hclparse.File, includeFromChi
}

if decoded.Inputs != nil {
val, err := updateUnknownCtyValValues(decoded.Inputs)
if err != nil {
return nil, err
}
decoded.Inputs = val

inputs, err := parseCtyValueToMap(*decoded.Inputs)
if err != nil {
return nil, err
Expand Down
52 changes: 40 additions & 12 deletions config/cty_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ func deepMergeCtyMapsMapOnly(target cty.Value, source cty.Value, opts ...func(*m
// we convert the given value to JSON using cty's JSON library and then convert the JSON back to a
// map[string]interface{} using the Go json library.
func parseCtyValueToMap(value cty.Value) (map[string]interface{}, error) {
updatedValue, err := updateUnknownCtyValValues(value)
if err != nil {
return nil, err
}
value = updatedValue

jsonBytes, err := ctyjson.Marshal(value, cty.DynamicPseudoType)
if err != nil {
return nil, errors.WithStackTrace(err)
Expand Down Expand Up @@ -313,22 +319,44 @@ func includeConfigAsCtyVal(ctx *ParsingContext, includeConfig IncludeConfig) (ct
return cty.NilVal, nil
}

// updateUnknownCtyValValues updates unknown values with default value
func updateUnknownCtyValValues(value *cty.Value) (*cty.Value, error) {
updatedValue := map[string]cty.Value{}
// updateUnknownCtyValValues deeply updates unknown values with default value
func updateUnknownCtyValValues(value cty.Value) (cty.Value, error) {
var updatedValue any

switch {
case !value.IsKnown():
return cty.StringVal(""), nil
case value.IsNull():
return value, nil
case value.Type().IsMapType(), value.Type().IsObjectType():
mapVals := value.AsValueMap()
for key, val := range mapVals {
val, err := updateUnknownCtyValValues(val)
if err != nil {
return cty.NilVal, errors.WithStackTrace(err)
}
mapVals[key] = val
}
updatedValue = mapVals

for key, value := range value.AsValueMap() {
if value.IsKnown() {
updatedValue[key] = value
} else {
updatedValue[key] = cty.StringVal("")
case value.Type().IsTupleType(), value.Type().IsListType():
sliceVals := value.AsValueSlice()
for key, val := range sliceVals {
val, err := updateUnknownCtyValValues(val)
if err != nil {
return cty.NilVal, errors.WithStackTrace(err)
}
sliceVals[key] = val
}
updatedValue = sliceVals

default:
return value, nil
}

res, err := gocty.ToCtyValue(updatedValue, value.Type())
value, err := gocty.ToCtyValue(updatedValue, value.Type())
if err != nil {
return nil, err
return cty.NilVal, errors.WithStackTrace(err)
}

return &res, nil
return value, nil
}
2 changes: 1 addition & 1 deletion configstack/stack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ func TestResolveTerraformModulesReadConfigFromParentConfig(t *testing.T) {
localsConfigs[name] = map[string]interface{}{
"dependencies": interface{}(nil),
"download_dir": "",
"generate": map[string]interface{}{},
"generate": interface{}(nil),
"iam_assume_role_duration": interface{}(nil),
"iam_assume_role_session_name": "",
"iam_role": "",
Expand Down
16 changes: 8 additions & 8 deletions docs/_docs/04_reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ This page documents the CLI commands and options available with Terragrunt:
- [terragrunt-diff](#terragrunt-diff)
- [terragrunt-hclfmt-file](#terragrunt-hclfmt-file)
- [terragrunt-hclvalidate-json](#terragrunt-hclvalidate-json)
- [terragrunt-hclvalidate-invalid](#terragrunt-hclvalidate-invalid)
- [terragrunt-hclvalidate-show-config-path](#terragrunt-hclvalidate-show-config-path)
- [terragrunt-override-attr](#terragrunt-override-attr)
- [terragrunt-json-out](#terragrunt-json-out)
- [terragrunt-json-disable-dependent-modules](#terragrunt-json-disable-dependent-modules)
Expand Down Expand Up @@ -432,12 +432,12 @@ Example:
terragrunt hclvalidate --terragrunt-hclvalidate-json
```

In addition, you can pass the `--terragrunt-hclvalidate-invalid` flag to only output the invalid files, delimited by newlines. This can be especially useful when combined with the [terragrunt-excludes-file](#terragrunt-excludes-file) flag.
In addition, you can pass the `--terragrunt-hclvalidate-show-config-path` flag to only output paths of the invalid config files, delimited by newlines. This can be especially useful when combined with the [terragrunt-excludes-file](#terragrunt-excludes-file) flag.

Example:

```bash
terragrunt hclvalidate --terragrunt-hclvalidate-invalid
terragrunt hclvalidate --terragrunt-hclvalidate-show-config-path
```

### aws-provider-patch
Expand Down Expand Up @@ -764,7 +764,7 @@ prefix `--terragrunt-` (e.g., `--terragrunt-config`). The currently available op
- [terragrunt-diff](#terragrunt-diff)
- [terragrunt-hclfmt-file](#terragrunt-hclfmt-file)
- [terragrunt-hclvalidate-json](#terragrunt-hclvalidate-json)
- [terragrunt-hclvalidate-invalid](#terragrunt-hclvalidate-invalid)
- [terragrunt-hclvalidate-show-config-path](#terragrunt-hclvalidate-show-config-path)
- [terragrunt-override-attr](#terragrunt-override-attr)
- [terragrunt-json-out](#terragrunt-json-out)
- [terragrunt-json-disable-dependent-modules](#terragrunt-json-disable-dependent-modules)
Expand Down Expand Up @@ -980,10 +980,10 @@ Path to a file with a list of directories that need to be excluded when running
excluded during execution of the commands. If a relative path is specified, it should be relative from
[--terragrunt-working-dir](#terragrunt-working-dir). This will only exclude the module, not its dependencies.

This flag has been designed to integrate nicely with the `hclvalidate` command, which can return a list of invalid files delimited by newlines when passed the `--terragrunt-hclvalidate-invalid` flag. To integrate the two, you can run something like the following using bash process substitution:
This flag has been designed to integrate nicely with the `hclvalidate` command, which can return a list of invalid files delimited by newlines when passed the `--terragrunt-hclvalidate-show-config-path` flag. To integrate the two, you can run something like the following using bash process substitution:

```bash
terragrunt run-all plan --terragrunt-excludes-file <(terragrunt hclvalidate --terragrunt-hclvalidate-invalid)
terragrunt run-all plan --terragrunt-excludes-file <(terragrunt hclvalidate --terragrunt-hclvalidate-show-config-path)
```

### terragrunt-exclude-dir
Expand Down Expand Up @@ -1130,9 +1130,9 @@ When passed in, run `hclfmt` only on specified hcl file.

When passed in, render the output in the JSON format.

### terragrunt-hclvalidate-invalid
### terragrunt-hclvalidate-show-config-path

**CLI Arg**: `--terragrunt-hclvalidate-invalid`<br/>
**CLI Arg**: `--terragrunt-hclvalidate-show-config-path`<br/>
**Environment Variable**: `TERRAGRUNT_HCLVALIDATE_INVALID` (set to `true`)<br/>
**Commands**:

Expand Down
2 changes: 1 addition & 1 deletion internal/view/human_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func NewHumanRender(disableColor bool) Render {
}
}

func (render *HumanRender) InvalidConfigPath(filenames []string) (string, error) {
func (render *HumanRender) ShowConfigPath(filenames []string) (string, error) {
var buf bytes.Buffer

for _, filename := range filenames {
Expand Down
2 changes: 1 addition & 1 deletion internal/view/json_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func (render *JSONRender) Diagnostics(diags diagnostic.Diagnostics) (string, err
return render.toJSON(diags)
}

func (render *JSONRender) InvalidConfigPath(filenames []string) (string, error) {
func (render *JSONRender) ShowConfigPath(filenames []string) (string, error) {
return render.toJSON(filenames)
}

Expand Down
8 changes: 4 additions & 4 deletions internal/view/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ type Render interface {
// Diagnostics renders early diagnostics, resulting from argument parsing.
Diagnostics(diags diagnostic.Diagnostics) (string, error)

// InvalidConfigPath renders paths to configurations that contain errors.
InvalidConfigPath(filenames []string) (string, error)
// ShowConfigPath renders paths to configurations that contain errors.
ShowConfigPath(filenames []string) (string, error)
}

// Writer is the base layer for command views, encapsulating a set of I/O streams, a colorize implementation, and implementing a human friendly view for diagnostics.
Expand All @@ -39,7 +39,7 @@ func (writer *Writer) Diagnostics(diags diagnostic.Diagnostics) error {
return writer.output(output)
}

func (writer *Writer) InvalidConfigPath(diags diagnostic.Diagnostics) error {
func (writer *Writer) ShowConfigPath(diags diagnostic.Diagnostics) error {
var filenames []string

for _, diag := range diags {
Expand All @@ -48,7 +48,7 @@ func (writer *Writer) InvalidConfigPath(diags diagnostic.Diagnostics) error {
}
}

output, err := writer.render.InvalidConfigPath(filenames)
output, err := writer.render.ShowConfigPath(filenames)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 2b40a24

Please sign in to comment.