Skip to content

Conversation

@ThisGuyCodes
Copy link

@ThisGuyCodes ThisGuyCodes commented Oct 30, 2025

Description

Terragrunt does not support using variables in the source or version fields of module blocks, but OpenTofu does.

This becomes a problem because when validating inputs via terragrunt hcl validate --inputs we use github.com/hashicorp/terraform-config-inspect to read the variables defined by the target module; since this is a Go module designed for Terraform it returns error diagnostics when variables are used as defined above.

Thus a valid OpenTofu module will fail a run of terragrunt hcl validate -inputs.

Fixes #4986

Despite these error diagnostics, as long as the syntax is valid we don't actually care (we just want to know what variables are defined).

Ideally OpenTofu would have an equivalent Go module we could use; but it doesn't. For now I've put in logic to selectively ignore diagnostics in this context; as Terraform and OpenTofu syntax's (may) continue to diverge, we may see more issues that this solution may not solve for.

Also reworded the docs on --inputs, as I found it a bit confusing.

TODOs

  • I authored this code entirely myself
  • I am submitting code based on open source software (e.g. MIT, MPL-2.0, Apache)]
  • I am adding or upgrading a dependency or adapted code and confirm it has a compatible open source license
  • Update the docs.
  • Run the relevant tests successfully, including pre-commit checks.
  • Include release notes. If this PR is backward incompatible, include a migration guide.

Release Notes (draft)

Allow variables in module source / version

Summary by CodeRabbit

  • New Features

    • Improved validation to ignore certain benign diagnostics when extracting module variables.
  • Tests

    • Added validation tests covering variable-sourced module configurations, including parallel per-configuration checks.
  • Documentation

    • Clarified validation wording to emphasize that variables a module requires must be set.

@ThisGuyCodes ThisGuyCodes self-assigned this Oct 30, 2025
@vercel
Copy link

vercel bot commented Oct 30, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
terragrunt-docs Ready Ready Preview Comment Oct 31, 2025 1:56pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 30, 2025

📝 Walkthrough

Walkthrough

This PR fixes validation failures when Terraform modules use variables in source or version fields by filtering out "variables not allowed" diagnostics during module variable extraction. It adds test fixtures and an integration test suite to verify the fix works correctly.

Changes

Cohort / File(s) Summary
Documentation
docs-starlight/src/data/flags/hcl-validate-inputs.mdx
Clarified wording to emphasize that validation ensures all variables required by a module are set, rather than implying the module sets variables.
Test Fixtures
test/fixtures/hclvalidate/valid/var-in-source/main.tf, test/fixtures/hclvalidate/valid/var-in-version/main.tf
Added new Terraform configuration fixtures demonstrating modules using local variables in source and version fields.
Integration Tests
test/integration_test.go
Added new test suite TestHclvalidateValidConfig with subtests for running terragrunt hcl validate --all --strict --inputs and validating individual fixture subdirectories.
Diagnostic Filtering
tf/tf.go
Introduced diagnosticDoesNotAffectModuleVariables helper function and updated ModuleVariables to filter out diagnostics that report "variables not allowed" errors before validation, enabling module variable extraction when variables are used in source or version fields.

Sequence Diagram

sequenceDiagram
    participant User
    participant Terragrunt
    participant TFConfig as Module Config
    participant Diagnostics
    
    User->>Terragrunt: terragrunt hcl validate --inputs
    Terragrunt->>TFConfig: Extract module variables
    TFConfig-->>Diagnostics: Generate diagnostics<br/>(includes "variables not allowed")
    
    alt Before Fix
        Diagnostics->>Terragrunt: Return all diagnostics
        Terragrunt->>Terragrunt: Error on "variables not allowed"
        Terragrunt-->>User: ❌ Validation failed
    else After Fix (This PR)
        Diagnostics->>Terragrunt: All diagnostics
        Terragrunt->>Terragrunt: Filter ignorable diagnostics<br/>(keep only input-affecting errors)
        Terragrunt->>Terragrunt: Validate inputs against<br/>extracted variables
        Terragrunt-->>User: ✓ Validation result (pass/fail on inputs)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • tf/tf.go: Review the diagnostic filtering logic and ensure diagnosticDoesNotAffectModuleVariables correctly identifies ignorable diagnostics without masking genuine issues.
  • test/integration_test.go: Verify the new test cases cover the intended scenarios and properly assert validation behavior with variables in source/version.
  • Test fixtures: Confirm fixtures correctly represent the use case (local variables in module source/version).

