How to setup EventHub disasterRecoveryConfigs using Azure bicep? - azure-eventhub

I have created a module 'resources.bicep' to create event hub namespace in two regions.
resource eventHubNamespace 'Microsoft.EventHub/namespaces#2021-11-01' = {
name: resourceName
location: location
sku: {
name:'Standard'
tier:'Standard'
capacity:1
}
}
resource eventHub 'Microsoft.EventHub/namespaces/eventhubs#2021-11-01' = if (shortRegion == 'wus2') {
name: 'city-temprature'
parent: eventHubNamespace
properties: {
messageRetentionInDays: 1
partitionCount: 2
}
}
From the parent bicep file I run the module as
module weatherWest 'resources.bicep' = {
name:'westResources'
scope:resourceGroup('${name}-wus2')
params: {
name: name
shortRegion: 'wus2'
location: 'westus2'
}
}
module weatherEast 'resources.bicep' = {
name:'eastResources'
scope:resourceGroup('${name}-eus2')
params: {
name: name
shortRegion: 'eus2'
location: 'eastus2'
}
}
How do I setup the GeoPairing?
I have not found a way to call Microsoft.EventHub/namespaces/disasterRecoveryConfigs#2021-11-01 from the parent bicep file.
Code is located in this branch
https://github.com/xavierjohn/SearchIndexDisasterRecoverNearRealTime/blob/bicep/bicep/weatherResources.bicep

