Hey :wave:, having a bit of struggle with filterin...
# typescript
k
Hey 👋, having a bit of struggle with filtering
pulumi.Output
arrays. Consider the following use case: • You are using Crosswalk for AWS and you have an instance of
awsx.ec2.Vpc
• You want only to use specific subnets from this VPC which you filter based on some criteria Now, due to the nature of
pulumi.Output
, the following code seems not to work as expected
Copy code
const vpc = new awsx.ec2.Vpc(...)

    const matches = vpc.subnets.apply((subnets) =>
      subnets.filter((subnet) => subnet.id.apply((id) => id === 'subnet-...'))
    );
matches
will contain all the entries in
subnets
. This, I assume, is because
subnets.id.apply()
returns an object, therefore, always true. Any suggestions to this?
b
It contains all entries because anything assigned to apply is async, you need to do your operation inside the apply.
you’re filtering subnets, what exactly do you want to do them with when you filter?
k
Few different use cases. One being to create a security group with rules that are only related private subnet CIDR blocks.
b
so you should have outputs for the private subnets and the public subnets, do you need individual subnets from those sections?
k
If each type of subnets would be available in awsx.ec2.Vpc, that would help quite much.
As separate arrays
b
vpc.publicSubnetIds
and
vpc.privateSubnetIds
are available
k
Yes, that is correct but those are just IDs, not fully detailed
aws.ec2.Subnet
objects.
But what I’m hearing is that the only way to do something is inside the innermost
apply()
and not to use
Array.filter()
at all? This is probably doable but just makes code a bit less readable.
b
you can use
aws.ec2.getSubnet
and pass the ID if needed
But what I’m hearing is that the only way to do something is inside the innermost apply() and not to use Array.filter() at all?
you can’t use
Array.filter
on an output no, you need to resolve the output then use it, which happens inside an apply
there are almost certainly simpler ways to achieve what you want to achieve, it’s not super clear what you want to do 🙂
k
Maybe this helps to understand the use case. Consider that you have an RDS instance running on a private subnet and you want to create a security group to allow access from public subnets to it:
Copy code
const vpc = new awsx.ec2.Vpc(...)

    const publicSubnetCidrs = ...

    const securityGroupRule = new aws.ec2.SecurityGroup(
      "allow-access-from-public-subnets",
      {
        name,
        description: 'Allow access from publicSubnet',
        vpcId: vpc.vpcId,
        ingress: [
          {
            description: 'Allow traffic from subnets',
            fromPort: 5432,
            toPort: 5432,
            protocol: 'tcp',
            cidrBlocks: publicSubnetCidrs,
          },
        ],
      });
I was hoping for a simple
vpc.subnets
array filter approach that returns an Output array of the CIDR blocks but I did forget that pulumi.Outputs are treated in a special way (which is understandable as those are not resolvable immediately). This is probably the first stumbling block for me that took a while to catch. Otherwise, everything has made sense and we haven’t had too many difficulties on our path to use Pulumi. To solve the above case, you just need to collect the cidr blocks inside the apply and create
SecurityGroup
inside it.
Looks like I’m not the only one struggling with the issue: https://pulumi-community.slack.com/archives/C84L4E3N1/p1691376055936789 Note that I still haven’t figured out a decent solution to this. Even with `apply()`s.
b
It’s definitely possible, but it’s going to have to happen inside an apply
I’m personally not really understanding why you wouldn’t just use the VPC cidr as the cidrBlock, the private subnets and public subnets exist in the same cidr 🙂
k
Be least permissive. But in this case using the VPC CIDR block would be fine. However, there are surely use cases where you don’t want to be that permissive depending on your deployment architecture. The tool enforcing this kind of complexity and limitation would not be desirable. Based on what I see in https://github.com/pulumi/pulumi-awsx/blob/master/awsx/ec2/vpc.ts#L168-L175, this wouldn’t be too difficult to contribute but I don’t feel that confident just yet 🙂
For now, it looks like we can settle with the following approach. Wrap the VPC to a custom resource and use
aws.ec2.getSubnet()
as suggested. This is a simplified example but you should get the point:
Copy code
export class Vpc extends pulumi.ComponentResource {
  readonly instance: awsx.ec2.Vpc;

  readonly publicSubnets: pulumi.Output<pulumi.Output<GetSubnetResult>[]>;
  readonly privateSubnets: pulumi.Output<pulumi.Output<GetSubnetResult>[]>;

  constructor(
    private readonly name: string,
    private readonly args: awsx.ec2.VpcArgs = {},
    private readonly opts: pulumi.ComponentResourceOptions = {}
  ) {
    super('pkg:index:MyComponent', name, {}, opts);

    this.instance = new awsx.ec2.Vpc(name, {}, { parent: this });

    this.publicSubnets = this.getSubnetsInternal(this.instance.publicSubnetIds);
    this.privateSubnets = this.getSubnetsInternal(this.instance.privateSubnetIds);

    this.registerOutputs();
  }

  private getSubnetsInternal(
    subnetIds: pulumi.Output<string[]>
  ): pulumi.Output<pulumi.Output<GetSubnetResult>[]> {
    return subnetIds.apply((ids) =>
      ids.map((id) => aws.ec2.getSubnetOutput({ id }))
    );
  }
}
One can then consume the resource as follows:
Copy code
const vpc = new Vpc('my-vpc');

const privateSubnetCidrBlocks = vpc.privateSubnets.apply((subnets) =>
  subnets.map((subnet) => subnet.cidrBlock)
);

const securityGroup = new aws.ec2.SecurityGroup('rds-security-group', {
  vpcId: vpc.instance.vpcId,
  ingress: [
    {
      description: 'Private subnet access to RDS',
      fromPort: 5432,
      toPort: 5432,
      protocol: 'tcp',
      cidrBlocks: privateSubnetCidrBlocks,
    },
  ],
});