Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: 0 additions & 2 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ jobs:
target: ./...
setup_scripts:
- .github/scripts/setup/experiment-mode.sh
ignore_errors: true
steps:
- name: Checkout code
uses: actions/checkout@v5
Expand Down Expand Up @@ -230,7 +229,6 @@ jobs:
echo "Skipping tests for $NAME as the skip flag is true."
fi
shell: bash
continue-on-error: ${{ matrix.integration.ignore_errors || false }}
env:
GITHUB_OAUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
71 changes: 49 additions & 22 deletions cli/commands/hcl/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"sort"
"strings"

"github.com/gruntwork-io/terragrunt/internal/component"
"github.com/gruntwork-io/terragrunt/internal/discovery"

"github.com/google/shlex"
Expand Down Expand Up @@ -58,29 +59,28 @@ func Run(ctx context.Context, l log.Logger, opts *options.TerragruntOptions) err
func RunValidate(ctx context.Context, l log.Logger, opts *options.TerragruntOptions) error {
var diags diagnostic.Diagnostics

parseOptions := []hclparse.Option{
hclparse.WithDiagnosticsHandler(func(file *hcl.File, hclDiags hcl.Diagnostics) (hcl.Diagnostics, error) {
for _, hclDiag := range hclDiags {
// Only report diagnostics that are actually in the file being parsed,
// not errors from dependencies or other files
if hclDiag.Subject != nil && file != nil {
fileFilename := file.Body.MissingItemRange().Filename

diagFilename := hclDiag.Subject.Filename
if diagFilename != fileFilename {
continue
}
// Diagnostics handler to collect validation errors
diagnosticsHandler := hclparse.WithDiagnosticsHandler(func(file *hcl.File, hclDiags hcl.Diagnostics) (hcl.Diagnostics, error) {
for _, hclDiag := range hclDiags {
// Only report diagnostics that are actually in the file being parsed,
// not errors from dependencies or other files
if hclDiag.Subject != nil && file != nil {
fileFilename := file.Body.MissingItemRange().Filename

diagFilename := hclDiag.Subject.Filename
if diagFilename != fileFilename {
continue
}
}

newDiag := diagnostic.NewDiagnostic(file, hclDiag)
if !diags.Contains(newDiag) {
diags = append(diags, newDiag)
}
newDiag := diagnostic.NewDiagnostic(file, hclDiag)
if !diags.Contains(newDiag) {
diags = append(diags, newDiag)
}
}

return nil, nil
}),
}
return nil, nil
})

opts.SkipOutput = true
opts.NonInteractive = true
Expand All @@ -95,20 +95,47 @@ func RunValidate(ctx context.Context, l log.Logger, opts *options.TerragruntOpti
return processDiagnostics(l, opts, diags, errors.New(err))
}

// Apply parse options to discovery
d = d.WithParserOptions(parseOptions)

components, err := d.Discover(ctx, l, opts)
if err != nil {
return processDiagnostics(l, opts, diags, errors.New(err))
}

parseOptions := []hclparse.Option{diagnosticsHandler}

parseErrs := []error{}

for _, c := range components {
parseOpts := opts.Clone()
parseOpts.WorkingDir = c.Path()

if _, ok := c.(*component.Stack); ok {
stackFilePath := filepath.Join(c.Path(), config.DefaultStackFile)
parseOpts.TerragruntConfigPath = stackFilePath

values, err := config.ReadValues(ctx, l, parseOpts, c.Path())
if err != nil {
parseErrs = append(parseErrs, errors.New(err))
}

parser := config.NewParsingContext(ctx, l, parseOpts).WithParseOption(parseOptions)
if values != nil {
parser = parser.WithValues(values)
}

file, err := hclparse.NewParser(parser.ParserOptions...).ParseFromFile(stackFilePath)
if err != nil {
parseErrs = append(parseErrs, errors.New(err))
continue
}

//nolint:contextcheck
if _, err := config.ParseStackConfig(l, parser, parseOpts, file, values); err != nil {
parseErrs = append(parseErrs, errors.New(err))
}

continue
}

// Determine which config filename to use for a full parse
configFilename := config.DefaultTerragruntConfigPath
if len(opts.TerragruntConfigPath) > 0 {
Expand Down
9 changes: 7 additions & 2 deletions cli/commands/run/download_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func downloadTerraformSource(
return nil, err
}

if err := DownloadTerraformSourceIfNecessary(ctx, l, terraformSource, opts, cfg, r); err != nil {
if err = DownloadTerraformSourceIfNecessary(ctx, l, terraformSource, opts, cfg, r); err != nil {
return nil, err
}

Expand Down Expand Up @@ -309,7 +309,10 @@ func downloadSource(ctx context.Context, l log.Logger, src *tf.Source, opts *opt
src.DownloadDir)

allowCAS := opts.Experiments.Evaluate(experiment.CAS)
if allowCAS {

isLocalSource := tf.IsLocalSource(src.CanonicalSourceURL)

if allowCAS && !isLocalSource {
l.Debugf("CAS experiment enabled: attempting to use Content Addressable Storage for source: %s", canonicalSourceURL)

c, err := cas.New(cas.Options{})
Expand All @@ -328,9 +331,11 @@ func downloadSource(ctx context.Context, l log.Logger, src *tf.Source, opts *opt
Getters: []getterv2.Getter{casGetter},
}

// Set Pwd to the working directory so go-getter v2 can resolve relative paths
req := &getterv2.Request{
Src: src.CanonicalSourceURL.String(),
Dst: src.DownloadDir,
Pwd: opts.WorkingDir,
}

if _, casErr := client.Get(ctx, req); casErr == nil {
Expand Down
10 changes: 6 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/fs"
"net/url"
"os"
"path"
Expand Down Expand Up @@ -1058,17 +1059,18 @@ func GetDefaultConfigPath(workingDir string) string {
func FindConfigFilesInPath(rootPath string, opts *options.TerragruntOptions) ([]string, error) {
configFiles := []string{}

walkFunc := filepath.Walk
walkFunc := filepath.WalkDir

if opts.Experiments.Evaluate(experiment.Symlinks) {
walkFunc = util.WalkWithSymlinks
walkFunc = util.WalkDirWithSymlinks
}

err := walkFunc(rootPath, func(path string, info os.FileInfo, err error) error {
err := walkFunc(rootPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

if !info.IsDir() {
if !d.IsDir() {
return nil
}

Expand Down
9 changes: 5 additions & 4 deletions config/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
Expand Down Expand Up @@ -1089,24 +1090,24 @@ func processLocals(l log.Logger, parser *ParsingContext, opts *options.Terragrun
// provided Terragrunt options.
func listStackFiles(l log.Logger, opts *options.TerragruntOptions, dir string) ([]string, error) {
walkWithSymlinks := opts.Experiments.Evaluate(experiment.Symlinks)
walkFunc := filepath.Walk

walkFunc := filepath.WalkDir
if walkWithSymlinks {
walkFunc = util.WalkWithSymlinks
walkFunc = util.WalkDirWithSymlinks
}

l.Debugf("Searching for stack files in %s", dir)

var stackFiles []string

// find all defaultStackFile files
if err := walkFunc(dir, func(path string, info os.FileInfo, err error) error {
if err := walkFunc(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
l.Warnf("Error accessing path %s: %w", path, err)
return nil
}

if info.IsDir() {
if d.IsDir() {
return nil
}

Expand Down
1 change: 1 addition & 0 deletions internal/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ func (d *Discovery) walkDirectoryConcurrently(
filePaths chan<- string,
) error {
walkFn := filepath.WalkDir

if opts.Experiments.Evaluate(experiment.Symlinks) {
walkFn = util.WalkDirWithSymlinks
}
Expand Down
15 changes: 9 additions & 6 deletions internal/experiment/experiment.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,20 @@ func (exps Experiments) Find(name string) *Experiment {

// ExperimentMode enables the experiment mode.
func (exps Experiments) ExperimentMode() {
for _, experiment := range exps.FilterByStatus(StatusOngoing) {
experiment.Enabled = true
for _, e := range exps {
if e.Status == StatusOngoing {
e.Enabled = true
}
}
}

// EnableExperiment validates that the specified experiment name is valid and enables this experiment.
func (exps Experiments) EnableExperiment(name string) error {
if experiment := exps.Find(name); experiment != nil {
experiment.Enabled = true

return nil
for _, e := range exps {
if e.Name == name {
e.Enabled = true
return nil
}
}

return NewInvalidExperimentNameError(exps.FilterByStatus(StatusOngoing).Names())
Expand Down
10 changes: 7 additions & 3 deletions internal/filter/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,13 @@ func (i *InfixExpression) String() string {
return i.Left.String() + " " + i.Operator + " " + i.Right.String()
}
func (i *InfixExpression) RequiresHCLParsing() (Expression, bool) {
if f, ok := i.Left.RequiresHCLParsing(); ok {
return f, true
if _, ok := i.Left.RequiresHCLParsing(); ok {
return i, true
}

return i.Right.RequiresHCLParsing()
if _, ok := i.Right.RequiresHCLParsing(); ok {
return i, true
}

return nil, false
}
10 changes: 5 additions & 5 deletions internal/filter/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,15 @@ func (f Filters) EvaluateOnFiles(files []string) (component.Components, error) {
return nil, FilterQueryRequiresHCLParsingError{Query: e.String()}
}

if len(f) == 0 {
return component.Components{}, nil
}

comps := make([]component.Component, 0, len(files))
comps := make(component.Components, 0, len(files))
for _, file := range files {
comps = append(comps, component.NewUnit(file))
}

if len(f) == 0 {
return comps, nil
}

return f.Evaluate(comps)
}

Expand Down
9 changes: 5 additions & 4 deletions internal/services/catalog/module/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package module
import (
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -95,18 +96,18 @@ func (repo *Repo) FindModules(ctx context.Context) (Modules, error) {
continue
}

walkFunc := filepath.Walk
walkFunc := filepath.WalkDir
if repo.walkWithSymlinks {
walkFunc = util.WalkWithSymlinks
walkFunc = util.WalkDirWithSymlinks
}

err := walkFunc(modulesPath,
func(dir string, remote os.FileInfo, err error) error {
func(dir string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

if !remote.IsDir() {
if !d.IsDir() {
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tools]
go = "1.25.0"
opentofu = "1.10.5"
opentofu = "1.10.6"
golangci-lint = "2.4.0"
"go:github.com/goph/licensei/cmd/licensei" = "v0.9.0"
"go:go.uber.org/mock/mockgen" = "v0.6.0"
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/buffer-module-output/app/main.tf
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
terraform {
required_providers {
null = {
source = "registry.terraform.io/hashicorp/null"
version = "3.2.3"
source = "hashicorp/null"
version = "~> 3.2.4"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/buffer-module-output/app2/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ provider "null" {}
terraform {
required_providers {
null = {
source = "registry.terraform.io/hashicorp/null"
version = "3.2.3"
source = "hashicorp/null"
version = "~> 3.2.4"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/buffer-module-output/app3/main.tf
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
terraform {
required_providers {
null = {
source = "registry.terraform.io/hashicorp/null"
version = "3.2.3"
source = "hashicorp/null"
version = "~> 3.2.4"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/download/hello-world/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
null = {
source = "hashicorp/null"
version = "3.2.3"
version = "~> 3.2.4"
}
}
}
Expand All @@ -29,6 +29,6 @@ output "test" {
}

module "remote" {
source = "github.com/gruntwork-io/terragrunt.git//test/fixtures/download/hello-world-no-remote?ref=v0.77.22"
source = "github.com/gruntwork-io/terragrunt.git//test/fixtures/download/hello-world-no-remote?ref=2ca67fe2dbd3001c4cffa66df9567ca497182cb1"
name = var.name
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
null = {
source = "hashicorp/null"
version = "3.2.3"
version = "~> 3.2.4"
}
}
}
Expand Down
Loading