Per the docs,
you need to specify a parent resource. You can look up an existing resource with the existing keyword.
Something along these lines should work.
resource primary 'Microsoft.EventHub/namespaces#2021-11-01' existing = {
name: 'primaryEventHubName'
resource secondary 'Microsoft.EventHub/namespaces#2021-11-01' existing = {
name: 'secondaryEventHubName'
resource symbolicname 'Microsoft.EventHub/namespaces/disasterRecoveryConfigs#2021-11-01' = {
name: 'foo'
parent: primary
properties: {
alternateName: 'string'
partnerNamespace: secondary.id
}
}

I got help from the Azure Bicep team and currently there is no way to pass a resource as output but they are working on a proposal. For now there is a trick that will work so till the elegant solution comes out,
use existing and set dependsOn on the Geo Pairing fragment.
The end code looks like below.
module allResources 'resources.bicep' = [for location in locations : {
name:'allResources'
scope:resourceGroup('${name}-${location.shortRegion}')
params: {
name: name
shortRegion: location.shortRegion
location: location.region
}
}]
resource primaryEventHubNamespace 'Microsoft.EventHub/namespaces#2021-11-01' existing = {
name: '${name}wus2'
}
resource disasterRecoveryConfigs 'Microsoft.EventHub/namespaces/disasterRecoveryConfigs#2021-11-01' = {
name: name
parent: primaryEventHubNamespace
properties: {
partnerNamespace: resourceId('${name}-eus2', 'Microsoft.EventHub/namespaces', '${name}eus2')
}
dependsOn: [
allResources
]
}

Related

Pulumi GCP MemoryStore Redis Cache Internal Server Error 13

I have a weird scenario here.
The following line in my Pulumi typescript code always fails the first time:
const redisCache = new gcp.redis.Instance("my-redis-cache", {
name: "my-metadata-cache",
tier: "BASIC",
memorySizeGb: 1,
authorizedNetwork: pulumi.interpolate`projects/someprojectid/global/networks/default`,
connectMode: "PRIVATE_SERVICE_ACCESS",
redisVersion: "REDIS_6_X",
displayName: "My Metadata Cache",
project: someprojectid,
}, defaultResourceOptions);
**
error: 1 error occurred:
* Error waiting to create Instance: Error waiting for Creating Instance: Error code 13, message: an internal error has occurred
**
Strangely, when I again run pulumi up, it succeeds. Has anyone else faced this before? Any clues?
Ok this turned out to be a case of working with a beast of a code. Once I started isolating the issue, things became clearer. For those who stumble across this one, here is a full working code.
import * as pulumi from "#pulumi/pulumi";
import * as gcp from "#pulumi/gcp";
export interface CacheComponentResourceArgs {
projectId : pulumi.Input<string>;
projectNumber: pulumi.Input<string>;
}
export class CacheComponentResource extends pulumi.ComponentResource {
constructor(name: string, resourceArgs: CacheComponentResourceArgs, opts?: pulumi.ResourceOptions) {
const inputs: pulumi.Inputs = {
options: opts,
};
super("ekahaa:abstracta:Cache", name, inputs, opts);
const serviceNetworkingAccessService = new gcp.projects.Service("service-nw-" + name , {
disableDependentServices: true,
project: resourceArgs.projectId,
service: "servicenetworking.googleapis.com",
}, {
parent : this
});
const redisService = new gcp.projects.Service("redis-service-" + name, {
disableDependentServices: true,
project: resourceArgs.projectId,
service: "redis.googleapis.com",
}, {
parent : this
});
const defaultGlobalAddress = new gcp.compute.GlobalAddress("default-ip-range-" + name, {
name: "default-ip-range",
purpose: "VPC_PEERING",
prefixLength: 16,
project: resourceArgs.projectId,
addressType: "INTERNAL",
network: pulumi.interpolate`projects/${resourceArgs.projectId}/global/networks/default`
}, {
parent : this,
dependsOn: [ redisService]
});
const privateServiceConnection = new gcp.servicenetworking.Connection("servicenetworking-" + name, {
service: "servicenetworking.googleapis.com",
network: pulumi.interpolate`projects/${resourceArgs.projectId}/global/networks/default`,
reservedPeeringRanges: [defaultGlobalAddress.name],
}, {
parent : this,
dependsOn: [ defaultGlobalAddress]
});
const iamBindingRedis2 = new gcp.projects.IAMBinding("iamredis2-" + name, {
members: [
pulumi.interpolate`serviceAccount:service-${resourceArgs.projectNumber}#service-networking.iam.gserviceaccount.com`
],
role: "roles/servicenetworking.serviceAgent",
project: resourceArgs.projectId
}, {
parent : this,
dependsOn: [privateServiceConnection]
});
const redisCache = new gcp.redis.Instance(name, {
name: name,
tier: "BASIC",
memorySizeGb: 1,
authorizedNetwork: pulumi.interpolate`projects/${resourceArgs.projectId}/global/networks/default`,
connectMode: "PRIVATE_SERVICE_ACCESS",
redisVersion: "REDIS_6_X",
displayName: "Abstracta Metadata Cache",
project: resourceArgs.projectId,
}, {
parent : this,
dependsOn : [redisService,serviceNetworkingAccessService,iamBindingRedis2]
});
this.registerOutputs({
redisCache : redisCache
});
}
}
let suffix = "20211018-002";
let org_name = `org-redis-demo-${suffix}`;
let projectId = `redis-demo-${suffix}` ;
const myGcpProject = new gcp.organizations.Project('ab-' + org_name, {
orgId: gcpOrgId,
projectId: projectId,
billingAccount: billingAccountId,
name: 'ab-' + org_name,
});
const myGcpProjectIAM = new gcp.projects.IAMBinding("iam-001", {
members: [
"user:vikram.vasudevan#ekahaa.com",
],
role: "roles/owner",
project: myGcpProject.projectId
});
const cacheComponentResource = new CacheComponentResource("my-cache", {
projectId : myGcpProject.projectId,
projectNumber : myGcpProject.number
}, {
dependsOn : [myGcpProjectIAM]
});

How to get GCP Logs to show up in CloudRun?

I see instructions at https://www.npmjs.com/package/#google-cloud/logging to start logging, but I can't seem to see the logs in https://console.cloud.google.com/run/detail/<location>/<service>/logs?project=<project> for CloudRun, and I'm not sure where they are (I invoked them with quickstart('my-project', 'my-log').
To find logs the logs from the tutorial, use the logs explorer https://console.cloud.google.com/logs/query with the query logName="projects/<project>/logs/<log_name>", so in your case, logName="projects/my-project/logs/my-log" of when you're editing the query you can find it in the dropdown under log name.
To log to CloudRun you need to set the following in the meta data:
{
...,
resource: {
type: 'cloud_run_revision',
labels: { service_name: 'my-service', location: 'us-east1' } },
}
You can use this sample function to test:
const log = async (text, name) => {
const gcpLogger = new Logging({ projectId: 'smodin-dev' })
const logSet = gcpLogger.log(name)
const metadata = {
severity: 'INFO',
resource: {
type: 'cloud_run_revision',
labels: { service_name: 'my-service', location: 'us-east1' }
},
}
const entry = logSet.entry(metadata, text)
await logSet.write(entry)
}
PS: If there is a different recommended method I'm all ears

CDK to enable DNS resolution for VPCPeering

I have VPC peering to connect to a lambda in one aws account to a RDS instance in another aws account. This works fine but required the VPC peering to have DNS resolution option enabled.
By default DNS resolution is set to :
DNS resolution from accepter VPC to private IP :Disabled.
This can be done via the AWS console and the CLI. I am not able to achieve the same using AWS CDK.
https://docs.aws.amazon.com/vpc/latest/peering/modify-peering-connections.html
The CfnVPCPeeringConnection does not seem to have this option.
https://docs.aws.amazon.com/cdk/api/latest/docs/#aws-cdk_aws-ec2.CfnVPCPeeringConnection.html
Is there any other way of achieving this via CDK ?
const cfnVPCPeeringConnection :CfnVPCPeeringConnection =
new CfnVPCPeeringConnection(
stack,
"vpcPeeringId",
{
peerVpcId : "<vpcId of acceptor account>",
vpcId : "<reference of the Id>",
peerOwnerId : "<aws acc number>",
peerRegion : "<region>",
peerRoleArn :"<arn created in the acceptor account>"",
}
);
//update route tables
rdsConnectorVpc.isolatedSubnets.forEach(({ routeTable: { routeTableId } }, index) => {
new CfnRoute(this.parentStack, 'PrivateSubnetPeeringConnectionRoute' + index, {
destinationCidrBlock: '<CIDR>',
routeTableId,
vpcPeeringConnectionId: cfnVPCPeeringConnection.ref,
})
});
You can use a CustomResource Construct in AWS CDK to achieve it:
import * as cdk from "#aws-cdk/core";
import ec2 = require("#aws-cdk/aws-ec2");
import iam = require("#aws-cdk/aws-iam");
import { AwsCustomResource, AwsCustomResourcePolicy, AwsSdkCall, PhysicalResourceId } from "#aws-cdk/custom-resources";
import { RetentionDays } from "#aws-cdk/aws-logs";
export interface AllowVPCPeeringDNSResolutionProps {
vpcPeering: ec2.CfnVPCPeeringConnection,
}
export class AllowVPCPeeringDNSResolution extends cdk.Construct {
constructor(scope: cdk.Construct, id: string, props: AllowVPCPeeringDNSResolutionProps) {
super(scope, id);
const onCreate:AwsSdkCall = {
service: "EC2",
action: "modifyVpcPeeringConnectionOptions",
parameters: {
VpcPeeringConnectionId: props.vpcPeering.ref,
AccepterPeeringConnectionOptions: {
AllowDnsResolutionFromRemoteVpc: true,
},
RequesterPeeringConnectionOptions: {
AllowDnsResolutionFromRemoteVpc: true
}
},
physicalResourceId: PhysicalResourceId.of(`allowVPCPeeringDNSResolution:${props.vpcPeering.ref}`)
};
const onUpdate = onCreate;
const onDelete:AwsSdkCall = {
service: "EC2",
action: "modifyVpcPeeringConnectionOptions",
parameters: {
VpcPeeringConnectionId: props.vpcPeering.ref,
AccepterPeeringConnectionOptions: {
AllowDnsResolutionFromRemoteVpc: false,
},
RequesterPeeringConnectionOptions: {
AllowDnsResolutionFromRemoteVpc: false
}
},
};
const customResource = new AwsCustomResource(this, "allow-peering-dns-resolution", {
policy: AwsCustomResourcePolicy.fromStatements([
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
resources: ["*"],
actions: [
"ec2:ModifyVpcPeeringConnectionOptions",
]
}),
]),
logRetention: RetentionDays.ONE_DAY,
onCreate,
onUpdate,
onDelete,
});
customResource.node.addDependency(props.vpcPeering);
}
}
and use it like this:
[...]
const peerConnection = new ec2.CfnVPCPeeringConnection(this, "peerConnection", {
vpcId: destinationVPC.vpcId,
peerVpcId: lambdaVPCToDestinationVPC.vpcId,
});
new AllowVPCPeeringDNSResolution(this, "peerConnectionDNSResolution", {
vpcPeering: peerConnection,
});
[...]

