Repository: gruntwork-io/terragrunt

Issue:
Native Terraform/OpenTofu function templatestring() does not work in Terragrunt as a builtin function.
## Describe the bug

A new function templatestring() was added in Terraform 1.9 and Opentofu 1.7. This New function does not work when attempting to use in terragrunt as a built-in function https://terragrunt.gruntwork.io/docs/reference/built-in-functions/#terraform-built-in-functions, both when using the Terraform binary and the OpenTofu binary.

## Steps To Reproduce

Define locals block that uses function [templatestring()](https://developer.hashicorp.com/terraform/language/functions/fileset).

example/terragrunt.hcl:
```hcl
locals {
  template = "$${stage}-test"
  name = templatestring(local.template, { stage = "staging" } )
}

inputs = {
  name = local.name
}
```

example/main.tf:
```hcl
variable "name" {}

resource "aws_sqs_queue" "example" {
  name  = var.name
}
```

## Expected behavior

Here is the expected behaviour

```
$ terragrunt init

OpenTofu has been successfully initialized!
```

Here is the actual behaviour

```
$ terragrunt init

ERRO[0000]   on /.../example/terragrunt.hcl line 7, in locals: 
ERRO[0000]    7:   name     = templatestring(local.template, { stage = "staging" }) 
ERRO[0000]                                              
ERRO[0000] with                                         
ERRO[0000] local.template as "${stage}-test"  

ERRO[0000] There is no function named "templatestring".
```

## Versions

- Terragrunt version: v0.59.6
- OpenTofu Version: v1.7.2
- Terraform version: v1.9.2

## Additional context

Function works properly when defined directly in .tf file using opentofu/terraform 

example/main.tf:
```hcl
locals {
  template = "$${stage}-test"
  name = templatestring(local.template, { stage = "staging" } )
}

resource "aws_sqs_queue" "test" {
  name = local.name
}
```

```
$ terragrunt init

OpenTofu has been successfully initialized!
```


Discussion:
@begemotik: @levkohimins would be wonderful to get it supported in Terragrunt. Do you have an estimation?@yhakbar: Hey @begemotik , this is a good request, but not a priority right now.

Could you provide any reasons why this should be prioritized higher? I imagine most folks needing this functionality would simply use `run_cmd` right now.@Jpk518: > Hey @begemotik , this is a good request, but not a priority right now.
> 
> Could you provide any reasons why this should be prioritized higher? I imagine most folks needing this functionality would simply use `run_cmd` right now.

Is it possible to update the documentation if going forward Terragrunt doesn't plan to support all built in Opentofu/Terraform functions. Can lead to a little confusion https://terragrunt.gruntwork.io/docs/reference/built-in-functions/#opentofuterraform-built-in-functions@yhakbar: > Is it possible to update the documentation if going forward Terragrunt doesn't plan to support all built in Opentofu/Terraform functions.

Sure thing, I can update that. 

To be clear, we're not refusing to support this, we're just trying to prioritize effectively. If there's a use-case that requires this behavior, we can look at prioritizing it.@begemotik: > Could you provide any reasons why this should be prioritized higher? I imagine most folks needing this functionality would simply use run_cmd right now.

We manage multiple ASGs within one TF state and need to be able to re-use one config template for different cases.
There is another case I can imagine with "generate" block and dynamic providers rendering.

We haven't tried `run_cmd`, I might be mistaken but it occurs to be that `run_cmd` would be [hard to re-use across many states as it references local file.](https://terragrunt.gruntwork.io/docs/reference/built-in-functions/#run_cmd)
> run_cmd(command, arg1, arg2…​) runs a shell command and returns the stdout as the result of the interpolation. The command is executed at the same folder as the terragrunt.hcl file@ThisGuyCodes: We're a current Gruntwork subscriber and we have a need for this now.

We have a template string defined in an `_envcommon` module, and the variables to flesh it out are defined in the downstream instances. Ideally we could use `templatestring` to make the downstream use look like this:
```terraform
templatestring(include.envcommon.locals.template, {
  env   = local.env
  other = local.other
})
```

Without this we're using `format`, which requires some ugly and error prone repetition:
```terraform
format(include.envcommon.locals.template,
  local.env,
  local.other,
  local.env,
  local.other,
  local.env,
  local.other,
)
```

`templatestring` would allow us to use the same values in the template multiple times, and allow for a keyword based input rather than positional.@yhakbar: Hey @ThisGuyCodes ,

I see that you do have a very practical need for `templatestring`, and I'll share some thoughts regarding the challenge of supporting it.

## There is a workaround

For basically any gap in HCL functionality that you might have, there is the escape hatch of [run_cmd](https://terragrunt.gruntwork.io/docs/reference/built-in-functions/#run_cmd). It'll let you execute arbitrary commands in order to get some value that you need, so you can write-up a bash script, use something like envsubst or [boilerplate](https://github.com/gruntwork-io/boilerplate) to template out strings that you might need.

I know this is not always an optimal solution, but we are a little constrained otherwise.

## We cannot use OpenTofu/Terraform built-in functions as a library

This might change in the future for OpenTofu, but right now, implementations for OpenTofu HCL functions are in `internal`, and thus not exported for re-use outside the project. As a result, the best we can do is implement something net-new on our own for now.

## Building an alternative to `templatestring` in Terragrunt isn't high priority right now

The availability of a different function that is very explicitly not `templatestring` (e.g. `template_to_string`) in Terragrunt is something that users have workarounds for, as you mentioned with `templatestring` and `run_cmd` for more advanced cases.

If we were to introduce a net-new function with novel implementation for Terragrunt, it would be in collaboration with the community to work out the right API, figure out the best patterns for use in Terragrunt and drive implementation.

I would also point out that we _wouldn't_ want to implement anything too close to `templatestring`, as:
1. The `templatestring` function might change in the future, and we wouldn't want to try to preserve any kind of parity there.
2. The OpenTofu team might decide to export their HCL functions as a library, which we would wholeheartedly integrate with high priority.

Hopefully this gives insight as to the complications inherent to this issue.@ThisGuyCodes: Thanks for that suggestion @yhakbar. I did actually go down this path a bit for a few things, but ran into a blocking bug (or maybe undesired behavior?), which breaks pipelines: https://github.com/gruntwork-io/terragrunt/issues/3850@yhakbar: @ThisGuyCodes 

Take a look at my response on the issue. Let me know if that helps@github-actions: This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for raising this issue.

Solution patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 212d5af3..556c512e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -543,6 +543,17 @@ jobs:
           root: .
           paths: [bin]
 
+  test_build_no_proxy:
+    # build Terragrunt without Go proxy to ensure all dependencies have correct checksums
+    resource_class: xlarge
+    <<: *defaults
+    steps:
+      - checkout
+      - run:
+          name: Building Terragrunt without Go proxy
+          command: |
+            GOPROXY=direct build-go-binaries --app-name terragrunt --dest-path bin --ld-flags "-X github.com/gruntwork-io/go-commons/version.Version=$CIRCLE_TAG -extldflags '-static'"
+
   test_signing:
     <<: *env
     macos:
@@ -806,6 +817,14 @@ workflows:
             - GCP__automated-tests
             - GITHUB__PAT__gruntwork-ci
             - APPLE__OSX__code-signing
+      - test_build_no_proxy:
+          filters:
+            tags:
+              only: /^v.*/
+          context:
+            - AWS__PHXDEVOPS__circle-ci-test
+            - GCP__automated-tests
+            - GITHUB__PAT__gruntwork-ci
       - deploy:
           requires:
             - build
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..0fe61bb2
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+
+  - package-ecosystem: "gomod"
+    directory: "/"
+    schedule:
+      interval: "weekly"
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index 7be9bdcc..7155f2a7 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -28,7 +28,7 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
       - name: Setup Ruby
-        uses: ruby/setup-ruby@8575951200e472d5f2d95c625da0c7bec8217c42 # v1.161.0
+        uses: ruby/setup-ruby@f26937343756480a8cb3ae1f623b9c8d89ed6984 # v1.196.0
         with:
           ruby-version: '3.3'
           bundler-cache: true
diff --git a/config/config_helpers.go b/config/config_helpers.go
index f8e4a9fc..f225670c 100644
--- a/config/config_helpers.go
+++ b/config/config_helpers.go
@@ -1110,7 +1110,7 @@ func extractSopsErrors(err error) *errors.MultiError {
 	}
 
 	// append the original error if no group results were found
-	if errs.Len() == 0 {
+	if errs == nil {
 		errs = errs.Append(err)
 	}
 
diff --git a/docs/_docs/04_reference/built-in-functions.md b/docs/_docs/04_reference/built-in-functions.md
index 73dd43d8..c829cbfe 100644
--- a/docs/_docs/04_reference/built-in-functions.md
+++ b/docs/_docs/04_reference/built-in-functions.md
@@ -43,7 +43,7 @@ Terragrunt allows you to use built-in functions anywhere in `terragrunt.hcl`, ju
 
 ## OpenTofu/Terraform built-in functions
 
-All [OpenTofu/Terraform built-in functions](https://opentofu.org/docs/language/functions/) are supported in Terragrunt config files:
+All [OpenTofu/Terraform built-in functions (as of v0.15.3)](https://opentofu.org/docs/language/functions/) are supported in Terragrunt config files:
 
 ```hcl
 terraform {
@@ -79,6 +79,14 @@ Then `assets.txt` could be read with the following function call:
 file("assets/mysql/assets.txt")
 ```
 
+**Note:**
+
+Terragrunt was originally able to take advantage of built-in OpenTofu/Terraform built-in functions automatically, as they were exposed via an exported package. Since `v0.15.3`, however, these functions are now `internal` to the respective codebases.
+
+As a result, Terragrunt users typically use different functions to resolve the same problems. e.g. Terragrunt users can execute arbitrary shell commands with [run\_cmd](#run_cmd) in whatever language they like instead of using a bespoke HCL function to solve a given problem. In the future, OpenTofu may expose these functions via a public package, which would allow Terragrunt to access them directly. Until such a time, Terragrunt will continue to provide its own set of functions to solve problems relevant to Terragrunt users.
+
+If there is a specific function you would like to see supported directly in Terragrunt, please [open an issue](https://github.com/gruntwork-io/terragrunt/issues) requesting it. Make sure to include the use case you have in mind so we can understand the problem you are trying to solve, and why existing Terragrunt functions are not sufficient.
+
 ## find_in_parent_folders
 
 `find_in_parent_folders()` searches up the directory tree from the current `terragrunt.hcl` file and returns the absolute path to the first `terragrunt.hcl` in a parent folder or exit with an error if no such file is found. This is primarily useful in an `include` block to automatically find the path to a parent `terragrunt.hcl` file:
diff --git a/docs/_docs/04_reference/strict-mode.md b/docs/_docs/04_reference/strict-mode.md
index 91e18f23..b949f3e8 100644
--- a/docs/_docs/04_reference/strict-mode.md
+++ b/docs/_docs/04_reference/strict-mode.md
@@ -33,7 +33,7 @@ $ terragrunt plan-all
 ```
 
 ```bash
-$ TERRAGRUNT_STRICT_MODE='true' tg plan-all
+$ TERRAGRUNT_STRICT_MODE='true' terragrunt plan-all
 15:26:23.685 ERROR  The `plan-all` command is no longer supported. Use `terragrunt run-all plan` instead.
 ```
 
diff --git a/go.mod b/go.mod
index f9153fa2..6ac74088 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/hashicorp/go-safetemp v1.0.0
 	github.com/hashicorp/go-version v1.7.0
-	github.com/hashicorp/hcl/v2 v2.19.1
+	github.com/hashicorp/hcl/v2 v2.22.0
 
 	// Many functions of terraform was converted to internal to avoid use as a library after v0.15.3. This means that we
 	// can't use terraform as a library after v0.15.3, so we pull that in here.
@@ -31,7 +31,7 @@ require (
 	golang.org/x/oauth2 v0.23.0
 	golang.org/x/sync v0.8.0
 	golang.org/x/sys v0.26.0
-	google.golang.org/api v0.200.0
+	google.golang.org/api v0.201.0
 )
 
 require (
@@ -52,7 +52,7 @@ require (
 require (
 	dario.cat/mergo v1.0.1
 	github.com/NYTimes/gziphandler v1.1.1
-	github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton
+	github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton
 	github.com/charmbracelet/bubbles v0.16.1
 	github.com/charmbracelet/bubbletea v0.24.2
 	github.com/charmbracelet/glamour v0.6.0
@@ -63,7 +63,7 @@ require (
 	github.com/google/uuid v1.6.0
 	github.com/gruntwork-io/boilerplate v0.5.19
 	github.com/gruntwork-io/go-commons v0.17.2
-	github.com/gruntwork-io/terragrunt-engine-go v0.0.2
+	github.com/gruntwork-io/terragrunt-engine-go v0.0.4
 	github.com/hashicorp/go-getter/v2 v2.2.1
 	github.com/hashicorp/go-hclog v1.6.3
 	github.com/hashicorp/go-plugin v1.6.1
@@ -205,7 +205,7 @@ require (
 	github.com/hashicorp/go-uuid v1.0.3 // indirect
 	github.com/hashicorp/logutils v1.0.0 // indirect
 	github.com/hashicorp/terraform-registry-address v0.2.0 // indirect
-	github.com/hashicorp/yamux v0.1.1 // indirect
+	github.com/hashicorp/yamux v0.1.2 // indirect
 	github.com/huandu/xstrings v1.5.0 // indirect
 	github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 // indirect
 	github.com/jessevdk/go-flags v1.5.0 // indirect
@@ -269,6 +269,7 @@ require (
 	go.opentelemetry.io/proto/otlp v1.1.0 // indirect
 	golang.org/x/mod v0.21.0 // indirect
 	golang.org/x/net v0.30.0 // indirect
+	golang.org/x/tools v0.26.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
 	google.golang.org/grpc/stats/opentelemetry v0.0.0-20241014145745-ad81c20503be // indirect
diff --git a/go.sum b/go.sum
index 97e71b2c..ecf83505 100644
--- a/go.sum
+++ b/go.sum
@@ -297,8 +297,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
 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.1.0-alpha.5-proton h1:KVBEgU3CJpmzLChnLiSuEyCuhGhcMt3eOST+7A+ckto=
-github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
+github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton h1:ZGewsAoeSirbUS5cO8L0FMQA+iSop9xR1nmFYifDBPo=
+github.com/ProtonMail/go-crypto v1.1.0-beta.0-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
 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=
@@ -714,8 +714,8 @@ github.com/gruntwork-io/boilerplate v0.5.19 h1:C/56sJIlHnheUpZUOhh0uE64BggvS1hRx
 github.com/gruntwork-io/boilerplate v0.5.19/go.mod h1:OkKFuHBdkowrPVzIIiwNSeWc2ju9kQ+LbwVcgkB46SE=
 github.com/gruntwork-io/go-commons v0.17.2 h1:14dsCJ7M5Vv2X3BIPKeG9Kdy6vTMGhM8L4WZazxfTuY=
 github.com/gruntwork-io/go-commons v0.17.2/go.mod h1:zs7Q2AbUKuTarBPy19CIxJVUX/rBamfW8IwuWKniWkE=
-github.com/gruntwork-io/terragrunt-engine-go v0.0.2 h1:fXSFxRf8uvpnhgDVMJ/WjmI3fEOwy7GenMoXBUhgdlc=
-github.com/gruntwork-io/terragrunt-engine-go v0.0.2/go.mod h1:HMv091Kli4te8XUi4QINVbAsQKjDJqa4i9c6akl64Rg=
+github.com/gruntwork-io/terragrunt-engine-go v0.0.4 h1:WjBm5CZek+3cO3d0S3bO8K5Mg3fplBHZ+HjUHD5w4/s=
+github.com/gruntwork-io/terragrunt-engine-go v0.0.4/go.mod h1:SGsdkLf81RsYAikZ/w63sbzPh4AjjQxALKFkTYUTO7E=
 github.com/gruntwork-io/terratest v0.47.2 h1:t6iWwsqJH7Gx0RwXleU/vjc+2c0JXRMdj3DxYXTBssQ=
 github.com/gruntwork-io/terratest v0.47.2/go.mod h1:LnYX8BN5WxUMpDr8rtD39oToSL4CBERWSCusbJ0d/64=
 github.com/hashicorp/aws-sdk-go-base v0.6.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY=
@@ -782,8 +782,8 @@ github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31
 github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
 github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
 github.com/hashicorp/hcl/v2 v2.10.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
-github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
-github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
+github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=
+github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
 github.com/hashicorp/jsonapi v0.0.0-20210420151930-edf82c9774bf/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik=
 github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
 github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
@@ -803,8 +803,8 @@ github.com/hashicorp/vault/api v1.14.0 h1:Ah3CFLixD5jmjusOgm8grfN9M0d+Y8fVR2SW0K
 github.com/hashicorp/vault/api v1.14.0/go.mod h1:pV9YLxBGSz+cItFDd8Ii4G17waWOQ32zVjMWHe/cOqk=
 github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
 github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
-github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
-github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
+github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
+github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -1181,8 +1181,9 @@ github.com/zclconf/go-cty v1.8.3/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUA
 github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
 github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA=
 github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
-github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI=
 github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
+github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
+github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
 github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
 github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc=
 github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
@@ -1582,6 +1583,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
 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=
@@ -1639,8 +1642,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ
 google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
 google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
 google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
-google.golang.org/api v0.200.0 h1:0ytfNWn101is6e9VBoct2wrGDjOi5vn7jw5KtaQgDrU=
-google.golang.org/api v0.200.0/go.mod h1:Tc5u9kcbjO7A8SwGlYj4IiVifJU01UqXtEgDMYmBmV8=
+google.golang.org/api v0.201.0 h1:+7AD9JNM3tREtawRMu8sOjSbb8VYcYXJG/2eEOmfDu0=
+google.golang.org/api v0.201.0/go.mod h1:HVY0FCHVs89xIW9fzf/pBvOEm+OolHa86G/txFezyq4=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
diff --git a/internal/strict/strict.go b/internal/strict/strict.go
index f6cd9d06..8038a8ae 100644
--- a/internal/strict/strict.go
+++ b/internal/strict/strict.go
@@ -9,9 +9,11 @@ package strict
 
 import (
 	"errors"
+	"fmt"
 	"strings"
 
 	"github.com/gruntwork-io/terragrunt/options"
+	"github.com/gruntwork-io/terragrunt/util"
 )
 
 // Control represents a control that can be enabled or disabled in strict mode.
@@ -103,18 +105,39 @@ var StrictControls = Controls{
 	},
 }
 
+// Names returns the names of all strict controls.
+func (controls Controls) Names() []string {
+	names := []string{}
+
+	for name := range controls {
+		names = append(names, name)
+	}
+
+	return names
+}
+
+var (
+	// ErrInvalidStrictControl is returned when an invalid strict control is used.
+	ErrInvalidStrictControl = errors.New("Invalid value(s) used for --strict-control.") //nolint:stylecheck,revive
+)
+
+// ValidateControlNames validates that the given control names are valid.
 func (controls Controls) ValidateControlNames(strictControlNames []string) error {
 	invalidControls := []string{}
+	validControls := controls.Names()
 
 	for _, controlName := range strictControlNames {
-		_, ok := controls[controlName]
-		if !ok {
+		if !util.ListContainsElement(validControls, controlName) {
 			invalidControls = append(invalidControls, controlName)
 		}
 	}
 
 	if len(invalidControls) > 0 {
-		return errors.New("Invalid strict controls: " + strings.Join(invalidControls, ", "))
+		return fmt.Errorf("%w\nInvalid value(s):\n- %s\nAllowed value(s):\n- %s",
+			ErrInvalidStrictControl,
+			strings.Join(invalidControls, "\n- "),
+			strings.Join(validControls, "\n- "),
+		)
 	}
 
 	return nil



Test patch:
diff --git a/test/fixtures/sops-missing/main.tf b/test/fixtures/sops-missing/main.tf
new file mode 100644
index 00000000..e69de29b
diff --git a/test/fixtures/sops-missing/terragrunt.hcl b/test/fixtures/sops-missing/terragrunt.hcl
new file mode 100644
index 00000000..c130ba2f
--- /dev/null
+++ b/test/fixtures/sops-missing/terragrunt.hcl
@@ -0,0 +1,3 @@
+locals {
+  secret_vars = yamldecode(sops_decrypt_file("${get_terragrunt_dir()}/missing.yaml"))
+}
diff --git a/test/integration_sops_test.go b/test/integration_sops_test.go
index d899ef1e..a19cc91f 100644
--- a/test/integration_sops_test.go
+++ b/test/integration_sops_test.go
@@ -12,8 +12,9 @@ import (
 )
 
 const (
-	testFixtureSops       = "fixtures/sops"
-	testFixtureSopsErrors = "fixtures/sops-errors"
+	testFixtureSops        = "fixtures/sops"
+	testFixtureSopsErrors  = "fixtures/sops-errors"
+	testFixtureSopsMissing = "fixtures/sops-missing"
 )
 
 func TestSopsDecryptedCorrectly(t *testing.T) {
@@ -97,3 +98,17 @@ func TestTerragruntLogSopsErrors(t *testing.T) {
 	assert.Contains(t, errorOut, "error decrypting key: [error decrypting key")
 	assert.Contains(t, errorOut, "error base64-decoding encrypted data key: illegal base64 data at input byte")
 }
+
+func TestSopsDecryptOnMissing(t *testing.T) {
+	t.Parallel()
+
+	cleanupTerraformFolder(t, testFixtureSopsMissing)
+	tmpEnvPath := copyEnvironment(t, testFixtureSopsMissing)
+	rootPath := util.JoinPath(tmpEnvPath, testFixtureSopsMissing)
+
+	// apply and check for errors
+	_, errorOut, err := runTerragruntCommandWithOutput(t, "terragrunt apply --terragrunt-non-interactive --terragrunt-log-level debug --terragrunt-working-dir "+rootPath)
+	require.Error(t, err)
+
+	assert.Contains(t, errorOut, "Encountered error while evaluating locals in file ./terragrunt.hcl")
+}
diff --git a/test/integration_test.go b/test/integration_test.go
index e2fa0422..74a057be 100644
--- a/test/integration_test.go
+++ b/test/integration_test.go
@@ -2464,9 +2464,10 @@ func TestTerragruntRemoteStateCodegenDoesNotGenerateWithSkip(t *testing.T) {
 	assert.False(t, fileIsInFolder(t, "foo.tfstate", generateTestCase))
 }
 
+// This function cannot be parallelized as it changes the global version.Version
+//
+//nolint:paralleltest
 func TestTerragruntValidateAllWithVersionChecks(t *testing.T) {
-	t.Parallel()
-
 	tmpEnvPath := copyEnvironment(t, "fixtures/version-check")
 
 	stdout := bytes.Buffer{}
@@ -2495,12 +2496,12 @@ func TestTerragruntIncludeParentHclFile(t *testing.T) {
 	assert.Equal(t, 1, strings.Count(out, "common_hcl"))
 }
 
-// The over all test here can run in parallel, but the subtests cannot.
+// The tests here cannot be parallelized.
 // This is due to a race condition brought about by overriding `version.Version` in
 // runTerragruntVersionCommand
-func TestTerragruntVersionConstraints(t *testing.T) { //nolint:tparallel
-	t.Parallel()
-
+//
+//nolint:paralleltest,funlen
+func TestTerragruntVersionConstraints(t *testing.T) {
 	tc := []struct {
 		name                 string
 		terragruntVersion    string
@@ -2640,9 +2641,10 @@ func TestIamRolesLoadingFromDifferentModules(t *testing.T) {
 	assert.NotEmptyf(t, component2, "Missing role for component 2")
 }
 
+// This function cannot be parallelized as it changes the global version.Version
+//
+//nolint:paralleltest
 func TestTerragruntVersionConstraintsPartialParse(t *testing.T) {
-	t.Parallel()
-
 	fixturePath := "fixtures/partial-parse/terragrunt-version-constraint"
 	cleanupTerragruntFolder(t, fixturePath)
 
@@ -3802,7 +3804,10 @@ func TestTerragruntTerraformOutputJson(t *testing.T) {
 	_, stderr, err := runTerragruntCommandWithOutput(t, "terragrunt apply --no-color --terragrunt-json-log --terragrunt-tf-logs-to-json --terragrunt-forward-tf-stdout --terragrunt-non-interactive --terragrunt-working-dir "+testPath)
 	require.Error(t, err)
 
-	assert.Contains(t, stderr, `"msg":"Initializing the backend..."`)
+	// Sometimes, this is the error returned by AWS.
+	if !strings.Contains(stderr, "Error: Failed to get existing workspaces: operation error S3: ListObjectsV2, https response error StatusCode: 301") {
+		assert.Contains(t, stderr, `"msg":"Initializing the backend..."`)
+	}
 
 	// check if output can be extracted in json
 	jsonStrings := strings.Split(stderr, "\n")