Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

terraform module that waits for another module to finish before starting

Tags:

terraform

I'm looking for a workaround as modules can't depend on other modules.

I believe this is how your supposed to create a module that waits for another module to finish before starting itself. however, I'm finding that this still does not work for me. Is there anything wrong with how i have set this up?

With this, I still see resources in module2 get built that don't have dependencies on anything else.

module "module1" {
    source = "./module1"
}

module "module2" {
    source = "./module2"
    somevar = "${module.module1.somevar}"
}

./module1/outputs.tf

output "somevar" {
  value = "nothing"
}


./module2/variables.tf

variable "somevar" {}

Many thanks.

like image 262
sebastian Avatar asked Jan 01 '23 16:01

sebastian


1 Answers

In Terraform today, the following items participate in the reference dependency graph:

  • Resources
  • Input variables
  • Output values
  • Local values

Because input variables and output values exist at the interface of a module, we can use them to create dependencies across module boundaries.

Dependencies In Using Input Variables

If you declare a variable in a child module then any references to that variable will create indirect dependencies to any object that the caller referred to when defining that variable's value.

For example, in the child module:

variable "vpc_id" {
  type = string
}

resource "aws_subnet" "example" {
  vpc_id = var.vpc
  # ...
}

Then in the calling module:

resource "aws_vpc" "example" {
  # ...
}

module "example" {
  source = "./modules/example"

  vpc_id = aws_vpc.example.id
}

With the configuration above, module.example.aws_subnet.example depends on module.example.var.vpc_id, which in turn depends on aws_vpc.example. Therefore the subnet will be applied only after the VPC is applied, due to the transitive dependency.

In unusual cases where you need to be able to create dependencies without passing any particular value, you can use a slight variant of the above. In the child module:

variable "subnet_depends_on" {
  type = any
}

resource "aws_subnet" "example" {
  depends_on = [var.subnet_depends_on]

  # ...
}

In the caller:

resource "aws_vpc" "example" {
  # ...
}

module "example" {
  source = "./modules/example"

  subnet_depends_on = [aws_vpc.example]
}

In this case, the value of subnet_depends_on is irrelevant, and we're instead using it only for the dependency relationships it has. I'd suggest using this pattern only if there isn't already a variable passing in the necessary object, but this pattern is handy on some edge cases. (It's not necessary in the example I showed above because you'd always need to populate the vpc_id of the subnet anyway, and that reference would create the necessary implicit dependency as shown above.)

Dependencies Out Using Output Values

A similar trick works the other way, with output values. If we invert the above example so that it's the child module that's creating the VPC and the parent module creating the subnet, the child module might look like this:

resource "aws_vpc" "example" {
  # ...
}

output "vpc_id" {
  value = aws_vpc.example.id
}

...and in the calling module:

module "example" {
  source = "./modules/example"
}

resource "aws_subnet" "example" {
  vpc_id = module.example.vpc_id
  # ...
}

Now aws_subnet.example depends on module.example.vpc_id, which in turn depends on module.example.aws_vpc.example. Therefore as before the subnet will be applied only after the VPC is applied, due to the transitive dependency.

There's also a variant of this that doesn't represent specific values, although in this direction we do still need to return some value, even if it's just a placeholder. In the child module:

resource "aws_vpc" "example" {
  # ...
}

output "vpc_applied" {
  value = true # the value is irrelevant

  depends_on = [aws_vpc.example]
}

Then in the calling module:

module "example" {
  source = "./modules/example"
}

resource "aws_subnet" "example" {
  depends_on = [module.example.vpc_applied]

  # ...
}

As before, this is for unusual cases only. It would not be appropriate in this case because in practice we need the VPC id to create the subnet. There are, however, situations where a dependency ordering must be applied regardless of any specific value, and the above pattern can achieve that.

For output values in particular, there's a compromise between these two: if you're returning a value that describes a particular object but you know that the value won't be useful until some other objects have been applied, you can mix the above by returning a value but also including some additional dependencies. For example, in a different child module that creates an EC2 instance with a security group whose rules must be applied fully before it becomes accessible:

output "instance_ip_address" {
  value = aws_instance.example.private_ip

  # Callers won't be able to connect to this until the
  # security group rules are all applied.
  depends_on = [
    aws_security_group_rule.example1,
    aws_security_group_rule.example2,
  ]
}

In this case, if a resource in the calling module refers to module.example.instance_ip_address then that resource will not be applied until the security groups are complete, even though the IP address value does not include any information about those security groups. That allows us to represent that the IP address isn't actually useful until the security group rules are fully populated, even though it's allocated earlier on.

Depending on a Whole Module

Terraform does also allow depending on a whole child module:

  depends_on = [module.example]

That works because module.example is an object that collects together all of the outputs of the module, and so referring to it is the same as referring to all of the outputs: all of their expressions must be evaluated to construct that object, and so all of the transitive dependencies are included.

like image 72
Martin Atkins Avatar answered May 28 '23 12:05

Martin Atkins