Pulumi: How to create domain validation records for multiple sub domains?

165 Views Asked by At

I need to create an AWS ACM certificate with pulumi for multiple wildcard SubjectAlternativeNames, e.g.

  • *.mydomain.tld
  • *.subdomain01.mydomain.tld
  • *.subdomain02.mydomain.tld

Using export as a dev helper, I managed to display the contents of the domain_validation_options certificate attribute:

from pulumi     import export
from pulumi_aws import acm

cert = acm.Certificate('cert',
                       domain_name='mydomain.tld',
                       subject_alternative_names=[
                         '*.mydomain.tld',
                         '*.subdomain01.mydomain.tld',
                         '*.subdomain02.mydomain.tld'
                       ],
                       validation_method="DNS")
export('domain_validation_options', cert.domain_validation_options)

which upon pulumi up displays something like

Outputs:
+ domain_validation_options : [
   + [0]: {
      + domain_name          : "*.subdomain01.mydomain.tld"
      + resource_record_name : "_a55 ... 935.subdomain01.mydomain.tld."
      + resource_record_type : "CNAME"
      + resource_record_value: "_1ba ... 7d6.npyrrzfbbp.acm-validations.aws."
     }
   + [1]: {
      + domain_name          : "*.subdomain02.mydomain.tld"
      + resource_record_name : "_be5 ... cd3.subdomain02.mydomain.tld."
      + resource_record_type : "CNAME"
      + resource_record_value: "_7b0 ... 5f3.hcnplcfwms.acm-validations.aws."
     }
   + [2]: {
      + domain_name          : "mydomain.tld"
      + resource_record_name : "_691 ... 20f.mydomain.tld."
      + resource_record_type : "CNAME"
      + resource_record_value: "_566 ... 582.hrrssmwwfk.acm-validations.aws."
     }
   + [3]: {
      + domain_name          : "*.mydomain.tld"
      + resource_record_name : "_691 ... 20f.mydomain.tld."
      + resource_record_type : "CNAME"
      + resource_record_value: "_566 ... 582.hrrssmwwfk.acm-validations.aws."
     }
]

My problem: I need to create AWS Route 53 records to validate the certificate (using DNS), but as I have more than mydomain.tld and *.mydomain.tld, I can not just take the first entry in the domain validation options list (domain_validation_options[0] in the pulumi example code).

Moreover, as the creation of AWS Route 53 records is part of a larger code base that creates all kinds of records, I can not take the route shown in the Referencing domain_validation_options With for_each Based Resources pulumi sample code (or at least what I think that sample code does - I have a real hard time understanding it...)

As domain_validation_options is a pulumi output, I managed to figure out that I need to use apply() to select the correct entry for the record being created.

So, with a name variable in my code with a value of e.g. *.api.focussheet.dev, how can I look up the correct entry ?

In "regular" python code, this seems fairly easy, e.g.

from pulumi_aws import route53

name = '*.api.focussheet.dev'

for dvo in cert.domain_validation_options:
    if name == dvo['domain_name']:
        args = {
            'name':     dvo.resource_record_name,
            'records': [dvo.resource_record_value],
            'type':     dvo.resource_record_type
        }
        new_record = route53.Record(name, **args)
        break

but that code fails with TypeError: 'Output' object is not iterable, consider iterating the underlying value inside an 'apply'.

I tried to come up with some apply code that uses a lambda and I also tried creating a function to pass to apply, but I just can't seem to get the syntax and the arguments right.

Can anybody help me ? How do I use pulumi's apply() to select the domain_validation_options entry with a domain_name attribute that is equal to a name variable / argument ?

1

There are 1 best solutions below

0
On

The trick here is understanding what an apply is.

The reason your code is throwing TypeError: 'Output' object is not iterable, consider iterating the underlying value inside an 'apply' is because you are trying to loop over a value that the python interpreter hasn't resolved correctly yet. There's a hint in the error message, consider iterating the underlying value inside an 'apply'.

So in practice, we need to do this:

def validate_domains(dvos):
    for count, dvo in enumerate(dvos):
        aws.route53.Record(
            f'validation-{dvo.domain_name}-{count}',
            name=dvo.resource_record_name,
            records=[dvo.resource_record_value],
            type=dvo.resource_record_type,
            zone_id=zone.zone_id,
            ttl=60,
        )

Here, we create a method to create the route53 validation record for a list of dvos. Then, we just need to use an apply to make sure those values are available to iterate correctly:

cert.domain_validation_options.apply(lambda dvos: validate_domains(dvos))

All of that together, looks like this

import pulumi
import pulumi_aws as aws

config = pulumi.Config()
domain = config.require('domain')

zone = aws.route53.get_zone(name=domain)

cert = aws.acm.Certificate('cert',
                       domain_name=domain,
                       subject_alternative_names=[
                         f'a.{domain}',
                         f'b.{domain}',
                         f'c.{domain}'
                       ],
                       validation_method="DNS")
pulumi.export('domain_validation_options', cert.domain_validation_options)

def validate_domains(dvos):
    for count, dvo in enumerate(dvos):
        aws.route53.Record(
            f'validation-{dvo.domain_name}-{count}',
            name=dvo.resource_record_name,
            records=[dvo.resource_record_value],
            type=dvo.resource_record_type,
            zone_id=zone.zone_id,
            ttl=60,
        )

cert.domain_validation_options.apply(lambda dvos: validate_domains(dvos))