Is there a reason this shouldn’t work:
# general
s
Is there a reason this shouldn’t work:
Copy code
import * as aws from "@pulumi/aws";

async function main() {
    const vpc = new aws.ec2.Vpc("vpc", {
        cidrBlock: "10.0.0.0/16"
    });

    const rt = await aws.ec2.getRouteTable({
        routeTableId: vpc.defaultRouteTableId
    });
}

const _ = main();
Results in:
Copy code
❯ pulumi preview
Previewing update of stack 'unparented-from-read-dev'
Previewing changes:

     Type                 Name                                           Plan       Info
 +   pulumi:pulumi:Stack  unparented-from-read-unparented-from-read-dev  create     3 errors
 +   └─ aws:ec2:Vpc       vpc                                            create

Diagnostics:
  pulumi:pulumi:Stack: unparented-from-read-unparented-from-read-dev
    error: Running program '/Users/James/Code/pulumi/repros/unparented-from-read' failed with an unhandled exception:

    error: Error: invocation of aws:ec2/getRouteTable:getRouteTable returned an error: invoking aws:ec2/getRouteTable:getRouteTable: One of route_table_id, vpc_id, subnet_id, filters, or tags must be assigned
        at monitor.invoke (/Users/James/Code/pulumi/repros/unparented-from-read/node_modules/@pulumi/pulumi/runtime/invoke.js:61:33)
        at Object.onReceiveStatus (/Users/James/Code/pulumi/repros/unparented-from-read/node_modules/grpc/src/client_interceptors.js:1189:9)
        at InterceptingListener._callNext (/Users/James/Code/pulumi/repros/unparented-from-read/node_modules/grpc/src/client_interceptors.js:564:42)
        at InterceptingListener.onReceiveStatus (/Users/James/Code/pulumi/repros/unparented-from-read/node_modules/grpc/src/client_interceptors.js:614:8)
        at callback (/Users/James/Code/pulumi/repros/unparented-from-read/node_modules/grpc/src/client_interceptors.js:841:24)

    error: an unhandled error occurred: Program exited with non-zero exit code: 1

error: an error occurred while advancing the preview
w
The issue here is that during a
preview
the VPC is not yet created, meaning that
vpc.defaultRouteTableId
doesn't have any value and
undefined
will get passed into data source, leading to the error you see. We plan to "fix" this by not allowing you to write this code this way - by making data-sources not allow
Input<T>
as inputs, only
T
. See https://github.com/pulumi/pulumi/issues/1483#issuecomment-399282301. The following is a way you can write this which should work correctly today, and will be the only way that works after the changes noted in the issue above.
Copy code
import * as aws from "@pulumi/aws";

const vpc = new aws.ec2.Vpc("vpc", {
    cidrBlock: "10.0.0.0/16"
});

const rt = vpc.defaultRouteTableId.apply(rtb => aws.ec2.getRouteTable({
    routeTableId: rtb,
}));
The rewritten code indicates via
.apply
that it will only run after there is a known value of the
vpc.defaultRouteTableId
that this code depends on.
s
Thanks, that makes sense - enforcing that via the type system will be nice.
Related to this, is the canonical way to (say) apply tags to a default route table to create a new resource in an apply and specify the existing ID in the
ResourceOptions
in order to “adopt” it?
Something like this:
Copy code
const publicRouteTable = await vpc.defaultRouteTableId.apply(rtb => aws.ec2.getRouteTable({
            routeTableId: rtb
        }));

        publicRouteTable.apply(id => {
            const publicRouteTableOptions = Object.assign({
                id: vpc.defaultRouteTableId
            }, vpcParent);
            const publicRouteTableTags = Object.assign({
                Name: `${inputs.description} Public RT`
            }, inputs.baseTags);

            return new aws.ec2.RouteTable(`${baseName}-public-rt`, {
                vpcId: vpc.id,
                tags: publicRouteTableTags
            }, publicRouteTableOptions);
        });

        new aws.ec2.Route(`${baseName}-route-public-sn-to-ig`, {
            routeTableId: publicRouteTable.apply(prt => prt.id),
            destinationCidrBlock: "0.0.0.0/0",
            gatewayId: internetGateway.id
        }, vpcParent);
w
That is a tough one. In general, modifying properties of resources that are not managed by a Pulumi deployment is something that isn't really supported today. Resource that are retrieved from outside Pulumi are treated as external and we will not do Update/Delete operations on them (just re-Read them when needed). Its an interesting idea to find some way to allow this combination though. /cc @incalculable-sundown-82514 and @microscopic-florist-22719 as well in case they have thoughts.
s
Ah OK, makes sense. In the Terraform AWS provider we had to add an
aws_default_route_table
(etc) to deal with that particular case.
Perhaps an overlay could add methods that return those resources for a given VPC?
w
Not sure I follow how this scenario could work in Terraform either? (outside of using importers I suppose).
s
There are special resources for it
It turns out that they made their way into Pulumi too, so you can do this:
Copy code
publicRouteTable.apply(id => {
            const publicRouteTableTags = Object.assign({
                Name: `${inputs.description} Public RT`
            }, inputs.baseTags);

            return new aws.ec2.DefaultRouteTable(`${baseName}-public-rt`, {
                defaultRouteTableId: id.id,
                tags: publicRouteTableTags
            }, vpcParent);
        });
(once you have the route table ID, at least)
The interesting thing though is of course the DefaultRouteTable never shows up in the preview because the
apply
is never executed at that time.
(Interesting to me, at least, it isn’t surprising given the documentation of
apply
!)
Yup, I can bring that outside and all appears well:
Copy code
Do you want to perform this update? yes
Updating stack 'pulumi-subnets-test-dev'
Performing changes:

     Type                                           Name                                         Status      Info
 +   pulumi:pulumi:Stack                            pulumi-subnets-test-pulumi-subnets-test-dev  created
 +   └─ operator-error:aws:Vpc                      demo                                         created
 +      └─ aws:ec2:Vpc                              demo-vpc                                     created
 +         ├─ aws:ec2:VpcDhcpOptions                demo-dhcp-options                            created
 +         │  └─ aws:ec2:VpcDhcpOptionsAssociation  demo-dhcp-options-assoc                      created
 +         ├─ aws:ec2:InternetGateway               demo-igw                                     created
 +         ├─ aws:ec2:Subnet                        demo-public-1                                created
 +         │  └─ aws:ec2:RouteTableAssociation      demo-public-rta-1                            created
 +         ├─ aws:ec2:Subnet                        demo-public-2                                created
 +         │  └─ aws:ec2:RouteTableAssociation      demo-public-rta-2                            created
 +         ├─ aws:ec2:Subnet                        demo-public-3                                created
 +         │  └─ aws:ec2:RouteTableAssociation      demo-public-rta-3                            created
 +         ├─ aws:ec2:Subnet                        demo-private-1                               created
 +         ├─ aws:ec2:Subnet                        demo-private-2                               created
 +         ├─ aws:ec2:Subnet                        demo-private-3                               created
 +         ├─ aws:route53:Zone                      demo-private-hosted-zone                     created
 +         └─ aws:ec2:DefaultRouteTable             demo-public-rt                               created
 +            └─ aws:ec2:Route                      demo-route-public-sn-to-ig                   created
👍 2