gruntwork-io / terragrunt

Terragrunt is a flexible orchestration tool that allows Infrastructure as Code written in OpenTofu/Terraform to scale.
https://terragrunt.gruntwork.io/
MIT License
8.04k stars 975 forks source link

Terragrunt apply fails for local Terragrunt files while passing -var in extra_arguments #1245

Closed mordowiciel closed 3 years ago

mordowiciel commented 4 years ago

Background: I'd like to create one security group with one rule attached and then pass its ARN to another module. I think that creating another Terraform module for such small task is a bit overkill, so I've decided to put regular Terraform files together with terragrunt.hcl in one of the directories.

Working environment: The directory structure is following:

.
    /dev
        /eu-west-1
            /environment
                /security-group
                    providers.tf
                    sg.tf
                    terragrunt.hcl
                    variables.tf
                /... (another modules in environment)
            environment.hcl
        region.hcl
terragrunt.hcl (root)

providers.tf:

provider "aws" {
  version = ">= 2.28.1"
  region  = var.region
}

sg.tf:

terraform {
  # Intentionally empty. Will be filled by Terragrunt.
  backend "s3" {}
}

resource "aws_security_group" "worker_group" {
  name_prefix = "test"
  vpc_id      = var.vpc_id
}

resource "aws_security_group_rule" "worker_ssh" {
  type      = "ingress"
  from_port = 22
  to_port   = 22
  protocol  = "tcp"
  cidr_blocks = [
  "0.0.0.0/0"]

  security_group_id = aws_security_group.worker_group.id
}

variables.tf:

variable "region" {}
variable "vpc_id" {}

terragrunt.hcl:

include {
  path = find_in_parent_folders()
}

locals {
  region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))
}

terraform {
  extra_arguments "common_vars" {
    commands = [
      "apply",
      "plan",
      "import",
      "push",
      "refresh"
    ]
    arguments = [
      "-var 'vpc_id=${local.region_vars.locals.vpc_id}'",
      "-var 'region=${local.region_vars.locals.region}'"
    ]
  }
}

environment.hcl:

locals {
  project =  "test"
  environment = "test"
}

region.hcl:

locals {
  region = "eu-west-1"
  vpc_id = "<<redacted>>"
  vpc_subnet_ids = ["<<redacted>>","<<redacted>>","<<redacted>>"]
}

terragrunt.hcl (root):

locals {
  # Automatically load region-level variables
  region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))

  # Automatically load environment-level variables
  environment_vars = read_terragrunt_config(find_in_parent_folders("environment.hcl"))
}

remote_state {
  backend = "s3"
  config = {
    bucket = "<<redacted>>"
    key = "${path_relative_to_include()}/terraform.tfstate"
    region = local.region_vars.locals.region
    encrypt = true
  }
}

Problem: While performing terragrunt apply in the security-group module, Terraform returns the message similar to the one where you call it with the wrong parameters:

terragrunt apply
[terragrunt] [/<<redacted>>/dev/eu-west-1/environment/security-group] 2020/07/03 15:20:03 Running command: terraform --version
[terragrunt] 2020/07/03 15:20:03 Terraform version: 0.12.28
[terragrunt] 2020/07/03 15:20:03 Reading Terragrunt config file at /<<redacted>>/dev/eu-west-1/environment/security-group/terragrunt.hcl
[terragrunt] [/<<redacted>>/dev/eu-west-1/environment/security-group] 2020/07/03 15:20:03 Initializing remote state for the s3 backend
[terragrunt] [/<<redacted>>/dev/eu-west-1/environment/security-group] 2020/07/03 15:20:03 WARNING: Versioning is not enabled for the remote state S3 bucket <<redacted>>. We recommend enabling versioning so that you can roll back to previous versions of your Terraform state in case of error.
[terragrunt] [/<<redacted>>/dev/eu-west-1/environment/security-group] 2020/07/03 15:20:03 Running command: terraform init -backend-config=bucket=<<redacted>> -backend-config=encrypt=true -backend-config=key=dev/eu-west-1/environment/security-group/terraform.tfstate -backend-config=region=eu-west-1

Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.69.0...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
[terragrunt] 2020/07/03 15:20:15 Running command: terraform apply -var 'vpc_id=<<redacted>>' -var 'region=eu-west-1'
Usage: terraform apply [options] [DIR-OR-PLAN]

  Builds or changes infrastructure according to Terraform configuration
  files in DIR.

  By default, apply scans the current directory for the configuration
  and applies the changes appropriately. However, a path to another
  configuration or an execution plan can be provided. Execution plans can be
  used to only execute a pre-determined set of actions.

Options:

  -auto-approve          Skip interactive approval of plan before applying.
  (...)
[terragrunt] 2020/07/03 15:20:15 Hit multiple errors:
exit status 1

However, calling the same command in the same directory, which was produced by the Terragrunt at the end of the run, using the vanilla Terraform, finishes without error:

terraform apply -var 'vpc_id=<<redacted>>' -var 'region=eu-west-1'

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_security_group.worker_group will be created
  + resource "aws_security_group" "worker_group" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = (known after apply)
      + name                   = (known after apply)
      + name_prefix            = "test"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + vpc_id                 = "<<redacted>>"
    }

  # aws_security_group_rule.worker_ssh will be created
  + resource "aws_security_group_rule" "worker_ssh" {
      + cidr_blocks              = [
          + "0.0.0.0/0",
        ]
      + from_port                = 22
      + id                       = (known after apply)
      + protocol                 = "tcp"
      + security_group_id        = (known after apply)
      + self                     = false
      + source_security_group_id = (known after apply)
      + to_port                  = 22
      + type                     = "ingress"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Component versions:

lorengordon commented 4 years ago

Try changing this part of your terragrunt.hcl:

    arguments = [
      "-var", "vpc_id=${local.region_vars.locals.vpc_id}",
      "-var", "region=${local.region_vars.locals.region}"
    ]
mordowiciel commented 4 years ago

@lorengordon Thanks, that did the trick!

However, isn't it inconsistent with the Terraform command template? When you'll call terraform apply --help:

Usage: terraform apply [options] [DIR-OR-PLAN]

  Builds or changes infrastructure according to Terraform configuration
  files in DIR.

  By default, apply scans the current directory for the configuration
  and applies the changes appropriately. However, a path to another
  configuration or an execution plan can be provided. Execution plans can be
  used to only execute a pre-determined set of actions.

Options:
(...)
 -var 'foo=bar'         Set a variable in the Terraform configuration. This
                        flag can be set multiple times.

You can clearly see that the passed variable needs to be surrounded by single quotes. I think it would be nice to mention the auto-completion of single quotes in Terragrunt documentation, or just allow both notations (with and without quotes) to be used.

lorengordon commented 4 years ago

No, this is a shell interpolation thing. Has nothing to do with terragrunt at all. The problem was your prior syntax was passing the literal string to the shell, e.g. terraform "-var 'vpc_id=us-east-1'". "-var 'vpc_id=us-east-1'" is not a valid argument to terraform.

The single quotes are needed when you have values with spaces or some kind of other complexity that the shell might interpret funny. You can still do that with terragrunt, if you want:

    arguments = [
      "-var", "'vpc_id=${local.region_vars.locals.vpc_id}'",
      "-var", "'region=${local.region_vars.locals.region}'"
    ]

The syntax requirement here is just separating the list items, so the terraform argument, -var, and the argument value, 'vpc_id=${local.region_vars.locals.vpc_id}', are separate items in the list.

yorinasub17 commented 3 years ago

Closing this as the original question has been answered. If there are any follow ups, please open a new ticket with updated context. Thanks!