AWS CDK - How to add an event notification to an existing S3 Bucket

I'm trying to modify this AWS-provided CDK example to instead use an existing bucket. Additional documentation indicates that importing existing resources is supported. So far I am unable to add an event notification to the existing bucket using CDK.
Here is my modified version of the example:
class S3TriggerStack(core.Stack):
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
# create lambda function
function = _lambda.Function(self, "lambda_function",
runtime=_lambda.Runtime.PYTHON_3_7,
handler="lambda-handler.main",
code=_lambda.Code.asset("./lambda"))
# **MODIFIED TO GET EXISTING BUCKET**
#s3 = _s3.Bucket(self, "s3bucket")
s3 = _s3.Bucket.from_bucket_arn(self, 's3_bucket',
bucket_arn='arn:<my_region>:::<my_bucket>')
# create s3 notification for lambda function
notification = aws_s3_notifications.LambdaDestination(function)
# assign notification for the s3 event type (ex: OBJECT_CREATED)
s3.add_event_notification(_s3.EventType.OBJECT_CREATED, notification)
This results in the following error when trying to add_event_notification:
AttributeError: '_IBucketProxy' object has no attribute 'add_event_notification'
The from_bucket_arn function returns an IBucket, and the add_event_notification function is a method of the Bucket class, but I can't seem to find any other way to do this. Maybe it's not supported. Any help would be appreciated.
I managed to get this working with a custom resource. It's TypeScript, but it should be easily translated to Python:
const uploadBucket = s3.Bucket.fromBucketName(this, 'BucketByName', 'existing-bucket');
const fn = new lambda.Function(this, 'MyFunction', {
runtime: lambda.Runtime.NODEJS_10_X,
handler: 'index.handler',
code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler'))
});
const rsrc = new AwsCustomResource(this, 'S3NotificationResource', {
onCreate: {
service: 'S3',
action: 'putBucketNotificationConfiguration',
parameters: {
// This bucket must be in the same region you are deploying to
Bucket: uploadBucket.bucketName,
NotificationConfiguration: {
LambdaFunctionConfigurations: [
{
Events: ['s3:ObjectCreated:*'],
LambdaFunctionArn: fn.functionArn,
Filter: {
Key: {
FilterRules: [{ Name: 'suffix', Value: 'csv' }]
}
}
}
]
}
},
// Always update physical ID so function gets executed
physicalResourceId: 'S3NotifCustomResource' + Date.now().toString()
}
});
fn.addPermission('AllowS3Invocation', {
action: 'lambda:InvokeFunction',
principal: new iam.ServicePrincipal('s3.amazonaws.com'),
sourceArn: uploadBucket.bucketArn
});
rsrc.node.addDependency(fn.permissionsNode.findChild('AllowS3Invocation'));
This is basically a CDK version of the CloudFormation template laid out in this example. See the docs on the AWS SDK for the possible NotificationConfiguration parameters.
since June 2021 there is a nicer way to solve this problem. Since approx. Version 1.110.0 of the CDK it is possible to use the S3 notifications with Typescript Code:
Example:
const s3Bucket = s3.Bucket.fromBucketName(this, 'bucketId', 'bucketName');
s3Bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(lambdaFunction), {
prefix: 'example/file.txt'
});
CDK Documentation:
https://docs.aws.amazon.com/cdk/api/latest/docs/aws-s3-notifications-readme.html
Pull Request:
https://github.com/aws/aws-cdk/pull/15158
Sorry I can't comment on the excellent James Irwin's answer above due to a low reputation, but I took and made it into a Construct.
The comment about "Access Denied" took me some time to figure out too, but the crux of it is that the function is S3:putBucketNotificationConfiguration, but the IAM Policy action to allow is S3:PutBucketNotification.
Here's the [code for the construct]:(https://gist.github.com/archisgore/0f098ae1d7d19fddc13d2f5a68f606ab)
import * as cr from '#aws-cdk/custom-resources';
import * as logs from '#aws-cdk/aws-logs';
import * as s3 from '#aws-cdk/aws-s3';
import * as sqs from '#aws-cdk/aws-sqs';
import * as iam from '#aws-cdk/aws-iam';
import {Construct} from '#aws-cdk/core';
// You can drop this construct anywhere, and in your stack, invoke it like this:
// const s3ToSQSNotification = new S3NotificationToSQSCustomResource(this, 's3ToSQSNotification', existingBucket, queue);
export class S3NotificationToSQSCustomResource extends Construct {
constructor(scope: Construct, id: string, bucket: s3.IBucket, queue: sqs.Queue) {
super(scope, id);
// https://stackoverflow.com/questions/58087772/aws-cdk-how-to-add-an-event-notification-to-an-existing-s3-bucket
const notificationResource = new cr.AwsCustomResource(scope, id+"CustomResource", {
onCreate: {
service: 'S3',
action: 'putBucketNotificationConfiguration',
parameters: {
// This bucket must be in the same region you are deploying to
Bucket: bucket.bucketName,
NotificationConfiguration: {
QueueConfigurations: [
{
Events: ['s3:ObjectCreated:*'],
QueueArn: queue.queueArn,
}
]
}
},
physicalResourceId: <cr.PhysicalResourceId>(id + Date.now().toString()),
},
onDelete: {
service: 'S3',
action: 'putBucketNotificationConfiguration',
parameters: {
// This bucket must be in the same region you are deploying to
Bucket: bucket.bucketName,
// deleting a notification configuration involves setting it to empty.
NotificationConfiguration: {
}
},
physicalResourceId: <cr.PhysicalResourceId>(id + Date.now().toString()),
},
policy: cr.AwsCustomResourcePolicy.fromStatements([new iam.PolicyStatement({
// The actual function is PutBucketNotificationConfiguration.
// The "Action" for IAM policies is PutBucketNotification.
// https://docs.aws.amazon.com/AmazonS3/latest/dev/list_amazons3.html#amazons3-actions-as-permissions
actions: ["S3:PutBucketNotification"],
// allow this custom resource to modify this bucket
resources: [bucket.bucketArn],
})]),
logRetention: logs.RetentionDays.ONE_DAY,
});
// allow S3 to send notifications to our queue
// https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#grant-destinations-permissions-to-s3
queue.addToResourcePolicy(new iam.PolicyStatement({
principals: [new iam.ServicePrincipal("s3.amazonaws.com")],
actions: ["SQS:SendMessage"],
resources: [queue.queueArn],
conditions: {
ArnEquals: {"aws:SourceArn": bucket.bucketArn}
}
}));
// don't create the notification custom-resource until after both the bucket and queue
// are fully created and policies applied.
notificationResource.node.addDependency(bucket);
notificationResource.node.addDependency(queue);
}
}
UPDATED: Source code from original answer will overwrite existing notification list for bucket which will make it impossible adding new lambda triggers. Here's the solution which uses event sources to handle mentioned problem.
import aws_cdk {
aws_s3 as s3,
aws_cdk.aws_lambda as lambda_
aws_lambda_event_sources as event_src
}
import path as path
class S3LambdaTrigger(core.Stack):
def __init__(self, scope: core.Construct, id: str):
super().__init__(scope, id)
bucket = s3.Bucket(
self, "S3Bucket",
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
bucket_name='BucketName',
encryption=s3.BucketEncryption.S3_MANAGED,
versioned=True
)
fn = lambda_.Function(
self, "LambdaFunction",
runtime=lambda_.Runtime.NODEJS_10_X,
handler="index.handler",
code=lambda_.Code.from_asset(path.join(__dirname, "lambda-handler"))
)
fn.add_permission(
's3-service-principal',
principal=aws_iam.ServicePrincipal('s3.amazonaws.com')
)
fn.add_event_source(
event_src.S3EventSource(
bucket,
events=[s3.EventType.OBJECT_CREATED, s3.EventType.OBJECT_REMOVED],
filters=[s3.NotificationKeyFilter(prefix="subdir/", suffix=".txt")]
)
)
ORIGINAL:
I took ubi's solution in TypeScript and successfully translated it to Python. His solution worked for me.
#!/usr/bin/env python
from typing import List
from aws_cdk import (
core,
custom_resources as cr,
aws_lambda as lambda_,
aws_s3 as s3,
aws_iam as iam,
)
class S3NotificationLambdaProps:
def __init__(self, bucket: s3.Bucket, function: lambda_.Function, events: List[str], prefix: str):
self.bucket = bucket
self.function = function
self.events = events
self.prefix = prefix
class S3NotificationLambda(core.Construct):
def __init__(self, scope: core.Construct, id: str, props: S3NotificationLambdaProps):
super().__init__(scope, id)
self.notificationResource = cr.AwsCustomResource(
self, f'CustomResource{id}',
on_create=cr.AwsSdkCall(
service="S3",
action="S3:putBucketNotificationConfiguration",
# Always update physical ID so function gets executed
physical_resource_id=cr.PhysicalResourceId.of(f'S3NotifCustomResource{id}'),
parameters={
"Bucket": props.bucket.bucket_name,
"NotificationConfiguration": {
"LambdaFunctionConfigurations": [{
"Events": props.events,
"LambdaFunctionArn": props.function.function_arn,
"Filter": {
"Key": {"FilterRules": [{"Name": "prefix", "Value": props.prefix}]}
}}
]
}
}
),
on_delete=cr.AwsSdkCall(
service="S3",
action="S3:putBucketNotificationConfiguration",
# Always update physical ID so function gets executed
physical_resource_id=cr.PhysicalResourceId.of(f'S3NotifCustomResource{id}'),
parameters={
"Bucket": props.bucket.bucket_name,
"NotificationConfiguration": {},
}
),
policy=cr.AwsCustomResourcePolicy.from_statements(
statements=[
iam.PolicyStatement(
actions=["S3:PutBucketNotification", "S3:GetBucketNotification"],
resources=[props.bucket.bucket_arn]
),
]
)
)
props.function.add_permission(
"AllowS3Invocation",
action="lambda:InvokeFunction",
principal=iam.ServicePrincipal("s3.amazonaws.com"),
source_arn=props.bucket.bucket_arn,
)
# don't create the notification custom-resource until after both the bucket and lambda
# are fully created and policies applied.
self.notificationResource.node.add_dependency(props.bucket)
self.notificationResource.node.add_dependency(props.function)
# Usage:
s3NotificationLambdaProps = S3NotificationLambdaProps(
bucket=bucket_,
function=lambda_fn_,
events=['s3:ObjectCreated:*'],
prefix='foo/'
)
s3NotificationLambda = S3NotificationLambda(
self, "S3NotifLambda",
self.s3NotificationLambdaProps
)
Here is a python solution for adding / replacing a lambda trigger to an existing bucket including the filter. #James Irwin your example was very helpful.
Thanks to #JørgenFrøland for pointing out that the custom resource config will replace any existing notification triggers based on the boto3 documentation https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.BucketNotification.put
One note is he access denied issue is
because if you do putBucketNotificationConfiguration action the policy creates a s3:PutBucketNotificationConfiguration action but that action doesn't exist https://github.com/aws/aws-cdk/issues/3318#issuecomment-584737465
Same issue happens if you set the policy using AwsCustomResourcePolicy.fromSdkCalls
I've added a custom policy that might need to be restricted further.
s3_bucket = s3.Bucket.from_bucket_name(
self, 's3-bucket-by-name', 'existing-bucket-name')
trigger_lambda = _lambda.Function(
self,
'{id}-s3-trigger-lambda',
environment=lambda_env,
code=_lambda.Code.from_asset('./ladle-sink/'),
runtime=_lambda.Runtime.PYTHON_3_7,
handler='lambda_function.lambda_handler',
memory_size=512,
timeout=core.Duration.minutes(3))
trigger_lambda.add_permission(
's3-trigger-lambda-s3-invoke-function',
principal=iam.ServicePrincipal('s3.amazonaws.com'),
action='lambda:InvokeFunction',
source_arn=base_resources.incoming_documents_bucket.bucket_arn)
custom_s3_resource = _custom_resources.AwsCustomResource(
self,
's3-incoming-documents-notification-resource',
policy=_custom_resources.AwsCustomResourcePolicy.from_statements([
iam.PolicyStatement(
effect=iam.Effect.ALLOW,
resources=['*'],
actions=['s3:PutBucketNotification']
)
]),
on_create=_custom_resources.AwsSdkCall(
service="S3",
action="putBucketNotificationConfiguration",
parameters={
"Bucket": s3_bucket.bucket_name,
"NotificationConfiguration": {
"LambdaFunctionConfigurations": [
{
"Events": ['s3:ObjectCreated:*'],
"LambdaFunctionArn": trigger_lambda.function_arn,
"Filter": {
"Key": {
"FilterRules": [
{'Name': 'suffix', 'Value': 'html'}]
}
}
}
]
}
},
physical_resource_id=_custom_resources.PhysicalResourceId.of(
f's3-notification-resource-{str(uuid.uuid1())}'),
region=env.region
))
custom_s3_resource.node.add_dependency(
trigger_lambda.permissions_node.find_child(
's3-trigger-lambda-s3-invoke-function'))
Thanks to the great answers above, see below for a construct for s3 -> lambda notification. It can be used like
const fn = new SingletonFunction(this, "Function", {
...
});
const bucket = Bucket.fromBucketName(this, "Bucket", "...");
const s3notification = new S3NotificationLambda(this, "S3Notification", {
bucket: bucket,
lambda: function,
events: ['s3:ObjectCreated:*'],
prefix: "some_prefix/"
})
Construct (drop-in to your project as a .ts file)
import * as cr from "#aws-cdk/custom-resources";
import * as logs from "#aws-cdk/aws-logs";
import * as s3 from "#aws-cdk/aws-s3";
import * as sqs from "#aws-cdk/aws-sqs";
import * as iam from "#aws-cdk/aws-iam";
import { Construct } from "#aws-cdk/core";
import * as lambda from "#aws-cdk/aws-lambda";
export interface S3NotificationLambdaProps {
bucket: s3.IBucket;
lambda: lambda.IFunction;
events: string[];
prefix: string;
}
export class S3NotificationLambda extends Construct {
constructor(scope: Construct, id: string, props: S3NotificationLambdaProps) {
super(scope, id);
const notificationResource = new cr.AwsCustomResource(
scope,
id + "CustomResource",
{
onCreate: {
service: "S3",
action: "putBucketNotificationConfiguration",
parameters: {
// This bucket must be in the same region you are deploying to
Bucket: props.bucket.bucketName,
NotificationConfiguration: {
LambdaFunctionConfigurations: [
{
Events: props.events,
LambdaFunctionArn: props.lambda.functionArn,
Filter: {
Key: {
FilterRules: [{ Name: "prefix", Value: props.prefix }],
},
},
},
],
},
},
physicalResourceId: <cr.PhysicalResourceId>(
(id + Date.now().toString())
),
},
onDelete: {
service: "S3",
action: "putBucketNotificationConfiguration",
parameters: {
// This bucket must be in the same region you are deploying to
Bucket: props.bucket.bucketName,
// deleting a notification configuration involves setting it to empty.
NotificationConfiguration: {},
},
physicalResourceId: <cr.PhysicalResourceId>(
(id + Date.now().toString())
),
},
policy: cr.AwsCustomResourcePolicy.fromStatements([
new iam.PolicyStatement({
// The actual function is PutBucketNotificationConfiguration.
// The "Action" for IAM policies is PutBucketNotification.
// https://docs.aws.amazon.com/AmazonS3/latest/dev/list_amazons3.html#amazons3-actions-as-permissions
actions: ["S3:PutBucketNotification", "S3:GetBucketNotification"],
// allow this custom resource to modify this bucket
resources: [props.bucket.bucketArn],
}),
]),
}
);
props.lambda.addPermission("AllowS3Invocation", {
action: "lambda:InvokeFunction",
principal: new iam.ServicePrincipal("s3.amazonaws.com"),
sourceArn: props.bucket.bucketArn,
});
// don't create the notification custom-resource until after both the bucket and queue
// are fully created and policies applied.
notificationResource.node.addDependency(props.bucket);
notificationResource.node.addDependency(props.lambda);
}
}
based on the answer from #ubi
in case of you don't need the SingletonFunction but Function + some cleanup
call like this:
const s3NotificationLambdaProps = < S3NotificationLambdaProps > {
bucket: bucket,
lambda: lambda,
events: ['s3:ObjectCreated:*'],
prefix: '', // or put some prefix
};
const s3NotificationLambda = new S3NotificationLambda(this, `${envNameUpperCase}S3ToLambdaNotification`, s3NotificationLambdaProps);
and the construct will be like this:
import * as cr from "#aws-cdk/custom-resources";
import * as s3 from "#aws-cdk/aws-s3";
import * as iam from "#aws-cdk/aws-iam";
import { Construct } from "#aws-cdk/core";
import * as lambda from "#aws-cdk/aws-lambda";
export interface S3NotificationLambdaProps {
bucket: s3.IBucket;
lambda: lambda.Function;
events: string[];
prefix: string;
}
export class S3NotificationLambda extends Construct {
constructor(scope: Construct, id: string, props: S3NotificationLambdaProps) {
super(scope, id);
const notificationResource = new cr.AwsCustomResource(
scope,
id + "CustomResource", {
onCreate: {
service: "S3",
action: "putBucketNotificationConfiguration",
parameters: {
// This bucket must be in the same region you are deploying to
Bucket: props.bucket.bucketName,
NotificationConfiguration: {
LambdaFunctionConfigurations: [{
Events: props.events,
LambdaFunctionArn: props.lambda.functionArn,
Filter: {
Key: {
FilterRules: [{
Name: "prefix",
Value: props.prefix
}],
},
},
}, ],
},
},
physicalResourceId: < cr.PhysicalResourceId > (
(id + Date.now().toString())
),
},
onDelete: {
service: "S3",
action: "putBucketNotificationConfiguration",
parameters: {
// This bucket must be in the same region you are deploying to
Bucket: props.bucket.bucketName,
// deleting a notification configuration involves setting it to empty.
NotificationConfiguration: {},
},
physicalResourceId: < cr.PhysicalResourceId > (
(id + Date.now().toString())
),
},
policy: cr.AwsCustomResourcePolicy.fromStatements([
new iam.PolicyStatement({
// The actual function is PutBucketNotificationConfiguration.
// The "Action" for IAM policies is PutBucketNotification.
// https://docs.aws.amazon.com/AmazonS3/latest/dev/list_amazons3.html#amazons3-actions-as-permissions
actions: ["S3:PutBucketNotification", "S3:GetBucketNotification"],
// allow this custom resource to modify this bucket
resources: [props.bucket.bucketArn],
}),
]),
}
);
props.lambda.addPermission("AllowS3Invocation", {
action: "lambda:InvokeFunction",
principal: new iam.ServicePrincipal("s3.amazonaws.com"),
sourceArn: props.bucket.bucketArn,
});
// don't create the notification custom-resource until after both the bucket and lambda
// are fully created and policies applied.
notificationResource.node.addDependency(props.bucket);
notificationResource.node.addDependency(props.lambda);
}
}
With the newer functionality, in python this can now be done as:
bucket = aws_s3.Bucket.from_bucket_name(
self, "bucket", "bucket-name"
)
bucket.add_event_notification(
aws_s3.EventType.OBJECT_CREATED,
aws_s3_notifications.LambdaDestination(your_lambda),
aws_s3.NotificationKeyFilter(
prefix="prefix/path/",
),
)
At the time of writing, the AWS documentation seems to have the prefix arguments incorrect in their examples so this was moderately confusing to figure out.
Thanks to #Kilian Pfeifer for starting me down the right path with the typescript example.
I used CloudTrail for resolving the issue, code looks like below and its more abstract:
const trail = new cloudtrail.Trail(this, 'MyAmazingCloudTrail');
const options: AddEventSelectorOptions = {
readWriteType: cloudtrail.ReadWriteType.WRITE_ONLY
};
// Adds an event selector to the bucket
trail.addS3EventSelector([{
bucket: bucket, // 'Bucket' is of type s3.IBucket,
}], options);
bucket.onCloudTrailWriteObject('MyAmazingCloudTrail', {
target: new targets.LambdaFunction(functionReference)
});
This is CDK solution.
Get a grab of existing bucket using fromBucketAttributes
Then for your bucket, use addEventNotification to trigger your lambda.
declare const myLambda: lambda.Function;
const bucket = s3.Bucket.fromBucketAttributes(this, 'ImportedBucket', {
bucketArn: 'arn:aws:s3:::my-bucket',
});
// now you can just call methods on the bucket
bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.LambdaDestination(myLambda), {prefix: 'home/myusername/*'});
More details can be found here
AWS now supports s3 eventbridge events, which allows for adding a source s3 bucket by name. So this worked for me. Note that you need to enable eventbridge events manually for the triggering s3 bucket.
new Rule(this, 's3rule', {
eventPattern: {
source: ['aws.s3'],
detail: {
'bucket': {'name': ['existing-bucket']},
'object': {'key' : [{'prefix' : 'prefix'}]}
},
detailType: ['Object Created']
},
targets: [new targets.LambdaFunction(MyFunction)]
}
);

