AWS CDK: how do I reference cross-stack resources in same app? - amazon-web-services

I have an App that has two stacks, both within the same region/account. One of those stacks requires the ARN of a lambda that exists in the other stack. How do I reference this?
// within stackA constructor
public StackA(Construct scope, String id, StackProps props) {
SingletonFunction myLambda = SingletonFunction.Builder.create(this, "myLambda")
// some code here
.build()
CfnOutput myLambdaArn = CfnOutput.Builder.create(this, "myLambdaArn")
.exportName("myLambdaArn")
.description("ARN of the lambda that I want to use in StackB")
.value(myLambda.getFunctionArn())
.build();
}
App app = new App();
Stack stackA = new StackA(app, "stackA", someAProps);
Stack stackB = new StackB(app, "stackB", someBProps);
stackB.dependsOn(stackA);
How do pass the ARN into StackB?

CDK's official documentation has a complete example for sharing a S3 bucket between stacks. I copied it below for quicker reference.
/**
* Stack that defines the bucket
*/
class Producer extends cdk.Stack {
public readonly myBucket: s3.Bucket;
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const bucket = new s3.Bucket(this, 'MyBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
this.myBucket = bucket;
}
}
interface ConsumerProps extends cdk.StackProps {
userBucket: s3.IBucket;
}
/**
* Stack that consumes the bucket
*/
class Consumer extends cdk.Stack {
constructor(scope: cdk.App, id: string, props: ConsumerProps) {
super(scope, id, props);
const user = new iam.User(this, 'MyUser');
props.userBucket.grantReadWrite(user);
}
}
const producer = new Producer(app, 'ProducerStack');
new Consumer(app, 'ConsumerStack', { userBucket: producer.myBucket });

You can access resources in a different stack, as long as they are in the same account and AWS Region. The following example defines the stack stack1, which defines an Amazon S3 bucket. Then it defines a second stack, stack2, which takes the bucket from stack1 as a constructor property.
// Helper method to build an environment
static Environment makeEnv(String account, String region) {
return Environment.builder().account(account).region(region)
.build();
}
App app = new App();
Environment prod = makeEnv("123456789012", "us-east-1");
StackThatProvidesABucket stack1 = new StackThatProvidesABucket(app, "Stack1",
StackProps.builder().env(prod).build());
// stack2 will take an argument "bucket"
StackThatExpectsABucket stack2 = new StackThatExpectsABucket(app, "Stack,",
StackProps.builder().env(prod).build(), stack1.getBucket());

Option 1:
pass the data from Stack A to Stack B using the constructor :
You can extend cdk.stack and create a new class that will contain stackA.
In that stack, expose the relevant data you want by using public XXX: string\number (etc) ( See line 2 in the example).
Later, just pass this data into StackB constructor ( you can pass it using props as well).
Working code snippet:
Stack A:
export class StackA extends cdk.Stack {
public YourKey: KEY_TYPE;
constructor(scope: cdk.Construct, id: string, props: cdk.StackProps ) {
super(scope, id, props);
Code goes here...
// Output the key
new cdk.CfnOutput(this, 'KEY', { value: this.YourKey });
}
}
Stack B:
export class StackB extends cdk.Stack {
constructor(scope: cdk.Construct, id: string,importedKey: KEY_TYPE, props: cdk.props) {
super(scope, id, props)
Code goes here...
console.log(importedKey)
}
}
bin ts:
const importedKey = new StackA(app, 'id',props).YourKey;
new StackB(app, 'id',importedKey,props);
Option 2:
Sometimes it's just better to save this kind of stuff in the parameter store and read it from there.
More info here.

