aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.47k stars 3.83k forks source link

Asynchronous processing (async/await) in a construct #8273

Closed gabor-s closed 4 years ago

gabor-s commented 4 years ago

:question: General Issue

The Question

Is it possible to do asynchronous processing in a Construct? Is there anything in CDK that will wait for a promise to resolve? It seems that everything is synchronous.
I need to resolve a string value to a path, but it could take some time so implemented it asynchronously.

Options considered:

Aspects

The visit method is synchronous.

Tokens

The resolve method is synchronous.

Create the app asynchronously
async function createApp(): Promise<App> {
    const result = await asyncProcessing();
    const app: App = new App();
    // pass result to every stack, bit ugly
    return app;
}
Context

The same as before, but put result into the context, so don't have to pass it to every stack/construct. Honestly, I don't really like this solution, because the construct is reaching out to some known location to get a value. Like process.env calls that are scattered throughout the code.

Is there any support in CDK for asynchronous processing? Or is it an anti-pattern and I'm doing the wrong way?

Environment

Other information

RichiCoder1 commented 1 year ago

It won't solve any other pains that are outlined above, but if you are genuinely blocked on async (ha) and only need it at the top stack level, I'd encourage checking out SST. It's a meta framework built on top of the CDK that, amongst other things, supports async in Stack declarations.

hanseltime commented 1 year ago

Just want to add here that I stumbled across this thread and was pretty disheartened about it as well. However, upon looking at the aws-quickstart-blueprints library for typescript, there is a pattern for async await through the cdk by nature of the fact that node will wait until all promises have resolved.

https://github.com/aws-quickstart/cdk-eks-blueprints/blob/1a8c8c4df6a9bafbffa69c1661828643c44b7dd6/lib/stacks/eks-blueprint-stack.ts#L194

While this isn't free, creating an underlying resource stack and then keeping a list of ordered calls to subsequent addons is a pattern that will chain async behaviors. This definitely has some edges to it, but allows for things like fetching an existing secret and running bcrypt on it for a kubernetes secret that is created for argocd.

https://github.com/aws-quickstart/cdk-eks-blueprints/blob/1a8c8c4df6a9bafbffa69c1661828643c44b7dd6/lib/addons/argocd/index.ts#L134

It would be nice to create a further abstraction of this current library that does not specifically target EKS cluster's but it can be cannabalized to basically be a build method that calls X number of "AddOn" interfaces that have an async deploy/postDeploy method in successive order after creating the underlying resource.

The only place I can see this being problematic would be if a resource is late created in one of these methods and immediately accessed in another constructor (so it is not without teeth, but I believe I can make this edge case go away for usage of custom_resources).

Would love to see this more readily supported by the base framework though

aprat84 commented 1 year ago

@eladb

The IP allow list above is a good example - performing an HTTP request during synthesis means that build output can change without any change to the source, but commiting a JSON file to your source control is a reasonable way to avoid this and get all the benefits of a deterministic build. The async constraint was the reason we even had that conversation :-)

In my case, I need to create a CloudFront PublicKey, so I have a script to create/rotate public/private key contents, and some metadata (createdAt), and save it to a System's Manager parameter. Then save the parameter name in cdk.context.json.

At synth time I need to retrieve the public key contents to create the PublicKey, so I'm forced to do an async call to the AWS SSM API...

I don't think saving key contents to source control is a good practice 😅

deuscapturus commented 1 year ago

Workaround using Lazy to resolve the value from a immediately invoked async function:

import { StringParameter } from 'aws-cdk-lib/aws-ssm';
import { Lazy, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { ECRClient, DescribeImagesCommand } from '@aws-sdk/client-ecr'

export class AsyncInConstructStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    let digest: string = "not set";

    (async () => {
        const ecrClient = new ECRClient({ region: "us-east-1" })
        const result = await ecrClient.send(new DescribeImagesCommand({
            repositoryName: "my-repo",
            imageIds: [{imageTag: "latest"}]
        }))

        digest = result.imageDetails![0].imageDigest!
    })()

    new StringParameter(this, 'parameter', {
      stringValue: Lazy.string({ produce: () => digest }),
    })
  }
}

Synth Output:

Resources:
  parameter76C24FC7:
    Type: AWS::SSM::Parameter
    Properties:
      Type: String
      Value: sha256:a8cc10cabc4278563c1a915a226db413dc0ec2c064d325d653435b20b08f4872
    Metadata:
      aws:cdk:path: AsyncInConstructStack/parameter/Resource
adam-nielsen commented 1 year ago

@deuscapturus Wouldn't that solution result in race conditions leading to possible random failures?

To test this, if you put a delay in the async function, does it still work? e.g.

await new Promise((resolve) => setTimeout(resolve, 30000)); // 30 second delay to simulate slow API
digest = ...
deuscapturus commented 1 year ago

@deuscapturus Wouldn't that solution result in race conditions leading to possible random failures?

To test this, if you put a delay in the async function, does it still work? e.g.

await new Promise((resolve) => setTimeout(resolve, 30000)); // 30 second delay to simulate slow API
digest = ...

I tested for race conditions before posting.

onhate commented 12 months ago

The explanation of non deterministic doesn't make sense at all because it only applies to nodejs nature of async operations, in Java cdk one could consult a database or make an http call and proceed with the cdk based on that response because Java will do that in "sync" way. The real issue is that the code architecture to use class constructors to build your infrastructure is non sense (this is the real anti pattern here), and in nodejs you cannot have async class instantiation.

arash-cid commented 9 months ago

@eladb open this ticket up, please This is ridiculous

Most people's views are against your view

arash-cid commented 9 months ago

steffenstolze

How do you use the same/current credentials of cdk into sdk when creating an instance from a class?

e.x.

.
.
         const sdk_apigw = new sdk.APIGateway({credentials: xxx});`