terragrunt icon indicating copy to clipboard operation
terragrunt copied to clipboard

Stacks: how to allow default module value ?

Open pomverte opened this issue 11 months ago • 6 comments

Describe the bug

If a unit requires an input, all stacks must provide it instead of using the module default value.

Steps To Reproduce

❯ tree
.
├── live
│   ├── dev
│   │   └── terragrunt.stack.hcl           ---> I don't wanna set the instance_server_type
│   ├── prod
│   │   └── terragrunt.stack.hcl
│   └── root.hcl
├── modules
│   └── scaleway_instance_server
│       └── main.tf                                ---> instance_server_type has a default value
└── units
    └── compute
        └── terragrunt.hcl
# live/dev/terragrunt.stack.hcl
unit "compute" {
  source = "${get_repo_root()}/units//compute"
  path   = "compute"
}

# live/prod/terragrunt.stack.hcl
unit "compute" {
  source = "${get_repo_root()}/units//compute"
  path   = "compute"
  values = {
    instance_server_type = "PLAY2-NANO"
  }
}

# live/root.hcl
locals {
  terraform_binary   = "/opt/homebrew/bin/terraform" # TG_TF_PATH
  terraform_version  = "1.11.4"
  terragrunt_version = "0.78.2"
}
generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite_terragrunt"
  contents  = <<EOF
terraform {
  required_providers {
    scaleway = {
      source = "scaleway/scaleway"
      version = "2.53.0"
    }
  }
  required_version = "${local.terraform_version}"
}
provider "scaleway" {}
EOF
}

# modules/scaleway_instance_server/main.tf
variable "instance_server_type" {
  default     = "PLAY2-PICO"
  type        = string
}
resource "scaleway_instance_server" "main" {
  name   = "test"
  type   = var.instance_server_type
  image  = "ubuntu_noble"
}

# units/compute/terragrunt.hcl
include "root" {
  path = find_in_parent_folders("root.hcl")
}

locals {
  root_vars          = read_terragrunt_config(find_in_parent_folders("root.hcl"))
  terraform_binary   = local.root_vars.locals.terraform_binary
  terraform_version  = local.root_vars.locals.terraform_version
  terragrunt_version = local.root_vars.locals.terragrunt_version
}

terraform {
  source = "${get_repo_root()}/modules//scaleway_instance_server"
}

inputs = {
  instance_server_type = values.instance_server_type
}

terraform_binary              = local.terraform_binary
terraform_version_constraint  = local.terraform_version
terragrunt_version_constraint = local.terragrunt_version

Nice to haves

terragrunt stack generate
23:55:44.281 INFO   Generating stack from ./dev/terragrunt.stack.hcl
23:55:44.295 INFO   Processing unit compute from ./dev/terragrunt.stack.hcl

terragrunt run --all plan
23:58:48.954 INFO   The stack at . will be processed in the following order for command plan:
Group 1
- Module ./.terragrunt-stack/compute

23:58:49.033 ERROR  [.terragrunt-stack/compute] Error: Unknown variable
23:58:49.033 ERROR  [.terragrunt-stack/compute]   on ./.terragrunt-stack/compute/terragrunt.hcl line 17:
23:58:49.033 ERROR  [.terragrunt-stack/compute]   17:   instance_server_type = values.instance_server_type
23:58:49.033 ERROR  [.terragrunt-stack/compute] There is no variable named "values".
23:58:49.036 ERROR  [.terragrunt-stack/compute] Module ./.terragrunt-stack/compute has finished with an error
23:58:49.036 ERROR  error occurred:

* ./.terragrunt-stack/compute/terragrunt.hcl:17,26-32: Unknown variable; There is no variable named "values".

23:58:49.405 ERROR  Unable to determine underlying exit code, so Terragrunt will exit with error code 1

Versions

  • Terragrunt version: 0.78.2
  • Terraform version: 1.11.4
  • Environment details (Ubuntu 20.04, Windows 10, etc.): macOs 15.4.1

pomverte avatar May 10 '25 22:05 pomverte

can you try speficing a default in this way

inputs = {
  instance_server_type = try (values.instance_server_type, "my_default_server_type")
}

mark854542 avatar May 12 '25 07:05 mark854542

second thing to try is. possible then the default of the terraform module is used

inputs = {
  instance_server_type = try (values.instance_server_type, null)
}

mark854542 avatar May 12 '25 08:05 mark854542

You can see examples of how we implement this in the example catalog repository: https://github.com/gruntwork-io/terragrunt-infrastructure-catalog-example/tree/main/examples/terragrunt/stacks

We might have an easier way to define defaults in the future, but for now, we're looking to provide a minimal set of future compatible solutions that allow users to use Stacks without issue.

Please close this issue if it addresses your question. Discord and Discussions are two other places to learn about best practices and techniques if you can't find them in the docs.

yhakbar avatar May 12 '25 18:05 yhakbar

@mark854542 @yhakbar

Currently, if a unit passes an input to its module like below, and if the resulting value is null, it is actually passed as null to terraform. Unfortunately, this means that the module's default value of the variable definition is then not used. This forces the unit author to essentially copy/paste the module's defaults into their terragrunt.hcl

inputs = {
  instance_server_type = try (values.instance_server_type, null)    # <-- module's default for var.instance_server_type is never used
}

Personally, I would prefer that terragrunt does not pass null values as inputs at all. To me, that would be more in line with the behaviour of terraform/opentofu for null:

Finally, there is one special value that has no type: null: a value that represents absence or omission. If you set an argument of a resource to null, OpenTofu behaves as though you had completely omitted it — it will use the argument's default value if it has one, or raise an error if the argument is mandatory. null is most useful in conditional expressions, so you can dynamically omit an argument if a condition isn't met.

https://opentofu.org/docs/language/expressions/types/#types

mhulscher avatar May 17 '25 13:05 mhulscher

I found this to cover my use-case. Values that I set now in stack files are forwarded. It should be possible to combine with merge() in case there are some values explicitly set as well.

However I could not remove try() block as if values is omitted entirely, it's not available downstream.

include "root" {
  path = find_in_parent_folders("root.hcl")
}

terraform {
  source = "${find_in_parent_folders("catalog/modules")}//prometheus-psc-consumer"
}

dependencies {
  paths = [
    "../infrastructure"
  ]
}

inputs = try(values, {})

rtammekivi avatar Oct 18 '25 10:10 rtammekivi

The fact that it's not possible to define inputs in terragrunt.stack.hcl means one has to duplicate all the variables of the Terraform module and wrap them in a try() in the unit blueprint that gets used in the Stack. This seems very suboptimal. (even worse with null forcing one to also duplicate the module's default values as mentioned here https://github.com/gruntwork-io/terragrunt/issues/4265#issuecomment-2888392679)

Because of this together with the lack of dynamic dependencies I don't really see much value in Stacks at the moment. See https://github.com/gruntwork-io/terragrunt/issues/4067#issuecomment-3542831327 for what as far as I can see would be a much simpler approach that is more flexible, easier to understand and doesn't have the issue of having to wrap all module variables in a try().

simonvanderveldt avatar Nov 20 '25 15:11 simonvanderveldt