13

Today I am working on building out several custom Terraform Modules. The issue that I am trying to figure out how to work around dependencies within modules. Until TF 0.12 is released we cannot declare a module to be dependent upon another module. So, in this root level main.tf:

# ROOT level main.tf
# -------------------------------------------------------------------
# Create NAT Gateway - Associates EIP as well
# -------------------------------------------------------------------
module "vpc_nat_gateway" {
  source            = "./vpc_nat_gateway"
  vpc_id            = "${ module.vpc.id }"
  public_subnet_ids = "${ module.vpc_subnets.public_subnet_ids }"
  private_cidr      = "${ var.private_cidr }"
  common_tags       = "${ local.common_tags }"
}

# -------------------------------------------------------------------
# Create Private Routes
# -------------------------------------------------------------------
module "vpc_private_route" {
  source         = "./vpc_private_route"
  vpc_id.        = "${ module.vpc.id }"
  nat_gateway_id = "${ module.vpc_nat_gateway.nat_gateway_id }"
  common_tags    = "${ local.common_tags }"
}

# vpc_private_route module - main.tf
data "aws_nat_gateway" "az1" {
  vpc_id = "${ var.vpc_id }"

  tags {
    Name = "*NAT GW AZ 1"
  }
}

data "aws_nat_gateway" "az2" {
  vpc_id = "${ var.vpc_id }"

  tags {
    Name = "*NAT GW AZ 2"
  }
}

The result output is:

------ SNIP -----
module.vpc_nat_gateway.aws_nat_gateway.nat[1]: Creation complete after 1m50s (ID: nat-02a7f4279cec3a6c8)
module.vpc_nat_gateway.aws_nat_gateway.nat.0: Still creating... (2m0s elapsed)
module.vpc_nat_gateway.aws_nat_gateway.nat[0]: Creation complete after 2m0s (ID: nat-0695a12d9c0305e4c)

    Error: Error applying plan:

    3 error(s) occurred:

    * module.vpc_private_route.data.aws_subnet_ids.private: data.aws_subnet_ids.private: no matching subnet found for vpc with id vpc-0b530d1885e74067b
    * module.vpc_private_route.data.aws_nat_gateway.az2: data.aws_nat_gateway.az2: no matching NAT gateway found: {
    Filter: [{
        Name: "vpc-id",
        Values: ["vpc-0b530d1885e74067b"]
        },{
        Name: "tag:Name",
        Values: ["*NAT GW AZ 2"]
        }]
    }
    * module.vpc_private_route.data.aws_nat_gateway.az1: data.aws_nat_gateway.az1: no matching NAT gateway found: {
    Filter: [{
        Name: "vpc-id",
        Values: ["vpc-0b530d1885e74067b"]
        },{
        Name: "tag:Name",
        Values: ["*NAT GW AZ 1"]
        }]
    }

As observed in the output the Nat Gateways are created. Terraform show output tells us the Filters are correct:

    module.vpc_nat_gateway.aws_nat_gateway.nat.0:
        id = nat-0695a12d9c0305e4c
        allocation_id = eipalloc-023ca087ad4fb830e
        network_interface_id = eni-015e39fc8d3bc0de3
        private_ip = 172.16.254.16
        public_ip = 18.215.5.116
        subnet_id = subnet-0f2c039e8fd804f30
        tags.% = 7
        tags.Environment = development
        tags.Infrastructure = No
        tags.Name = **redacted** NAT GW AZ 1
    module.vpc_nat_gateway.aws_nat_gateway.nat.1:
        id = nat-02a7f4279cec3a6c8
        allocation_id = eipalloc-0a95264c2eef26673
        network_interface_id = eni-03bddcca2fbeeff44
        private_ip = 172.16.254.84
        public_ip = 3.91.167.246
        subnet_id = subnet-08ee61f3aa43acbe9
        tags.% = 7
        tags.Environment = development
        tags.Infrastructure = No
        tags.Name = **redacted** NAT GW AZ 2

Executing a subsequent terraform apply runs without any errors.

# Yes, the IDs are different in this example than from above.
# ---- SNIP ----
    module.vpc_private_route.data.aws_nat_gateway.az1:
        id = nat-0c127e538a26b2bd5
        allocation_id = eipalloc-01775b8e88502d4b9
        network_interface_id = eni-0b0bd2203bd3f5873
        private_ip = 172.16.254.45
        public_ip = 3.83.199.207
        state = available
        subnet_id = subnet-050f6fc499a455a97
        tags.% = 7
        tags.Environment = development
        tags.Infrastructure = No
        tags.Name = **redacted** NAT GW AZ 1
        vpc_id = vpc-057a1208002394e1b
    module.vpc_private_route.data.aws_nat_gateway.az2:
        id = nat-0325fe2ba1184815b
        allocation_id = eipalloc-0df309e8b533b35b6
        network_interface_id = eni-00e850031318b2a41
        private_ip = 172.16.254.92
        public_ip = 3.88.44.14
        state = available
        subnet_id = subnet-0191ae48f099aa808
        tags.% = 7
        tags.Infrastructure = No
        tags.Name = **redacted** NAT GW AZ 2
        vpc_id = vpc-057a1208002394e1b
    module.vpc_private_route.data.aws_subnet_ids.private:
        id = vpc-057a1208002394e1b
        ids.# = 2
        ids.1528047303 = subnet-03a4f5228ae9f1714
        ids.1908543416 = subnet-0d915cc4899877eb9
        tags.% = 1
        tags.Name = *Private*
        vpc_id = vpc-057a1208002394e1b