Possibly related issues

Suggested reviewers

  • denis256
  • yhakbar

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The pull request successfully addresses the core requirements from issue #4986. The changes implement the necessary functionality to allow input validation with variables in module source/version fields: the tf.go modifications add diagnostic filtering logic to ignore "Variables not allowed" errors in the validation context, test fixtures (var-in-source and var-in-version) demonstrate the valid OpenTofu patterns, and new integration tests validate that the --inputs flag works correctly. Documentation was reworded to clarify the validation behavior. All coding-related objectives from the linked issue are met.
Out of Scope Changes Check ✅ Passed All code changes in this pull request are directly related to the PR objective and linked issue #4986. The documentation updates clarify the --inputs validation behavior, the new test fixtures demonstrate the supported patterns (variables in source and version fields), the integration tests validate the new functionality, and the tf.go modifications implement the core fix through diagnostic filtering. Each change contributes to enabling input validation with variables in module source/version fields, with no extraneous modifications detected.
Title Check ✅ Passed The pull request title "allow input validation with variable in source or version" accurately captures the core objective of the changeset. The modifications across multiple files—adding diagnostic filtering logic in tf/tf.go, creating test fixtures demonstrating variables in source and version fields, adding integration tests, and updating documentation—are all oriented toward enabling the specific functionality described in the title. The title is specific enough to convey the feature being introduced and concise without extraneous information.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch thisguycodes/allow-vars-in-source-version

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

---

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
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
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
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.

Comment on lines +620 to +628
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)
})
Copy link
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.

@ThisGuyCodes ThisGuyCodes marked this pull request as ready for review October 31, 2025 04:47
Comment on lines +135 to +143
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
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.

@ThisGuyCodes ThisGuyCodes changed the title [WIP] allow input validation with variable in source or version allow input validation with variable in source or version Oct 31, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between adad0cb and 6017839.

📒 Files selected for processing (5)
  • docs-starlight/src/data/flags/hcl-validate-inputs.mdx (1 hunks)
  • test/fixtures/hclvalidate/valid/var-in-source/main.tf (1 hunks)
  • test/fixtures/hclvalidate/valid/var-in-version/main.tf (1 hunks)
  • test/integration_test.go (1 hunks)
  • tf/tf.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
docs-starlight/**/*.md*

⚙️ CodeRabbit configuration file

Review the documentation for clarity, grammar, and spelling. Make sure that the documentation is easy to understand and follow. There is currently a migration underway from the Jekyll based documentation in docs to the Starlight + Astro based documentation in docs-starlight. Make sure that the docs-starlight documentation is accurate and up-to-date with the docs documentation, and that any difference between them results in an improvement in the docs-starlight documentation.

Files:

  • docs-starlight/src/data/flags/hcl-validate-inputs.mdx
