Open hassankhan opened 6 years ago
Hi @hassankhan,
The aws-cdk
NodeJS package does expose some library functions that can be used from JavaScript/TypeScript projects directly. However, it's not a polished experience at the moment, because our attention has been focused more towards the construct library so far. You're very welcome to try it and tell us if you run into any issues, though. As a last resort, you can always shell out to the command-line tool.
I'm also curious: could you tell us something about the kinds of integration that you're thinking of?
Hi @rix0rrr,
Thanks for the info! In an ideal world, I'd love to see something like:
import CDK from '@aws-cdk/cdk';
import MyApp from './MyApp';
const cdk = new CDK({ app: '', versionReporting: false });
cdk.deploy(new MyApp())
.then(() => {
return remove(new App());
})
With regards to integration, essentially, our aim was to define resources such as Lambdas and DynamoDB tables once in our application code, and generate valid snippets from them and insert them into a template for deployment. For example, a theoretical DynamoDB
model would look something like this (using dynamodb-data-mapper
):
@table('pets')
class Pet {
@hashKey()
id: string;
@rangeKey({defaultProvider: () => new Date()})
createdAt: Date;
@attribute()
isCat?: boolean;
}
The table schema would then be read from this class as part of the deployment process, converted into its constituent Construct
and added to the App
/Stack
. We could use the same approach for other resource types such as SNS Topics and Lambda functions.
Okay, thanks. I definitely see the value of the first one, especially in defining one-off tasks like EMR jobs or integ tests.
For the second one: you can already do this today, even without tool integration.
If you define you data model in a library, you can use this library both in your runtime code as well as introspect it in your infrastructure code. Your build step would produce both a CloudFormation template as well as a JAR, and during deployment you deploy both together.
+1 I mentioned this in a duplicate ticket as well.
I'd like this too, in my case I'd like to wrap a deploy
of an ECS cluster so I can scale it manually after the deploy is done (I create it with desiredCount: 0
so the CloudFormation creating is quicker).
Additional use cases from https://github.com/awslabs/aws-cdk/issues/1133 - synthesizing stacks from an IDE debugger and embedding into JVM build systems (like Gradle) without having to shell out to a CLI that depends on node.js.
My team is currently programmatically invoking CDK to support dynamic composition of constructs to create stacks based on the answers to question we ask in a custom CLI tool we wrote. We are currently primarily using it to spin up either SAM apps, or static sites using codepipeline, codebuild, rds, vpc, ssm params, s3 buckets, cloudfront, certs, zones, etc.
I'd very much like an officially supported way of accomplishing this. Currently we are doing some very dirty things to mimic the nature of the CDK CLI and the fact it executes the cdk app multiple times to meet dependencies.
If the CDK team does not have plans to officially support this, I'd very much appreciate not making it harder to achieve this as the notes in https://github.com/awslabs/aws-cdk/issues/2044 and https://github.com/awslabs/aws-cdk/issues/2016 suggest.
Is there anyway of triggering AWS CDK ClI (CDK synth , CDK deploy) commands pro-grammatically in typescript ?
I found a way to handle this. I'll create an example repo (hopefully) to demonstrate how I accomplished this process, but the short version looks like this:
Create an AppStack
instance and set the synthesizer
to the output of app.run()
:
import { AppStacks } from 'aws-cdk/lib/api/cxapp/stacks'
import { Configuration } from 'aws-cdk/lib/settings';
// ...
const out = this._app.run();
const argv = {
'$0': this._argv['$0'],
'_': this._argv['_']
}
const configuration = new Configuration(argv)
await configuration.load()
const appStacks = new AppStacks({
aws: this.sdk,
configuration: configuration,
synthesizer: async () => out
})
Using the AppStack
instance, create an instance of the CdkToolkit
:
const appStacks = await this.getAppStacks();
const provisioner = new CloudFormationDeploymentTarget({
aws: this.sdk,
})
const cli = new CdkToolkit({ appStacks, provisioner });
In order to get the credentials, we'll need to muck around at a low-level with the AWS-SDK:
import { debug } from '@aws-rocket/core';
import AWS = require('aws-sdk')
import { SDK } from 'aws-cdk/lib/api/util/sdk';
import { Mode } from 'aws-cdk/lib/api/aws-auth/credentials'
async function getCredentialsConfig(sdk: SDK): Promise<any> {
const region = await sdk.defaultRegion()
const defaultAccount = await sdk.defaultAccount()
const credentials = await (sdk as any).credentialsCache.get(defaultAccount, Mode.ForReading)
return {
region,
credentials
}
}
export const awsCredentialsMiddleware = async (argv: any) => {
debug(`Setting profile to: ${argv.profile}`)
if (argv.profile) {
const sdk = new SDK({
profile: argv.profile
})
const credentials = await getCredentialsConfig(sdk)
AWS.config.update(credentials)
AWS.config.setPromisesDependency(Promise);
argv.AWS = AWS
argv.SDK = sdk
}
return argv
}
Finally, this all comes together using the actual deploy like so:
const appStacks = await this.getAppStacks();
const allStacks = await appStacks.listStacks()
const allStackNames = allStacks
.map((s: cxapi.CloudFormationStackArtifact) => s.name)
const cli = await this.getCdkToolkit()
try {
const res = await cli.deploy({
stackNames: allStackNames,
// `requireApproval` is so that the user never needs to
// accept anything for the deploy to actually occur
requireApproval: RequireApproval.Never,
})
console.log('deploy result ->', res);
} catch (e) {
console.error(`Error deploying`, e);
process.exit(-1)
}
Hope this helps and hopfully this becomes a part of the actual library. I'll try to submit a PR if I can, but no promises. If anyone else has any better way of handling this, I'd love suggestions.
This is all very interesting and hopeful that it will get implemented 🤞
@auser - Were you ever able to put together a repo / gist with all of this information? I follow the logic but I'm not sure how I follow how to assemble all of the pieces. Is each code snippet supposed to be in the same file and if so does each code section follow one an other? I can guess, but I also don't know where the this._app
and this._argv
come from.
@fulghum / @shivlaks - It looks like there is some movement towards an implementation. Do feel programmatic access might be limited in some areas or is the goal to have a full API access the same you would have on command line?
More or less, do you think something like the following would be supported:
import AWS from 'aws-sdk';
import { deploy } from 'aws-cdk';
import MyStack from './my-stack';
const cfnOutput = await deploy(new MyStack());
console.log(`Successfully deployed stack:\n${cfnOutput}`);
const s3FileAndDetails = { /* ... */ };
const s3 = new AWS.S3();
await s3.putObject(s3FileAndDetails).promise();
Also, instead of needing to run the configuration like @auser has above:
const argv = {
'$0': this._argv['$0'],
'_': this._argv['_']
}
const configuration = new Configuration(argv)
await configuration.load()
It would be great if we could skip the command line altogether and just pass in a json object with relevant configuration. (Thereby eliminating the need for a cdk.json
, but one could exist with overrides)
// Use factory function to allow reading config overrides in async
const configuration = await Configuration.create({ /* json from cdk.json */ })
If you want to synth programmatically to the directory of your choice...
// Let's say you want it synthed to ./build
const outdir = path.join(process.cwd(), 'build')
// App's constructor let's you specify the outdir
const app = new cdk.App({outdir})
new ApiLambdaCrudDynamoDBStack(app, 'ApiLambdaCrudDynamoDBExample')
app.synth()
If you want to inspect what was produced app.synth()
returns a CloudAssembly
which can be interrogated with getStack('your stack name')
I haven't tried deploying programmatically yet.
When implementing this feature, would it make sense to do it in the context of aws-sdk-js-v3
?
https://github.com/aws/aws-sdk-js-v3
(One of) the premise of CDK being "programmatic CloudFormation", it opens the way for fully programmable infrastructure, potentially even starting with the account creation (and that's awesome, we (building an "Ops as a Service" product) have been hoping for this for quite some time, even looking into developing our own solution). Creating a full, real-life infrastructure requires more features than CloudFormation can offers, even considering CustomResources. Some of those will require the SDK ; some others need to talk to 3rd party services ; fetch data before ; output/notify after.
Being able to use CDK not so much as a toolkit/cli, not as a complete solution, but as a lib, as a way to integrate the infrastructure-control layer into a programm would be great. As great as CDK promise to be, it's not a complete solution, probably can't, and shouldn't even try to be one. Of course, one can always shell out, wrap around CDK CLI, but that's not ideal.
It ties in with a few other GitHub issues:
Another use case : when your CDK stack creates Cognito UserPool Client Apps, it generates client id and secrets that need to be injected in your clients. If your client is a webapp in S3, that means you have to do steps in this order:
If it was possible to deploy programmatically, it could be possible to have the whole process written in the same tool (for example nodejs). Otherwise (as of now I do this way), you have to rely to some other external tool to call the CLI multiple times and schedule these actions (like a bash script, or other nodejs script through process exec..).
I just found a nice little helper that wraps some of this here: https://github.com/dmitrii-t/cdk-util May be worth a look for people that want to do this type of thing. Definitely can't wait to see it in the core API with first class support.
@jfaixo
If it was possible to deploy programmatically
It is.
import * as s3 from "@aws-cdk/aws-s3";
import * as s3deploy from "@aws-cdk/aws-s3-deployment"; // <<--- this right here
import * as cdk from "@aws-cdk/core";
export class WebStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const websiteBucket = new s3.Bucket(this, "WebsiteBucket", {
websiteIndexDocument: "index.html",
publicReadAccess: true,
versioned: true
});
new s3deploy.BucketDeployment(this, "DeployWebsite", {
sources: [s3deploy.Source.asset("./build")], // <<--- folder to upload
destinationBucket: websiteBucket
});
}
}
Unfortunately I have found that the code and helpers I can find online are all out of date with the latest version of the CDK. How are people using the CDK in the case where you have a command line tool that needs to dynamically allocate and manage resources on AWS?
I am looking to use it from JAVA.
I've been able to sort of get this working programatically but have discovered that the cli.deploy etc functions return void, undefined, boolean and 0/1 values instead of useful values like cloudformation stack arns, outputs etc.
from @benwainwright
Currently, I can get a list of stacks that will be generated by my project in the order they should be deployed (this is important; see below) by running the command cdk list It would be useful if the output from this command could be exposed as an api in a way that doesn't require full synthesis of the templates under the hood.
full use case in aws/aws-cdk#8436
This would be wonderful. Now it feels like CI integration is a pain.. You have to parse output for changeset names and stack ids after deploying to do any more serious CI flow. Using the synthesizing & deploy parts of the tool programmatically and also having the CLI able to output in a JSON format instead of just console logs with necessary information would be perfect
Hi,
any update on this one ?
I'm trying to automate the creation of AWS resources from a lambda function, and would be great to have a programmatic API in addition to the cdk CLI.
Alternatively, is there any way to simply get the cloudformation template string generated by synth(), and then pass it to boto3 cloudformation API ?
This would be very useful for my use case: dynamically deploying MediaLive broadcast pipelines. I don't really see shelling out to the CLI as a viable option.
This would also be very useful for us for running integration tests. We currently shell out to CDK CLI for this, but that means we have to do some annoying things like having a separate app in test folder that adds more outputs, etc. Ability to deploy stacks with programmatic usage would help greatly for our use case.
I managed to do this following some @ryan-mars suggestions.
yarn add aws-cdk
yarn add @aws-cdk/core
# plus all the @aws-cdk/... you need
import { App, Stack } from '@aws-cdk/core';
import { SdkProvider } from 'aws-cdk/lib/api/aws-auth';
import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments';
class ExampleStack extends Stack { /* your stack here */ }
const app = new App();
const stack = new ExampleStack(app, 'StackName', {});
const stackArtifact = app.synth().getStackByName(stack.stackName);
const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({
profile: 'your ~/.aws/config profile name here',
});
const cloudFormation = new CloudFormationDeployments({ sdkProvider });
const deployResultPromise = cloudFormation.deployStack({
stack: stackArtifact,
});
I managed to do this following some @ryan-mars suggestions.
yarn add aws-cdk yarn add @aws-cdk/core # plus all the @aws-cdk/... you need
import { App, Stack } from '@aws-cdk/core'; import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments'; class ExampleStack extends Stack { /* your stack here */ } const app = new App(); const stack = new ExampleStack(app, 'StackName', {}); const stackArtifact = app.synth().getStackByName(stack.stackName); const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ profile: 'your ~/.aws/config profile name here', }); const cloudFormation = new CloudFormationDeployments({ sdkProvider }); const deployResultPromise = cloudFormation.deployStack({ stack: stackArtifact, });
What does the deployResultPromise
response look like? Is there a way at that point to get the stack resolved outputs?
What does the
deployResultPromise
response look like? Is there a way at that point to get the stack resolved outputs?
It's an (experimental) DeployStackResult when resolved.
I haven't tried to parse it yet, not sure how to use it. Yet I'm guessing the outputs
map contains everything. Worst case the stackArn
could be used with the regular SDK to get the stack status
@thenikso noice! Thanks! I've gonna give something a try now.
This workaround has been really useful for me. We are now using it to dynamically deploy our MediaLive+MediaPackage+CloudFront stacks at runtime.
To keep things relatively reliable and avoid request timeouts, I don't await
the result of deployment. Instead I:
I'd love to see this become an officially supported API, since it's much better than the alternatives of using the SDK manually or using traditional CloudFormation configuration files. Hopefully this example code helps anyone else who's trying to do something similar 🙂
import { SdkProvider } from 'aws-cdk/lib/api/aws-auth'; import { CloudFormationDeployments } from 'aws-cdk/lib/api/cloudformation-deployments'; ........................... ............................. const cloudFormation = new CloudFormationDeployments({ sdkProvider }); const deployResultPromise = cloudFormation.deployStack({ stack: stackArtifact, });
@thenikso I understand that CDK Toolkit is written in TypeScript. But is there some way I could use python to do a similar deployment? As most of our codebase is in python. It would be helpful to us if you could point me in the right direction, I could take it from there.
Something like :
from aws_cdk import ( aws_cdk.lib as cloud_formation_deployment ) ..... cloud_formation_deployment.deploy_stack(stack)
See @thenikso comment of 12 March 2021 for context.
I had to formulate the deploy call as an async call at the module level. This compiled and ran. My particular application requires that I pass in a variable stack name. Although I'm happy to run from a shell, I need to parameterize the name. A programmatic API would be one way to solve this problem.
(async () => {
const synth = app.synth().getStackByName(stack.stackName)
const sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ profile: '<aws profile>' });
const cloudFormation = new CloudFormationDeployments({ sdkProvider: sdkProvider});
const deployResultPromise = cloudFormation.deployStack({ stack: synth });
})();
I wound up going a different way. Inside my ./bin/platform.ts invocation code, I look for an environment variable and use that instead. Even so, the above code worked well for me at the time.
@thenikso, how did you get top-level await working in cdk?
Also, I'm reading through this other issue and wondering the same thing about how to get programmtic use of CDK to use the same settings and defaults as the CLI. For starters how to pass in context or get it to use the context file?
as @codeedog noted, you might want to wrap it all in an async
function
are there any plans to officially support using only the api functions without the cli ? This issue is open for 3 years now ...
+1 for this. My team has been using troposphere
for a while, and I'm seriously envious of the CDK community. Here are some use cases our team has been using the programmatic API of troposphere
(a Python IaC library) to solve:
A custom CLI tool to abstract the hard parts of creating REST APIs. Our data scientists are able to place a app-config.yml
file in a repository. They then write a FastAPI app compatible with Lambda to serve their models. Then they run our-custom-cli-tool deploy serverless-rest-api --config app-config.yml
. This is great, because the data scientists don't have to know anything about troposphere
, aws-cdk
, or any other IaC tool, but they can easily deploy things.
Integration testing: with troposphere
, we can create a stack--for example, a serverless REST API--and then run tests against it. That could mean sending several requests to a REST API and watching for expected responses. If the tests fail, we tear down the stack. Sure, you can do this with a sequence of CLI calls, or maybe by calling CDK as a subprocess, but it feels clunky.
Using stack outputs as inputs to other stacks at runtime. Suppose I want to create an IAM user and put those user credentials into the user-data.sh
script of an AWS Lightsail instance. First, I would create an IAM user stack and save the credentials in AWS Secrets Manager. Then, I would wait for that creation to complete and fetch the credentials. Finally, I would create a stack with an AWS Lightsail instance, rendering the credentials into the user-data.sh
body.
Conclusion:
I saw that pulumi
has an "Automation API" in all the same languages as CDK (they're probably using the JSII
inspired by AWS CDK, haha). That said, I've really enjoyed the abstraction of CDK and the community support. The other day I created an S3 bucket with the CDK construct. I set the "expire" policy so objects would auto-delete. Then I saw that to achieve that, an entire lambda function had been created to react to "expiry" events and delete the associated objects. That abstraction is so much nicer than anything we've had with troposphere
!
I would dearly love to re-write these apps using AWS CDK. I have no idea the complexity it would take to implement this, since it goes against a fundamental assumption of CDK as of now, but I'd sure be grateful.
Yeah pulumi is on my try-out list.
I made my experience with CDK and it had a benefit for simple small serverless workloads. For all other things it was just a pain to work with. In the end it`s just a wrapper for cloudformation and that is the root cause of all nasty IaC problems.
Will use my old good terraform or try out cdktf for real usecases.
Watch out, programmatic usage of the CDK was strongly impacted in the latest releases: https://github.com/aws/aws-cdk/pull/18667
If you used that approach this is now broken.
Now that the CDK is bundled into a single package this all seems like a bit of a lost cause - at least for us it's impractically big to include in our production bundles. I think it's probably better to use CDK to generate CloudFormation templates ahead of time.
We widely use AWS CDK programmatically, so we will be forced to stick with aws-cdk 2.13.0. I really hope we won't be forced to fork CDK in future 🤦♂️
+1. We also use AWS CDK intensively and programmatically.
programmatic access to the CLI (or at least its functionality) has been a major pain point at two enterprises I worked for and personally I find it unacceptable to just shell out to the CDK CLI to do anything. shelling out is not a secure practice in any environment outside of dev, specifically when people want to integrate with CDK in existing pipelines.
Two doable workarounds exist as of today (that I know of):
cdk-web
: https://github.com/3p3r/cdk-web/blob/main/cdk-web-cli.jscxapi
and reloading the artifacts from disk/memory as mentioned hereUnfortunately both approaches rely on CloudFormationDeployments
class which is an export out of the CLI package. it seems like offering this API directly as an official API is a great place to start working on resolving this RFC.
It is worth noting that using the AWS SDK to "just deploy" the artifacts does not work at all. Complicated stacks require assets, context lookups, and other metadata related tasks to be done normally via the CLI. so that route is almost like duplication of the CLI's source.
Fortunately, my work has been in Node, JS, and TS and I am lucky to have require
and deep imports in Node. other languages and environments are not that lucky.
Unfortunately both approaches rely on CloudFormationDeployments class which is an export out of the CLI package.
That's the problem, this class is no longer usable since https://github.com/aws/aws-cdk/pull/18667
@mnapoli it is usable in Node still. You need to direct require it outside its deep import. That’s a node core feature. It can’t be taken away. Only import syntax won’t work directly. It will always be possible to require anything until node core stops supporting that.
@mnapoli Sorry if this is a naive question. What would it take for this feature to move forward?
I feel like this is potentially such an amazing feature that would close a large gap between what CDK offers and what Pulumi offers (thinking of their "Automation API"). As the comments in this thread point out, it would open up a world of totally relevant use cases that many of us IaC users have to meet.
For clarity I don't work on the CDK, I'm just reporting a discussion that happened in an issue.
But from what I discussed with some of the CDK maintainers it seems that it's mostly about the effort and they are not ready to invest into it yet, and also that they think they would need to provide a cross-language API for all languages CDK supports (instead of just Node).
My personal wish is that they'd focus on Node only first, and start by not breaking the public API that's already available (i.e. it works already, just don't make it worse).
My personal wish is that they'd focus on Node only first, and start by not breaking the public API that's already available (i.e. it works already, just don't make it worse).
Totally agree!
I'm also curious: could you tell us something about the kinds of integration that you're thinking of?
I'm writing integration tests in Kotlin (because the project's most important parts are lambda functions written in Kotlin, in the same monorepo), while keeping the cdk definitions in typescript, and would like to deploy the infrastructure in --hotswap mode without spawning a system command, to give a better error report in the IDE's test results panel when something goes wrong.
Three years and still no suitable solution, or even feedback about this...
We are in the same boat as everyone else, with having a custom CLI tool for deploying to AWS and so far the only option is to redo this all through the AWS CLI, which doesn't even allow clean parameter passing and what not unless you use --context
.
The this thread started off promising with the ability to invoke the synth and deploy through code, but with that being broken now as well this option is out....
Is there any progress or other tickets we can track for this feature? :)
Description
Hi there, thanks for creating this package! I was just wondering if there's any way of triggering CDK CLI commands programatically? This would really help with respect to integrating with existing tools and workflows.
RFC 300: Programmatic access of AWS CDK CLIRoles
Workflow
status/proposed
)status/review
)api-approved
applied to pull request)status/final-comments-period
)status/approved
)status/planning
)status/implementing
)status/done
)