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-07-06 17:06:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/terragrunt (Old) and /work/SRC/openSUSE:Factory/.terragrunt.new.1903 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "terragrunt" Sun Jul 6 17:06:05 2025 rev:242 rq:1290070 version:0.82.4 Changes: -------- --- /work/SRC/openSUSE:Factory/terragrunt/terragrunt.changes 2025-06-30 14:02:04.612647206 +0200 +++ /work/SRC/openSUSE:Factory/.terragrunt.new.1903/terragrunt.changes 2025-07-06 17:08:41.872911873 +0200 @@ -1,0 +2,43 @@ +Thu Jul 03 04:50:12 UTC 2025 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- Update to version 0.82.4: + * New Features + - Support for OpenTofu 1.10 Native S3 Locking + The remote_state S3 backend now integrates natively with the + OpenTofu 1.10 feature of state locking via S3 without the + usage of DynamoDB using the new use_lockfile attribute. + + # Configure OpenTofu/Terraform state to be stored in S3 + with native S3 locking instead of DynamoDB. + # This uses S3 object conditional writes for state locking, + which requires OpenTofu >= 1.10. + remote_state { + backend = "s3" + config = { + bucket = "my-tofu-state" + key = "${path_relative_to_include()}/tofu.tfstate" + region = "us-east-1" + encrypt = true + use_lockfile = true + } + } + + In previous releases, if users wanted to integrate with this + OpenTofu feature, they would have to use the generate + attribute, which opts users out of more advanced features of + Terragrunt remote state management like automatic + provisioning of state resources. + By using the native attribute in config, users can retain the + benefits of automatic backend bootstrapping in addition to + native integration with the new OpenTofu feature. + You can learn more about backend configurations in the HCL + docs. + https://terragrunt.gruntwork.io/docs/reference/config-blocks-and-attributes/#backend + * What's Changed + - feat: Adding support for native state locking (#4485) + - chore: update external dependencies (#4486) + - fix: Use a constant for min version of tofu for auto provider + cache dir (#4479) + - fix: resolve failing CAS & DAG tests (#4480) + +------------------------------------------------------------------- Old: ---- terragrunt-0.82.3.obscpio New: ---- terragrunt-0.82.4.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ terragrunt.spec ++++++ --- /var/tmp/diff_new_pack.xwPnAr/_old 2025-07-06 17:08:44.453018634 +0200 +++ /var/tmp/diff_new_pack.xwPnAr/_new 2025-07-06 17:08:44.457018799 +0200 @@ -17,7 +17,7 @@ Name: terragrunt -Version: 0.82.3 +Version: 0.82.4 Release: 0 Summary: Thin wrapper for Terraform for working with multiple Terraform modules License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.xwPnAr/_old 2025-07-06 17:08:44.529021779 +0200 +++ /var/tmp/diff_new_pack.xwPnAr/_new 2025-07-06 17:08:44.533021944 +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.82.3</param> + <param name="revision">v0.82.4</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.xwPnAr/_old 2025-07-06 17:08:44.553022772 +0200 +++ /var/tmp/diff_new_pack.xwPnAr/_new 2025-07-06 17:08:44.557022937 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/gruntwork-io/terragrunt</param> - <param name="changesrevision">a31ef0943c02cd68b648c81a0d1ed037a57e24e9</param></service></servicedata> + <param name="changesrevision">34af1f1518c336c96623fd3c6eb63c53b9afa7eb</param></service></servicedata> (No newline at EOF) ++++++ terragrunt-0.82.3.obscpio -> terragrunt-0.82.4.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/cli/commands/commands.go new/terragrunt-0.82.4/cli/commands/commands.go --- old/terragrunt-0.82.3/cli/commands/commands.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/cli/commands/commands.go 2025-07-02 15:24:07.000000000 +0200 @@ -191,6 +191,8 @@ return errGroup.Wait() } +const minTofuVersionForAutoProviderCacheDir = "1.10.0" + // setupAutoProviderCacheDir configures native provider caching by setting TF_PLUGIN_CACHE_DIR. // // Only works with OpenTofu version >= 1.10. Returns error if conditions aren't met. @@ -222,7 +224,7 @@ return errors.New("cannot determine OpenTofu version") } - requiredVersion, err := version.NewVersion("1.10.0") + requiredVersion, err := version.NewVersion(minTofuVersionForAutoProviderCacheDir) if err != nil { return fmt.Errorf("failed to parse required version: %w", err) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/docs/_docs/04_reference/04-config-blocks-and-attributes.md new/terragrunt-0.82.4/docs/_docs/04_reference/04-config-blocks-and-attributes.md --- old/terragrunt-0.82.3/docs/_docs/04_reference/04-config-blocks-and-attributes.md 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/docs/_docs/04_reference/04-config-blocks-and-attributes.md 2025-07-02 15:24:07.000000000 +0200 @@ -459,6 +459,7 @@ - `external_id` - (Optional) The external ID to use when assuming the role. - `session_name` - (Optional) The session name to use when assuming the role. - `dynamodb_table` - (Optional) The name of a DynamoDB table to use for state locking and consistency. The table must have a primary key named LockID. If not present, locking will be disabled. +- `use_lockfile` - (Optional) When `true`, enables native S3 locking using S3 object conditional writes for state locking. This feature requires OpenTofu >= 1.10. Can be used simultaneously with `dynamodb_table` during migration (both locks must be acquired successfully), but typically used as a replacement for DynamoDB locking. - `skip_bucket_versioning`: When `true`, the S3 bucket that is created to store the state will not be versioned. - `skip_bucket_ssencryption`: When `true`, the S3 bucket that is created to store the state will not be configured with server-side encryption. - `skip_bucket_accesslogging`: *DEPRECATED* If provided, will be ignored. A log warning will be issued in the console output to notify the user. @@ -601,6 +602,44 @@ } ``` +Example with S3 using native S3 locking (OpenTofu >= 1.10): + +```hcl +# Configure OpenTofu/Terraform state to be stored in S3 with native S3 locking instead of DynamoDB. +# This uses S3 object conditional writes for state locking, which requires OpenTofu >= 1.10. +remote_state { + backend = "s3" + config = { + bucket = "my-tofu-state" + key = "${path_relative_to_include()}/tofu.tfstate" + region = "us-east-1" + encrypt = true + use_lockfile = true + } +} +``` + +Example with S3 using both DynamoDB and native S3 locking during migration (OpenTofu >= 1.10): + +```hcl +# Configure OpenTofu/Terraform state with dual locking during migration from DynamoDB to S3 native locking. +# Both locks must be successfully acquired before operations can proceed. +# After the migration period, remove dynamodb_table to use only S3 native locking. +# Note: This won't delete the DynamoDB table, it will just be unused. +# You can delete it manually after the migration period. +remote_state { + backend = "s3" + config = { + bucket = "my-tofu-state" + key = "${path_relative_to_include()}/tofu.tfstate" + region = "us-east-1" + encrypt = true + dynamodb_table = "my-lock-table" # Remove this after migration period + use_lockfile = true # New native S3 locking + } +} +``` + #### encryption The encryption map needs a `key_provider` property, which can be set to one of `pbkdf2`, `aws_kms` or `gcp_kms`. @@ -1365,7 +1404,7 @@ allowed_account_ids = ["1234567890"] } EOF -} + } ``` Then in a `terragrunt.hcl` file, you could dynamically set `generate` as an attribute as follows: @@ -1978,8 +2017,8 @@ **DEPRECATED: Use [exclude](#exclude) instead.** -The terragrunt `skip` boolean flag can be used to protect modules you don’t want any changes to or just to skip modules -that don’t define any infrastructure by themselves. When set to true, all terragrunt commands will skip the selected +The terragrunt `skip` boolean flag can be used to protect modules you don't want any changes to or just to skip modules +that don't define any infrastructure by themselves. When set to true, all terragrunt commands will skip the selected module. Consider the following file structure: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx new/terragrunt-0.82.4/docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx --- old/terragrunt-0.82.3/docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx 2025-07-02 15:24:07.000000000 +0200 @@ -405,6 +405,7 @@ - `external_id` - (Optional) The external ID to use when assuming the role. - `session_name` - (Optional) The session name to use when assuming the role. - `dynamodb_table` - (Optional) The name of a DynamoDB table to use for state locking and consistency. The table must have a primary key named LockID. If not present, locking will be disabled. +- `use_lockfile` - (Optional) When `true`, enables native S3 locking using S3 object conditional writes for state locking. This feature requires OpenTofu >= 1.10. Can be used simultaneously with `dynamodb_table` during migration (both locks must be acquired successfully), but typically used as a replacement for DynamoDB locking. - `skip_bucket_versioning`: When `true`, the S3 bucket that is created to store the state will not be versioned. - `skip_bucket_ssencryption`: When `true`, the S3 bucket that is created to store the state will not be configured with server-side encryption. - `skip_bucket_accesslogging`: *DEPRECATED* If provided, will be ignored. A log warning will be issued in the console output to notify the user. @@ -555,6 +556,44 @@ } ``` +Example with S3 using native S3 locking (OpenTofu >= 1.10): + +```hcl +# Configure OpenTofu/Terraform state to be stored in S3 with native S3 locking instead of DynamoDB. +# This uses S3 object conditional writes for state locking, which requires OpenTofu >= 1.10. +remote_state { + backend = "s3" + config = { + bucket = "my-tofu-state" + key = "${path_relative_to_include()}/tofu.tfstate" + region = "us-east-1" + encrypt = true + use_lockfile = true + } +} +``` + +Example with S3 using both DynamoDB and native S3 locking during migration (OpenTofu >= 1.10): + +```hcl +# Configure OpenTofu/Terraform state with dual locking during migration from DynamoDB to S3 native locking. +# Both locks must be successfully acquired before operations can proceed. +# After the migration period, remove dynamodb_table to use only S3 native locking. +# Note: This won't delete the DynamoDB table, it will just be unused. +# You can delete it manually after the migration period. +remote_state { + backend = "s3" + config = { + bucket = "my-tofu-state" + key = "${path_relative_to_include()}/tofu.tfstate" + region = "us-east-1" + encrypt = true + dynamodb_table = "my-lock-table" # Remove this after migration period + use_lockfile = true # New native S3 locking + } +} +``` + ### encryption The encryption map needs a `key_provider` property, which can be set to one of `pbkdf2`, `aws_kms` or `gcp_kms`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/go.mod new/terragrunt-0.82.4/go.mod --- old/terragrunt-0.82.3/go.mod 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/go.mod 2025-07-02 15:24:07.000000000 +0200 @@ -10,9 +10,9 @@ github.com/aws/aws-sdk-go v1.55.7 github.com/aws/aws-sdk-go-v2 v1.36.5 github.com/charmbracelet/bubbles v0.21.0 - github.com/charmbracelet/bubbletea v1.3.4 + github.com/charmbracelet/bubbletea v1.3.5 github.com/charmbracelet/glamour v0.8.0 - github.com/charmbracelet/lipgloss v1.1.0 + github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/creack/pty v1.1.24 github.com/fatih/structs v1.1.0 github.com/getsops/sops/v3 v3.10.2 @@ -58,17 +58,17 @@ github.com/terraform-linters/tflint v0.55.0 github.com/urfave/cli/v2 v2.27.7 github.com/zclconf/go-cty v1.16.3 - go.opentelemetry.io/otel v1.36.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 - go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 - go.opentelemetry.io/otel/metric v1.36.0 - go.opentelemetry.io/otel/sdk v1.36.0 - go.opentelemetry.io/otel/sdk/metric v1.36.0 - go.opentelemetry.io/otel/trace v1.36.0 + go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 + go.opentelemetry.io/otel/metric v1.37.0 + go.opentelemetry.io/otel/sdk v1.37.0 + go.opentelemetry.io/otel/sdk/metric v1.37.0 + go.opentelemetry.io/otel/trace v1.37.0 golang.org/x/mod v0.25.0 golang.org/x/oauth2 v0.30.0 golang.org/x/sync v0.15.0 @@ -82,10 +82,11 @@ ) require ( - github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0 github.com/charmbracelet/x/exp/teatest v0.0.0-20250611152503-f53cdd7e01ef github.com/charmbracelet/x/term v0.2.1 github.com/invopop/jsonschema v0.13.0 + github.com/wI2L/jsondiff v0.7.0 github.com/xeipuuv/gojsonschema v1.2.0 go.uber.org/mock v0.5.2 golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b @@ -206,7 +207,7 @@ github.com/gookit/color v1.5.4 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect @@ -268,6 +269,10 @@ github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/terraform-linters/tflint-plugin-sdk v0.22.0 // indirect github.com/terraform-linters/tflint-ruleset-terraform v0.10.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect github.com/ulikunitz/xz v0.5.12 // indirect github.com/urfave/cli v1.22.16 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect @@ -287,7 +292,7 @@ go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect golang.org/x/crypto v0.39.0 // indirect golang.org/x/net v0.41.0 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/go.sum new/terragrunt-0.82.4/go.sum --- old/terragrunt-0.82.3/go.sum 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/go.sum 2025-07-02 15:24:07.000000000 +0200 @@ -837,8 +837,8 @@ github.com/aws/aws-sdk-go-v2/service/rds v1.91.0/go.mod h1:h2jc7IleH3xHY7y+h8FH7WAZcz3IVLOB6/jXotIQ/qU= github.com/aws/aws-sdk-go-v2/service/route53 v1.46.2 h1:wmt05tPp/CaRZpPV5B4SaJ5TwkHKom07/BzHoLdkY1o= github.com/aws/aws-sdk-go-v2/service/route53 v1.46.2/go.mod h1:d+K9HESMpGb1EU9/UmmpInbGIUcAkwmcY6ZO/A3zZsw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0 h1:1GmCadhKR3J2sMVKs2bAYq9VnwYeCqfRyZzD4RASGlA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.81.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0 h1:JubM8CGDDFaAOmBrd8CRYNr49ZNgEAiLwGwgNMdS0nw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6 h1:1KDMKvOKNrpD667ORbZ/+4OgvUoaok1gg/MLzrHF9fw= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6/go.mod h1:DmtyfCfONhOyVAJ6ZMTrDSFIeyCBlEO93Qkfhxwbxu0= github.com/aws/aws-sdk-go-v2/service/sns v1.33.6 h1:lEUtRHICiXsd7VRwRjXaY7MApT2X4Ue0Mrwe6XbyBro= @@ -896,14 +896,14 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= -github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI= -github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo= +github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc= +github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54= github.com/charmbracelet/colorprofile v0.3.0 h1:KtLh9uuu1RCt+Hml4s6Hz+kB1PfV3wi++1h5ia65yKQ= github.com/charmbracelet/colorprofile v0.3.0/go.mod h1:oHJ340RS2nmG1zRGPmhJKJ/jf4FPNNk0P39/wBPA1G0= github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= +github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= @@ -1236,8 +1236,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0 h1:+epNPbD5EqgpEMm5wrl4Hqts3jZt8+kYaqUisuuIGTk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/gruntwork-io/boilerplate v0.6.3 h1:c7gdVH4U/z5ReK9+iCN2v/+MXnsWo6fLTrPiKqL+VQs= github.com/gruntwork-io/boilerplate v0.6.3/go.mod h1:eAuKtP/udtyVE0gSQWUzpKK+JpHzFS9th6bhlR8OqhA= github.com/gruntwork-io/go-commons v0.17.2 h1:14dsCJ7M5Vv2X3BIPKeG9Kdy6vTMGhM8L4WZazxfTuY= @@ -1689,6 +1689,16 @@ github.com/terraform-linters/tflint-plugin-sdk v0.22.0/go.mod h1:Cag3YJjBpHdQzI/limZR+Cj7WYPLTIE61xsCdIXoeUI= github.com/terraform-linters/tflint-ruleset-terraform v0.10.0 h1:L+3K3oGvZe5UdQ9F6PMQ6n69A2+Q11dBSg+5nTvxJi8= github.com/terraform-linters/tflint-ruleset-terraform v0.10.0/go.mod h1:wT8nMRBpCg1cIL0Td3LQ3XPcnTTHwBhbCNrFp4jWFrI= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tombuildsstuff/giovanni v0.15.1/go.mod h1:0TZugJPEtqzPlMpuJHYfXY6Dq2uLPrXf98D2XQSxNbA= github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= @@ -1711,6 +1721,8 @@ github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ= +github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= @@ -1774,30 +1786,30 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0 h1:zwdo1gS2eH26Rg+CoqVQpEK1h8gvt5qyU5Kk5Bixvow= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.36.0/go.mod h1:rUKCPscaRWWcqGT6HnEmYrK+YNe5+Sw64xgQTOJ5b30= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0 h1:gAU726w9J8fwr4qRDqu1GYMNNs4gXrU+Pv20/N1UpB4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0/go.mod h1:RboSDkp7N292rgu+T0MgVt2qgFGu6qa1RpZDOtpL76w= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0 h1:JgtbA0xkWHnTmYk7YusopJFX6uleBmAuZ8n05NEh8nQ= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.36.0/go.mod h1:179AK5aar5R3eS9FucPy6rggvU0g52cvKId8pv4+v0c= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0 h1:9PgnL3QNlj10uGxExowIDIZu66aVBwWhXmbOp1pa6RA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.37.0/go.mod h1:0ineDcLELf6JmKfuo0wvvhAVMuxWFYvkTin2iV4ydPQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/internal/cas/cas.go new/terragrunt-0.82.4/internal/cas/cas.go --- old/terragrunt-0.82.3/internal/cas/cas.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/internal/cas/cas.go 2025-07-02 15:24:07.000000000 +0200 @@ -63,6 +63,10 @@ opts.StorePath = filepath.Join(home, ".cache", "terragrunt", "cas", "store") } + if err := os.MkdirAll(opts.StorePath, DefaultDirPerms); err != nil { + return nil, fmt.Errorf("failed to create CAS store path: %w", err) + } + store := NewStore(opts.StorePath) git, err := NewGitRunner() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/internal/remotestate/backend/s3/backend_test.go new/terragrunt-0.82.4/internal/remotestate/backend/s3/backend_test.go --- old/terragrunt-0.82.3/internal/remotestate/backend/s3/backend_test.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/internal/remotestate/backend/s3/backend_test.go 2025-07-02 15:24:07.000000000 +0200 @@ -138,6 +138,56 @@ }, true, }, + { + "use-lockfile-native-s3-locking", + backend.Config{ + "bucket": "foo", + "key": "bar", + "region": "us-east-1", + "use_lockfile": true, + }, + map[string]any{ + "bucket": "foo", + "key": "bar", + "region": "us-east-1", + "use_lockfile": true, + }, + true, + }, + { + "use-lockfile-false", + backend.Config{ + "bucket": "foo", + "key": "bar", + "region": "us-east-1", + "use_lockfile": false, + }, + map[string]any{ + "bucket": "foo", + "key": "bar", + "region": "us-east-1", + "use_lockfile": false, + }, + true, + }, + { + "dual-locking-dynamodb-and-s3", + backend.Config{ + "bucket": "foo", + "key": "bar", + "region": "us-east-1", + "dynamodb_table": "my-lock-table", + "use_lockfile": true, + }, + map[string]any{ + "bucket": "foo", + "key": "bar", + "region": "us-east-1", + "dynamodb_table": "my-lock-table", + "use_lockfile": true, + }, + true, + }, } for _, tc := range testCases { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/internal/remotestate/backend/s3/remote_state_config.go new/terragrunt-0.82.4/internal/remotestate/backend/s3/remote_state_config.go --- old/terragrunt-0.82.3/internal/remotestate/backend/s3/remote_state_config.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/internal/remotestate/backend/s3/remote_state_config.go 2025-07-02 15:24:07.000000000 +0200 @@ -189,6 +189,7 @@ AssumeRole RemoteStateConfigS3AssumeRole `mapstructure:"assume_role"` Encrypt bool `mapstructure:"encrypt"` S3ForcePathStyle bool `mapstructure:"force_path_style"` + UseLockfile bool `mapstructure:"use_lockfile"` } // CacheKey returns a unique key for the given S3 config that can be used to cache the initialization diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/telemetry/meter.go new/terragrunt-0.82.4/telemetry/meter.go --- old/terragrunt-0.82.3/telemetry/meter.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/telemetry/meter.go 2025-07-02 15:24:07.000000000 +0200 @@ -16,7 +16,7 @@ "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + semconv "go.opentelemetry.io/otel/semconv/v1.34.0" ) const ( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/telemetry/tracer.go new/terragrunt-0.82.4/telemetry/tracer.go --- old/terragrunt-0.82.3/telemetry/tracer.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/telemetry/tracer.go 2025-07-02 15:24:07.000000000 +0200 @@ -16,7 +16,7 @@ "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + semconv "go.opentelemetry.io/otel/semconv/v1.34.0" "go.opentelemetry.io/otel/trace" ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/test/fixtures/s3-backend/dual-locking/terragrunt.hcl new/terragrunt-0.82.4/test/fixtures/s3-backend/dual-locking/terragrunt.hcl --- old/terragrunt-0.82.3/test/fixtures/s3-backend/dual-locking/terragrunt.hcl 1970-01-01 01:00:00.000000000 +0100 +++ new/terragrunt-0.82.4/test/fixtures/s3-backend/dual-locking/terragrunt.hcl 2025-07-02 15:24:07.000000000 +0200 @@ -0,0 +1,22 @@ +# Configure OpenTofu/Terraform state to be stored in S3 with DUAL locking (migration scenario) +# This uses both DynamoDB and S3 native locking simultaneously +# Both locks must be successfully acquired before operations can proceed +remote_state { + backend = "s3" + generate = { + path = "backend.tf" + if_exists = "overwrite" + } + config = { + bucket = "__FILL_IN_BUCKET_NAME__" + key = "dual-locking/terraform.tfstate" + region = "__FILL_IN_REGION__" + encrypt = true + dynamodb_table = "__FILL_IN_LOCK_TABLE_NAME__" # Traditional DynamoDB locking + use_lockfile = true # New S3 native locking + } +} + +terraform { + source = "tfr://registry.terraform.io/yorinasub17/terragrunt-registry-test/null//modules/one?version=0.0.2" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/test/fixtures/s3-backend/use-lockfile/terragrunt.hcl new/terragrunt-0.82.4/test/fixtures/s3-backend/use-lockfile/terragrunt.hcl --- old/terragrunt-0.82.3/test/fixtures/s3-backend/use-lockfile/terragrunt.hcl 1970-01-01 01:00:00.000000000 +0100 +++ new/terragrunt-0.82.4/test/fixtures/s3-backend/use-lockfile/terragrunt.hcl 2025-07-02 15:24:07.000000000 +0200 @@ -0,0 +1,20 @@ +# Configure OpenTofu/Terraform state to be stored in S3 with native S3 locking +# This uses S3 object conditional writes for state locking, which requires OpenTofu >= 1.10 +remote_state { + backend = "s3" + generate = { + path = "backend.tf" + if_exists = "overwrite" + } + config = { + bucket = "__FILL_IN_BUCKET_NAME__" + key = "use-lockfile/terraform.tfstate" + region = "__FILL_IN_REGION__" + encrypt = true + use_lockfile = true + } +} + +terraform { + source = "tfr://registry.terraform.io/yorinasub17/terragrunt-registry-test/null//modules/one?version=0.0.2" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/test/helpers/package.go new/terragrunt-0.82.4/test/helpers/package.go --- old/terragrunt-0.82.3/test/helpers/package.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/test/helpers/package.go 2025-07-02 15:24:07.000000000 +0200 @@ -73,6 +73,8 @@ allPermissions = 0777 caKeyBits = 4096 + + semverPartsLen = 3 ) type TerraformOutput struct { @@ -208,39 +210,87 @@ t.Logf("Deleting test s3 bucket %s", bucketName) - out, err := client.ListObjectVersions(&s3.ListObjectVersionsInput{Bucket: aws.String(bucketName)}) - if err != nil { - t.Logf("Failed to list object versions in s3 bucket %s: %v", bucketName, err) - return err - } + cleanS3Bucket(t, client, bucketName) - objectIdentifiers := []*s3.ObjectIdentifier{} - for _, version := range out.Versions { - objectIdentifiers = append(objectIdentifiers, &s3.ObjectIdentifier{ - Key: version.Key, - VersionId: version.VersionId, - }) - } + if _, err := client.DeleteBucket(&s3.DeleteBucketInput{Bucket: aws.String(bucketName)}); err != nil { + t.Logf("Failed to delete S3 bucket %s: %v", bucketName, err) - if len(objectIdentifiers) > 0 { - deleteInput := &s3.DeleteObjectsInput{ - Bucket: aws.String(bucketName), - Delete: &s3.Delete{Objects: objectIdentifiers}, - } - if _, err := client.DeleteObjects(deleteInput); err != nil { - t.Logf("Error deleting all versions of all objects in bucket %s: %v", bucketName, err) + // If the bucket is not empty, try to clean it again before deleting it. + // This is a workaround for a race condition in eventual consistency. + // Sleep for a little bit first to give the bucket a chance to be ready. + time.Sleep(1 * time.Second) + + cleanS3Bucket(t, client, bucketName) + + if _, err = client.DeleteBucket(&s3.DeleteBucketInput{Bucket: aws.String(bucketName)}); err != nil { + t.Logf("Failed to delete S3 bucket %s: %v", bucketName, err) return err } - } - if _, err := client.DeleteBucket(&s3.DeleteBucketInput{Bucket: aws.String(bucketName)}); err != nil { - t.Logf("Failed to delete S3 bucket %s: %v", bucketName, err) return err } return nil } +func cleanS3Bucket(t *testing.T, client *s3.S3, bucketName string) { + t.Helper() + + t.Logf("Cleaning S3 bucket %s", bucketName) + + // Use pagination to handle large numbers of objects/versions + versionsInput := &s3.ListObjectVersionsInput{Bucket: aws.String(bucketName)} + + for { + out, err := client.ListObjectVersions(versionsInput) + require.NoError(t, err) + + objectIdentifiers := []*s3.ObjectIdentifier{} + + // Handle delete markers (created when versioned objects are deleted) + for _, deleteMarker := range out.DeleteMarkers { + objectIdentifiers = append(objectIdentifiers, &s3.ObjectIdentifier{ + Key: deleteMarker.Key, + VersionId: deleteMarker.VersionId, + }) + } + + // Handle object versions + for _, version := range out.Versions { + objectIdentifiers = append(objectIdentifiers, &s3.ObjectIdentifier{ + Key: version.Key, + VersionId: version.VersionId, + }) + } + + // Delete objects in batches (AWS limit is 1000 per request) + if len(objectIdentifiers) > 0 { + const maxBatchSize = 1000 + for i := 0; i < len(objectIdentifiers); i += maxBatchSize { + end := min(i+maxBatchSize, len(objectIdentifiers)) + + batch := objectIdentifiers[i:end] + deleteInput := &s3.DeleteObjectsInput{ + Bucket: aws.String(bucketName), + Delete: &s3.Delete{Objects: batch}, + } + + _, err := client.DeleteObjects(deleteInput) + require.NoError(t, err) + } + } + + // Check if there are more objects to process (pagination) + if out.IsTruncated == nil || !*out.IsTruncated { + break + } + + // Set up for next page + versionsInput.KeyMarker = out.NextKeyMarker + versionsInput.VersionIdMarker = out.NextVersionIdMarker + } +} + func FileIsInFolder(t *testing.T, name string, path string) bool { t.Helper() @@ -669,7 +719,9 @@ } // IsTerraform110OrHigher checks if the installed Terraform binary is version 1.10.0 or higher. -func IsTerraform110OrHigher() bool { +func IsTerraform110OrHigher(t *testing.T) bool { + t.Helper() + const ( requiredMajor = 1 requiredMinor = 10 @@ -680,18 +732,63 @@ } output, err := exec.Command(WrappedBinary(), "-version").Output() - if err != nil { - return false - } + require.NoError(t, err) matches := regexp.MustCompile(`Terraform v(\d+)\.(\d+)\.`).FindStringSubmatch(string(output)) + require.Len(t, matches, semverPartsLen, "Expected Terraform version to be in the format 'Terraform v1.10.0'") - major, _ := strconv.Atoi(matches[1]) - minor, _ := strconv.Atoi(matches[2]) + major, err := strconv.Atoi(matches[1]) + require.NoError(t, err) + + minor, err := strconv.Atoi(matches[2]) + require.NoError(t, err) return major > requiredMajor || (major == requiredMajor && minor >= requiredMinor) } +// IsNativeS3LockingSupported checks if the installed Terraform binary supports native S3 locking. +// This is the case when using Terraform 1.11 or higher, or using OpenTofu 1.10 or higher. +func IsNativeS3LockingSupported(t *testing.T) bool { + t.Helper() + + const ( + terraformRequiredMajor = 1 + terraformRequiredMinor = 11 + tofuRequiredMajor = 1 + tofuRequiredMinor = 10 + ) + + if IsTerraform() { + output, err := exec.Command(TerraformBinary, "-version").Output() + require.NoError(t, err) + + matches := regexp.MustCompile(`Terraform v(\d+)\.(\d+)\.`).FindStringSubmatch(string(output)) + require.Len(t, matches, semverPartsLen, "Expected Terraform version to be in the format 'Terraform v1.10.0'") + + major, err := strconv.Atoi(matches[1]) + require.NoError(t, err) + + minor, err := strconv.Atoi(matches[2]) + require.NoError(t, err) + + return major > terraformRequiredMajor || (major == terraformRequiredMajor && minor >= terraformRequiredMinor) + } + + output, err := exec.Command(TofuBinary, "-version").Output() + require.NoError(t, err) + + matches := regexp.MustCompile(`OpenTofu v(\d+)\.(\d+)\.`).FindStringSubmatch(string(output)) + require.Len(t, matches, semverPartsLen, "Expected OpenTofu version to be in the format 'OpenTofu v1.10.0'") + + major, err := strconv.Atoi(matches[1]) + require.NoError(t, err) + + minor, err := strconv.Atoi(matches[2]) + require.NoError(t, err) + + return major > tofuRequiredMajor || (major == tofuRequiredMajor && minor >= tofuRequiredMinor) +} + func FindFilesWithExtension(dir string, ext string) ([]string, error) { var files []string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/test/integration_aws_test.go new/terragrunt-0.82.4/test/integration_aws_test.go --- old/terragrunt-0.82.3/test/integration_aws_test.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/test/integration_aws_test.go 2025-07-02 15:24:07.000000000 +0200 @@ -49,6 +49,8 @@ testFixtureOutputFromRemoteState = "fixtures/output-from-remote-state" testFixtureOutputFromDependency = "fixtures/output-from-dependency" testFixtureS3Backend = "fixtures/s3-backend" + testFixtureS3BackendDualLocking = "fixtures/s3-backend/dual-locking" + testFixtureS3BackendUseLockfile = "fixtures/s3-backend/use-lockfile" testFixtureAssumeRoleWithExternalIDWithComma = "fixtures/assume-role/external-id-with-comma" qaMyAppRelPath = "qa/my-app" @@ -116,10 +118,12 @@ s3BucketName := "terragrunt-test-bucket-" + testID dynamoDBName := "terragrunt-test-dynamodb-" + testID - defer func() { - deleteS3Bucket(t, s3BucketName, helpers.TerraformRemoteStateS3Region) - cleanupTableForTest(t, dynamoDBName, helpers.TerraformRemoteStateS3Region) - }() + if tc.name != "no bootstrap s3 backend without flag" { + defer func() { + deleteS3Bucket(t, helpers.TerraformRemoteStateS3Region, s3BucketName) + cleanupTableForTest(t, dynamoDBName, helpers.TerraformRemoteStateS3Region) + }() + } commonConfigPath := util.JoinPath(rootPath, "common.hcl") helpers.CopyTerragruntConfigAndFillPlaceholders(t, commonConfigPath, commonConfigPath, s3BucketName, dynamoDBName, helpers.TerraformRemoteStateS3Region) @@ -145,7 +149,7 @@ dynamoDBName := "terragrunt-test-dynamodb-" + testID defer func() { - deleteS3Bucket(t, s3BucketName, helpers.TerraformRemoteStateS3Region) + deleteS3Bucket(t, helpers.TerraformRemoteStateS3Region, s3BucketName) cleanupTableForTest(t, dynamoDBName, helpers.TerraformRemoteStateS3Region) }() @@ -175,6 +179,91 @@ assert.NotContains(t, stderr, "Use the explicit `--backend-bootstrap` flag to automatically provision backend resources before they're needed.") } +func TestAwsDualLockingBackend(t *testing.T) { + t.Parallel() + + if !helpers.IsNativeS3LockingSupported(t) { + t.Skip("Wrapped binary does not support native S3 locking") + return + } + + helpers.CleanupTerraformFolder(t, testFixtureS3BackendDualLocking) + tmpEnvPath := helpers.CopyEnvironment(t, testFixtureS3BackendDualLocking) + rootPath := util.JoinPath(tmpEnvPath, testFixtureS3BackendDualLocking) + + testID := strings.ToLower(helpers.UniqueID()) + + s3BucketName := "terragrunt-test-bucket-" + testID + dynamoDBName := "terragrunt-test-dynamodb-" + testID + + defer func() { + deleteS3Bucket(t, helpers.TerraformRemoteStateS3Region, s3BucketName) + cleanupTableForTest(t, dynamoDBName, helpers.TerraformRemoteStateS3Region) + }() + + terragruntConfigPath := util.JoinPath(rootPath, "terragrunt.hcl") + helpers.CopyTerragruntConfigAndFillPlaceholders(t, terragruntConfigPath, terragruntConfigPath, s3BucketName, dynamoDBName, helpers.TerraformRemoteStateS3Region) + + // Test backend bootstrap with dual locking + stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run apply --backend-bootstrap --non-interactive --log-level debug --working-dir "+rootPath+" -- -auto-approve") + require.NoError(t, err) + + // Validate both S3 bucket and DynamoDB table are created + validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, true, nil) + validateDynamoDBTableExistsAndIsTaggedAndIsSSEncrypted(t, helpers.TerraformRemoteStateS3Region, dynamoDBName, nil, false) + + t.Logf("Dual locking test completed successfully. Output: %s, Errors: %s", stdout, stderr) + + // Test that subsequent runs work with dual locking (both locks should be acquired) + stdout2, stderr2, err2 := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run plan --non-interactive --log-level debug --working-dir "+rootPath) + require.NoError(t, err2) + + t.Logf("Dual locking plan test completed successfully. Output: %s, Errors: %s", stdout2, stderr2) +} + +func TestAwsNativeS3LockingBackend(t *testing.T) { + t.Parallel() + + if !helpers.IsNativeS3LockingSupported(t) { + t.Skip("Wrapped binary does not support native S3 locking") + return + } + + helpers.CleanupTerraformFolder(t, testFixtureS3BackendUseLockfile) + tmpEnvPath := helpers.CopyEnvironment(t, testFixtureS3BackendUseLockfile) + rootPath := util.JoinPath(tmpEnvPath, testFixtureS3BackendUseLockfile) + + testID := strings.ToLower(helpers.UniqueID()) + + s3BucketName := "terragrunt-test-bucket-" + testID + // Note: No DynamoDB table needed for native S3 locking + + defer func() { + deleteS3Bucket(t, helpers.TerraformRemoteStateS3Region, s3BucketName) + // Note: No DynamoDB cleanup needed for S3 native locking + }() + + terragruntConfigPath := util.JoinPath(rootPath, "terragrunt.hcl") + helpers.CopyTerragruntConfigAndFillPlaceholders(t, terragruntConfigPath, terragruntConfigPath, s3BucketName, "unused-dynamodb-name", helpers.TerraformRemoteStateS3Region) + + // Test backend bootstrap with S3 native locking only + stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run apply --backend-bootstrap --non-interactive --log-level debug --working-dir "+rootPath+" -- -auto-approve") + require.NoError(t, err) + + // Validate S3 bucket is created and versioned + validateS3BucketExistsAndIsTaggedAndVersioning(t, helpers.TerraformRemoteStateS3Region, s3BucketName, true, nil) + + // Note: No DynamoDB table validation - S3 native locking doesn't use DynamoDB + + t.Logf("S3 native locking test completed successfully. Output: %s, Errors: %s", stdout, stderr) + + // Test that subsequent runs work with S3 native locking only + stdout2, stderr2, err2 := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run plan --non-interactive --log-level debug --working-dir "+rootPath) + require.NoError(t, err2) + + t.Logf("S3 native locking plan test completed successfully. Output: %s, Errors: %s", stdout2, stderr2) +} + func TestAwsBootstrapBackendWithoutVersioning(t *testing.T) { t.Parallel() @@ -188,7 +277,7 @@ dynamoDBName := "terragrunt-test-dynamodb-" + testID defer func() { - deleteS3Bucket(t, s3BucketName, helpers.TerraformRemoteStateS3Region) + deleteS3Bucket(t, helpers.TerraformRemoteStateS3Region, s3BucketName) cleanupTableForTest(t, dynamoDBName, helpers.TerraformRemoteStateS3Region) }() @@ -222,8 +311,8 @@ dynamoDBName := "terragrunt-test-dynamodb-" + testID defer func() { - deleteS3Bucket(t, s3BucketName, helpers.TerraformRemoteStateS3Region) - deleteS3Bucket(t, s3AccessLogsBucketName, helpers.TerraformRemoteStateS3Region) + deleteS3Bucket(t, helpers.TerraformRemoteStateS3Region, s3BucketName) + deleteS3Bucket(t, helpers.TerraformRemoteStateS3Region, s3AccessLogsBucketName) cleanupTableForTest(t, dynamoDBName, helpers.TerraformRemoteStateS3Region) }() @@ -252,7 +341,7 @@ dynamoDBName := "terragrunt-test-dynamodb-" + testID defer func() { - deleteS3Bucket(t, s3BucketName, helpers.TerraformRemoteStateS3Region) + deleteS3Bucket(t, helpers.TerraformRemoteStateS3Region, s3BucketName) cleanupTableForTest(t, dynamoDBName, helpers.TerraformRemoteStateS3Region) }() @@ -1760,7 +1849,7 @@ } // createS3Bucket create test S3 bucket. -func createS3Bucket(t *testing.T, awsRegion string, bucketName string) { +func createS3Bucket(t *testing.T, awsRegion, bucketName string) { t.Helper() client := helpers.CreateS3ClientForTest(t, awsRegion) @@ -1771,7 +1860,7 @@ require.NoError(t, err, "Failed to create S3 bucket") } -func deleteS3Bucket(t *testing.T, bucketName string, awsRegion string) { +func deleteS3Bucket(t *testing.T, awsRegion, bucketName string) { t.Helper() helpers.DeleteS3Bucket(t, awsRegion, bucketName) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/test/integration_find_test.go new/terragrunt-0.82.4/test/integration_find_test.go --- old/terragrunt-0.82.3/test/integration_find_test.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/test/integration_find_test.go 2025-07-02 15:24:07.000000000 +0200 @@ -11,6 +11,7 @@ "github.com/gruntwork-io/terragrunt/test/helpers" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/wI2L/jsondiff" ) const ( @@ -156,7 +157,7 @@ assert.Empty(t, stderr) if strings.Contains(tc.args, "--json") { - assert.JSONEq(t, tc.expected, stdout) + jsonStringsEqual(t, tc.expected, stdout) } else { assert.Equal(t, tc.expected, stdout) } @@ -164,6 +165,17 @@ } } +// jsonStringsEqual compares two JSON strings for equivalence, ignoring the order of nested arrays. +func jsonStringsEqual(t *testing.T, expected, actual string, msgAndArgs ...interface{}) bool { + t.Helper() + + patch, err := jsondiff.CompareJSON([]byte(expected), []byte(actual), jsondiff.Equivalent()) + require.NoErrorf(t, err, fmt.Sprintf("Error comparing JSON strings: %v", err), msgAndArgs...) + require.Emptyf(t, patch, fmt.Sprintf("JSON strings are not equal\nExpected: %s\nActual: %s", expected, actual), msgAndArgs...) + + return true +} + func TestFindExternalDependencies(t *testing.T) { t.Parallel() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/terragrunt-0.82.3/test/integration_test.go new/terragrunt-0.82.4/test/integration_test.go --- old/terragrunt-0.82.3/test/integration_test.go 2025-06-27 15:59:16.000000000 +0200 +++ new/terragrunt-0.82.4/test/integration_test.go 2025-07-02 15:24:07.000000000 +0200 @@ -4102,7 +4102,7 @@ func TestTF110EphemeralVars(t *testing.T) { t.Parallel() - if !helpers.IsTerraform110OrHigher() { + if !helpers.IsTerraform110OrHigher(t) { t.Skip("This test requires Terraform 1.10 or higher") return ++++++ terragrunt.obsinfo ++++++ --- /var/tmp/diff_new_pack.xwPnAr/_old 2025-07-06 17:08:45.841076069 +0200 +++ /var/tmp/diff_new_pack.xwPnAr/_new 2025-07-06 17:08:45.849076400 +0200 @@ -1,5 +1,5 @@ name: terragrunt -version: 0.82.3 -mtime: 1751032756 -commit: a31ef0943c02cd68b648c81a0d1ed037a57e24e9 +version: 0.82.4 +mtime: 1751462647 +commit: 34af1f1518c336c96623fd3c6eb63c53b9afa7eb ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/terragrunt/vendor.tar.gz /work/SRC/openSUSE:Factory/.terragrunt.new.1903/vendor.tar.gz differ: char 13, line 1