Codependent values across different modules?

I’m trying to bring together three Terraform modules° that comprise a webapp. Separately the modules deploy OK, and my goal now is to arrange things so there’s one terraform apply to run.

A problem seems to arise when module A generates values at apply time that module B needs to know about, and vice versa. Were it strictly a linear dependency situation I imagine I could get away with output to pass values on, but in this case AFAICT, the generated values needs to traverse across modules back and forth.

I’ll try to explain my conundrum with an example. As you may know, typically you need CORS config when BE and FE live on separate origins. The BE needs to be configured with the origin of the FE, and vice versa.

For reasons outside of my control°° I’m using the random_id module to append a random id to certain names (similar to Kubernetes). For example:

# Backend
resource "random_id" "instance_suffix" {
  byte_length = 2
}
resource "azurerm_app_service" "backend" {
  name = "mybackend-${random_id.instance_suffix.hex}"
  ...
}
# Frontend
...
resource "azurerm_app_service" "frontend" {
  name = "myfrontend-${random_id.instance_suffix.hex}"
  ...
}

becomes e.g.
BE name: “mybackend-ab23.azurewebsites.net
FE name: “myfrontend-ed78.azurewebsites.net

Ultimately the FE should have the correct BE origin, something like:

# Frontend
resource "azurerm_app_service" "frontend" {
  ...
  cors = [ "https://mybackend-ab23.azurewebsites.net" ]
 ...
}

but not sure how I should write that into my FE module. Is there a way to use a local?
A data value? Not sure.
It doesn’t seem very declarative/idiomatic to configure CORS in some kind of “final pass” when both values are known, but if that’s necessary happy to try.

Been reading https://www.terraform.io/docs/configuration/expressions.html#values-not-yet-known but not sure it applies.

Hoping there is a best practice on this.
Appreciate your insight! :slight_smile:


° The three modules are: a static FE, BE, and DB. Running on Azure.
°° Azure uses a global shared namespace for certain public facing things

Hi @onpaws,

Unfortunately I’m not really familiar enough with the specific services you are using here to be able to offer concrete advice, but I do want to note something that you will probably find useful here: when Terraform is resolving dependencies between modules, each individual variable and output block has its own dependencies, so you can in theory have what appears to be a “dependency cycle” between module inputs and outputs as long as the individual input variables and output values in the module are not connected in a way that would create a cycle.

As a very simple (though also contrived and useless) example, consider the following module that looks like it refers to itself:

module "contrived" {
  source = "./module/contrived"

  in = module.contrived.out
}

The above would be considered valid by Terraform as long as output "out" in the module doesn’t make use of var.in (directly or indirectly) in its value expression. Here’s a really silly but straightforward example of a module implementation where that condition would hold:

variable "in" {
  type = string
}

resource "null_resource" "example" {
  triggers = {
    # We can use var.in here as long as
    # output "out" doesn't refer to
    # null_resource.example.
    in = var.in
  }
}

output "out" {
  value = "doesn't use var.in"
}

Whether it would be practical to make use of this capability across your three real-world modules I’m not sure, but in principle if it were possible to declare what you need all in a single module then it should also be possible to split it over three modules as long as you think carefully about the dependencies (or lack thereof) between the output values and the input variables.

Thanks for posting, I think I follow what you’re saying.
I’ve tried putting the three separate modules into a parent folder

resource "azurerm_resource_group" "resource_group" {
  name     = "${var.customer}-${var.environment}-resource-group"
  location = var.location
}

module "postgres" { 
   source = "./postgres"
   
   customer = var.customer
   environment = var.environment
   location = var.location
   postgres_administrator_login = var.postgres_administrator_login
}

module "backend" {
   source = "./backend"

   customer = var.customer
   environment = var.environment
   location = var.location
   # frontend_origin = module.frontend.origin
}

module "frontend" {
   source = "./frontend"

   customer = var.customer
   environment = var.environment
   location = var.location
   # backend_origin = module.backend.default_site_hostname
}

My hope is to configure the two mutually dependent origins this way. Does that look appropriate?

Hm, seem to have run into a new issue. Now when I run terraform plan from the parent folder containing the three modules, with the main.tf as indicated above, I get several variants of the following error - “undeclared resource” “has not been declared in the root module” - huh?

Error: Reference to undeclared resource

  on output.tf line 8, in output "appservice_dns_name":
   8:   value = "${azurerm_app_service.backend.default_site_hostname}"

A managed resource "azurerm_app_service" "backend" has not been declared in
the root module.

The error seems to suggest I should re-declare resources in the root module, but wasn’t the point of the root module to call out to the three components? Not sure what’s wrong…

Hi @onpaws,

If you want to have a root module output that shows a value from one of your child modules then you’d declare the output also in the child module, referring directly to the resource in that module, and then in your root module your output would refer to the child module’s output:

output "appservice_dns_name" {
  value = module.backend.appservice_dns_name
}

The way to think about modules is that each one creates a separate namespace of objects. Input variables and output values allow you to pass values indirectly between those namespaces.

1 Like