Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package terragrunt for openSUSE:Factory checked in at 2025-06-20 16:50:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/terragrunt (Old) and /work/SRC/openSUSE:Factory/.terragrunt.new.31170 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "terragrunt" Fri Jun 20 16:50:03 2025 rev:238 rq:1287009 version:0.81.8 Changes: -------- --- /work/SRC/openSUSE:Factory/terragrunt/terragrunt.changes 2025-06-17 18:23:12.756777706 +0200 +++ /work/SRC/openSUSE:Factory/.terragrunt.new.31170/terragrunt.changes 2025-06-20 16:51:45.186077084 +0200 @@ -1,0 +2,24 @@ +Thu Jun 19 17:32:59 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 0.81.8: + * New Features + - Scaffold now supports Boilerplate dependencies and partials + The Scaffold feature now supports leveraging Boilerplate + dependencies and partials. + This was a missing feature in the integration between + Scaffold and Boilerplate, and Terragrunt users do not have to + change anything in how they invoke Scaffold to have custom + Boilerplate templates properly render using those Boilerplate + features. + * What's Changed + - fix: Add scaffold support for boilerplate dependencies and + partials (#4437) + - Improve error message when terragrunt.hcl file does not exist + (#4425) + - build(deps): bump ruby/setup-ruby from 1.242.0 to 1.245.0 + (#4426) + - perf: Improving CAS performance a bit (#4439) + - fix: Fixing heading level for experiments (#4433) + - fix: Fixing usage of `dependencies` in discovery (#4429) + +------------------------------------------------------------------- Old: ---- terragrunt-0.81.7.obscpio New: ---- terragrunt-0.81.8.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ terragrunt.spec ++++++ --- /var/tmp/diff_new_pack.TgXvoU/_old 2025-06-20 16:51:48.358208501 +0200 +++ /var/tmp/diff_new_pack.TgXvoU/_new 2025-06-20 16:51:48.362208667 +0200 @@ -17,7 +17,7 @@ Name: terragrunt -Version: 0.81.7 +Version: 0.81.8 Release: 0 Summary: Thin wrapper for Terraform for working with multiple Terraform modules License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.TgXvoU/_old 2025-06-20 16:51:48.418210987 +0200 +++ /var/tmp/diff_new_pack.TgXvoU/_new 2025-06-20 16:51:48.426211318 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/gruntwork-io/terragrunt</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.81.7</param> + <param name="revision">v0.81.8</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.TgXvoU/_old 2025-06-20 16:51:48.446212147 +0200 +++ /var/tmp/diff_new_pack.TgXvoU/_new 2025-06-20 16:51:48.450212313 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/gruntwork-io/terragrunt</param> - <param name="changesrevision">6ec20b51cdc6b46c625a74937a01b2dca0c56527</param></service></servicedata> + <param name="changesrevision">47dd829095234f44752aa0f1b2149f4032e4a7df</param></service></servicedata> (No newline at EOF) ++++++ terragrunt-0.81.7.obscpio -> terragrunt-0.81.8.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/.github/workflows/pages.yml new/terragrunt-0.81.8/.github/workflows/pages.yml --- old/terragrunt-0.81.7/.github/workflows/pages.yml 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/.github/workflows/pages.yml 2025-06-19 17:41:10.000000000 +0200 @@ -28,7 +28,7 @@ - name: Checkout uses: actions/checkout@v4 - name: Setup Ruby - uses: ruby/setup-ruby@cb0fda56a307b8c78d38320cd40d9eb22a3bf04e # v1.242.0 + uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0 with: ruby-version: '3.3' bundler-cache: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/cli/commands/scaffold/scaffold.go new/terragrunt-0.81.8/cli/commands/scaffold/scaffold.go --- old/terragrunt-0.81.7/cli/commands/scaffold/scaffold.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/cli/commands/scaffold/scaffold.go 2025-06-19 17:41:10.000000000 +0200 @@ -5,6 +5,7 @@ "fmt" "net/url" "os" + "path/filepath" "regexp" "strings" @@ -236,33 +237,50 @@ return boilerplateDir, nil } -// downloadTemplate - parse URL and download files +// downloadTemplate - parse URL, download files, and handle subfolders func downloadTemplate(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, templateURL string, tempDir string) (string, error) { parsedTemplateURL, err := tf.ToSourceURL(templateURL, tempDir) if err != nil { return "", errors.New(err) } - parsedTemplateURL, err = rewriteTemplateURL(ctx, l, opts, parsedTemplateURL) + // Split the processed URL to get the base URL and subfolder + baseURL, subFolder, err := tf.SplitSourceURL(l, parsedTemplateURL) if err != nil { return "", errors.New(err) } - // regenerate template url with all changes - templateURL = parsedTemplateURL.String() - // prepare temporary directory for template - templateDir, err := os.MkdirTemp("", "template") + // Go-getter expects a pathspec or . for file paths + if baseURL.Scheme == "" || baseURL.Scheme == "file" { + baseURL.Path = filepath.ToSlash(strings.TrimSuffix(baseURL.Path, "/")) + "//." + } + + baseURL, err = rewriteTemplateURL(ctx, l, opts, baseURL) if err != nil { return "", errors.New(err) } - // downloading templateURL - l.Infof("Using template from %s", templateURL) + templateDir, err := os.MkdirTemp(tempDir, "template") + if err != nil { + return "", errors.New(err) + } - if _, err := getter.GetAny(ctx, templateDir, templateURL); err != nil { + l.Infof("Downloading template from %s into %s", baseURL.String(), templateDir) + // Downloading baseURL to support boilerplate dependencies and partials. Go-getter discards all but specified folder if one is provided. + if _, err := getter.GetAny(ctx, templateDir, baseURL.String()); err != nil { return "", errors.New(err) } + // Add subfolder to templateDir if provided, as scaffold needs path to boilerplate.yml file + if subFolder != "" { + subFolder = strings.TrimPrefix(subFolder, "/") + templateDir = filepath.Join(templateDir, subFolder) + // Verify that subfolder exists + if _, err := os.Stat(templateDir); os.IsNotExist(err) { + return "", errors.Errorf("subfolder \"//%s\" not found in downloaded template from %s", subFolder, templateURL) + } + } + return templateDir, nil } @@ -298,7 +316,7 @@ boilerplateDir = tempTemplateDir } else { - defaultTempDir, err := os.MkdirTemp("", "boilerplate") + defaultTempDir, err := os.MkdirTemp(tempDir, "boilerplate") if err != nil { return "", errors.New(err) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/config/config.go new/terragrunt-0.81.8/config/config.go --- old/terragrunt-0.81.7/config/config.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/config/config.go 2025-06-19 17:41:10.000000000 +0200 @@ -1189,6 +1189,10 @@ fileInfo, err := os.Stat(configPath) if err != nil { + if os.IsNotExist(err) { + return TerragruntConfigNotFoundError{Path: configPath} + } + return errors.Errorf("failed to get file info: %w", err) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/config/config_partial.go new/terragrunt-0.81.8/config/config_partial.go --- old/terragrunt-0.81.7/config/config_partial.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/config/config_partial.go 2025-06-19 17:41:10.000000000 +0200 @@ -284,6 +284,10 @@ fileInfo, err := os.Stat(configPath) if err != nil { + if os.IsNotExist(err) { + return nil, TerragruntConfigNotFoundError{Path: configPath} + } + return nil, errors.New(err) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/config/errors.go new/terragrunt-0.81.8/config/errors.go --- old/terragrunt-0.81.7/config/errors.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/config/errors.go 2025-06-19 17:41:10.000000000 +0200 @@ -142,7 +142,7 @@ } func (err TerragruntConfigNotFoundError) Error() string { - return fmt.Sprintf("Terragrunt config %s not found", err.Path) + return fmt.Sprintf("You attempted to run terragrunt in a folder that does not contain a terragrunt.hcl file. Please add a terragrunt.hcl file and try again.\n\nPath: %q", err.Path) } type InvalidSourceURLError struct { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/docs/_docs/04_reference/06-experiments.md new/terragrunt-0.81.8/docs/_docs/04_reference/06-experiments.md --- old/terragrunt-0.81.7/docs/_docs/04_reference/06-experiments.md 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/docs/_docs/04_reference/06-experiments.md 2025-06-19 17:41:10.000000000 +0200 @@ -119,7 +119,7 @@ - [ ] Add support for storing and retrieving OpenTofu/Terraform modules from the CAS. - [ ] Add support for storing and retrieving Unit/Stack configurations from the CAS. -#### `report` +### `report` Support for Terragrunt Run Reports and Summaries. @@ -142,7 +142,7 @@ - [ ] Add comprehensive integration tests for the `report` experiment. - [ ] Finalize the design of run summaries and reports. -#### `runner-pool` +### `runner-pool` Proposes replacing Terragrunt’s group-based execution with a dynamic runner pool that schedules Units as soon as dependencies are resolved. This improves efficiency, reduces bottlenecks, and limits the impact of individual failures. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/docs-starlight/src/content/docs/04-reference/04-experiments.md new/terragrunt-0.81.8/docs-starlight/src/content/docs/04-reference/04-experiments.md --- old/terragrunt-0.81.7/docs-starlight/src/content/docs/04-reference/04-experiments.md 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/docs-starlight/src/content/docs/04-reference/04-experiments.md 2025-06-19 17:41:10.000000000 +0200 @@ -119,7 +119,7 @@ - [ ] Add support for storing and retrieving OpenTofu/Terraform modules from the CAS. - [ ] Add support for storing and retrieving Unit/Stack configurations from the CAS. -#### `report` +### `report` Support for Terragrunt Run Reports and Summaries. @@ -142,7 +142,7 @@ - [ ] Add comprehensive integration tests for the `report` experiment. - [ ] Finalize the design of run summaries and reports. -#### `runner-pool` +### `runner-pool` Proposes replacing Terragrunt’s group-based execution with a dynamic runner pool that schedules Units as soon as dependencies are resolved. This improves efficiency, reduces bottlenecks, and limits the impact of individual failures. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/go.mod new/terragrunt-0.81.8/go.mod --- old/terragrunt-0.81.7/go.mod 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/go.mod 2025-06-19 17:41:10.000000000 +0200 @@ -6,7 +6,7 @@ cloud.google.com/go/storage v1.54.0 dario.cat/mergo v1.0.1 github.com/NYTimes/gziphandler v1.1.1 - github.com/ProtonMail/go-crypto v1.2.0 + github.com/ProtonMail/go-crypto v1.3.0 github.com/aws/aws-sdk-go v1.55.7 github.com/aws/aws-sdk-go-v2 v1.36.3 github.com/charmbracelet/bubbles v0.21.0 @@ -88,7 +88,7 @@ github.com/invopop/jsonschema v0.13.0 github.com/xeipuuv/gojsonschema v1.2.0 go.uber.org/mock v0.5.2 - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 + golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b ) require ( @@ -255,9 +255,11 @@ github.com/pquerna/otp v1.4.0 // indirect github.com/pterm/pterm v0.12.80 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sourcegraph/go-lsp v0.0.0-20240223163137-f80c5dd31dfd // indirect github.com/sourcegraph/jsonrpc2 v0.2.0 // indirect @@ -290,7 +292,7 @@ golang.org/x/crypto v0.38.0 // indirect golang.org/x/net v0.40.0 // indirect golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.33.0 // indirect google.golang.org/genproto v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/go.sum new/terragrunt-0.81.8/go.sum --- old/terragrunt-0.81.7/go.sum 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/go.sum 2025-06-19 17:41:10.000000000 +0200 @@ -716,8 +716,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs= -github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= @@ -1613,8 +1613,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= @@ -1627,8 +1627,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -1848,8 +1848,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA= +golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -2248,8 +2248,8 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/internal/cas/.gitignore new/terragrunt-0.81.8/internal/cas/.gitignore --- old/terragrunt-0.81.7/internal/cas/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/terragrunt-0.81.8/internal/cas/.gitignore 2025-06-19 17:41:10.000000000 +0200 @@ -0,0 +1 @@ +*.test diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/internal/cas/benchmark_test.go new/terragrunt-0.81.8/internal/cas/benchmark_test.go --- old/terragrunt-0.81.7/internal/cas/benchmark_test.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/internal/cas/benchmark_test.go 2025-06-19 17:41:10.000000000 +0200 @@ -24,6 +24,8 @@ b.ResetTimer() for i := 0; b.Loop(); i++ { + b.StopTimer() + storePath := filepath.Join(tempDir, "store", strconv.Itoa(i)) targetPath := filepath.Join(tempDir, "repo", strconv.Itoa(i)) @@ -34,6 +36,8 @@ b.Fatal(err) } + b.StartTimer() + if err := c.Clone(b.Context(), l, &cas.CloneOptions{ Dir: targetPath, }, repo); err != nil { @@ -60,7 +64,10 @@ } b.ResetTimer() + for i := 0; b.Loop(); i++ { + b.StopTimer() + targetPath := filepath.Join(tempDir, "repo", strconv.Itoa(i)) c, err := cas.New(cas.Options{ @@ -70,6 +77,8 @@ b.Fatal(err) } + b.StartTimer() + if err := c.Clone(b.Context(), l, &cas.CloneOptions{ Dir: targetPath, }, repo); err != nil { @@ -91,7 +100,12 @@ b.Run("store", func(b *testing.B) { for i := 0; b.Loop(); i++ { - hash := fmt.Sprintf("benchmark%d", i) + b.StopTimer() + + hash := "benchmark" + strconv.Itoa(i) + + b.StartTimer() + if err := content.Store(l, hash, testData); err != nil { b.Fatal(err) } @@ -127,7 +141,11 @@ func BenchmarkGitOperations(b *testing.B) { // Setup a git repository for testing repoDir := b.TempDir() - git := cas.NewGitRunner().WithWorkDir(repoDir) + git, err := cas.NewGitRunner() + if err != nil { + b.Fatal(err) + } + git = git.WithWorkDir(repoDir) ctx := b.Context() @@ -136,7 +154,12 @@ } b.Run("ls-remote", func(b *testing.B) { - git := cas.NewGitRunner() // No workDir needed for ls-remote + git, err := cas.NewGitRunner() + if err != nil { + b.Fatal(err) + } + git = git.WithWorkDir(repoDir) + b.ResetTimer() for b.Loop() { _, err := git.LsRemote(ctx, "https://github.com/gruntwork-io/terragrunt.git", "HEAD") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/internal/cas/cas.go new/terragrunt-0.81.8/internal/cas/cas.go --- old/terragrunt-0.81.7/internal/cas/cas.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/internal/cas/cas.go 2025-06-19 17:41:10.000000000 +0200 @@ -66,9 +66,14 @@ store := NewStore(opts.StorePath) + git, err := NewGitRunner() + if err != nil { + return nil, err + } + return &CAS{ store: store, - git: NewGitRunner(), + git: git, opts: opts, }, nil } @@ -161,6 +166,27 @@ } func (c *CAS) storeRootTree(ctx context.Context, l log.Logger, hash string, opts *CloneOptions) error { + // Get all blobs recursively in a single git ls-tree -r call at the root + allBlobsTree, err := c.git.LsTreeRecursive(ctx, hash, ".") + if err != nil { + return err + } + + // Collect all blobs from recursive listing + var allBlobs []TreeEntry + + for _, entry := range allBlobsTree.Entries() { + if entry.Type == "blob" { + allBlobs = append(allBlobs, entry) + } + } + + // Store all blobs concurrently (single batch from recursive call) + if err := c.storeBlobs(ctx, allBlobs); err != nil { + return err + } + + // Now store the tree structure (which won't need to handle blobs again) if err := c.storeTree(ctx, l, hash, ""); err != nil { return err } @@ -213,80 +239,32 @@ return nil } + // Get tree structure (no recursive blobs needed - they're already stored) tree, err := c.git.LsTree(ctx, hash, ".") if err != nil { return err } - // Optimistically assume half the entries are trees and half are blobs - var ( - trees = make([]TreeEntry, 0, len(tree.Entries())/2) //nolint:mnd - blobs = make([]TreeEntry, 0, len(tree.Entries())/2) //nolint:mnd - ) + // Only collect immediate tree entries (blobs are already handled at root) + var immediateTrees []TreeEntry for _, entry := range tree.Entries() { if prefix != "" { entry.Path = filepath.Join(prefix, entry.Path) } - switch entry.Type { - case "blob": - blobs = append(blobs, entry) - case "tree": - trees = append(trees, entry) - } - } - - ch := make(chan error, 2) //nolint:mnd - - var wg sync.WaitGroup - - wg.Add(1) - - go func() { - defer wg.Done() - - if err := c.storeBlobs(ctx, blobs); err != nil { - ch <- err - - return - } - - ch <- nil - }() - - wg.Add(1) - - go func() { - defer wg.Done() - - if err := c.storeTrees(ctx, l, trees, prefix); err != nil { - ch <- err - - return - } - - ch <- nil - }() - - wg.Wait() - - close(ch) - - errs := []error{} - - for err := range ch { - if err != nil { - errs = append(errs, err) + if entry.Type == "tree" { + immediateTrees = append(immediateTrees, entry) } } - if len(errs) > 0 { - return errors.Join(errs...) + // Store tree objects recursively + if err := c.storeTrees(ctx, l, immediateTrees, prefix); err != nil { + return err } + // Store the current tree object content := NewContent(c.store) - if err := content.Ensure(l, hash, tree.Data()); err != nil { return err } @@ -294,8 +272,8 @@ return nil } -// storeBlobs concurrently stores blobs in the CAS -func (c *CAS) storeBlobs(ctx context.Context, entries []TreeEntry) error { +// storeTrees concurrently stores trees in the CAS +func (c *CAS) storeTrees(ctx context.Context, l log.Logger, entries []TreeEntry, prefix string) error { ch := make(chan error, len(entries)) var wg sync.WaitGroup @@ -310,9 +288,8 @@ go func(hash string) { defer wg.Done() - if err := c.ensureBlob(ctx, hash); err != nil { + if err := c.storeTree(ctx, l, hash, prefix); err != nil { ch <- err - return } @@ -321,10 +298,9 @@ } wg.Wait() - close(ch) - errs := []error{} + var errs []error for err := range ch { if err != nil { @@ -339,8 +315,8 @@ return nil } -// storeTrees concurrently stores trees in the CAS -func (c *CAS) storeTrees(ctx context.Context, l log.Logger, entries []TreeEntry, prefix string) error { +// storeBlobs concurrently stores blobs in the CAS +func (c *CAS) storeBlobs(ctx context.Context, entries []TreeEntry) error { ch := make(chan error, len(entries)) var wg sync.WaitGroup @@ -355,7 +331,7 @@ go func(hash string) { defer wg.Done() - if err := c.storeTree(ctx, l, hash, prefix); err != nil { + if err := c.ensureBlob(ctx, hash); err != nil { ch <- err return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/internal/cas/getter.go new/terragrunt-0.81.8/internal/cas/getter.go --- old/terragrunt-0.81.7/internal/cas/getter.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/internal/cas/getter.go 2025-06-19 17:41:10.000000000 +0200 @@ -61,8 +61,8 @@ // We need to switch to a valid Git URL to clone the repository // Like this: // g...@github.com:gruntwork-io/terragrunt.git - if strings.HasPrefix(urlStr, "ssh://") { - urlStr = strings.TrimPrefix(urlStr, "ssh://") + if after, ok := strings.CutPrefix(urlStr, "ssh://"); ok { + urlStr = after // Replace the first slash with a colon urlStr = strings.Replace(urlStr, "/", ":", 1) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/internal/cas/git.go new/terragrunt-0.81.8/internal/cas/git.go --- old/terragrunt-0.81.7/internal/cas/git.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/internal/cas/git.go 2025-06-19 17:41:10.000000000 +0200 @@ -18,20 +18,30 @@ // GitRunner handles git command execution type GitRunner struct { + GitPath string WorkDir string } // NewGitRunner creates a new GitRunner instance -func NewGitRunner() *GitRunner { - return &GitRunner{} +func NewGitRunner() (*GitRunner, error) { + gitPath, err := exec.LookPath("git") + if err != nil { + return nil, &WrappedError{ + Op: "git", + Context: "git not found", + Err: ErrCommandSpawn, + } + } + + return &GitRunner{GitPath: gitPath}, nil } // WithWorkDir returns a new GitRunner with the specified working directory func (g *GitRunner) WithWorkDir(workDir string) *GitRunner { - // Create new instance instead of modifying existing one - return &GitRunner{ - WorkDir: workDir, - } + copy := *g + copy.WorkDir = workDir + + return © } // RequiresWorkDir returns an error if no working directory is set @@ -206,6 +216,32 @@ return ParseTree(stdout.String(), path) } +// LsTreeRecursive runs git ls-tree -r and returns all blobs recursively +// This eliminates the need for multiple separate ls-tree calls on subtrees +func (g *GitRunner) LsTreeRecursive(ctx context.Context, reference, path string) (*Tree, error) { + if err := g.RequiresWorkDir(); err != nil { + return nil, err + } + + // Use recursive ls-tree to get all blobs in a single command + cmd := g.prepareCommand(ctx, "ls-tree", "-r", reference) + cmd.Dir = g.WorkDir + + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + return nil, &WrappedError{ + Op: "git_ls_tree_recursive", + Context: stderr.String(), + Err: ErrReadTree, + } + } + + return ParseTree(stdout.String(), path) +} + // CatFile writes the contents of a git object // to a given writer. func (g *GitRunner) CatFile(ctx context.Context, hash string, out io.Writer) error { @@ -240,5 +276,5 @@ } func (g *GitRunner) prepareCommand(ctx context.Context, name string, args ...string) *exec.Cmd { - return exec.CommandContext(ctx, "git", append([]string{name}, args...)...) + return exec.CommandContext(ctx, g.GitPath, append([]string{name}, args...)...) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/internal/cas/git_test.go new/terragrunt-0.81.8/internal/cas/git_test.go --- old/terragrunt-0.81.7/internal/cas/git_test.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/internal/cas/git_test.go 2025-06-19 17:41:10.000000000 +0200 @@ -12,7 +12,8 @@ func TestGitRunner_LsRemote(t *testing.T) { t.Parallel() - runner := cas.NewGitRunner() + runner, err := cas.NewGitRunner() + require.NoError(t, err) ctx := t.Context() @@ -52,8 +53,10 @@ t.Run("shallow clone", func(t *testing.T) { t.Parallel() cloneDir := t.TempDir() - runner := cas.NewGitRunner().WithWorkDir(cloneDir) - err := runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt.git", true, 1, "main") + runner, err := cas.NewGitRunner() + require.NoError(t, err) + runner = runner.WithWorkDir(cloneDir) + err = runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt.git", true, 1, "main") require.NoError(t, err) // Verify it's a git repository @@ -63,8 +66,9 @@ t.Run("clone without workdir fails", func(t *testing.T) { t.Parallel() - runner := cas.NewGitRunner() - err := runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt.git", true, 1, "main") + runner, err := cas.NewGitRunner() + require.NoError(t, err) + err = runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt.git", true, 1, "main") require.Error(t, err) var wrappedErr *cas.WrappedError require.ErrorAs(t, err, &wrappedErr) @@ -74,8 +78,10 @@ t.Run("invalid repository", func(t *testing.T) { t.Parallel() cloneDir := t.TempDir() - runner := cas.NewGitRunner().WithWorkDir(cloneDir) - err := runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt-fake.git", false, 1, "") + runner, err := cas.NewGitRunner() + require.NoError(t, err) + runner = runner.WithWorkDir(cloneDir) + err = runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt-fake.git", false, 1, "") require.Error(t, err) var wrappedErr *cas.WrappedError require.ErrorAs(t, err, &wrappedErr) @@ -85,7 +91,8 @@ func TestCreateTempDir(t *testing.T) { t.Parallel() - git := cas.NewGitRunner() + git, err := cas.NewGitRunner() + require.NoError(t, err) dir, cleanup, err := git.CreateTempDir() require.NoError(t, err) t.Cleanup(func() { @@ -142,10 +149,12 @@ t.Run("valid repository", func(t *testing.T) { t.Parallel() cloneDir := t.TempDir() - runner := cas.NewGitRunner().WithWorkDir(cloneDir) + runner, err := cas.NewGitRunner() + require.NoError(t, err) + runner = runner.WithWorkDir(cloneDir) // First clone a repository - err := runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt.git", true, 1, "main") + err = runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt.git", true, 1, "main") require.NoError(t, err) // Then try to ls-tree HEAD @@ -169,8 +178,10 @@ t.Run("ls-tree without workdir fails", func(t *testing.T) { t.Parallel() - runner := cas.NewGitRunner() - _, err := runner.LsTree(ctx, "HEAD", ".") + runner, err := cas.NewGitRunner() + require.NoError(t, err) + + _, err = runner.LsTree(ctx, "HEAD", ".") require.Error(t, err) var wrappedErr *cas.WrappedError require.ErrorAs(t, err, &wrappedErr) @@ -180,10 +191,12 @@ t.Run("invalid reference", func(t *testing.T) { t.Parallel() cloneDir := t.TempDir() - runner := cas.NewGitRunner().WithWorkDir(cloneDir) + runner, err := cas.NewGitRunner() + require.NoError(t, err) + runner = runner.WithWorkDir(cloneDir) // First clone a repository - err := runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt.git", true, 1, "main") + err = runner.Clone(ctx, "https://github.com/gruntwork-io/terragrunt.git", true, 1, "main") require.NoError(t, err) // Try to ls-tree an invalid reference @@ -196,10 +209,12 @@ t.Run("invalid repository", func(t *testing.T) { t.Parallel() - runner := cas.NewGitRunner().WithWorkDir(t.TempDir()) + runner, err := cas.NewGitRunner() + require.NoError(t, err) + runner = runner.WithWorkDir(t.TempDir()) // Try to ls-tree in an empty directory - _, err := runner.LsTree(ctx, "HEAD", ".") + _, err = runner.LsTree(ctx, "HEAD", ".") require.Error(t, err) var wrappedErr *cas.WrappedError require.ErrorAs(t, err, &wrappedErr) @@ -212,15 +227,18 @@ t.Run("with workdir", func(t *testing.T) { t.Parallel() - runner := cas.NewGitRunner().WithWorkDir(t.TempDir()) - err := runner.RequiresWorkDir() + runner, err := cas.NewGitRunner() + require.NoError(t, err) + runner = runner.WithWorkDir(t.TempDir()) + err = runner.RequiresWorkDir() assert.NoError(t, err) }) t.Run("without workdir", func(t *testing.T) { t.Parallel() - runner := cas.NewGitRunner() - err := runner.RequiresWorkDir() + runner, err := cas.NewGitRunner() + require.NoError(t, err) + err = runner.RequiresWorkDir() require.Error(t, err) var wrappedErr *cas.WrappedError require.ErrorAs(t, err, &wrappedErr) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/internal/cas/integration_test.go new/terragrunt-0.81.8/internal/cas/integration_test.go --- old/terragrunt-0.81.7/internal/cas/integration_test.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/internal/cas/integration_test.go 2025-06-19 17:41:10.000000000 +0200 @@ -120,7 +120,10 @@ }, "https://github.com/gruntwork-io/terragrunt.git")) // Get the commit hash - git := cas.NewGitRunner().WithWorkDir(filepath.Join(tempDir, "repo")) + git, err := cas.NewGitRunner() + require.NoError(t, err) + + git = git.WithWorkDir(filepath.Join(tempDir, "repo")) results, err := git.LsRemote(ctx, "https://github.com/gruntwork-io/terragrunt.git", "HEAD") require.NoError(t, err) require.NotEmpty(t, results) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/internal/cas/tree.go new/terragrunt-0.81.8/internal/cas/tree.go --- old/terragrunt-0.81.7/internal/cas/tree.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/internal/cas/tree.go 2025-06-19 17:41:10.000000000 +0200 @@ -6,14 +6,10 @@ "os" "path/filepath" "strings" - "sync" - - "github.com/gruntwork-io/terragrunt/internal/errors" ) const ( minTreePartsLength = 4 - maxConcurrentLinks = 4 ) // TreeEntry represents a single entry in a git tree @@ -101,96 +97,41 @@ // LinkTree writes the tree to a target directory func (t *Tree) LinkTree(ctx context.Context, store *Store, targetDir string) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - var ( - errMu sync.Mutex - errs []error - ) - - var wg sync.WaitGroup - - semaphore := make(chan struct{}, maxConcurrentLinks) + content := NewContent(store) for _, entry := range t.entries { - wg.Add(1) + // Check for cancellation + select { + case <-ctx.Done(): + return ctx.Err() + default: + } - go func(entry TreeEntry) { - defer wg.Done() + entryPath := filepath.Join(targetDir, entry.Path) + if err := os.MkdirAll(filepath.Dir(entryPath), DefaultDirPerms); err != nil { + return wrapError("mkdir_all", entryPath, err) + } - select { - case <-ctx.Done(): - return - default: + switch entry.Type { + case "blob": + if err := content.Link(entry.Hash, entryPath); err != nil { + return wrapError("link_blob", entryPath, err) } - - select { - case semaphore <- struct{}{}: - defer func() { <-semaphore }() - case <-ctx.Done(): - return + case "tree": + treeData, err := content.Read(entry.Hash) + if err != nil { + return wrapError("read_tree", entry.Hash, err) } - entryPath := filepath.Join(targetDir, entry.Path) - if err := os.MkdirAll(filepath.Dir(entryPath), DefaultDirPerms); err != nil { - errMu.Lock() - errs = append(errs, wrapError("mkdir_all", entryPath, err)) - errMu.Unlock() - cancel() - - return + subTree, err := ParseTree(string(treeData), entryPath) + if err != nil { + return wrapError("parse_tree", entry.Hash, err) } - content := NewContent(store) - - switch entry.Type { - case "blob": - if err := content.Link(entry.Hash, entryPath); err != nil { - errMu.Lock() - errs = append(errs, wrapError("link_blob", entryPath, err)) - errMu.Unlock() - cancel() - - return - } - case "tree": - treeData, err := content.Read(entry.Hash) - if err != nil { - errMu.Lock() - errs = append(errs, wrapError("read_tree", entry.Hash, err)) - errMu.Unlock() - cancel() - - return - } - - subTree, err := ParseTree(string(treeData), entryPath) - if err != nil { - errMu.Lock() - errs = append(errs, wrapError("parse_tree", entry.Hash, err)) - errMu.Unlock() - cancel() - - return - } - - if err := subTree.LinkTree(ctx, store, entryPath); err != nil { - errMu.Lock() - errs = append(errs, wrapError("link_subtree", entryPath, err)) - errMu.Unlock() - cancel() - - return - } + if err := subTree.LinkTree(ctx, store, entryPath); err != nil { + return wrapError("link_subtree", entryPath, err) } - }(entry) - } - - wg.Wait() - - if len(errs) > 0 { - return errors.Join(errs...) + } } return nil diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/internal/discovery/discovery.go new/terragrunt-0.81.8/internal/discovery/discovery.go --- old/terragrunt-0.81.7/internal/discovery/discovery.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/internal/discovery/discovery.go 2025-06-19 17:41:10.000000000 +0200 @@ -511,7 +511,7 @@ depPaths = append(depPaths, depPath) } - if dCfg.Dependencies != nil { + if dCfg.Parsed.Dependencies != nil { for _, dependency := range dCfg.Parsed.Dependencies.Paths { if !filepath.IsAbs(dependency) { dependency = filepath.Join(dCfg.Path, dependency) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/find/dag/c-mixed-deps/terragrunt.hcl new/terragrunt-0.81.8/test/fixtures/find/dag/c-mixed-deps/terragrunt.hcl --- old/terragrunt-0.81.7/test/fixtures/find/dag/c-mixed-deps/terragrunt.hcl 1970-01-01 01:00:00.000000000 +0100 +++ new/terragrunt-0.81.8/test/fixtures/find/dag/c-mixed-deps/terragrunt.hcl 2025-06-19 17:41:10.000000000 +0200 @@ -0,0 +1,8 @@ +# Uses both dependency and dependencies blocks +dependency "single_dep" { + config_path = "../a-dependent" +} + +dependencies { + paths = ["../d-dependencies-only"] +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/find/dag/d-dependencies-only/terragrunt.hcl new/terragrunt-0.81.8/test/fixtures/find/dag/d-dependencies-only/terragrunt.hcl --- old/terragrunt-0.81.7/test/fixtures/find/dag/d-dependencies-only/terragrunt.hcl 1970-01-01 01:00:00.000000000 +0100 +++ new/terragrunt-0.81.8/test/fixtures/find/dag/d-dependencies-only/terragrunt.hcl 2025-06-19 17:41:10.000000000 +0200 @@ -0,0 +1,4 @@ +# Uses only dependencies block (plural) +dependencies { + paths = ["../a-dependent"] +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/scaffold/custom-default-template/root.hcl new/terragrunt-0.81.8/test/fixtures/scaffold/custom-default-template/root.hcl --- old/terragrunt-0.81.7/test/fixtures/scaffold/custom-default-template/root.hcl 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/test/fixtures/scaffold/custom-default-template/root.hcl 2025-06-19 17:41:10.000000000 +0200 @@ -1,3 +1,3 @@ catalog { - default_template = "g...@github.com:gruntwork-io/terragrunt.git//test/fixtures/scaffold/external-template" + default_template = "g...@github.com:gruntwork-io/terragrunt.git//test/fixtures/scaffold/external-template/template" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/boilerplate.yml new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/boilerplate.yml --- old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/boilerplate.yml 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/boilerplate.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -# boilerplate config -variables: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/dependency/dependency.txt new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/dependency/dependency.txt --- old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/dependency/dependency.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/dependency/dependency.txt 2025-06-19 17:41:10.000000000 +0200 @@ -0,0 +1 @@ +# Dependency \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/external-template.txt new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/external-template.txt --- old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/external-template.txt 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/external-template.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -# External template \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/template/boilerplate.yml new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/template/boilerplate.yml --- old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/template/boilerplate.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/template/boilerplate.yml 2025-06-19 17:41:10.000000000 +0200 @@ -0,0 +1,6 @@ +# boilerplate config + +dependencies: + - name: test-dependency + template-url: ../dependency + output-folder: dependency/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/template/external-template.txt new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/template/external-template.txt --- old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/template/external-template.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/template/external-template.txt 2025-06-19 17:41:10.000000000 +0200 @@ -0,0 +1 @@ +# External template \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/template/terragrunt.hcl new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/template/terragrunt.hcl --- old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/template/terragrunt.hcl 1970-01-01 01:00:00.000000000 +0100 +++ new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/template/terragrunt.hcl 2025-06-19 17:41:10.000000000 +0200 @@ -0,0 +1,9 @@ +# Project template dir +terraform { + source = "{{ .sourceUrl }}" +} + +inputs = { + project_name = "template" + +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/terragrunt.hcl new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/terragrunt.hcl --- old/terragrunt-0.81.7/test/fixtures/scaffold/external-template/terragrunt.hcl 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/test/fixtures/scaffold/external-template/terragrunt.hcl 1970-01-01 01:00:00.000000000 +0100 @@ -1,9 +0,0 @@ -# Project template dir -terraform { - source = "{{ .sourceUrl }}" -} - -inputs = { - project_name = "template" - -} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/integration_find_test.go new/terragrunt-0.81.8/test/integration_find_test.go --- old/terragrunt-0.81.7/test/integration_find_test.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/test/integration_find_test.go 2025-06-19 17:41:10.000000000 +0200 @@ -96,8 +96,8 @@ sort string expected string }{ - {name: "alpha", sort: "alpha", expected: "a-dependent\nb-dependency\n"}, - {name: "dag", sort: "dag", expected: "b-dependency\na-dependent\n"}, + {name: "alpha", sort: "alpha", expected: "a-dependent\nb-dependency\nc-mixed-deps\nd-dependencies-only\n"}, + {name: "dag", sort: "dag", expected: "b-dependency\na-dependent\nd-dependencies-only\nc-mixed-deps\n"}, } for _, tc := range testCases { @@ -121,6 +121,49 @@ } } +func TestFindDAGWithMixedDependencies(t *testing.T) { + t.Parallel() + + helpers.CleanupTerraformFolder(t, testFixtureFindDAG) + + testCases := []struct { + name string + args string + expected string + }{ + { + name: "dag with dependencies output", + args: "--dag --dependencies", + expected: "b-dependency\na-dependent\nd-dependencies-only\nc-mixed-deps\n", + }, + { + name: "dag with dependencies json output", + args: "--dag --dependencies --json", + expected: `[{"type":"unit","path":"b-dependency"},{"type":"unit","path":"a-dependent","dependencies":["b-dependency"]},{"type":"unit","path":"d-dependencies-only","dependencies":["a-dependent"]},{"type":"unit","path":"c-mixed-deps","dependencies":["a-dependent","d-dependencies-only"]}]`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + helpers.CleanupTerraformFolder(t, testFixtureFindDAG) + + cmd := "terragrunt find --no-color --working-dir " + testFixtureFindDAG + " " + tc.args + + stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t, cmd) + require.NoError(t, err) + + assert.Empty(t, stderr) + if strings.Contains(tc.args, "--json") { + assert.JSONEq(t, tc.expected, stdout) + } else { + assert.Equal(t, tc.expected, stdout) + } + }) + } +} + func TestFindExternalDependencies(t *testing.T) { t.Parallel() @@ -233,7 +276,8 @@ t.Parallel() // I'm using the list fixture here because it's more convenient. - helpers.CleanupTerraformFolder(t, testFixtureListDag) + testFixtureQueueConstruct := "fixtures/list/dag" + helpers.CleanupTerraformFolder(t, testFixtureQueueConstruct) testCases := []struct { name string @@ -275,9 +319,9 @@ t.Run(tc.name, func(t *testing.T) { t.Parallel() - helpers.CleanupTerraformFolder(t, testFixtureListDag) + helpers.CleanupTerraformFolder(t, testFixtureQueueConstruct) - cmd := fmt.Sprintf("terragrunt find --json --no-color --working-dir %s %s", testFixtureListDag, tc.args) + cmd := fmt.Sprintf("terragrunt find --json --no-color --working-dir %s %s", testFixtureQueueConstruct, tc.args) stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t, cmd) require.NoError(t, err) assert.Empty(t, stderr) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/integration_parse_test.go new/terragrunt-0.81.8/test/integration_parse_test.go --- old/terragrunt-0.81.7/test/integration_parse_test.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/test/integration_parse_test.go 2025-06-19 17:41:10.000000000 +0200 @@ -143,20 +143,34 @@ fields := strings.Fields(stdout) - aDepLine := 0 - bDepLine := 0 + // Find positions of all fixtures in the output + aDepLine := -1 + bDepLine := -1 + cMixedLine := -1 + dDepsLine := -1 for i, field := range fields { - if field == "fixtures/find/dag/a-dependent" { + switch field { + case "fixtures/find/dag/a-dependent": aDepLine = i - } - - if field == "fixtures/find/dag/b-dependency" { + case "fixtures/find/dag/b-dependency": bDepLine = i + case "fixtures/find/dag/c-mixed-deps": + cMixedLine = i + case "fixtures/find/dag/d-dependencies-only": + dDepsLine = i } } - assert.Greater(t, aDepLine, bDepLine) + // Verify DAG ordering: + // b-dependency (no deps) should come first + // a-dependent (depends on b) should come after b + // d-dependencies-only (depends on b) should come after b + // c-mixed-deps (depends on a and d) should come last + assert.Greater(t, aDepLine, bDepLine, "a-dependent should come after b-dependency") + assert.Greater(t, dDepsLine, bDepLine, "d-dependencies-only should come after b-dependency") + assert.Greater(t, cMixedLine, aDepLine, "c-mixed-deps should come after a-dependent") + assert.Greater(t, cMixedLine, dDepsLine, "c-mixed-deps should come after d-dependencies-only") }) } } @@ -187,20 +201,38 @@ fields := strings.Fields(stdout) - aDepLine := 0 - bDepLine := 0 + // Find positions of all fixtures in the output + aDepLine := -1 + bDepLine := -1 + cMixedLine := -1 + dDepsLine := -1 for i, field := range fields { - if field == "fixtures/find/dag/a-dependent" { + switch field { + case "fixtures/find/dag/a-dependent": aDepLine = i - } - - if field == "fixtures/find/dag/b-dependency" { + case "fixtures/find/dag/b-dependency": bDepLine = i + case "fixtures/find/dag/c-mixed-deps": + cMixedLine = i + case "fixtures/find/dag/d-dependencies-only": + dDepsLine = i } } - assert.Greater(t, aDepLine, bDepLine) + // Verify DAG ordering for the core dependencies + // The exact ordering may vary with external dependencies included, + // but the basic dependency relationship should hold + if aDepLine >= 0 && bDepLine >= 0 { + assert.Greater(t, aDepLine, bDepLine, "a-dependent should come after b-dependency") + } + if dDepsLine >= 0 && bDepLine >= 0 { + assert.Greater(t, dDepsLine, bDepLine, "d-dependencies-only should come after b-dependency") + } + if cMixedLine >= 0 && aDepLine >= 0 && dDepsLine >= 0 { + assert.Greater(t, cMixedLine, aDepLine, "c-mixed-deps should come after a-dependent") + assert.Greater(t, cMixedLine, dDepsLine, "c-mixed-deps should come after d-dependencies-only") + } }) } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/integration_runner_pool_test.go new/terragrunt-0.81.8/test/integration_runner_pool_test.go --- old/terragrunt-0.81.7/test/integration_runner_pool_test.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/test/integration_runner_pool_test.go 2025-06-19 17:41:10.000000000 +0200 @@ -39,5 +39,5 @@ // run destroy with runner pool and check the order of the modules stdout, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run --experiment runner-pool --all destroy --non-interactive --tf-forward-stdout --working-dir "+rootPath) require.NoError(t, err) - assert.Regexp(t, `(?smi)(?:(Module A|Module B|Module C).*){3}(?:(Module D|Module E).*){2}`, stdout) + assert.Regexp(t, `(?smi)(?:(Module B|Module D).*){2}(?:(Module A|Module E|Module C).*){3}`, stdout) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/integration_scaffold_ssh_test.go new/terragrunt-0.81.8/test/integration_scaffold_ssh_test.go --- old/terragrunt-0.81.7/test/integration_scaffold_ssh_test.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/test/integration_scaffold_ssh_test.go 2025-06-19 17:41:10.000000000 +0200 @@ -22,6 +22,13 @@ "github.com/stretchr/testify/require" ) +const ( + testScaffoldModuleGit = "g...@github.com:gruntwork-io/terragrunt.git//test/fixtures/scaffold/scaffold-module" + testScaffoldTemplateModule = "g...@github.com:gruntwork-io/terragrunt.git//test/fixtures/scaffold/module-with-template" + testScaffoldExternalTemplateModule = "g...@github.com:gruntwork-io/terragrunt.git//test/fixtures/scaffold/external-template/template" + testScaffoldWithCustomDefaultTemplate = "fixtures/scaffold/custom-default-template" +) + func TestSSHScaffoldWithCustomDefaultTemplate(t *testing.T) { t.Parallel() @@ -52,6 +59,7 @@ assert.Contains(t, stderr, "Scaffolding completed") // check that exists file from external template assert.FileExists(t, tmpEnvPath+"/external-template.txt") + assert.FileExists(t, tmpEnvPath+"/dependency/dependency.txt") } func TestSSHScaffoldModuleDifferentRevisionAndSSH(t *testing.T) { @@ -69,7 +77,6 @@ t.Parallel() tmpEnvPath := t.TempDir() - _, stderr, err := helpers.RunTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt scaffold --non-interactive --working-dir %s %s", tmpEnvPath, testScaffoldModuleGit)) require.NoError(t, err) assert.Contains(t, stderr, "Scaffolding completed") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.81.7/test/integration_scaffold_test.go new/terragrunt-0.81.8/test/integration_scaffold_test.go --- old/terragrunt-0.81.7/test/integration_scaffold_test.go 2025-06-16 19:41:05.000000000 +0200 +++ new/terragrunt-0.81.8/test/integration_scaffold_test.go 2025-06-19 17:41:10.000000000 +0200 @@ -14,15 +14,11 @@ ) const ( - testScaffoldModuleURL = "https://github.com/gruntwork-io/terragrunt.git//test/fixtures/scaffold/scaffold-module" - testScaffoldModuleGit = "g...@github.com:gruntwork-io/terragrunt.git//test/fixtures/scaffold/scaffold-module" - testScaffoldModuleShort = "github.com/gruntwork-io/terragrunt.git//test/fixtures/inputs" - testScaffoldTemplateModule = "g...@github.com:gruntwork-io/terragrunt.git//test/fixtures/scaffold/module-with-template" - testScaffoldExternalTemplateModule = "g...@github.com:gruntwork-io/terragrunt.git//test/fixtures/scaffold/external-template" - testScaffoldLocalModulePath = "fixtures/scaffold/scaffold-module" - testScaffoldWithRootHCL = "fixtures/scaffold/root-hcl" - testScaffold3rdPartyModulePath = "git::https://github.com/Azure/terraform-azurerm-avm-res-compute-virtualmachine.git//.?ref=v0.15.0" - testScaffoldWithCustomDefaultTemplate = "fixtures/scaffold/custom-default-template" + testScaffoldModuleURL = "https://github.com/gruntwork-io/terragrunt.git//test/fixtures/scaffold/scaffold-module" + testScaffoldModuleShort = "github.com/gruntwork-io/terragrunt.git//test/fixtures/inputs" + testScaffoldLocalModulePath = "fixtures/scaffold/scaffold-module" + testScaffoldWithRootHCL = "fixtures/scaffold/root-hcl" + testScaffold3rdPartyModulePath = "git::https://github.com/Azure/terraform-azurerm-avm-res-compute-virtualmachine.git//.?ref=v0.15.0" ) func TestScaffoldModule(t *testing.T) { ++++++ terragrunt.obsinfo ++++++ --- /var/tmp/diff_new_pack.TgXvoU/_old 2025-06-20 16:51:49.710264515 +0200 +++ /var/tmp/diff_new_pack.TgXvoU/_new 2025-06-20 16:51:49.714264681 +0200 @@ -1,5 +1,5 @@ name: terragrunt -version: 0.81.7 -mtime: 1750095665 -commit: 6ec20b51cdc6b46c625a74937a01b2dca0c56527 +version: 0.81.8 +mtime: 1750347670 +commit: 47dd829095234f44752aa0f1b2149f4032e4a7df ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/terragrunt/vendor.tar.gz /work/SRC/openSUSE:Factory/.terragrunt.new.31170/vendor.tar.gz differ: char 13, line 1