diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource-provider/index.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource-provider/index.ts index 99b97a74578b9..b962cc2cc7ea3 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource-provider/index.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource-provider/index.ts @@ -62,11 +62,18 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent const call: AwsSdkCall | undefined = event.ResourceProperties[event.RequestType]; if (call) { - const awsService = new (AWS as any)[call.service](call.apiVersion && { apiVersion: call.apiVersion }); + const awsService = new (AWS as any)[call.service]({ + apiVersion: call.apiVersion, + region: call.region, + }); try { const response = await awsService[call.action](call.parameters && decodeBooleans(call.parameters)).promise(); - flatData = flatten(response); + flatData = { + apiVersion: awsService.config.apiVersion, // For test purposes: check if apiVersion was correctly passed. + region: awsService.config.region, // For test purposes: check if region was correctly passed. + ...flatten(response), + }; data = call.outputPath ? filterKeys(flatData, k => k.startsWith(call.outputPath!)) : flatData; diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource.ts index 064de0584f135..bf23fa7e2c973 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource.ts @@ -42,7 +42,7 @@ export interface AwsSdkCall { * resource id. Either `physicalResourceId` or `physicalResourceIdPath` * must be specified for onCreate or onUpdate calls. * - * @default no path + * @default - no path */ readonly physicalResourceIdPath?: string; @@ -51,7 +51,7 @@ export interface AwsSdkCall { * `physicalResourceId` or `physicalResourceIdPath` must be specified for * onCreate or onUpdate calls. * - * @default no physical resource id + * @default - no physical resource id */ readonly physicalResourceId?: string; @@ -60,7 +60,7 @@ export interface AwsSdkCall { * `Error` object will be tested against this pattern. If there is a match an * error will not be thrown. * - * @default do not catch errors + * @default - do not catch errors */ readonly catchErrorPattern?: string; @@ -68,10 +68,19 @@ export interface AwsSdkCall { * API version to use for the service * * @see https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/locking-api-versions.html - * @default use latest available API version + * @default - use latest available API version */ readonly apiVersion?: string; + /** + * The region to send service requests to. + * **Note: Cross-region operations are generally considered an anti-pattern.** + * **Consider first deploying a stack in that region.** + * + * @default - the region where this custom resource is deployed + */ + readonly region?: string; + /** * Restrict the data returned by the custom resource to a specific path in * the API response. Use this to limit the data returned by the custom diff --git a/packages/@aws-cdk/custom-resources/lib/sdk-api-metadata.json b/packages/@aws-cdk/custom-resources/lib/sdk-api-metadata.json deleted file mode 100644 index e8fa54a288461..0000000000000 --- a/packages/@aws-cdk/custom-resources/lib/sdk-api-metadata.json +++ /dev/null @@ -1,770 +0,0 @@ -{ - "acm": { - "name": "ACM", - "cors": true - }, - "apigateway": { - "name": "APIGateway", - "cors": true - }, - "applicationautoscaling": { - "prefix": "application-autoscaling", - "name": "ApplicationAutoScaling", - "cors": true - }, - "appstream": { - "name": "AppStream" - }, - "autoscaling": { - "name": "AutoScaling", - "cors": true - }, - "batch": { - "name": "Batch" - }, - "budgets": { - "name": "Budgets" - }, - "clouddirectory": { - "name": "CloudDirectory", - "versions": [ - "2016-05-10*" - ] - }, - "cloudformation": { - "name": "CloudFormation", - "cors": true - }, - "cloudfront": { - "name": "CloudFront", - "versions": [ - "2013-05-12*", - "2013-11-11*", - "2014-05-31*", - "2014-10-21*", - "2014-11-06*", - "2015-04-17*", - "2015-07-27*", - "2015-09-17*", - "2016-01-13*", - "2016-01-28*", - "2016-08-01*", - "2016-08-20*", - "2016-09-07*", - "2016-09-29*", - "2016-11-25*", - "2017-03-25*", - "2017-10-30*", - "2018-06-18*", - "2018-11-05*" - ], - "cors": true - }, - "cloudhsm": { - "name": "CloudHSM", - "cors": true - }, - "cloudsearch": { - "name": "CloudSearch" - }, - "cloudsearchdomain": { - "name": "CloudSearchDomain" - }, - "cloudtrail": { - "name": "CloudTrail", - "cors": true - }, - "cloudwatch": { - "prefix": "monitoring", - "name": "CloudWatch", - "cors": true - }, - "cloudwatchevents": { - "prefix": "events", - "name": "CloudWatchEvents", - "versions": [ - "2014-02-03*" - ], - "cors": true - }, - "cloudwatchlogs": { - "prefix": "logs", - "name": "CloudWatchLogs", - "cors": true - }, - "codebuild": { - "name": "CodeBuild", - "cors": true - }, - "codecommit": { - "name": "CodeCommit", - "cors": true - }, - "codedeploy": { - "name": "CodeDeploy", - "cors": true - }, - "codepipeline": { - "name": "CodePipeline", - "cors": true - }, - "cognitoidentity": { - "prefix": "cognito-identity", - "name": "CognitoIdentity", - "cors": true - }, - "cognitoidentityserviceprovider": { - "prefix": "cognito-idp", - "name": "CognitoIdentityServiceProvider", - "cors": true - }, - "cognitosync": { - "prefix": "cognito-sync", - "name": "CognitoSync", - "cors": true - }, - "configservice": { - "prefix": "config", - "name": "ConfigService", - "cors": true - }, - "cur": { - "name": "CUR", - "cors": true - }, - "datapipeline": { - "name": "DataPipeline" - }, - "devicefarm": { - "name": "DeviceFarm", - "cors": true - }, - "directconnect": { - "name": "DirectConnect", - "cors": true - }, - "directoryservice": { - "prefix": "ds", - "name": "DirectoryService" - }, - "discovery": { - "name": "Discovery" - }, - "dms": { - "name": "DMS" - }, - "dynamodb": { - "name": "DynamoDB", - "cors": true - }, - "dynamodbstreams": { - "prefix": "streams.dynamodb", - "name": "DynamoDBStreams", - "cors": true - }, - "ec2": { - "name": "EC2", - "versions": [ - "2013-06-15*", - "2013-10-15*", - "2014-02-01*", - "2014-05-01*", - "2014-06-15*", - "2014-09-01*", - "2014-10-01*", - "2015-03-01*", - "2015-04-15*", - "2015-10-01*", - "2016-04-01*", - "2016-09-15*" - ], - "cors": true - }, - "ecr": { - "name": "ECR", - "cors": true - }, - "ecs": { - "name": "ECS", - "cors": true - }, - "efs": { - "prefix": "elasticfilesystem", - "name": "EFS", - "cors": true - }, - "elasticache": { - "name": "ElastiCache", - "versions": [ - "2012-11-15*", - "2014-03-24*", - "2014-07-15*", - "2014-09-30*" - ], - "cors": true - }, - "elasticbeanstalk": { - "name": "ElasticBeanstalk", - "cors": true - }, - "elb": { - "prefix": "elasticloadbalancing", - "name": "ELB", - "cors": true - }, - "elbv2": { - "prefix": "elasticloadbalancingv2", - "name": "ELBv2", - "cors": true - }, - "emr": { - "prefix": "elasticmapreduce", - "name": "EMR", - "cors": true - }, - "es": { - "name": "ES" - }, - "elastictranscoder": { - "name": "ElasticTranscoder", - "cors": true - }, - "firehose": { - "name": "Firehose", - "cors": true - }, - "gamelift": { - "name": "GameLift", - "cors": true - }, - "glacier": { - "name": "Glacier" - }, - "health": { - "name": "Health" - }, - "iam": { - "name": "IAM", - "cors": true - }, - "importexport": { - "name": "ImportExport" - }, - "inspector": { - "name": "Inspector", - "versions": [ - "2015-08-18*" - ], - "cors": true - }, - "iot": { - "name": "Iot", - "cors": true - }, - "iotdata": { - "prefix": "iot-data", - "name": "IotData", - "cors": true - }, - "kinesis": { - "name": "Kinesis", - "cors": true - }, - "kinesisanalytics": { - "name": "KinesisAnalytics" - }, - "kms": { - "name": "KMS", - "cors": true - }, - "lambda": { - "name": "Lambda", - "cors": true - }, - "lexruntime": { - "prefix": "runtime.lex", - "name": "LexRuntime", - "cors": true - }, - "lightsail": { - "name": "Lightsail" - }, - "machinelearning": { - "name": "MachineLearning", - "cors": true - }, - "marketplacecommerceanalytics": { - "name": "MarketplaceCommerceAnalytics", - "cors": true - }, - "marketplacemetering": { - "prefix": "meteringmarketplace", - "name": "MarketplaceMetering" - }, - "mturk": { - "prefix": "mturk-requester", - "name": "MTurk", - "cors": true - }, - "mobileanalytics": { - "name": "MobileAnalytics", - "cors": true - }, - "opsworks": { - "name": "OpsWorks", - "cors": true - }, - "opsworkscm": { - "name": "OpsWorksCM" - }, - "organizations": { - "name": "Organizations" - }, - "pinpoint": { - "name": "Pinpoint" - }, - "polly": { - "name": "Polly", - "cors": true - }, - "rds": { - "name": "RDS", - "versions": [ - "2014-09-01*" - ], - "cors": true - }, - "redshift": { - "name": "Redshift", - "cors": true - }, - "rekognition": { - "name": "Rekognition", - "cors": true - }, - "resourcegroupstaggingapi": { - "name": "ResourceGroupsTaggingAPI" - }, - "route53": { - "name": "Route53", - "cors": true - }, - "route53domains": { - "name": "Route53Domains", - "cors": true - }, - "s3": { - "name": "S3", - "dualstackAvailable": true, - "cors": true - }, - "s3control": { - "name": "S3Control", - "dualstackAvailable": true - }, - "servicecatalog": { - "name": "ServiceCatalog", - "cors": true - }, - "ses": { - "prefix": "email", - "name": "SES", - "cors": true - }, - "shield": { - "name": "Shield" - }, - "simpledb": { - "prefix": "sdb", - "name": "SimpleDB" - }, - "sms": { - "name": "SMS" - }, - "snowball": { - "name": "Snowball" - }, - "sns": { - "name": "SNS", - "cors": true - }, - "sqs": { - "name": "SQS", - "cors": true - }, - "ssm": { - "name": "SSM", - "cors": true - }, - "storagegateway": { - "name": "StorageGateway", - "cors": true - }, - "stepfunctions": { - "prefix": "states", - "name": "StepFunctions" - }, - "sts": { - "name": "STS", - "cors": true - }, - "support": { - "name": "Support" - }, - "swf": { - "name": "SWF" - }, - "xray": { - "name": "XRay" - }, - "waf": { - "name": "WAF", - "cors": true - }, - "wafregional": { - "prefix": "waf-regional", - "name": "WAFRegional" - }, - "workdocs": { - "name": "WorkDocs", - "cors": true - }, - "workspaces": { - "name": "WorkSpaces" - }, - "codestar": { - "name": "CodeStar" - }, - "lexmodelbuildingservice": { - "prefix": "lex-models", - "name": "LexModelBuildingService", - "cors": true - }, - "marketplaceentitlementservice": { - "prefix": "entitlement.marketplace", - "name": "MarketplaceEntitlementService" - }, - "athena": { - "name": "Athena" - }, - "greengrass": { - "name": "Greengrass" - }, - "dax": { - "name": "DAX" - }, - "migrationhub": { - "prefix": "AWSMigrationHub", - "name": "MigrationHub" - }, - "cloudhsmv2": { - "name": "CloudHSMV2" - }, - "glue": { - "name": "Glue" - }, - "mobile": { - "name": "Mobile" - }, - "pricing": { - "name": "Pricing", - "cors": true - }, - "costexplorer": { - "prefix": "ce", - "name": "CostExplorer", - "cors": true - }, - "mediaconvert": { - "name": "MediaConvert" - }, - "medialive": { - "name": "MediaLive" - }, - "mediapackage": { - "name": "MediaPackage" - }, - "mediastore": { - "name": "MediaStore" - }, - "mediastoredata": { - "prefix": "mediastore-data", - "name": "MediaStoreData", - "cors": true - }, - "appsync": { - "name": "AppSync" - }, - "guardduty": { - "name": "GuardDuty" - }, - "mq": { - "name": "MQ" - }, - "comprehend": { - "name": "Comprehend", - "cors": true - }, - "iotjobsdataplane": { - "prefix": "iot-jobs-data", - "name": "IoTJobsDataPlane" - }, - "kinesisvideoarchivedmedia": { - "prefix": "kinesis-video-archived-media", - "name": "KinesisVideoArchivedMedia", - "cors": true - }, - "kinesisvideomedia": { - "prefix": "kinesis-video-media", - "name": "KinesisVideoMedia", - "cors": true - }, - "kinesisvideo": { - "name": "KinesisVideo", - "cors": true - }, - "sagemakerruntime": { - "prefix": "runtime.sagemaker", - "name": "SageMakerRuntime" - }, - "sagemaker": { - "name": "SageMaker" - }, - "translate": { - "name": "Translate", - "cors": true - }, - "resourcegroups": { - "prefix": "resource-groups", - "name": "ResourceGroups", - "cors": true - }, - "alexaforbusiness": { - "name": "AlexaForBusiness" - }, - "cloud9": { - "name": "Cloud9" - }, - "serverlessapplicationrepository": { - "prefix": "serverlessrepo", - "name": "ServerlessApplicationRepository" - }, - "servicediscovery": { - "name": "ServiceDiscovery" - }, - "workmail": { - "name": "WorkMail" - }, - "autoscalingplans": { - "prefix": "autoscaling-plans", - "name": "AutoScalingPlans" - }, - "transcribeservice": { - "prefix": "transcribe", - "name": "TranscribeService" - }, - "connect": { - "name": "Connect" - }, - "acmpca": { - "prefix": "acm-pca", - "name": "ACMPCA" - }, - "fms": { - "name": "FMS" - }, - "secretsmanager": { - "name": "SecretsManager", - "cors": true - }, - "iotanalytics": { - "name": "IoTAnalytics" - }, - "iot1clickdevicesservice": { - "prefix": "iot1click-devices", - "name": "IoT1ClickDevicesService" - }, - "iot1clickprojects": { - "prefix": "iot1click-projects", - "name": "IoT1ClickProjects" - }, - "pi": { - "name": "PI" - }, - "neptune": { - "name": "Neptune" - }, - "mediatailor": { - "name": "MediaTailor" - }, - "eks": { - "name": "EKS" - }, - "macie": { - "name": "Macie" - }, - "dlm": { - "name": "DLM" - }, - "signer": { - "name": "Signer" - }, - "chime": { - "name": "Chime" - }, - "pinpointemail": { - "prefix": "pinpoint-email", - "name": "PinpointEmail" - }, - "ram": { - "name": "RAM" - }, - "route53resolver": { - "name": "Route53Resolver" - }, - "pinpointsmsvoice": { - "prefix": "sms-voice", - "name": "PinpointSMSVoice" - }, - "quicksight": { - "name": "QuickSight" - }, - "rdsdataservice": { - "prefix": "rds-data", - "name": "RDSDataService" - }, - "amplify": { - "name": "Amplify" - }, - "datasync": { - "name": "DataSync" - }, - "robomaker": { - "name": "RoboMaker" - }, - "transfer": { - "name": "Transfer" - }, - "globalaccelerator": { - "name": "GlobalAccelerator" - }, - "comprehendmedical": { - "name": "ComprehendMedical", - "cors": true - }, - "kinesisanalyticsv2": { - "name": "KinesisAnalyticsV2" - }, - "mediaconnect": { - "name": "MediaConnect" - }, - "fsx": { - "name": "FSx" - }, - "securityhub": { - "name": "SecurityHub" - }, - "appmesh": { - "name": "AppMesh", - "versions": [ - "2018-10-01*" - ] - }, - "licensemanager": { - "prefix": "license-manager", - "name": "LicenseManager" - }, - "kafka": { - "name": "Kafka" - }, - "apigatewaymanagementapi": { - "name": "ApiGatewayManagementApi" - }, - "apigatewayv2": { - "name": "ApiGatewayV2" - }, - "docdb": { - "name": "DocDB" - }, - "backup": { - "name": "Backup" - }, - "worklink": { - "name": "WorkLink" - }, - "textract": { - "name": "Textract" - }, - "managedblockchain": { - "name": "ManagedBlockchain" - }, - "mediapackagevod": { - "prefix": "mediapackage-vod", - "name": "MediaPackageVod" - }, - "groundstation": { - "name": "GroundStation" - }, - "iotthingsgraph": { - "name": "IoTThingsGraph" - }, - "iotevents": { - "name": "IoTEvents" - }, - "ioteventsdata": { - "prefix": "iotevents-data", - "name": "IoTEventsData" - }, - "personalize": { - "name": "Personalize", - "cors": true - }, - "personalizeevents": { - "prefix": "personalize-events", - "name": "PersonalizeEvents", - "cors": true - }, - "personalizeruntime": { - "prefix": "personalize-runtime", - "name": "PersonalizeRuntime", - "cors": true - }, - "applicationinsights": { - "prefix": "application-insights", - "name": "ApplicationInsights" - }, - "servicequotas": { - "prefix": "service-quotas", - "name": "ServiceQuotas" - }, - "ec2instanceconnect": { - "prefix": "ec2-instance-connect", - "name": "EC2InstanceConnect" - }, - "eventbridge": { - "name": "EventBridge" - }, - "lakeformation": { - "name": "LakeFormation" - }, - "forecastservice": { - "prefix": "forecast", - "name": "ForecastService", - "cors": true - }, - "forecastqueryservice": { - "prefix": "forecastquery", - "name": "ForecastQueryService", - "cors": true - }, - "qldb": { - "name": "QLDB" - }, - "qldbsession": { - "prefix": "qldb-session", - "name": "QLDBSession" - }, - "workmailmessageflow": { - "name": "WorkMailMessageFlow" - } -} diff --git a/packages/@aws-cdk/custom-resources/test/test.aws-custom-resource-provider.ts b/packages/@aws-cdk/custom-resources/test/test.aws-custom-resource-provider.ts index ce4e47d09adcd..010197f858603 100644 --- a/packages/@aws-cdk/custom-resources/test/test.aws-custom-resource-provider.ts +++ b/packages/@aws-cdk/custom-resources/test/test.aws-custom-resource-provider.ts @@ -297,4 +297,41 @@ export = { test.done(); }, + + async 'can specify apiVersion and region'(test: Test) { + const publishFake = sinon.fake.resolves({}); + + AWS.mock('SNS', 'publish', publishFake); + + const event: AWSLambda.CloudFormationCustomResourceCreateEvent = { + ...eventCommon, + RequestType: 'Create', + ResourceProperties: { + ServiceToken: 'token', + Create: { + service: 'SNS', + action: 'publish', + parameters: { + Message: 'message', + TopicArn: 'topic' + }, + apiVersion: '2010-03-31', + region: 'eu-west-1', + physicalResourceId: 'id', + } as AwsSdkCall + } + }; + + const request = createRequest(body => + body.Status === 'SUCCESS' && + body.Data!.apiVersion === '2010-03-31' && + body.Data!.region === 'eu-west-1' + ); + + await handler(event, {} as AWSLambda.Context); + + test.equal(request.isDone(), true); + + test.done(); + }, };