Can you intermingle data and resource on the same route53 hosted zone?

I am reading some terraform code and this seems like a bad practice to me. I see this code

data "aws_route53_zone" "public_zone" {
  count = var.is_lb_public || var.is_private_nlb_with_public_cert_and_dns || var.is_public_dns ? 1 : 0

  name         = var.public_route53_zone_name
  private_zone = false

and then elsewhere that zone is defined as a resource. Shouldn’t the above data block actually not exist and we should instead depend on where we actually define the hosted zone?

i wonder if this is why I run into an error on creating a new datacenter and have to sometimes manually create/delete hosted zones to get my full apply working as it errors sometimes on zone not exist (perhaps because data expects it to be there already and it is not there yet).

I found the other declaration elsewhere in the code to be

resource “aws_route53_zone” “public_zone” {
count = var.is_public == true ? 1 : 0

name = var.zone_name
comment = var.description

Related post amazon ecs - Terraform: What's the point using Both Data Source and Resource on the same type? - Stack Overflow

I feel like we could be left with a race condition on first creation of datacenter where data reads before the resource creates???, true/false?

Hi @deantray,

Indeed, trying to treat the same object both as an external dependency (with data) and a managed object (with resource) in the same configuration is a relatively common cause of this sort of ordering problem.

Unless the configuration is written very carefully, it’s easy to accidentally write things so that Terraform cannot understand that the read of the object depends on the management of the same object elsewhere, and so it will fail during planning trying to read an object that doesn’t exist yet, or worse it will read a stale version of the object (from the previous apply) and then successfully use that stale information to plan other stuff.

Instead, I would recommend designing the module that currently contains data "aws_route53_zone" "public_zone" to accept an object-typed variable whose type constraint matches whatever subset of an aws_route53_zone managed resource object is needed by that module.

For example, if you only need the name, the zone ID, and the ARN (the three main “identifying attributes”) then you could write it like this:

variable "route53_zone" {
  type = object({
    name    = string
    zone_id = string
    arn     = string

When calling the module, the caller can typically just assign the entire zone resource instance object, because it has compatible attributes:

module "example" {
  # ...

  route53_zone = aws_route53_zone.public_zone

Back inside the module with that variable "route53_zone" block, you can refer to the given object when declaring downstream objects like records:

resource "aws_route53_record" "example" {
  zone_id = var.route53_zone.zone_id
  # ...

This idea of passing whole objects between modules is one of the patterns described in the following article, which also has some other recommendations about how to split declarations across multiple composable modules: