Repository: pulumi/pulumi-awsx
Pull request #1580 (2025-04-04 02:28:33)

[vpc/vpcEndpoints] use shared tags & expose endpoints
👋 I had some internal tagging policies fail when using the awsx.ec2.Vpc. I included the shared tags for the failing children (vpc endpoints) here. When testing it, I discovered that the vpc weren't being added to the component data, so I added that too. Along the way, I wrote some tests for other things that might have had the same problem, and I left them because they seem benign enough, and maybe even helpful.

I'm not familiar with this codebase, but it looks reasonable that `build_sdks` produced no output. 


Patch:
diff --git a/awsx/ec2/vpc.ts b/awsx/ec2/vpc.ts
index aaac1bb5..8fa2588f 100644
--- a/awsx/ec2/vpc.ts
+++ b/awsx/ec2/vpc.ts
@@ -146,7 +146,7 @@ export class Vpc extends schema.Vpc<VpcData> {
           routeTableIds: spec.routeTableIds,
           securityGroupIds: spec.securityGroupIds,
           subnetIds: spec.subnetIds,
-          tags: spec.tags,
+          tags: { ...sharedTags, ...spec.tags },
           vpcEndpointType: spec.vpcEndpointType,
           vpcId: vpc.id,
           serviceName: spec.serviceName,
@@ -156,6 +156,7 @@ export class Vpc extends schema.Vpc<VpcData> {
           dependsOn: [vpc],
         },
       );
+      vpcEndpoints.push(vpcEndpoint);
     });
 
     for (let i = 0; i < availabilityZones.length; i++) {


Test patch:
diff --git a/awsx/ec2/vpc.test.ts b/awsx/ec2/vpc.test.ts
index ecc52f02..4244705d 100644
--- a/awsx/ec2/vpc.test.ts
+++ b/awsx/ec2/vpc.test.ts
@@ -31,6 +31,9 @@ import {
   Vpc,
 } from "./vpc";
 import { Netmask, long2ip, ip2long } from "netmask";
+import * as runtime from "@pulumi/pulumi/runtime";
+import * as pulumiAws from "@pulumi/aws";
+import * as pulumi from "@pulumi/pulumi";
 
 describe("validateEips", () => {
   it("should not throw an exception if NAT Gateway strategy is Single and no EIPs are supplied", () => {
@@ -363,3 +366,107 @@ describe("validating vpc args", () => {
     ).toThrowError();
   });
 });
+
+describe("child resource api", () => {
+  function unwrap<T>(x: pulumi.Output<T> | T): Promise<T> {
+    return new Promise((resolve) => (pulumi.Output.isInstance(x) ? x.apply(resolve) : resolve(x)));
+  }
+
+  beforeAll(async () => {
+    await runtime.setMocks({
+      call(args) {
+        switch (args.token) {
+          case "aws:index/getAvailabilityZones:getAvailabilityZones":
+            const result: pulumiAws.GetAvailabilityZonesResult = {
+              id: "mocked-az-result",
+              zoneIds: [1, 2, 3].map((i) => `${pulumiAws.USEast1Region}${i}`),
+              names: [1, 2, 3].map((i) => `${pulumiAws.USEast1Region}${i}`),
+              groupNames: [1, 2, 3].map((i) => `${pulumiAws.USEast1Region}${i}`),
+            };
+            return result;
+          default:
+            throw new Error(`Mock not implemented: ${args.token}`);
+        }
+      },
+      newResource(args) {
+        return {
+          id: `mocked::${args.type}::${args.name}-id`,
+          state: args.inputs,
+        };
+      },
+    });
+  });
+
+  let vpc: Vpc;
+  beforeEach(() => {
+    vpc = new Vpc("test", {
+      tags: {
+        share1: "parent",
+        share2: "parent",
+      },
+      vpcEndpointSpecs: [{ serviceName: "test", tags: { share2: "override" } }],
+      subnetStrategy: "Auto",
+      subnetSpecs: [
+        { type: "Public", tags: { share2: "override" } },
+        { type: "Private", tags: { share2: "override" } },
+      ],
+    });
+  });
+
+  it("internetGateway", async () => {
+    const igw = await unwrap(vpc.internetGateway);
+    const igwTags = await unwrap(igw.tags);
+    expect(igwTags?.share1).toBe("parent");
+    expect(igwTags?.share2).toBe("parent");
+  });
+
+  it("natGateway", async () => {
+    const ngs = await unwrap(vpc.natGateways);
+    expect(ngs).toHaveLength(3);
+    for (const ng of ngs) {
+      const ngTags = await unwrap(ng.tags);
+      expect(ngTags?.share1).toBe("parent");
+      expect(ngTags?.share2).toBe("parent");
+    }
+  });
+
+  it("vpcEndpoints", async () => {
+    const endpoints = await unwrap(vpc.vpcEndpoints);
+    expect(endpoints).toHaveLength(1);
+    for (const endpoint of endpoints) {
+      const tags = await unwrap(endpoint.tags);
+      expect(tags?.share1).toBe("parent");
+      expect(tags?.share2).toBe("override");
+    }
+  });
+
+  it("subnets", async () => {
+    const subnets = await unwrap(vpc.subnets);
+    expect(subnets).toHaveLength(6);
+    for (const subnet of subnets) {
+      const tags = await unwrap(subnet.tags);
+      expect(tags?.share1).toBe("parent");
+      expect(tags?.share2).toBe("override");
+    }
+  });
+
+  it("eips", async () => {
+    const eips = await unwrap(vpc.eips);
+    expect(eips).toHaveLength(3);
+    for (const eip of eips) {
+      const tags = await unwrap(eip.tags);
+      expect(tags?.share1).toBe("parent");
+      expect(tags?.share2).toBe("parent");
+    }
+  });
+
+  it("routeTables", async () => {
+    const routeTables = await unwrap(vpc.routeTables);
+    expect(routeTables).toHaveLength(6);
+    for (const routeTable of routeTables) {
+      const tags = await unwrap(routeTable.tags);
+      expect(tags?.share1).toBe("parent");
+      expect(tags?.share2).toBe("parent");
+    }
+  });
+});