What should I be doing different to cause this TF Module to wait for the resources to be created before polling for the resource? In Ansible I could just issue a wait command or run a loop searching for criteria before proceeding.

Thank you for your thoughts!

PLEASE NOTE all of the "ids" shown in this output will be dead before you read this post. All data has been sanitized based on environmental security policies.

4 Answers 4

7

Update: As of TF 0.13x, depends_on is supported to create dependencies between modules. Using depends_on should be preferable over using the method detailed below.

The previous most popular answer is out of date with Terraform 0.12.24.

depends_on is a protected variable, and cannot be used in a module. In addition there are a few syntax differences.

I've updated the example below.

# ROOT level main.tf
# -------------------------------------------------------------------
# Create NAT Gateway - Associates EIP as well
# -------------------------------------------------------------------
module "vpc_nat_gateway" {
  source            = "./vpc_nat_gateway"
  vpc_id            = module.vpc.id
  public_subnet_ids = module.vpc_subnets.public_subnet_ids
  private_cidr      = var.private_cidr
  common_tags       = local.common_tags
}

# -------------------------------------------------------------------
# Create Private Routes
# -------------------------------------------------------------------
module "vpc_private_route" {
  source         = "./vpc_private_route"
  vpc_id.        = module.vpc.id
  nat_gateway_id = module.vpc_nat_gateway.nat_gateway_id
  common_tags    = local.common_tags

  # Depends is a custom variable, depends_on is a reserved keyword.
  depends = [module.vpc_nat_gateway.nat_gateway_id]
}
# vpc_private_route module - main.tf
variable "depends" {
  default = []
}

resource "null_resource" "depends_on" {
  triggers = {
    depends_on = "${join("", var.depends)}"
  }
}

data "aws_nat_gateway" "az1" {
  vpc_id = var.vpc_id

  depends_on = [
    null_resource.depends_on
  ]
}

data "aws_nat_gateway" "az2" {
  vpc_id = var.vpc_id

  depends_on = [
    null_resource.depends_on
  ]
}
5

These are hacks (understandably as you couldn't explicitly do this in Terraform < 0.13).

Now however, with 0.13, we have depends_on for modules: https://github.com/hashicorp/terraform/blob/v0.13/CHANGELOG.md#0130-august-10-2020

4

There is a hacky workaround for getting Terraform to do module dependencies. You can force the module to be aware of the Terraform calling it like this:

# ROOT level main.tf
# -------------------------------------------------------------------
# Create NAT Gateway - Associates EIP as well
# -------------------------------------------------------------------
module "vpc_nat_gateway" {
  source            = "./vpc_nat_gateway"
  vpc_id            = "${ module.vpc.id }"
  public_subnet_ids = "${ module.vpc_subnets.public_subnet_ids }"
  private_cidr      = "${ var.private_cidr }"
  common_tags       = "${ local.common_tags }"
}

# -------------------------------------------------------------------
# Create Private Routes
# -------------------------------------------------------------------
module "vpc_private_route" {
  source         = "./vpc_private_route"
  vpc_id.        = "${ module.vpc.id }"
  nat_gateway_id = "${ module.vpc_nat_gateway.nat_gateway_id }"
  common_tags    = "${ local.common_tags }"

  depends_on = ["${module.vpc_nat_gateway.nat_gateway_id}"]
}
# vpc_private_route module - main.tf
variable "depends_on" {
  default = []
}

resource "null_resource" "depends_on" {
  triggers {
    depends_on = "${join("", var.depends_on)}"
  }
}

data "aws_nat_gateway" "az1" {
  vpc_id = "${ var.vpc_id }"

  tags {
    Name = "*NAT GW AZ 1"
  }

  depends_on = [
    "null_resource.depends_on"
  ]
}

data "aws_nat_gateway" "az2" {
  vpc_id = "${ var.vpc_id }"

  tags {
    Name = "*NAT GW AZ 2"
  }

  depends_on = [
    "null_resource.depends_on"
  ]
}

It adds a lot of boilerplate but you will get the desired effect.

1
  • Thank you @Briansbum for this input and excellent example. I will try this my next day in office and report back. Commented Feb 2, 2019 at 2:17
0

I've not tried this, but you could have a null_resource with a local-exec running a sleep command and make your resource dependent on that resource with a depends_on lifecycle argument.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.