Problems with getting inputs from dependent module outputs - is it a syntax problem? Typing?

I’m brand new at this (and only incrementally more experienced with vanilla Terraform), so possibly making a bunch of mistakes simultaneously, and I can’t find enough examples online to stumble across the answer. Here we go:

I have a root module with relevant child modules “vpc” and “app”. The output of my vpc module is

PS D:\src\[root module]\vpc> 
[terragrunt] 2020/12/02 09:43:04 Running command: terraform output
private_subnet_ids = [
  "subnet-048948095292e22ee",
  "subnet-0de11934aef029a94",
  "subnet-0043048d1712f8257",
  "subnet-03795d70ee7ceabc2",
]
public_subnet_id = subnet-0da6edbf27b087755
vpc_cidr = 10.101.0.0/16
vpc_id = vpc-05078e9744554134e

I then have the dependencies and inputs declared in the app module

#d:\src\[root module]\app\terragrunt.hcl

include {
  path = find_in_parent_folders()
}
dependency "vpc" {
  config_path = "../vpc"
  #do these need to match the regexs they should be?
  mock_outputs = {
    vpc_subnet_ids = ["foo", "bar"]
    vpc_id = "foobar"
    public_subnet_id = "bar"
  }
}
inputs = {
  private_subnet_ids = dependency.vpc.outputs.private_subnet_ids
  vpc_id = dependency.vpc.outputs.vpc_id
  vpc_cidr = dependency.vpc.outputs.vpc_cidr
  public_subnet_id = dependency.vpc.outputs.public_subnet_id
}

And then I have a security.tf file for my AWS security groups as such

#this allows me to use terragrunt module inputs inside of the cidr_blocks lists. It might be a reasonable practice to declare all terragrunt inputs as locals to enhance readability?
locals {
    vpc_cidr = {}
}

variable "onprem_cidr"{
    type = string
    default = "[redacted IPv4]"
}
resource "aws_security_group" "rdp_ping" {
    name        = "allow_rdp_and_ping"
    description = "Allow RDP and ping inbound traffic"
    vpc_id      = {}
    ingress {
        description = "RDP"
        from_port   = 3389
        to_port     = 3389
        protocol    = "tcp"
        cidr_blocks = [var.onprem_cidr,local.vpc_cidr]
    }
    ingress {
        description = "ICMP"
        from_port   = -1
        to_port     = -1
        protocol    = "icmp"
        cidr_blocks = [var.onprem_cidr,local.vpc_cidr]
    }
}

As the comment indicates, I needed a way to take the output from the vpc module and combine them in arbitrary groupings (this is a simple example, but I can easily imagine more complex ones), so I dumped one input into a local. (Incidentally, I’m not sure if I’ve got the input {} syntax correct). But when I run terragrunt plan-all I get

[terragrunt] [D:\src\[root module]\vpc] 2020/12/02 09:26:01 Running command: terraform output -json

Error: Incorrect attribute value type

  on 2-security.tf line 15, in resource "aws_security_group" "rdp_ping":
  15:     vpc_id      = {}

Inappropriate value for attribute "vpc_id": string required.


Error: Incorrect attribute value type

  on 2-security.tf line 21, in resource "aws_security_group" "rdp_ping":
  21:         cidr_blocks = [var.onprem_cidr,local.vpc_cidr]
    |----------------
    | local.vpc_cidr is object with no attributes

Inappropriate value for attribute "cidr_blocks": element 1: string required.

The first error may be saying "I expected a string type, but didn’t get any type. The second error pretty explicitly says “I need a string type, I got nothing”. I’m not sure what specifically I’m doing wrong to cause it.

Help please.

Hi Eli_Zwillinger,

The main issue here is that terragrunt only feeds forward variables, and not locals. You can think of terragrunt inputs as a more enhanced tfvars file that can reference extra stuff like dependencies.

Given that, the following should work:

variable "vpc_id" {
  type = string
  # This is passed in from terragrunt, via the `inputs` attribute.
}

variable "vpc_cidr" {
  type = string
  # This is passed in from terragrunt, via the `inputs` attribute.
}

variable "onprem_cidr"{
    type = string
    default = "[redacted IPv4]"
}

resource "aws_security_group" "rdp_ping" {
    name        = "allow_rdp_and_ping"
    description = "Allow RDP and ping inbound traffic"
    vpc_id      = var.vpc_id
    ingress {
        description = "RDP"
        from_port   = 3389
        to_port     = 3389
        protocol    = "tcp"
        cidr_blocks = [var.onprem_cidr, var.vpc_cidr]
    }
    ingress {
        description = "ICMP"
        from_port   = -1
        to_port     = -1
        protocol    = "icmp"
        cidr_blocks = [var.onprem_cidr, var.vpc_cidr]
    }
}

Duh. Now that I know how it works, it’s obvious.

Thank you for being a resource for the community.