Create user with custom attribute using AdminCreateUser in AWS Cognito

I am trying to create user in AWS Cognito with adminCreateUser API with the below code
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
var params = {
UserPoolId: "us-east-1_302HlhnaC", /* required */
Username : "test#yopmail.com",
ForceAliasCreation: true,
TemporaryPassword: 'test#yopmail.com',
UserAttributes: [
{
Name: 'given_name', /* required */
Value: 'test'
},
{
Name: 'family_name', /* required */
Value: 'kumar'
},
{
Name: 'name', /* required */
Value: 'test'
},
{
Name: 'custom:dob', /* required */
Value: '1990-07-25'
},
{
Name: 'email', /* required */
Value: 'test#yopmail.com',
},
{
Name: 'email_verified', /* required */
Value: 'true',
}
/* more items */
],
};
cognitoidentityserviceprovider.adminCreateUser(params, function(error, data) {
console.log(error,data);
res.send("test");
});
It always throwing following exception :
InvalidParameterException: Attributes did not conform to the schema: custom:dob: Attribute does not exist in the schema.
Is am doing anything wrong,if yes please let me know the solution.
Thanks
You must add the custom attribute ahead of time. You can create custom attributes by visiting the User Pool and clicking the Attributes link.
Just adding my case here.
In my CloudFormation, I have:
Schema:
- AttributeDataType: String
Name: role
DeveloperOnlyAttribute: true
Mutable: true
Required: false
In the console, it translated into:
In the application adminCreateUser call, I had to provide it as dev:custom:role:
cognitoService.adminCreateUser({
UserPoolId: config.cognitoUserPoolId,
Username: email,
UserAttributes: [{
Name: 'dev:custom:role',
Value: role,
}]
}).promise()
Figured it out by trying. Wish I knew where the docs for this are.
Darcy's answer is correct. But I wanted to elaborate as that answer was focused on the AWS web console.
Also the other answer viz., prefixing "dev:" is probably an undocumented workaround (hence no documentation) and might stop working without warning.
First, the custom attributes has to be created when the Userpool is Created.
CreateUserPoolRequest request = new CreateUserPoolRequest
{
...
Schema = new List<SchemaAttributeType>
{
new SchemaAttributeType
{
Name = "email",
AttributeDataType = AttributeDataType.String,
Required = true,
Mutable = false
},
new SchemaAttributeType //custom attribute
{
Name = "blah",
AttributeDataType = AttributeDataType.String,
Mutable = false
},
...
};
And then when the user is created, it can be set.
var request = new AdminCreateUserRequest
{
...
UserAttributes = new List<AttributeType>
{
new AttributeType
{
Name = "email",
Value = "xyz#xyz.com"
},
new AttributeType //custom attribute
{
Name = $"custom:blah",
Value = "value for blah"
}
}
};
Now, just prefixing with "custom:" works.
Also note AWS continues with its tradition of having inconsistent api's by not having to prefix when creating the user pool and having the prefix when creating the user.