Struggling with a stack export, and a hosted zone...
# general
b
Struggling with a stack export, and a hosted zone ID. It works simply from the top level class, but not from a component, if I pull that property to the top level file. Example working:
Copy code
hosted_zone = aws.route53.Zone(
    "myHostedZone",
    name="testzone.testing",  # replace with your domain name
)

pulumi.export("zone_id", hosted_zone.id)
Example, not working:
Copy code
# a component class with this function, called from the constructor. 

    def create_private_zone(
        self: pulumi.ComponentResource,
        args: Route53ZoneArgs,
        opts: pulumi.ResourceOptions,
    ):
        zone = aws.route53.Zone(
            f"{args.domain_name}",
            name=args.domain_name,
            comment=args.comment,
            vpcs=[
                aws.route53.ZoneVpcArgs(vpc_id=args.vpc_id, vpc_region=args.vpc_region)
            ],
            force_destroy=args.force_destroy,
            tags=args.tags,
            opts=opts,
        )

        self.zone
and the usage:
Copy code
publicAwsDomain = Route53Zone(
        "public-zone",
        Route53ZoneArgs(
            domain_name="aws.test.com",
        ),
    )

    pulumi.export(
        Outputs.PUBLIC_ZONE_ID, publicAwsDomain.zone.zone_id
    )
Gives me this error:
Copy code
File "/Users/bswift/src/client/xxx/datacenter_migration/account/venv/lib/python3.11/site-packages/pulumi/runtime/stack.py", line 242, in <dictcomp>
        key: massage(attr[key], seen) for key in attr if not key.startswith("_")
                                                             ^^^^^^^^^^^^^^
    AttributeError: 'Outputs' object has no attribute 'startswith'
l
Could args.domain_name be an output? You can't use an output as a resource name. The name has to be known at runtime.
Also, you can't interpolate an output.
Is it ever necessary to have
f"{singlevar}"
? If you see that, you can use
singlevar
, right?
b
yes, singleVar can of course. That above was left from taking a clients name out of the front for the example 🙂 The domain name is not an output but just a string passed in. This one was rather dumb, but the output Enum I had overridden the string method - which works in most cases but not with pulumi outputs I guess.
Copy code
class Outputs(Enum):
...

  def __str__(self):
      return str(self.value).
The error didn't really give me any stack trace on my line of code that would make sense.
Anyways, after getting around that, I was passing in zone name and zone id into a CNAME record. Another oddity, I had to put the entire Route53 code inside a
domain_name.apply()
block, however the zone_id was not required. Even though both were brought in as ouptuts.
Copy code
zone_id = self.account_stack.get_output(Outputs.PUBLIC_ZONE_ID.value)
        zone_name = self.account_stack.get_output(Outputs.PUBLIC_ZONE_NAME.value)

        # zone ID is known but zone name is not, so it has to be applied here.
        zone_name.apply(
            lambda x: aws.route53.Record(
                "cnameRecord",
                name=f"{self.stack}.{x}",  # replace with your domain name
                records=[self.alb.dns_name],
                ttl=300,
                type="CNAME",
                zone_id=zone_id,
            )
        )
l
You shouldn't need to wrap the entire thing in an apply. You can put the apply on the line where it's needed, inside the Record constructor args.
apply returns another output, and constructor args can take outputs
b
Trying to not complain too much, but it's a little odd. I have used CDK a lot more than Pulumi, and with CDK these "Tokens" as they're known are all handled seamlessly, and we don't end up with this type of situation of having to put things into async "ish" blocks. Just takes a bit more to wrap your head around I guess.
I tried this at first, but didn't work:
Copy code
record_name = zone_name.apply(lambda x: f"{self.stack}.{zone_name}")

        cname_record = aws.route53.Record(
            "cnameRecord",
            name=record_name,  # replace with your domain name
            records=[self.alb.dns_name],
            ttl=300,
            type="CNAME",
            zone_id=zone_id,
        )
oh, sorry I see the error, used zone_name instead of x. 😄 nm. 🙂
l
You shouldn't need that either.
Why can't you use zone_name directly on the name=record_name line?
It's an output, it should work.
If you're interpolating, you need to do a bit more.
b
i dunno, it was puking errors.
i'll try that in the morning and see if I can learn something from this.
basicallythe pulumi up was failing
because it was setting the cname record to a pulumi exception string
l
That usually happens when you're interpolating, or when you're passing the output into a function/method that casts the parameter to a string.
It has to remain an output (or better, an input) the entire way.
b
ok.
Copy code
+ aws:route53/record:Record: (create)
        [urn=urn:pulumi:bswift::LB::aws:route53/record:Record::cnameRecord]
        [provider=urn:pulumi:bswift::LB::pulumi:providers:aws::default_6_10_0::d5cf0383-2cb0-435f-b791-5a48e913f244]
        name      : "bswift.Calling __str__ on an Output[T] is not supported.\n\nTo get the value of an Output[T] as an Output[str] consider:\n1. o.apply(lambda v: f\"prefix{v}suffix\")\n\nSee <https://www.pulumi.com/docs/concepts/inputs-outputs> for more details.\nThis function may throw in a future version of Pulumi."
        records   : [
            [0]: "<http://bswift-load-balancer-lb-4f6f446-1324424032.us-west-2.elb.amazonaws.com|bswift-load-balancer-lb-4f6f446-1324424032.us-west-2.elb.amazonaws.com>"
        ]
        ttl       : 300
        type      : "CNAME"
l
Yes, that's the expected error
I just noticed in your original post that the type
Route53ZoneArgs
isn't a Pulumi type. Is that right?
b
yea that was the error. I thought I was just passing the output around and not changing it. but apparently not
right that's the custom args for our Route53Zone class. it has a zone property on it. But that zone isn't really a usable object.
l
But is domain_name a string in that custom type?
b
yes
l
That's your problem.
All those outputs have to remain as outputs. You can't cast them to strings.
What's the Python equivalent of Input<string>? You need to use that.
b
got it
l
Actually I meant zone_name. But hopefully you get the gist: don't auto-cast anything to a string: if it starts off as an output, the cast will create one of those Pulumi exception messages as a string, and that'll be the value you're using.
It kind of hides the error until runtime, which is really annoying. But it becomes easy to spot with experience.
b
yea, the difficulty is that it's an implicit cast so hard to catch.
I noticed you must come from a typescript background, is typescript a bit more intuitive with Pulumi? I'm guessing it gives you more compile time errors - as opposed to the magic dynamically typed python.
or inferred rather from your use of generics.
l
I don't know, I've never use Python. Typescript would catch this if you had strongly-typed everything (which I do), but the idiom in Typescript is to not strongly-type unless you need to.. so it wouldn't catch it.
But I use Typescript as if I was a Java or C# programmer, so yes, it catches a lot for me.
b
Well thanks again. I will be able to teach my team something from this, as we are still pretty new to Pulumi.