AWS CDK resource create sequence? - aws-cloudformation

I'm using the AWS CDK. When I deploy, according to CloudFormation Events in the AWS console, resources(CfnTrigger) in createWorkflow are initialized before createDDBCrawler and createS3Crawler. Which is causing create_failed(Entity not found) since createWorkflow depends on resources in these two.
So I am wondering:
AWS CDK resource generating sequence (since I see no async or promise in functions, so TypeScript is handling the code in sequence. Then it's a CDK/CloudFormation behavior?)
How to avoid this or arrange resource create sequence, except creating two stack.
export class QuicksightGlue extends Construct {
constructor(scope: Construct, name: string, props: QuicksightGlueProps) {
super(scope, name);
this.createGlueDb(props.glueDb);
for (let zone of zones) {
const ddbCrawler = this.createDDBCrawler(...);
const etlJob = this.createEtlJob(...);
// crawler for processed data
this.createS3Crawler(...);
// create workflow that use crawler
this.createWorkflow(...);
}
}
private createS3Crawler(...) {
return new glue.CfnCrawler(this, 's3Crawler_' + zone, {
name: 's3Crawler_' + zone,
databaseName: glueDb,
role: roleArn,
targets: {
s3Targets: [{ path: s3 }]
}
});
}
private createWorkflow(...) {
const extracDdbWorkflow = new glue.CfnWorkflow(this, `ExtractDdb_` + zone, {
name: `udhcpExtractDdb_` + zone.toLowerCase(),
description: "Workflow to extract and process data from DDB"
});
const scheduledTriggerDdbCrawler = new glue.CfnTrigger(this, 'DdbTrigger_' + zone, {
workflowName: extracDdbWorkflow.name,
type: "SCHEDULED",
schedule: scheduleCronExpression, //"cron(0 * * * ? *)",
description: "Trigger to start the workflow every hour to update ddb data",
actions: [{
crawlerName: ddbCrawler,
}],
});
...

You can cause a construct to become dependent on another construct by calling addDependency on the construct's node property, like this:
// Normally these two constructs would be created in parallel
const construct1 = ...;
const construct2 = ...;
// But with this line, construct2 will not be created until construct 1 is
construct2.node.addDependency(construct1);
Here is a practical example.
Probably, you'd want to save the return value of createS3Crawler to a variable, and then pass that variable as an argument to createWorkflow. Then, createWorkflow will call .node.addDependency(createS3Crawler) on each construct that it creates internally which is dependent on the S3 crawler.

Related

Google analytics user property react-ga4

I'm using react-ga4.
I wonder how i could send user properties using this library and set it up in the google analytics panel as I think that i'm doing something wrong.
This is how i initialize ReactGA4
ReactGA.initialize(
[
{
trackingId: id,
gaOptions: {
role: userRole,
}
},
]
)
any suggestion?
It depends what user properties that you want to send. You can send custom user properties that not reserved by Google.
For example, I want to send account_verified with boolean value and name with string value in user property.
You can use ReactGA.gtag(...args) in this library, and then you can use it directly or put it into utils/analytics.js and wrap it to export function with parameter, so you can use it whenever it needs.
import ReactGA from "react-ga4";
ReactGA.gtag("set", "user_properties", {
account_verified: true,
});
ReactGA.gtag("set", "user_properties", {
name: "John",
});
or
import ReactGA from "react-ga4";
export const setAccountProperty = (value: boolean) => {
ReactGA.gtag("set", "user_properties", {
account_verified: value,
});
};
export const setNameProperty = (value: string) => {
ReactGA.gtag("set", "user_properties", {
name: value,
});
};
After that, you can check in your Google Analytics in DebugView directly to ensure your user properties works well.

AWS CDK parameter error: Value of property Parameters must be an object with String (or simple type) properties

Update
The accepted answer bellow by Victor is the correct workaround.
I get in contact with AWS Support that confirmed the bug. They are aware of the issue and linked me to those 2 links to follow the case.
... During my investigation and replication of the issue, I noticed that this issue seems to be with how CDK handles the VPCEndpoint resource as its parameters include a list of DNS entries with them.
This is a known issue with the resource:
https://github.com/aws/aws-cdk/issues/5897
https://github.com/aws/aws-cdk/issues/9488
Until then if you face this problem, the solution is here.
Original question
I was expecting to be able to use Fn.select and Fn.split out of the box, but the bellow code have some very weird behavior, wondering if I'm doing something unexpected.
I expected the output to contains the declared functions fn::Select [1, fn:split [ fn:select [0, getAttr [ something ] ] ] ]
But actually what I see is just getAttr [something], what leads to Value of property Parameters must be an object with String (or simple type) properties during deployment
Any help is appreciated
Here's the code to reproduce the error
#!/usr/bin/env node
import 'source-map-support/register';
import {App, aws_ec2, aws_lambda, Fn, NestedStack, NestedStackProps, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
const app = new App();
export class MainStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);
new Nested1(this, 'first', {})
}
}
export class Nested1 extends NestedStack {
constructor(scope: Construct, id: string, props: NestedStackProps) {
super(scope, id, props);
const vpc = new aws_ec2.Vpc(this, 'vpc', {
subnetConfiguration: [{
cidrMask: 28,
name: 'private-test-bug',
subnetType: aws_ec2.SubnetType.PRIVATE_ISOLATED,
}]
})
const apiEndpoint = new aws_ec2.InterfaceVpcEndpoint(this, `apiEndpoint`, {
service: {
name: 'com.amazonaws.eu-central-1.execute-api',
port: 443,
},
vpc: vpc,
subnets: {
subnets: vpc.isolatedSubnets,
onePerAz: true
},
open: true,
privateDnsEnabled: false,
});
// BUG IS HERE
new Nested2(this, 'nested2', { VpcEndpoint: apiEndpoint }) // CDK should handle and parse the reference, but it dont
}
}
export interface Nested2StackProps extends NestedStackProps { VpcEndpoint: aws_ec2.InterfaceVpcEndpoint; }
export class Nested2 extends NestedStack {
constructor(scope: Construct, id: string, props: Nested2StackProps) {
super(scope, id, props);
const lambda = new aws_lambda.Function(this, 'lambda-function', {
code: aws_lambda.Code.fromInline(`exports.handler = (event, context) => { console.log(process.env.dns_name) }`),
handler: 'index.handler',
runtime: aws_lambda.Runtime.NODEJS_16_X,
environment: { dns_name: Fn.select(1, Fn.split(':', Fn.select(0, props.VpcEndpoint.vpcEndpointDnsEntries))) } // USAGE IS HERE
})
}
}
new MainStack (app, 'TestStack', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
In a nutshell, it looks like a bug in the CDK. The CDK tries to send an array as a parameter to the nested template. Instead, it should join the array and pass it as a string.
I suggest a workaround for the issue. I split my code into two parts. First, I explain the idea and show some code snippets. Finally, I show the complete solution.
We define parameter in the nested stack like this:
const stack = new NestedStack(scope, id, {
parameters: {
VpcEndpointDnsEntries: props.VpcEndpointDnsEntries
},
})
In the code, we create an object for this parameter and use it to get the parameter value.
const endpoints = new CfnParameter(stack, 'VpcEndpointDnsEntries', {
type: 'List<String>',
description: 'List of entries.'
})
// Read the value:
const strings = endpoints.valueAsList
In the final step, we pass parameters to the nested stack almost as usual, except that we join the string.
createNestedStack(stack, 'NestedStack',
{ VpcEndpointDnsEntries: Fn.join(',', apiEndpoint.vpcEndpointDnsEntries) })
Note that the lambda function is not working, it throws a runtime exception. Otherwise, the stack and nested stack deploys normally.
Please find the complete code below.
Sorry, I am really in a hurry now. I plan to fix grammar and update the answer this evening.
import { App, CfnParameter, Fn, NestedStack, Stack } from 'aws-cdk-lib'
import { env } from 'process'
import { InterfaceVpcEndpoint, Vpc } from 'aws-cdk-lib/aws-ec2'
import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda'
function createMainStack (scope, id, props) {
const stack = new Stack(scope, id, props)
const vpc = Vpc.fromLookup(stack, 'Vpc', { vpcName: 'WarehousePrinters' })
const apiEndpoint = new InterfaceVpcEndpoint(stack, `ApiEndpoint`, {
service: {
name: 'com.amazonaws.eu-west-1.execute-api',
port: 443,
},
vpc,
subnets: { subnets: vpc.privateSubnets, onePerAz: true },
open: true,
privateDnsEnabled: false,
})
createNestedStack(stack, 'NestedStack', { VpcEndpointDnsEntries: Fn.join(',', apiEndpoint.vpcEndpointDnsEntries) })
return stack
}
function createNestedStack (scope, id, props) {
const stack = new NestedStack(scope, id, {
parameters: {
VpcEndpointDnsEntries: props.VpcEndpointDnsEntries
},
})
const endpoints = new CfnParameter(stack, 'VpcEndpointDnsEntries', {
type: 'List<String>',
description: 'List of entries.'
})
new Function(stack, 'LambdaFunction', {
code: Code.fromInline(`export function handler = () => console.log(env.dns_name)`),
handler: 'index.handler',
runtime: Runtime.NODEJS_16_X,
environment: {
dns_name: Fn.select(1, Fn.split(':', Fn.select(0, endpoints.valueAsList)))
}
})
return stack
}
const app = new App()
createMainStack(app, 'MainStack', {
env: { account: env.CDK_DEFAULT_ACCOUNT, region: env.CDK_DEFAULT_REGION }
})

Pulumi create instance in existing subnet

I am trying to create an EC2 instance from a custom vpc. I have vpc as Output. But i want to assign to string. Would appreciate help in this context.
Option 1:
const vpcId = args.network.vpc.id.apply(id => id); //type Output<string>; network extends awsx:Vpc
const mySubnet = aws.ec2.gtSubnet({
availabilityZone: 'ap-southeast-2a',
filters: [
{ name: "tag:Name", values: ["*private-0"]}
],
vpcId: vpcId; //Error: Type 'Output<string>' is not assignable to type 'string'
});
this.vm = new aws.ec2.Instance("ec2Instance", {
ami: amiId,
instanceType: instanceClass,
networkInterfaces: [{
networkInterfaceId: networkInterface.id,
deviceIndex: 0,
}]
});
Option 2:
const sg = new awsx.ec2.SecurityGroup("webserver-sg", { vpc }); //Here again I need vpcid string in the SecurityGroupArgs
Output<T> is a promise, also called a future. It's a value that's not yet available. It will be available when the Pulumi engine starts executing. You can pass a callback which will be run when the value is available (and you'll immediately get a new promise for the new, not-yet-available return value of the callback) or you can pass it to methods which will accept a promise (these usually take Input<T>).
Unfortunately, getSubnet (currently) does not accept Input or Promise, so option #2 is out.
What you can do is:
const mySubnetId = args.network.vpc.id.apply(id => {
const getSubnetResult = aws.ec2.getSubnet({
vpcId: id;
})
return getSubnetResult.id
})
This will give you an Output<string> which you can pass to new Instance({subnetId: ...}) because it accepts an Input<string>.

How to integrate Api Gateway and Step Fucnctions in CDK?

I have a statemachine.
const task1 = new sfn.Task(this, 'Assign Case', {
task: new tasks.InvokeFunction(Lambda1),
});
const task2 = new sfn.Task(this, 'Close Case', {
task: new tasks.InvokeFunction(Lambda2),
});
const chain = sfn.Chain.start(task1)
.next(task2);
const StateMachine = new sfn.StateMachine(this, `StateMachine`, {
definition: chain
});
And I need to call this statemachine from Api-gateway resource.I have used the below code and it throws an error like 'statemacine is not assignable to paramaeter of type AwsIntegrationProps'
const resource = this.api.root.addResource(path);
resource.addMethod(method, new apigw.AwsIntegration(handler), { apiKeyRequired: true });
//handler is above statemachine
My api gateway integration request looks like this in console.
You should use apigateway.LambdaIntegration which extends AwsIntegration.
export declare class LambdaIntegration extends AwsIntegration {
private readonly handler;
private readonly enableTest;
constructor(handler: lambda.IFunction, options?: LambdaIntegrationOptions);
bind(method: Method): void;
}
For example :
const getBookIntegration = new apigateway.LambdaIntegration(getBookHandler);
Later, use the lambdaIntegration when creating a new method:
book.addMethod('GET', getBookIntegration);
More about LambdaIntegration.
The error 'statemacine is not assignable to paramaeter of type AwsIntegrationProps' is referring to you instantiation.
The AwsIntegration class takes an AwsIntegrationProps struct as input.
new AwsIntegration(props: AwsIntegrationProps)
Getting API Gateway to kickoff Step Functions directly is a little strange. I found this Creating a Step Functions API Using API Gateway tutorial helpful. The State Machine ARN is passed in the request body of the call, so you need to pass a request template if you don't want to require the user to specify the State Machine.
resource.addMethod(
method,
new apigw.AwsIntegration({
handler: 'states',
action: 'StartExecution',
options: {
requestTemplates: {
'application/json': `{
"stateMachineArn": "${handler.ref}",
"input": "$util.escapeJavaScript($input.body)"
}`
},
},
}),
{ apiKeyRequired: true }
);
(Note: I'm translating my code from Python so I'm not 100% on the strings in TypeScript.)
I also filled in credentialsRole, passthroughBehavior, and integrationResponses to the options to get mine setup the way I wanted.
check here:
api to state
and here:
state machine

[vscode extension API]: How to create communication channel between javascript included in `vscode.previewHtml` and outside thread?

The thing is, i am trying to create a tool with vscode.previewHtml command, I need some javascript in it listening on user's action, and send a signal to outside thread, so that i can do few high cost "native" job in background, and refresh the previewHtml with new content.
Is it possible?
I assume by "outside thread" you actually mean the hosting environment (VS code). There is no direct way (e.g. a listener/callback/method) to communicate, but you can use postMessage like so:
/* This is in a .js file you include in previewHTML. */
function exportToSVG(type) {
let html = document.querySelectorAll('html')[0];
let svg = document.querySelectorAll('svg')[0];
const args = { name: objectName, type: type, svg: svg.outerHTML, html: html.outerHTML };
window.parent.postMessage({
command: "did-click-link",
data: `command:_antlr.saveSVG?${encodeURIComponent(JSON.stringify(args))}`
}, "file://");
}
In vscode only a few commands are handled. One is that did-click-link command. You then can register to that command in your extension and do the actual work:
// This is in your extension code (usually the activate() method).
context.subscriptions.push(commands.registerCommand('_antlr.saveSVG', (args: { name: string, type: string, svg: string, html: string }) => {
window.showInputBox({
value: workspace.getConfiguration("antlr4." + args.type)["saveDir"] + "/" + args.name + ".svg",
placeHolder: "<Enter full file name here>",
prompt: "Enter the name to an svg file."
}).then((value: string) => {
...
You can use the same approach for the other direction. See for instance the markdown preview extension how to do that.