How to Access Data List Attribute with Python Within Resource

I have this Terraform HCL code which works perfectly:

data "aws_availability_zones" "zones" {
  state = "available"
}

resource "aws_vpc" "CustomVpc" {
  cidr_block       = "10.0.0.0/16"

  tags = {
    Name = "dcb-vpc"
  }
}

resource "aws_subnet" "Subnet" {
  count = length(data.aws_availability_zones.zones.names)
  vpc_id     = aws_vpc.CustomVpc.id
  availability_zone = data.aws_availability_zones.zones.names[count.index]
  cidr_block = cidrsubnet("10.0.0.0/16", 8, count.index)

  tags = {
    Name = "dcb-subnet"
  }
}

output availability_zones {
  value = data.aws_availability_zones.zones.names[*]
}

Created this CDTF python code that does not work:

        zones = datasources.DataAwsAvailabilityZones(self, "zones",
                                                    state = "available")

        myVpc = vpc.Vpc(self, "CustomVpc",
                        tags = {"Name":"dcb-vpc"},
                        cidr_block = '10.0.0.0/16')

        subnet = vpc.Subnet(self, "Subnet",
                            vpc_id = myVpc.id,
                            availability_zone="${{zones.names[count.index]}}",
                            cidr_block="${cidrsubnet(\"10.0.0.0/16\", 8, count.index)}",
                            tags = {"Name":"dcb-subnet"})

        subnet.add_override("count", Fn.length_of(zones.names))

        TerraformOutput(self, "azs",
                        value=zones.names)

Getting this error:

│ Error: Reference to undeclared resource
│
│   on cdk.tf.json line 138, in resource.aws_subnet.Subnet:
│  138:         "availability_zone": "${zones.names[count.index]}",
│
│ A managed resource "zones" "names" has not been declared in the root

You could try something like
availability_zone=zones.names.get(Token().as_number("count.index"))

Thanks for the reply but that did not work:

      File "C:\Users\dcbar\GitRepos\learn-cdktf-vpc\vpc-ec2_instance\main.py", line 39, in __init__
        availability_zone=zones.names.get(Token().as_number("count.index")),
    AttributeError: 'list' object has no attribute 'get'

Another attempt:
"data." + zones.terraform_resource_type + "." + zones.friendly_unique_id + ".names[count.index]"

@jsteinich Again thank you for suggestion but that did not work either as I got:

Error: error creating EC2 Subnet: InvalidParameterValue: Value (data.aws_availability_zones.zones.names[count.index]) for parameter availabilityZone is invalid. Subnets can currently only be created in the following availability zones: us-east-2a, us-east-2b, us-east-2c.

Sounds like this maybe a limitation of AWS: Add state filter to aws_availability_zones data source. by kwilczynski · Pull Request #7965 · hashicorp/terraform · GitHub

@jsteinich No that is not it as when I do this:

        TerraformOutput(self, "azs",
                        value=zones.names)

I get all 3 zones for my target region reported.

@jsteinich I finally got something working, see below code. But I cannot determine the length or number of zone names. Would like to just determine the actual number of zones dynamically and use in the for loop. I did try a few combinations w/o success.
i.e.

numazs = int(Fn.length_of(Token.as_list(azones.names)))
and
numazs = int(Fn.length_of(Token.as_list(azones.get_list_attribute("names")))
and
numazs = int(Fn.length_of(azones.names))

Working code but hard coding the number of zones to loop over:

        azones = datasources.DataAwsAvailabilityZones(self, "zones",
                                                    state = "available")

        subnets = []
        n = 0
        for index in range(3):
            n = index + 1
            azname = Fn.element(zones.names, index)
            cidrblk = "10.0."+str(n)+".0/24"
            subnet = vpc.Subnet(self, "Subnet"+str(n),
                                vpc_id = myVpc.id,
                                availability_zone=azname,
                                cidr_block=cidrblk,
                                tags = {"Name":myTag + "-subnet"})
            subnets.append(subnet)

I see the problem. You want to have availability_zone="${data." + zones.terraform_resource_type + "." + zones.friendly_unique_id + ".names[count.index]}". Apologies for the error there.

When looking up from data sources values aren’t known until Terraform runtime. This means that you can’t directly access the information from your python code (cdktf synth time). This is an area the team is looking to improve.

1 Like

@jsteinich Yes thank you, thank you, thank you, that worked.

So why is this SO HARD to reference values from data sources? I would have never figured that syntax out as exiting documentation or examples are very limited and poor.

There are a few reasons why it is difficult. Some may be addressed soon; others will probably take longer.

  1. cdktf operates by generating a Terraform configuration file; because data sources are then not evaluated until Terraform itself runs, those values aren’t available directly in code. See Making cdktf dynamic · Issue #435 · hashicorp/terraform-cdk · GitHub for some more details.
  2. cdktf is built on a library called jsii which makes multiple language support possible. That doesn’t support generics, so making nice utilities to work with count is more difficult.
  3. It’s an area that not a lot of development time has gone to yet. Design support for dynamic blocks, iterators, count · Issue #1089 · hashicorp/terraform-cdk · GitHub is an issue that tracks the general effort.
  4. It was unintentionally made a bit worse in a recent update: Resource FQN tokens aren't resolved in a user friendly way · Issue #1641 · hashicorp/terraform-cdk · GitHub

We can definitely add to the documentation / samples for how to accomplish these types of tasks in the current version. Just created Improve documentation / samples for using count with data sources · Issue #1720 · hashicorp/terraform-cdk · GitHub if you’d like to add anything.

@jsteinich Thank you for the info/background. I truly appreciate all Hashi and your efforts and work on the CDKTF. We implement Terraform in every client we engage but recently there has been a Q in regards to Pulumi vs Terraform thus I embarked on trying to investigate CDKTF features.

But… I have another Q. Now that I have the code below working.

        subnet = vpc.Subnet(self, "Subnet",
                            vpc_id = myVpc.id,
                            availability_zone="${data." + azones.terraform_resource_type + "." + azones.friendly_unique_id + ".names[count.index]}",
                            cidr_block="${cidrsubnet(\"10.0.0.0/16\", 8, count.index)}",
                            tags = {"Name":myTag + "-subnet"})

        subnet.add_override("count", Fn.length_of(azones.names))

How do I get each subnet id output from the resource? In Terraform HCL I just would have:

output "subnet_ids" {
  value = aws_subnet.Subnet.*.id
}

I’d be interested in your answer. Why cdktf over Pulumi is a question that I’d like to have a better answer to, but I need to find some time to try Pulumi.

Unfortunately you’ll have to go through some hoops for that as well.
I haven’t tested it, but should be along the lines of:

TerraformOutput(self, "subnet_ids", value="${" + subnet.terraform_resource_type + "." + subnet.friendly_unique_id + ".*.id}")

@jsteinich So as I said we implement Terraform at all our clients for IaC. But one client had a developer who played w/ Pulumi so they asked us if Hashi had something similar, i.e., modern programming language for IaC support. So, I knew about CDKTF and I told them we would research the options and provide feedback. Course they have a lot of Terraform right now and a solid process in place.