chrsmith / pulumi-aws-travis-cicd-demo

Demo of using Pulumi to deploy AWS resources via Travis CI
9 stars 1 forks source link

Referencing types from @pulumi/aws at runtime #5

Open abjrcode opened 3 years ago

abjrcode commented 3 years ago

Hi Chris,

Thank you for the very useful example. I was going through it and adjusting it for my needs but then I came across this line:

https://github.com/chrsmith/pulumi-aws-travis-cicd-demo/blob/master/infrastructure/key-rotator/iam-key-rotator.ts#L102

I don't get how this could work at runtime as you are referencing aws.iam.User type from within the @pulumi/aws package and according to the docs this shouldn't be possible by design?

Am I missing something here?

chrsmith commented 3 years ago

Hi @abjrcode!

It is correct that the way Pulumi interacts with cloud resources at runtime, and how resources get referenced/captured inside closures that get serialized and passed to AWS Lambda is super-tricky. But I can assure you that this code totally works. We are using a newer version internally at Pulumi for rotating our own CI/CD accounts.

Off the top of my head, I cannot explain precisely how things are working under the hood. But let me call out a few aspects of the code that might help you get unblocked with your setup.

you are referencing aws.iam.User type from within the @pulumi/aws package and according to the docs this shouldn't be possible by design?

You can reference a cloud resource, such as aws.iam.User at runtime, but it's definitely a tricky situation.

The problem is that, suppose you pass an instance of aws.iam.User to a function. At runtime, how would you expect that to work? Would it contain the live view of that resource? Or the static version that the Pulumi CLI saw when you were running pulumi up.

Let's look at the code you pointed out more carefully:

https://github.com/chrsmith/pulumi-aws-travis-cicd-demo/blob/549a879106580af5f9b118c6a83fe97e1bec1a05/infrastructure/key-rotator/iam-key-rotator.ts#L102-L162

This line of code is referencing the Pulumi resource (that was passed into this function) at runtime. So this is a situation where the value gets "baked into" the JavaScript code that gets uploaded to Lambda. But notice it is just referencing the IAM User's name, which is ~immutable once the resource is created.

const iamUserName = iamUser.name.get(); 

Later, we use the AWS API directly to interact with IAM. This is important -- we aren't creating/updating/deleting the IAM User Access Keys using Pulumi. Rather we are performing those operations using AWS's APIs directly. Only the IAM User resource is created using Pulumi.

const iam = new aws.sdk.IAM(); 

So the part that seems tricky -- and again, I'll be honest and say I cannot explain why/how this works -- but it looks like we are passing the iam.User resource into the Lambda's source here:

https://github.com/chrsmith/pulumi-aws-travis-cicd-demo/blob/549a879106580af5f9b118c6a83fe97e1bec1a05/infrastructure/key-rotator/iam-key-rotator.ts#L189-L208

... and reviewing the "v2" of this Pulumi component that we are using internally, the code is nearly identical.

Does that help? Let me know if you are still having trouble here. If you include a code snippet and the error messages you are seeing, that would be useful to help troubleshoot your problem.

abjrcode commented 3 years ago

Thank you so kindly for this elaborate and detailed reply and the patience. I tried to learn Pulumi and TypeScript and apply them at my new job all in one week so I was seeking fast answers.

With that being said, I managed to find a solution to my problem by just apply()ing the username and passing that onwards to the lambda as a primitive type.

My scenario that I was running into was the result of perhaps... well first of all the error that I was getting was about the lambda trying to load a type from the @pulumi/aws package and that was the only thing that came to mind and of course I didn't have source maps setup to see what exact line is actually causing that :(

What I was doing in my flawed version of the code and which I will demonstrate with a code snippet is the following: I wanted that the Pulumi program either creates the IAM user when deploying to a test account or looks up existing users when deploying to the production account.

I was looking up the existing user using getUser() then I extended the Resource Component to accept a union of that. Let me show:

type NewIamUser = aws.iam.User;
type ExistingIamUser = pulumi.Output<aws.iam.GetUserResult>;

type IamUser = ExistingIamUser | NewIamUser;

I would pass the IamUser type to the lambda runtime and later on extract the username from the union types.

function getUsername(user: IamUser): string {
  if (user instanceof ExistingIamUser) {
    return user.userName;
  }
  return user.name
}

Well that's what I had and it didn't work, but the problem is solved now although at some point I would love to find out why it didn't work.

I guess the part that really still puzzles me is the following:

When creating Custom Components, it feels like the args to it should actually be Input<T> but your example and others don't seem to favor that but rather pass primitives or existing types from @pulumi/aws for instance.