I found all of the answers to be on the right path, but none explained it fully and/or well. In this example, I'm passing a VPC from a VPC stack to an ECS cluster.
First, add a property to the originating stack. This property is set whenever the asset is created:
export class VpcStack extends cdk.Stack {
readonly vpc: Vpc;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Here
this.vpc = new Vpc(this, 'vpc', {
maxAzs: 3,
cidr: '10.0.0.0/16',
});
});
}
Next, require this property as a parameter to the consuming stack:
// Create an interface that extends cdk.StackProps
// The VPC property is added here
interface EcsClusterStackProps extends cdk.StackProps {
vpc: Vpc,
}
export class EcsClusterStack extends cdk.Stack {
// Use your interface instead of the regular cdk.StackProps
constructor(scope: cdk.Construct, id: string, props: EcsClusterStackProps) {
super(scope, id, props);
// Use the passed-in VPC where you need it
new Cluster(this, "myCluster", {
capacity: {
instanceType: InstanceType.of(InstanceClass.M6I, InstanceSize.LARGE)
},
clusterName: "myCluster",
vpc: props.vpc, // Here
});
}
}
Third, pass the reference in your app file:
const app = new cdk.App();
// Create the VPC stack
const vpcStack = new VpcStack(app, 'vpc-stack', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
// Pass the VPC directly to the consuming stack's constructor
const ecsClusterStack = new EcsClusterStack(app, 'ecs-cluster-stack', {
vpc: vpcStack.vpc, // Here
});
Hopefully this helps clarify some of the ambiguous areas.

Related

AWS CDK: No export named XYZ found

I've following stack which deploys the two constructs within. The construct DynamoDBConstruct exports the table name, and the construct IAMRoleConstruct consumes it. However, during deployment, it fails stating No export named dbTableName found, despite the fact that dependency is added/specified, the IAMRoleConstruct gets deployed first, why?
Stack:
public AllStacks(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
{
var db = new DynamoDBConstruct(this, "DynamoDB");
var iam = new IAMRoleConstruct(this, "IAMRole");
iam.Node.AddDependency(db);
}
DynamoDBConstruct
public DynamoDBConstruct(Construct scope, string id): base(scope, id)
{
var dbTable = new Table(this, "dbTable", new TableProps()
{
PartitionKey = new Attribute
{
Name = "contactID",
Type = AttributeType.STRING
},
TableClass = TableClass.STANDARD,
TableName = (string)Node.TryGetContext("dbTableName"),
RemovalPolicy = RemovalPolicy.DESTROY
});
new CfnOutput(this, "OutputTableName", new CfnOutputProps()
{
ExportName = "dbTableName",
Value = dbTable.TableName
});
}
IAMRoleConstruct
public IAMRoleConstruct(Construct scope, string id) : base(scope, id)
{
var dbTableName = Fn.ImportValue("dbTableName");
/*
Some code
.
*/
}
With the disclaimer that I am not sure what language your code is in, I'm going to write in CDK's native language (which I recommend you to do as well) - Typescript.
The problem comes most likely from the fact that you are using the export within the same CDK/CFN stack. The export won't be available during stack creation, as that is part of the stack creation itself.
When you're working within a single stack, the simplest, most intuitive way of "moving data" from one construct to another is to just expose values through a public member of your class, e.g.:
class DynamoDBConstruct extends Construct {
public readonly tableName: string;
constructor(scope: Construct, id: string, props: Whatever) {
super(scope, id);
const table = new Table(this, 'Table', {
partitionKey: { name: 'id', type: AttributeType.STRING },
billingMode: BillingMode.PAY_PER_REQUEST,
// omitting table name on purpose - it will be generated by CDK
});
this.tableName = table.tableName;
}
}
Now inside your stack, you can simply use that table name:
class MyStack extends Stack {
constructor(scope: App, id: string, props: Whatever) {
const table = new DynamoDBConstruct(...);
const myOtherConstruct = new MyOtherConstruct(this, 'myOtherConstruct', {
// using table name here
tableName: table.tableName,
});
}
}
The reason for the error is that you are trying to produce and consume a Stack Output in the same stack. That won't work:
Docs: Output values are available after the stack operation is complete. Stack output values aren't available when a stack status is in any of the IN_PROGRESS status.
No worries! As #Victor says, there is a much easier alternative. Get rid of the Outputs. Instead, share data between your custom constructs by declaring public fields (e.g. public Table table) in the providing class, passing the references as props to the consuming class. This is what the CDK constructs do.
See the C# example stacks in the aws-cdk-examples repo.

Using a resource created in one cdk construct in another construct

I'm building the infrastructure for an application using AWS-CDK.
I have a construct that builds multiple S3 buckets and another construct that creates a lambda function that fetches data from these buckets.
In order to be able to give my lambda permissions to fetch data from the bucket I need the buckets ARN.
Is there a way in which I could export the bucket arn from the construct that produces the buckets and import it into the lambda construct?
Sure, maybe something like this:
export class ConsumingStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const producingStack = new BucketProducingStack(this, 'BucketProducingStack');
const { bucket1, bucket2 } = producingStack;
//Create new lambda stack here
//const lambdaStack = new LambdaStack(this, { bucket1, bucket2} );
}
}
export class BucketProducingStack extends cdk.NestedStack {
bucket1: string;
bucket2: string;
constructor(scope: cdk.Construct, id: string, props?: cdk.NestedStackProps) {
const bucket1 = new Bucket(this, 'BucketOne');
const bucket2 = new Bucket(this, 'BucketTwo');
this.bucket1 = bucket1.bucketArn;
this.bucket2 = bucket2.bucketArn;
}
}
No guarantee this compiles as it was written entirely in this window, but hopefully conveys the idea.
If you are using python, you can add
#property
def main_source_bucket(self) -> _s3.IBucket:
return self.bucket
Reference it in your app stack like this ..
bucket = S3Construct(self, "bucket", "bucket1")
LambdaConstruct(self, "lambda1", "dev", bucket.main_source_bucket

How to parse input from AWS event rule in lambda?

If I use the input in an events rule.. how am I supposed parse it in the lambda?
Now I have:
MyJobScheduledRule:
Type: AWS::Events::Rule
Properties:
Description: Scheduled Rule
ScheduleExpression: !Sub "rate(${IntervalMinutes} minutes)"
State: "ENABLED"
Targets:
- Id: "MyJobLambda"
Arn: !GetAtt MyJobLambda.Arn
Input: "\"{\\\"key\\\":\\\"value\\\"}\"
With the following lambda:
public class MyJobLambda implements RequestHandler<Map<String, String>, Void> {
private static Logger LOGGER = LoggerFactory.getLogger(MyJobLambda.class);
#Override
public Void handleRequest(Map<String, String> event, Context context) {
LOGGER.debug("MyJob got value {} from input", event.get("key"));
return null;
}
}
But I get the following runtime exception:
An error occurred during JSON parsing: java.lang.RuntimeException
java.lang.RuntimeException: An error occurred during JSON parsing
Caused by: java.io.UncheckedIOException: com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `java.util.LinkedHashMap` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('
{
"key": "value"
}
I have also tried using a POJO as input to the lambda. Any ideas?
Use a ScheduledEvent object, and parse the detail field of the event
import com.amazonaws.services.lambda.runtime.events.ScheduledEvent;
public class MyJobLambda implements RequestHandler<Map<String, String>, Void> {
private static Logger LOGGER = LoggerFactory.getLogger(MyJobLambda.class);
#Override
public Void handleRequest(ScheduledEvent event, Context context) {
LOGGER.debug("MyJob got value {} from input", mapScheduledEventDetail(event.getDetail());
return null;
}
private String mapScheduledEventDetail(Map<String,Object> detailObject) {
return ParserUtil.parseObjectToJson(detailObject.get("clientId"));
}
}

Generic test in TypeScript that asserts each property on a class has a value assigned

I have a TypeScript +2.4 project where I'm using Jest for my unit tests. The project has a lot of poco models, without a default value. For example:
export class Foo {
public id: number
public name: string;
public when: Date;
}
Each of these models is mapped from raw json to this class. It is a requirement for my tests that all properties are assigned, e.g. have values. This leads to the following test that has to be written for all models:
test('Foo() should have its properties assigned', () => {
const target: Foo = {
id: 1001, name: 'whatever', when: new Date()
};
// manually assert each propertie here
expect(target.id).toBeDefined();
expect(target.name).toBeDefined();
expect(target.when).toBeDefined();
}
To me, that's not so DRY to do for each test. Not to mention error prone and cumbersome. What I would like to do is create a helper that iterates through each property and asserts that it has a value assigned.
Example 1 - Object.keys
This example is incorrect because Object.keys only iterates through the already assigned properties, ignoring the non-set properties (and thus always is positive):
public static AssertAllPropertiesAreAssigned(target: object): void {
Object.keys(target).forEach((key, index) => {
expect(target[key]).toBeDefined();
});
Example 2 - Object.getOwnPropertyNames()
The same as example 1:
public static AssertAllPropertiesAreAssigned(target: object): void {
Object.getOwnPropertyNames(target).forEach((name, index) => {
expect(target[name]).toBeDefined();
});
Example 3 - Set default values
By assigning a default value to each poco, like null, I can make the earlier samples work. But I'd sure like to avoid that at all cost:
export class Foo {
public id: number = null;
public name: string = null;
public when: Date = null;
}
The question: is there a way to create a helper that asserts that each property of my TypeScript poco object is actually assigned a value, in my test? Or, as an alternative, does Jest have some util for this?
There are similar questions on SO, but they are not related to asserting the values in a test. This makes this question, as far as I've looked around, differ from the others:
How to dynamically assign value to class property in TypeScript
How to iterate through all properties and its values in Typescript class
Typescript looping trough class type properties
Also, I'm aware that the Javascript compiled output of my poco will probably leads to that the unset properties are simply not available:
var Foo = (function() {
// nothing here...
}());
But with TypeScript's strong typing power and recent changes and/or Jest helpers, there might be some additional options to get this done?
Most of your options aren't any better than the answers to those other questions: initialize the properties (good idea); use property decorators (tedious).
Personally, I think it should be an error to declare a class property as a can't-be-undefined type like string and then not define it in the constructor, but that feature isn't part of TypeScript yet, even if you turn on strictNullChecks (which you should). I don't know why you don't want to initialize the variables, but this would work:
export class Foo {
public id: number | undefined = void 0;
public name: string | undefined = void 0;
public when: Date | undefined = void 0;
}
Now an instance of Foo will have the relevant keys if you do Object.keys() even though the values will still be undefined.
Wait a minute, you're not even using the class at runtime:
const target: Foo = {
id: 1001, name: 'whatever', when: new Date()
}; // object literal, not constructed class instance
console.log(target instanceof Foo) // false
Then I suggest you use an interface instead of a class, and just turn on strictNullChecks:
export interface Foo {
id: number;
name: string;
when: Date;
}
const target: Foo = {
id: 1001, name: 'whatever', when: new Date()
};
const badTarget: Foo = {
id: 1002;
}; // error, Property 'name' is missing
Now TypeScript will not let you assign a possibly-undefined value to those properties and you don't have to bother looping over anything at runtime.
Hope that helps!

TypeScript - Casting to Object When Initializing Required Property with an Array Literal

I'm trying to initialize an object that has an interface type with an object literal:
return {
id: 0,
name: name,
localizedLabels: [
{localeCd: defaultLocaleCd, label: name}
],
localizedDescriptions: [],
useForNetworkBuild: false,
temporalSplitting: false,
compounds: [],
primaryElements: [],
defaultMapIcon: null,
defaultRegularIcon: null,
markerColor: null,
nodeShape: null
};
The type of the object is an interface I created called Entity. It's got several parent interfaces. The one causing the problem is the "localizedDescriptions" property of LocalizableObject (sorry for the deep hierarchy):
export interface Entity extends hubBase.PersistableObject,
hubLocalization.LocalizableObject,
hubBase.GraphicallyDisplayable {
name: string;
useForNetworkBuild: boolean;
temporalSplitting: boolean;
compounds: Compound[];
primaryElements: Element[];
}
export interface LocalizedLabel extends hubBase.PersistableObject {
localeCd: string
label: string;
}
export interface LocalizedDescription extends hubBase.PersistableObject {
localeCd: string
description: string;
}
export interface LocalizableObject {
label?: string;
localizedLabels: LocalizedLabel[];
description?: string;
localizedDescriptions: LocalizedDescription;
}
The compiler is giving an error about the "localCd" property not being available, complaining about the [] initializer for "localizedDescriptions":
I tried to casting the entire object literal to Entity. I also tried to put a cast before the [] in the assignment to "localizedDescriptions". The error persists. Is there a way to cast this property to remove this error?
I'm using TypeScript 1.4.1.
export interface LocalizableObject {
label?: string;
localizedLabels: LocalizedLabel[];
description?: string;
// Not an array type, did you mean LocalizedDescription[] ?
localizedDescriptions: LocalizedDescription;
}