Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs-starlight/src/data/flags/hcl-validate-inputs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ env:
- TG_INPUTS
---

When enabled, Terragrunt will validate that all variables are set by the module a unit provisions.
When enabled, Terragrunt will validate that all variables a module (provisioned by a unit) requires are set.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open to input on this, or even removing it. But I definitely don't think the current wording is correct (since we're checking that the unit sets variables consumed by the module, the module doesn't set them).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would better wording be: "When enabled, Terragrunt will validate that all inputs set by Terragrunt units are valid OpenTofu/Terraform variables consumed by their respective modules"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that only is accurate when using --strict, without --strict you can set inputs that are not consumed / don't exist as variables


Example:

Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/hclvalidate/valid/var-in-source/main.tf
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to make an even more minimal example than the one in #4986

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the names on these fixtures. Reading the name of the fixture should tell you what the fixture is for.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
locals {
variable_source = "github.com/foo/bar"
}

module "module" {
source = local.variable_source
version = "0.0.0"
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know empty files seem silly, but in the spirit of "minimal reproduction" I don't think you can get more minimal than empty!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing wrong with it! If you want to be explicit about this, you can leave a one-line comment in the file that it's intentionally empty.

Empty file.
8 changes: 8 additions & 0 deletions test/fixtures/hclvalidate/valid/var-in-version/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
locals {
variable_version = "0.0.0"
}

module "module" {
source = "github.com/foo/bar"
version = local.variable_version
}
Empty file.
41 changes: 41 additions & 0 deletions test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,47 @@ func TestTerragruntExcludesFile(t *testing.T) {
}
}

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

t.Run("using --all", func(t *testing.T) {
t.Parallel()
helpers.CleanupTerraformFolder(t, testFixtureHclvalidate)
tmpEnvPath := helpers.CopyEnvironment(t, testFixtureHclvalidate)
rootPath := util.JoinPath(tmpEnvPath, testFixtureHclvalidate)

_, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt hcl validate --all --strict --inputs --working-dir "+path.Join(rootPath, "valid"))
require.NoError(t, err)
})
Comment on lines 620 to 628
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this test, but actually it was already passing! Is --all not supposed to return non-zero?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's entirely possible there's a bug in the error handling. I think it should be returning an error if anything fails during an --all run.


t.Run("each validate individually", func(t *testing.T) {
t.Parallel()

helpers.CleanupTerraformFolder(t, testFixtureHclvalidate)
tmpEnvPath := helpers.CopyEnvironment(t, testFixtureHclvalidate)
rootPath := util.JoinPath(tmpEnvPath, testFixtureHclvalidate, "valid")

// Test each subdirectory individually
entries, err := os.ReadDir(rootPath)
require.NoError(t, err)

for _, entry := range entries {
if !entry.IsDir() {
continue
}

subPath := filepath.Join(rootPath, entry.Name())

t.Run(entry.Name(), func(t *testing.T) {
t.Parallel()

_, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt hcl validate --strict --inputs --working-dir "+subPath)
require.NoError(t, err)
})
}
})
}

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

Expand Down
27 changes: 27 additions & 0 deletions tf/tf.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package tf

import (
"slices"

"github.com/gruntwork-io/terragrunt/internal/errors"
"github.com/hashicorp/terraform-config-inspect/tfconfig"
)
Expand Down Expand Up @@ -127,10 +129,35 @@ var (
}
)

// diagnosticDoesNotAffectModuleVariables tells you if a diagnostic can be ignored for the purpose of extracting
// variables defined in a module.
func diagnosticDoesNotAffectModuleVariables(d tfconfig.Diagnostic) bool {
ignorableErrors := []tfconfig.Diagnostic{
// These two occur when a module block uses a variable in the `version` or `source` fields.
// Terraform doesn't support this, but OpenTofu does. Either way our ability to extract variables is unaffected.
//
// What we really need is an OpenTofu version of terraform-config-inspect. This may work for now but as / if
// syntax continues to diverge we may run into other issues.
{Summary: "Variables not allowed", Detail: "Variables may not be used here."},
{Summary: "Unsuitable value type", Detail: "Unsuitable value: value must be known"},
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep this scoped to this function, plus putting it outside the function would require a more descriptive name.

I'm 90% sure the compiler notices it's never changed and pre-allocates it, but am happy to be proven wrong (and will move it if so).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a big deal either way.

if d.Severity != tfconfig.DiagError {
return true
}

i := slices.IndexFunc(ignorableErrors, func(ie tfconfig.Diagnostic) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that this can be simplified to

	return slices.ContainsFunc(ignorableErrors, func(ie tfconfig.Diagnostic) bool {
		return ie.Summary == d.Summary && ie.Detail == d.Detail
	})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes way more sense. I already know about contains so I don't know what I was thinking when I wrote this

return ie.Summary == d.Summary && ie.Detail == d.Detail
})

return i != -1
}

// ModuleVariables will return all the variables defined in the downloaded terraform modules, taking into
// account all the generated sources. This function will return the required and optional variables separately.
func ModuleVariables(modulePath string) ([]string, []string, error) {
module, diags := tfconfig.LoadModule(modulePath)

diags = slices.DeleteFunc(diags, diagnosticDoesNotAffectModuleVariables)
if diags.HasErrors() {
return nil, nil, errors.New(diags)
}
Expand Down
Loading