**/*.go

⚙️ CodeRabbit configuration file

Review the Go code for quality and correctness. Make sure that the Go code follows best practices, is performant, and is easy to understand and maintain.

Files:

  • tf/tf.go
  • test/integration_test.go
🧠 Learnings (2)
📚 Learning: 2025-08-19T16:05:54.723Z
Learnt from: Resonance1584
Repo: gruntwork-io/terragrunt PR: 4683
File: go.mod:86-90
Timestamp: 2025-08-19T16:05:54.723Z
Learning: When analyzing Go module dependencies for removal, always check for both direct imports and API usage across all Go files in the repository, not just a quick search. The github.com/mattn/go-zglob library is used for filesystem walking and glob expansion in multiple Terragrunt files including util/file.go, format commands, and AWS provider patch functionality.

Applied to files:

  • tf/tf.go
📚 Learning: 2025-02-10T13:36:19.542Z
Learnt from: levkohimins
Repo: gruntwork-io/terragrunt PR: 3723
File: cli/commands/stack/action.go:160-160
Timestamp: 2025-02-10T13:36:19.542Z
Learning: The project uses a custom error package `github.com/gruntwork-io/terragrunt/internal/errors` which provides similar functionality to `fmt.Errorf` but includes stack traces. Prefer using this package's error functions (e.g., `errors.Errorf`, `errors.New`) over the standard library's error handling.

Applied to files:

  • tf/tf.go
🧬 Code graph analysis (2)
tf/tf.go (1)
internal/view/diagnostic/diagnostic.go (1)
  • Diagnostic (21-27)
test/integration_test.go (2)
test/helpers/package.go (3)
  • CleanupTerraformFolder (879-886)
  • CopyEnvironment (89-102)
  • RunTerragruntCommandWithOutput (1004-1008)
util/file.go (1)
  • JoinPath (626-628)
🪛 Checkov (3.2.334)
test/fixtures/hclvalidate/valid/var-in-source/main.tf

[medium] 5-8: Ensure Terraform module sources use a commit hash

(CKV_TF_1)


[high] 5-8: Ensure Terraform module sources use a tag with a version number

(CKV_TF_2)

test/fixtures/hclvalidate/valid/var-in-version/main.tf

[medium] 5-8: Ensure Terraform module sources use a commit hash

(CKV_TF_1)


[high] 5-8: Ensure Terraform module sources use a tag with a version number

(CKV_TF_2)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Pull Request has non-contributor approval
  • GitHub Check: base_tests / Test (macos)
  • GitHub Check: base_tests / Test (ubuntu)
  • GitHub Check: Pull Request has non-contributor approval

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
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

Copy link
Collaborator

@yhakbar yhakbar left a comment

Choose a reason for hiding this comment

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

I think this does the job, so I'll approve it. It's not how I thought you'd tackle this.

I was assuming you'd do something like this untested code:

type Variable struct {
	Name    string `hcl:"name,label"`
	Default string `hcl:"default,optional"`

	Remainder hcl.Body `hcl:",remain"`
}

type ModuleVars struct {
	Variables []Variable `hcl:"variable,block"`
}

// 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) {
	parser := hclparse.NewParser()

	files, err := os.ReadDir(modulePath)
	if err != nil {
		return nil, nil, err
	}

	hclFiles := []*hcl.File{}
	errs := []error{}

	for _, file := range files {
		if file.IsDir() {
			continue
		}

		// This would only solve the issue for HCL files. We'd need to also check JSON files.
		if !strings.HasSuffix(file.Name(), ".tf") && !strings.HasSuffix(file.Name(), ".tofu") {
			continue
		}

		hclFile, err := parser.ParseFromFile(file.Name())
		if err != nil {
			errs = append(errs, err)
		}

		hclFiles = append(hclFiles, hclFile.File)
	}

	if len(errs) > 0 {
		return nil, nil, errors.New(errs)
	}

	body := hcl.MergeFiles(hclFiles)

	moduleVars := ModuleVars{}

	if err := gohcl.DecodeBody(body, nil, &moduleVars); err != nil {
		return nil, nil, err
	}

	required := []string{}
	optional := []string{}

	for _, variable := range moduleVars.Variables {
		if variable.Default != "" {
			optional = append(optional, variable.Name)
		}
	}

	return required, optional, nil
}

To fully drop the dependency on github.com/hashicorp/terraform-config-inspect/tfconfig.

It's a bigger change than what you're doing here, so I'll accept that you might prefer your current solution. Please confirm that you've evaluated this, and don't want to make this or the changes that @denis256 recommended before merging if you're happy with the solution.

---

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
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
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.

Comment on lines +620 to +628
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)
})
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.

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.

Comment on lines +135 to +143
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
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Terragrunt cannot validate inputs if Terraform module uses variables in "source" or "version" fields

4 participants