Skip to content

Commit 958f0c9

Browse files
authored
feat: performance improvement on -version execution (#4288)
* Version check fixes * Version saving * Add fetching of context cache * Version caching fixes * Add check for single version run * Lint issues fixes * extracted version files * Added test to track that version is invoked in unit with custom version
1 parent a8a3b98 commit 958f0c9

File tree

11 files changed

+147
-7
lines changed

11 files changed

+147
-7
lines changed

cli/app.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"os"
88
"strings"
99

10+
"github.com/gruntwork-io/terragrunt/cli/commands/run"
11+
1012
"github.com/gruntwork-io/terragrunt/engine"
1113
"github.com/gruntwork-io/terragrunt/internal/os/signal"
1214
"github.com/gruntwork-io/terragrunt/telemetry"
@@ -106,6 +108,8 @@ func (app *App) RunContext(ctx context.Context, args []string) error {
106108
// configure engine context
107109
ctx = engine.WithEngineValues(ctx)
108110

111+
ctx = run.WithRunVersionCache(ctx)
112+
109113
defer func(ctx context.Context) {
110114
if err := engine.Shutdown(ctx, app.opts); err != nil {
111115
_, _ = app.ErrWriter.Write([]byte(err.Error()))

cli/commands/run/context.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package run
2+
3+
import (
4+
"context"
5+
6+
"github.com/gruntwork-io/terragrunt/internal/cache"
7+
)
8+
9+
type configKey byte
10+
11+
const (
12+
versionCacheContextKey configKey = iota
13+
versionCacheName = "versionCache"
14+
)
15+
16+
// WithRunVersionCache initializes the version cache in the context for the run package.
17+
func WithRunVersionCache(ctx context.Context) context.Context {
18+
ctx = context.WithValue(ctx, versionCacheContextKey, cache.NewCache[string](versionCacheName))
19+
return ctx
20+
}
21+
22+
// GetRunVersionCache retrieves the version cache from the context for the run package.
23+
func GetRunVersionCache(ctx context.Context) *cache.Cache[string] {
24+
return cache.ContextCache[string](ctx, versionCacheContextKey)
25+
}

cli/commands/run/version_check.go

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"path/filepath"
78
"regexp"
89
"strings"
910

11+
"encoding/hex"
12+
1013
"github.com/gruntwork-io/terragrunt/internal/errors"
1114
"github.com/gruntwork-io/terragrunt/options"
1215
"github.com/gruntwork-io/terragrunt/tf"
16+
"github.com/gruntwork-io/terragrunt/util"
1317
"github.com/hashicorp/go-version"
1418
)
1519

@@ -27,6 +31,8 @@ var TerraformVersionRegex = regexp.MustCompile(`^(\S+)\s(v?\d+\.\d+\.\d+)`)
2731

2832
const versionParts = 3
2933

34+
var versionFiles = []string{".terraform-version", ".tool-versions", "mise.toml", ".mise.toml"}
35+
3036
// CheckVersionConstraints checks the version constraints of both terragrunt and terraform. Note that as a side effect this will set the
3137
// following settings on terragruntOptions:
3238
// - TerraformPath
@@ -85,18 +91,36 @@ func CheckVersionConstraints(ctx context.Context, terragruntOptions *options.Ter
8591

8692
// PopulateTerraformVersion populates the currently installed version of Terraform into the given terragruntOptions.
8793
func PopulateTerraformVersion(ctx context.Context, terragruntOptions *options.TerragruntOptions) error {
88-
// Discard all log output to make sure we don't pollute stdout or stderr with this extra call to '--version'
94+
versionCache := GetRunVersionCache(ctx)
95+
cacheKey := computeVersionFilesCacheKey(terragruntOptions.WorkingDir)
96+
97+
if cachedOutput, found := versionCache.Get(ctx, cacheKey); found {
98+
terraformVersion, err := ParseTerraformVersion(cachedOutput)
99+
if err != nil {
100+
return err
101+
}
102+
103+
tfImplementation, err := parseTerraformImplementationType(cachedOutput)
104+
105+
if err != nil {
106+
return err
107+
}
108+
109+
terragruntOptions.TerraformVersion = terraformVersion
110+
111+
terragruntOptions.TerraformImplementation = tfImplementation
112+
113+
return nil
114+
}
115+
89116
terragruntOptionsCopy, err := terragruntOptions.CloneWithConfigPath(terragruntOptions.TerragruntConfigPath)
90117
if err != nil {
91118
return err
92119
}
93120

94121
terragruntOptionsCopy.Writer = io.Discard
95122
terragruntOptionsCopy.ErrWriter = io.Discard
96-
// Remove any TF_CLI_ARGS before version checking. These are appended to
97-
// the arguments supplied on the command line and cause issues when running
98-
// the --version command.
99-
// https://www.terraform.io/docs/commands/environment-variables.html#tf_cli_args-and-tf_cli_args_name
123+
100124
for key := range terragruntOptionsCopy.Env {
101125
if strings.HasPrefix(key, "TF_CLI_ARGS") {
102126
delete(terragruntOptionsCopy.Env, key)
@@ -108,6 +132,9 @@ func PopulateTerraformVersion(ctx context.Context, terragruntOptions *options.Te
108132
return err
109133
}
110134

135+
// Save output to cache
136+
versionCache.Put(ctx, cacheKey, output.Stdout.String())
137+
111138
terraformVersion, err := ParseTerraformVersion(output.Stdout.String())
112139
if err != nil {
113140
return err
@@ -212,6 +239,29 @@ func parseTerraformImplementationType(versionCommandOutput string) (options.Terr
212239
}
213240
}
214241

242+
// Helper to compute a cache key from the checksums of .terraform-version and .tool-versions
243+
func computeVersionFilesCacheKey(workingDir string) string {
244+
var hashes []string
245+
246+
for _, file := range versionFiles {
247+
path := filepath.Join(workingDir, file)
248+
if util.FileExists(path) {
249+
hash, err := util.FileSHA256(path)
250+
if err == nil {
251+
hashes = append(hashes, file+":"+hex.EncodeToString(hash))
252+
}
253+
}
254+
}
255+
256+
cacheKey := "no-version-files"
257+
258+
if len(hashes) != 0 {
259+
cacheKey = strings.Join(hashes, "|")
260+
}
261+
262+
return util.EncodeBase64Sha1(cacheKey)
263+
}
264+
215265
// Custom error types
216266

217267
type InvalidTerraformVersionSyntax string
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
variable "input_value" {}
2+
3+
output "output_value" {
4+
value = var.input_value
5+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
dependency "dependency" {
3+
config_path = "../dependency"
4+
}
5+
6+
dependency "dependency-with-custom-version" {
7+
config_path = "../dependency-with-custom-version"
8+
}
9+
10+
inputs = {
11+
input_value = dependency.dependency.outputs.result
12+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
tofu 1.9.4
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
output "result" {
2+
3+
value = "42"
4+
}

test/fixtures/version-invocation/dependency-with-custom-version/terragrunt.hcl

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
output "result" {
2+
3+
value = "42"
4+
}

test/fixtures/version-invocation/dependency/terragrunt.hcl

Whitespace-only changes.

0 commit comments

Comments
 (0)