From 231b8ddfb6d56dd5703c7ad22519d57e788bdb97 Mon Sep 17 00:00:00 2001 From: Lorenz Vanthillo Date: Thu, 26 Jun 2025 19:45:32 +0200 Subject: [PATCH 1/6] feat: add support for tag replication for aws synthetics --- ...efaultTestDeployAssertF883002A.assets.json | 20 ++ ...aultTestDeployAssertF883002A.template.json | 36 +++ ...yResourcesToReplicateTagsStack.assets.json | 34 +++ ...esourcesToReplicateTagsStack.template.json | 284 ++++++++++++++++++ .../canary.mjs | 87 ++++++ .../nodejs/node_modules/canary.js | 52 ++++ .../nodejs/node_modules/folder/canary.js | 52 ++++ .../playwright/canary.mjs | 87 ++++++ .../python/canary.py | 62 ++++ .../cdk.out | 1 + .../integ.json | 13 + .../manifest.json | 181 +++++++++++ .../tree.json | 1 + ...nteg.canary-resources-to-replicate-tags.ts | 36 +++ packages/aws-cdk-lib/aws-synthetics/README.md | 18 ++ .../aws-cdk-lib/aws-synthetics/lib/canary.ts | 16 +- .../aws-synthetics/test/canary.test.ts | 22 ++ 17 files changed, 1001 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsStack.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsStack.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/canary.mjs create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/canary.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/folder/canary.js create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/playwright/canary.mjs create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/python/canary.py create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/tree.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.assets.json new file mode 100644 index 0000000000000..e63a80b4e451d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.assets.json @@ -0,0 +1,20 @@ +{ + "version": "44.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A Template", + "source": { + "path": "SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsStack.assets.json new file mode 100644 index 0000000000000..12b6e439c740c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsStack.assets.json @@ -0,0 +1,34 @@ +{ + "version": "44.0.0", + "files": { + "5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f": { + "displayName": "TagReplicationCanary/Code", + "source": { + "path": "asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "05afbffbd3fd4bafcca6e1f1f35525efe38124e6892f294ec7421e1d97072304": { + "displayName": "SyntheticsCanaryResourcesToReplicateTagsStack Template", + "source": { + "path": "SyntheticsCanaryResourcesToReplicateTagsStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "05afbffbd3fd4bafcca6e1f1f35525efe38124e6892f294ec7421e1d97072304.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsStack.template.json new file mode 100644 index 0000000000000..63dba8cbccf50 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/SyntheticsCanaryResourcesToReplicateTagsStack.template.json @@ -0,0 +1,284 @@ +{ + "Resources": { + "TagReplicationCanaryArtifactsBucket66A919F9": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "Tags": [ + { + "Key": "Environment", + "Value": "test" + }, + { + "Key": "Owner", + "Value": "cdk-team" + }, + { + "Key": "Project", + "Value": "synthetics-tag-replication" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "TagReplicationCanaryArtifactsBucketPolicyCFD9E77B": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "TagReplicationCanaryArtifactsBucket66A919F9" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "TagReplicationCanaryArtifactsBucket66A919F9", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TagReplicationCanaryArtifactsBucket66A919F9", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "TagReplicationCanaryServiceRoleE1E5A2B6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:ListAllMyBuckets", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "s3:GetBucketLocation", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TagReplicationCanaryArtifactsBucket66A919F9", + "Arn" + ] + } + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TagReplicationCanaryArtifactsBucket66A919F9", + "Arn" + ] + }, + "/*" + ] + ] + } + }, + { + "Action": "cloudwatch:PutMetricData", + "Condition": { + "StringEquals": { + "cloudwatch:namespace": "CloudWatchSynthetics" + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/lambda/cwsyn-*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "canaryPolicy" + } + ], + "Tags": [ + { + "Key": "Environment", + "Value": "test" + }, + { + "Key": "Owner", + "Value": "cdk-team" + }, + { + "Key": "Project", + "Value": "synthetics-tag-replication" + } + ] + } + }, + "TagReplicationCanary4B22C124": { + "Type": "AWS::Synthetics::Canary", + "Properties": { + "ArtifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "TagReplicationCanaryArtifactsBucket66A919F9" + } + ] + ] + }, + "Code": { + "Handler": "canary.handler", + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f.zip" + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "TagReplicationCanaryServiceRoleE1E5A2B6", + "Arn" + ] + }, + "Name": "tag-replication", + "ResourcesToReplicateTags": [ + "lambda-function" + ], + "RunConfig": { + "MemoryInMB": 1024, + "TimeoutInSeconds": 180 + }, + "RuntimeVersion": "syn-nodejs-puppeteer-7.0", + "Schedule": { + "DurationInSeconds": "0", + "Expression": "rate(5 minutes)" + }, + "StartCanaryAfterCreation": true, + "Tags": [ + { + "Key": "Environment", + "Value": "test" + }, + { + "Key": "Owner", + "Value": "cdk-team" + }, + { + "Key": "Project", + "Value": "synthetics-tag-replication" + } + ] + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/canary.mjs b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/canary.mjs new file mode 100644 index 0000000000000..eb6bd4e9b823d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/canary.mjs @@ -0,0 +1,87 @@ +import { URL } from 'url'; +import { synthetics } from '@amzn/synthetics-playwright'; + +const loadBlueprints = async function () { + const urls = [process.env.URL]; + + const browser = await synthetics.launch(); + const browserContext = await browser.newContext(); + let page = await synthetics.newPage(browserContext); + + for (const url of urls) { + await loadUrl(page, url); + } + + // Ensure browser is closed + await synthetics.close(); +}; + +// Reset the page in-between +const resetPage = async function(page) { + try { + // Set page.goto timeout to 30 seconds, adjust as needed + // See https://playwright.dev/docs/api/class-page for page.goto options + await page.goto('about:blank', { waitUntil: 'load', timeout: 30000 }); + } catch (e) { + console.error('Unable to open a blank page. ', e); + } +}; + +const loadUrl = async function (page, url) { + let stepName = null; + let domcontentloaded = false; + + try { + stepName = new URL(url).hostname; + } catch (e) { + const errorString = `Error parsing url: ${url}. ${e}`; + log.error(errorString); + /* If we fail to parse the URL, don't emit a metric with a stepName based on it. + It may not be a legal CloudWatch metric dimension name and we may not have an alarms + setup on the malformed URL stepName. Instead, fail this step which will + show up in the logs and will fail the overall canary and alarm on the overall canary + success rate. + */ + throw e; + }; + + await synthetics.executeStep(stepName, async function () { + try { + /* You can customize the wait condition here. + 'domcontentloaded' - consider operation to be finished when the DOMContentLoaded event is fired. + 'load' - consider operation to be finished when the load event is fired. + 'networkidle' - DISCOURAGED consider operation to be finished when there are no network connections for at least 500 ms. Don't use this method for testing, rely on web assertions to assess readiness instead. + 'commit' - consider operation to be finished when network response is received and the document started loading. + + Set page.goto timeout to 30 seconds, adjust as needed + See https://playwright.dev/docs/api/class-page for page.goto options + */ + const response = await page.goto(url, { waitUntil: 'load', timeout: 30000 }); + if (response) { + domcontentloaded = true; + const status = response.status(); + console.log(`Response status: ${status}`); + + // If the response status code is not a 2xx success code + if (status < 200 || status > 299) { + console.error(`Failed to load url: ${url}, status code: ${status}`); + throw new Error('Failed'); + } + } else { + console.error(`No response returned for url: ${url}`); + throw new Error(logNoResponseString); + } + } catch (e) { + const errorString = `Error navigating to url: ${url}. ${e}`; + console.error(errorString); + throw e; + } + }); + + // Reset page + await resetPage(page); +}; + +export const handler = async (event, context) => { + return await loadBlueprints(); +}; \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/canary.js b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/canary.js new file mode 100644 index 0000000000000..d7936811fd8c1 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/canary.js @@ -0,0 +1,52 @@ +var synthetics = require('Synthetics'); +const log = require('SyntheticsLogger'); +const https = require('https'); +const http = require('http'); + +const apiCanaryBlueprint = async function () { + const postData = ""; + + const verifyRequest = async function (requestOption) { + return new Promise((resolve, reject) => { + log.info("Making request with options: " + JSON.stringify(requestOption)); + let req + if (requestOption.protocol === 'https:') { + req = https.request(requestOption); + } else { + req = http.request(requestOption); + } + req.on('response', (res) => { + log.info(`Status Code: ${res.statusCode}`) + log.info(`Response Headers: ${JSON.stringify(res.headers)}`) + if (res.statusCode !== 200) { + reject("Failed: " + requestOption.pathname); + } + res.on('data', (d) => { + log.info("Response: " + d); + }); + res.on('end', () => { + resolve(); + }) + }); + + req.on('error', (error) => { + reject(error); + }); + + if (postData) { + req.write(postData); + } + req.end(); + }); + } + + const headers = {} + headers['User-Agent'] = [synthetics.getCanaryUserAgentString(), headers['User-Agent']].join(' '); + const requestOptions = new URL(process.env.URL); + requestOptions['headers'] = headers; + await verifyRequest(requestOptions); +}; + +exports.handler = async () => { + return await apiCanaryBlueprint(); +}; \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/folder/canary.js b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/folder/canary.js new file mode 100644 index 0000000000000..2ee399fbc633f --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/folder/canary.js @@ -0,0 +1,52 @@ +var synthetics = require('Synthetics'); +const log = require('SyntheticsLogger'); +const https = require('https'); +const http = require('http'); + +const apiCanaryBlueprint = async function () { + const postData = ""; + + const verifyRequest = async function (requestOption) { + return new Promise((resolve, reject) => { + log.info("Making request with options: " + JSON.stringify(requestOption)); + let req + if (requestOption.protocol === 'https:') { + req = https.request(requestOption); + } else { + req = http.request(requestOption); + } + req.on('response', (res) => { + log.info(`Status Code: ${res.statusCode}`) + log.info(`Response Headers: ${JSON.stringify(res.headers)}`) + if (res.statusCode !== 200) { + reject("Failed: " + requestOption.pathname); + } + res.on('data', (d) => { + log.info("Response: " + d); + }); + res.on('end', () => { + resolve(); + }) + }); + + req.on('error', (error) => { + reject(error); + }); + + if (postData) { + req.write(postData); + } + req.end(); + }); + } + + const headers = {} + headers['User-Agent'] = [synthetics.getCanaryUserAgentString(), headers['User-Agent']].join(' '); + const requestOptions = new URL(process.env.URL); + requestOptions['headers'] = headers; + await verifyRequest(requestOptions); +}; + +exports.functionName = async () => { + return await apiCanaryBlueprint(); +}; \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/playwright/canary.mjs b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/playwright/canary.mjs new file mode 100644 index 0000000000000..eb6bd4e9b823d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/playwright/canary.mjs @@ -0,0 +1,87 @@ +import { URL } from 'url'; +import { synthetics } from '@amzn/synthetics-playwright'; + +const loadBlueprints = async function () { + const urls = [process.env.URL]; + + const browser = await synthetics.launch(); + const browserContext = await browser.newContext(); + let page = await synthetics.newPage(browserContext); + + for (const url of urls) { + await loadUrl(page, url); + } + + // Ensure browser is closed + await synthetics.close(); +}; + +// Reset the page in-between +const resetPage = async function(page) { + try { + // Set page.goto timeout to 30 seconds, adjust as needed + // See https://playwright.dev/docs/api/class-page for page.goto options + await page.goto('about:blank', { waitUntil: 'load', timeout: 30000 }); + } catch (e) { + console.error('Unable to open a blank page. ', e); + } +}; + +const loadUrl = async function (page, url) { + let stepName = null; + let domcontentloaded = false; + + try { + stepName = new URL(url).hostname; + } catch (e) { + const errorString = `Error parsing url: ${url}. ${e}`; + log.error(errorString); + /* If we fail to parse the URL, don't emit a metric with a stepName based on it. + It may not be a legal CloudWatch metric dimension name and we may not have an alarms + setup on the malformed URL stepName. Instead, fail this step which will + show up in the logs and will fail the overall canary and alarm on the overall canary + success rate. + */ + throw e; + }; + + await synthetics.executeStep(stepName, async function () { + try { + /* You can customize the wait condition here. + 'domcontentloaded' - consider operation to be finished when the DOMContentLoaded event is fired. + 'load' - consider operation to be finished when the load event is fired. + 'networkidle' - DISCOURAGED consider operation to be finished when there are no network connections for at least 500 ms. Don't use this method for testing, rely on web assertions to assess readiness instead. + 'commit' - consider operation to be finished when network response is received and the document started loading. + + Set page.goto timeout to 30 seconds, adjust as needed + See https://playwright.dev/docs/api/class-page for page.goto options + */ + const response = await page.goto(url, { waitUntil: 'load', timeout: 30000 }); + if (response) { + domcontentloaded = true; + const status = response.status(); + console.log(`Response status: ${status}`); + + // If the response status code is not a 2xx success code + if (status < 200 || status > 299) { + console.error(`Failed to load url: ${url}, status code: ${status}`); + throw new Error('Failed'); + } + } else { + console.error(`No response returned for url: ${url}`); + throw new Error(logNoResponseString); + } + } catch (e) { + const errorString = `Error navigating to url: ${url}. ${e}`; + console.error(errorString); + throw e; + } + }); + + // Reset page + await resetPage(page); +}; + +export const handler = async (event, context) => { + return await loadBlueprints(); +}; \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/python/canary.py b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/python/canary.py new file mode 100644 index 0000000000000..fac8b8004a7a7 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/python/canary.py @@ -0,0 +1,62 @@ +# This example comes from the AWS Synthetics service console "API canary" blueprint + +import os +import json +import http.client +import urllib.parse +from aws_synthetics.selenium import synthetics_webdriver as syn_webdriver +from aws_synthetics.common import synthetics_logger as logger + + +def verify_request(method, url, post_data=None, headers={}): + parsed_url = urllib.parse.urlparse(url) + user_agent = str(syn_webdriver.get_canary_user_agent_string()) + if "User-Agent" in headers: + headers["User-Agent"] = " ".join([user_agent, headers["User-Agent"]]) + else: + headers["User-Agent"] = "{}".format(user_agent) + + logger.info("Making request with Method: '%s' URL: %s: Data: %s Headers: %s" % ( + method, url, json.dumps(post_data), json.dumps(headers))) + + if parsed_url.scheme == "https": + conn = http.client.HTTPSConnection(parsed_url.hostname, parsed_url.port) + else: + conn = http.client.HTTPConnection(parsed_url.hostname, parsed_url.port) + + conn.request(method, url, post_data, headers) + response = conn.getresponse() + logger.info("Status Code: %s " % response.status) + logger.info("Response Headers: %s" % json.dumps(response.headers.as_string())) + + if not response.status or response.status < 200 or response.status > 299: + try: + logger.error("Response: %s" % response.read().decode()) + finally: + if response.reason: + conn.close() + raise Exception("Failed: %s" % response.reason) + else: + conn.close() + raise Exception("Failed with status code: %s" % response.status) + + logger.info("Response: %s" % response.read().decode()) + logger.info("HTTP request successfully executed") + conn.close() + + +def main(): + + url = os.environ['URL'] + method = 'GET' + postData = "" + headers = {} + + verify_request(method, url, None, headers) + + logger.info("Canary successfully executed") + + +def handler(event, context): + logger.info("Selenium Python API canary") + main() \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/cdk.out new file mode 100644 index 0000000000000..b3a26d44a5f73 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"44.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/integ.json new file mode 100644 index 0000000000000..d0d696a4b8c7f --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "44.0.0", + "testCases": { + "SyntheticsCanaryResourcesToReplicateTags/DefaultTest": { + "stacks": [ + "SyntheticsCanaryResourcesToReplicateTagsStack" + ], + "assertionStack": "SyntheticsCanaryResourcesToReplicateTags/DefaultTest/DeployAssert", + "assertionStackName": "SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A" + } + }, + "minimumCliVersion": "2.1018.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/manifest.json new file mode 100644 index 0000000000000..96a7fa9f07bf3 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/manifest.json @@ -0,0 +1,181 @@ +{ + "version": "44.0.0", + "artifacts": { + "SyntheticsCanaryResourcesToReplicateTagsStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "SyntheticsCanaryResourcesToReplicateTagsStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "SyntheticsCanaryResourcesToReplicateTagsStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "SyntheticsCanaryResourcesToReplicateTagsStack.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/05afbffbd3fd4bafcca6e1f1f35525efe38124e6892f294ec7421e1d97072304.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "SyntheticsCanaryResourcesToReplicateTagsStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "SyntheticsCanaryResourcesToReplicateTagsStack.assets" + ], + "metadata": { + "/SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "canaryName": "*", + "runtime": "*", + "test": "*", + "memory": "*", + "timeout": "*" + } + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ArtifactsBucket": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "encryption": "KMS_MANAGED", + "enforceSSL": true, + "lifecycleRules": "*" + } + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ArtifactsBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TagReplicationCanaryArtifactsBucket66A919F9" + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ArtifactsBucket/Policy": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "bucket": "*" + } + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ArtifactsBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TagReplicationCanaryArtifactsBucketPolicyCFD9E77B" + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + }, + "inlinePolicies": "*", + "managedPolicies": [] + } + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ServiceRole/ImportServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TagReplicationCanaryServiceRoleE1E5A2B6" + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TagReplicationCanary4B22C124" + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/SyntheticsCanaryResourcesToReplicateTagsStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "SyntheticsCanaryResourcesToReplicateTagsStack" + }, + "SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "SyntheticsCanaryResourcesToReplicateTagsDefaultTestDeployAssertF883002A.assets" + ], + "metadata": { + "/SyntheticsCanaryResourcesToReplicateTags/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/SyntheticsCanaryResourcesToReplicateTags/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "SyntheticsCanaryResourcesToReplicateTags/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + }, + "minimumCliVersion": "2.1018.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/tree.json new file mode 100644 index 0000000000000..a7e6f8f68bf09 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.js.snapshot/tree.json @@ -0,0 +1 @@ +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"SyntheticsCanaryResourcesToReplicateTagsStack":{"id":"SyntheticsCanaryResourcesToReplicateTagsStack","path":"SyntheticsCanaryResourcesToReplicateTagsStack","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"TagReplicationCanary":{"id":"TagReplicationCanary","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary","constructInfo":{"fqn":"aws-cdk-lib.aws_synthetics.Canary","version":"0.0.0","metadata":[{"canaryName":"*","runtime":"*","test":"*","memory":"*","timeout":"*"}]},"children":{"ArtifactsBucket":{"id":"ArtifactsBucket","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ArtifactsBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.Bucket","version":"0.0.0","metadata":[{"encryption":"KMS_MANAGED","enforceSSL":true,"lifecycleRules":"*"}]},"children":{"Resource":{"id":"Resource","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ArtifactsBucket/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.CfnBucket","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::S3::Bucket","aws:cdk:cloudformation:props":{"bucketEncryption":{"serverSideEncryptionConfiguration":[{"serverSideEncryptionByDefault":{"sseAlgorithm":"aws:kms"}}]},"tags":[{"key":"Environment","value":"test"},{"key":"Owner","value":"cdk-team"},{"key":"Project","value":"synthetics-tag-replication"}]}}},"Policy":{"id":"Policy","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ArtifactsBucket/Policy","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketPolicy","version":"0.0.0","metadata":[{"bucket":"*"}]},"children":{"Resource":{"id":"Resource","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ArtifactsBucket/Policy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.CfnBucketPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::S3::BucketPolicy","aws:cdk:cloudformation:props":{"bucket":{"Ref":"TagReplicationCanaryArtifactsBucket66A919F9"},"policyDocument":{"Statement":[{"Action":"s3:*","Condition":{"Bool":{"aws:SecureTransport":"false"}},"Effect":"Deny","Principal":{"AWS":"*"},"Resource":[{"Fn::GetAtt":["TagReplicationCanaryArtifactsBucket66A919F9","Arn"]},{"Fn::Join":["",[{"Fn::GetAtt":["TagReplicationCanaryArtifactsBucket66A919F9","Arn"]},"/*"]]}]}],"Version":"2012-10-17"}}}}}}}},"ServiceRole":{"id":"ServiceRole","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"inlinePolicies":"*","managedPolicies":[]}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/ServiceRole/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"}}],"Version":"2012-10-17"},"policies":[{"policyName":"canaryPolicy","policyDocument":{"Statement":[{"Action":"s3:ListAllMyBuckets","Effect":"Allow","Resource":"*"},{"Action":"s3:GetBucketLocation","Effect":"Allow","Resource":{"Fn::GetAtt":["TagReplicationCanaryArtifactsBucket66A919F9","Arn"]}},{"Action":"s3:PutObject","Effect":"Allow","Resource":{"Fn::Join":["",[{"Fn::GetAtt":["TagReplicationCanaryArtifactsBucket66A919F9","Arn"]},"/*"]]}},{"Action":"cloudwatch:PutMetricData","Condition":{"StringEquals":{"cloudwatch:namespace":"CloudWatchSynthetics"}},"Effect":"Allow","Resource":"*"},{"Action":["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],"Effect":"Allow","Resource":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":logs:",{"Ref":"AWS::Region"},":",{"Ref":"AWS::AccountId"},":log-group:/aws/lambda/cwsyn-*"]]}}],"Version":"2012-10-17"}}],"tags":[{"key":"Environment","value":"test"},{"key":"Owner","value":"cdk-team"},{"key":"Project","value":"synthetics-tag-replication"}]}}}}},"Code":{"id":"Code","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/Code","constructInfo":{"fqn":"aws-cdk-lib.aws_s3_assets.Asset","version":"0.0.0"},"children":{"Stage":{"id":"Stage","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/Code/Stage","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"AssetBucket":{"id":"AssetBucket","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/Code/AssetBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketBase","version":"0.0.0","metadata":[]}}}},"Resource":{"id":"Resource","path":"SyntheticsCanaryResourcesToReplicateTagsStack/TagReplicationCanary/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_synthetics.CfnCanary","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Synthetics::Canary","aws:cdk:cloudformation:props":{"artifactS3Location":{"Fn::Join":["",["s3://",{"Ref":"TagReplicationCanaryArtifactsBucket66A919F9"}]]},"code":{"handler":"canary.handler","s3Bucket":{"Fn::Sub":"cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"},"s3Key":"5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f.zip"},"executionRoleArn":{"Fn::GetAtt":["TagReplicationCanaryServiceRoleE1E5A2B6","Arn"]},"name":"tag-replication","resourcesToReplicateTags":["lambda-function"],"runConfig":{"memoryInMb":1024,"timeoutInSeconds":180},"runtimeVersion":"syn-nodejs-puppeteer-7.0","schedule":{"durationInSeconds":"0","expression":"rate(5 minutes)"},"startCanaryAfterCreation":true,"tags":[{"key":"Environment","value":"test"},{"key":"Owner","value":"cdk-team"},{"key":"Project","value":"synthetics-tag-replication"}]}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"SyntheticsCanaryResourcesToReplicateTagsStack/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"SyntheticsCanaryResourcesToReplicateTagsStack/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"SyntheticsCanaryResourcesToReplicateTags":{"id":"SyntheticsCanaryResourcesToReplicateTags","path":"SyntheticsCanaryResourcesToReplicateTags","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"SyntheticsCanaryResourcesToReplicateTags/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"SyntheticsCanaryResourcesToReplicateTags/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"SyntheticsCanaryResourcesToReplicateTags/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"SyntheticsCanaryResourcesToReplicateTags/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"SyntheticsCanaryResourcesToReplicateTags/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts new file mode 100644 index 0000000000000..3b1ca84ddfb94 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts @@ -0,0 +1,36 @@ +import * as path from 'node:path'; +import { App, Duration, Size, Stack, StackProps, Tags } from 'aws-cdk-lib/core'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { Construct } from 'constructs'; +import * as synthetics from 'aws-cdk-lib/aws-synthetics'; + +class TestStack extends Stack { + public canary: synthetics.Canary; + + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + this.canary = new synthetics.Canary(this, 'TagReplicationCanary', { + canaryName: 'tag-replication', + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_7_0, + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')), + }), + memory: Size.mebibytes(1024), + timeout: Duration.minutes(3), + resourcesToReplicateTags: ['lambda-function'], + }); + + Tags.of(this.canary).add('Environment', 'test'); + Tags.of(this.canary).add('Owner', 'cdk-team'); + Tags.of(this.canary).add('Project', 'synthetics-tag-replication'); + } +} + +const app = new App(); +const testStack = new TestStack(app, 'SyntheticsCanaryResourcesToReplicateTagsStack'); + +new IntegTest(app, 'SyntheticsCanaryResourcesToReplicateTags', { + testCases: [testStack], +}); diff --git a/packages/aws-cdk-lib/aws-synthetics/README.md b/packages/aws-cdk-lib/aws-synthetics/README.md index d8c7b847d8b92..c9f77d02a1b66 100644 --- a/packages/aws-cdk-lib/aws-synthetics/README.md +++ b/packages/aws-cdk-lib/aws-synthetics/README.md @@ -368,3 +368,21 @@ const canary = new synthetics.Canary(this, 'MyCanary', { }); ``` +### Tag replication + +You can configure a canary to replicate its tags to the underlying Lambda function. This is useful when you want the same tags that are applied to the canary to also be applied to the Lambda function that the canary uses. + +```ts +const canary = new synthetics.Canary(this, 'MyCanary', { + schedule: synthetics.Schedule.rate(Duration.minutes(5)), + test: synthetics.Test.custom({ + code: synthetics.Code.fromAsset(path.join(__dirname, 'canary')), + handler: 'index.handler', + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_7_0, + resourcesToReplicateTags: ['lambda-function'], +}); +``` + +When you specify `'lambda-function'` in the `resourcesToReplicateTags` property, CloudWatch Synthetics will keep the tags of the canary and the Lambda function synchronized. Any future changes you make to the canary's tags will also be applied to the function. + diff --git a/packages/aws-cdk-lib/aws-synthetics/lib/canary.ts b/packages/aws-cdk-lib/aws-synthetics/lib/canary.ts index 5c2fc521b7429..9582b9cb036fa 100644 --- a/packages/aws-cdk-lib/aws-synthetics/lib/canary.ts +++ b/packages/aws-cdk-lib/aws-synthetics/lib/canary.ts @@ -299,7 +299,7 @@ export interface CanaryProps { * * If set to true, CDK will execute a dry run to validate the changes before applying them to the canary. * If the dry run succeeds, the canary will be updated with the changes. - * If the dry run fails, the CloudFormation deployment will fail with the dry run’s failure reason. + * If the dry run fails, the CloudFormation deployment will fail with the dry run's failure reason. * * If set to false or omitted, the canary will be updated directly without first performing a dry run. * @@ -308,6 +308,19 @@ export interface CanaryProps { * @default undefined - AWS CloudWatch default is false */ readonly dryRunAndUpdate?: boolean; + + /** + * Specifies which resources should have their tags replicated to this canary. + * + * To have the tags that you apply to this canary also be applied to the Lambda + * function that the canary uses, specify this property with the value + * 'lambda-function'. If you do this, CloudWatch Synthetics will keep the tags of the canary and the + * Lambda function synchronized. Any future changes you make to the canary's tags + * will also be applied to the function. + * + * @default - No resources will have their tags replicated to this canary + */ + readonly resourcesToReplicateTags?: string[]; } /** @@ -423,6 +436,7 @@ export class Canary extends cdk.Resource implements ec2.IConnectable { : 'OFF' : undefined, dryRunAndUpdate: props.dryRunAndUpdate, + resourcesToReplicateTags: props.resourcesToReplicateTags, }); this._resource = resource; diff --git a/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts b/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts index 0563317244cab..f680c325e4eed 100644 --- a/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts +++ b/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts @@ -1190,3 +1190,25 @@ describe('artifact encryption test', () => { }).toThrow('Artifact encryption is only supported for canaries that use Synthetics runtime version \`syn-nodejs-puppeteer-3.3\` or later and the Playwright runtime, got syn-python-selenium-3.0.'); }); }); + +test('can configure resourcesToReplicateTags', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + canaryName: 'mycanary', + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_8_0, + resourcesToReplicateTags: ['lambda-function'], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Synthetics::Canary', { + Name: 'mycanary', + ResourcesToReplicateTags: ['lambda-function'], + }); +}); From 43141d618fea78e34a54f18d8cd6ea0facc7b3b6 Mon Sep 17 00:00:00 2001 From: Lorenz Vanthillo Date: Sat, 28 Jun 2025 22:39:46 +0200 Subject: [PATCH 2/6] feat: use emum for ResourceToReplicateTags --- .../test/integ.canary-resources-to-replicate-tags.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts index 3b1ca84ddfb94..d44c4cc6f8e0d 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts @@ -19,7 +19,7 @@ class TestStack extends Stack { }), memory: Size.mebibytes(1024), timeout: Duration.minutes(3), - resourcesToReplicateTags: ['lambda-function'], + resourcesToReplicateTags: [synthetics.ResourceToReplicateTags.LAMBDA_FUNCTION], }); Tags.of(this.canary).add('Environment', 'test'); From d093802815ae252aa7a032e44b5f9048464f0b13 Mon Sep 17 00:00:00 2001 From: Lorenz Vanthillo Date: Sat, 28 Jun 2025 22:40:50 +0200 Subject: [PATCH 3/6] feat: use emum for ResourceToReplicateTags --- packages/aws-cdk-lib/aws-synthetics/README.md | 4 ++-- .../aws-cdk-lib/aws-synthetics/lib/canary.ts | 18 ++++++++++++++++-- .../aws-synthetics/test/canary.test.ts | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/aws-cdk-lib/aws-synthetics/README.md b/packages/aws-cdk-lib/aws-synthetics/README.md index c9f77d02a1b66..f43f75a5baffb 100644 --- a/packages/aws-cdk-lib/aws-synthetics/README.md +++ b/packages/aws-cdk-lib/aws-synthetics/README.md @@ -380,9 +380,9 @@ const canary = new synthetics.Canary(this, 'MyCanary', { handler: 'index.handler', }), runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_7_0, - resourcesToReplicateTags: ['lambda-function'], + resourcesToReplicateTags: [synthetics.ResourceToReplicateTags.LAMBDA_FUNCTION], }); ``` -When you specify `'lambda-function'` in the `resourcesToReplicateTags` property, CloudWatch Synthetics will keep the tags of the canary and the Lambda function synchronized. Any future changes you make to the canary's tags will also be applied to the function. +When you specify `ResourceToReplicateTags.LAMBDA_FUNCTION` in the `resourcesToReplicateTags` property, CloudWatch Synthetics will keep the tags of the canary and the Lambda function synchronized. Any future changes you make to the canary's tags will also be applied to the function. diff --git a/packages/aws-cdk-lib/aws-synthetics/lib/canary.ts b/packages/aws-cdk-lib/aws-synthetics/lib/canary.ts index 9582b9cb036fa..abbc5f1852b97 100644 --- a/packages/aws-cdk-lib/aws-synthetics/lib/canary.ts +++ b/packages/aws-cdk-lib/aws-synthetics/lib/canary.ts @@ -77,6 +77,20 @@ export enum Cleanup { LAMBDA = 'lambda', } +/** + * Resources that tags applied to a canary should be replicated to. + */ +export enum ResourceToReplicateTags { + /** + * Replicate canary tags to the Lambda function. + * + * When specified, CloudWatch Synthetics will keep the tags of the canary + * and the Lambda function synchronized. Any future changes made to the + * canary's tags will also be applied to the function. + */ + LAMBDA_FUNCTION = 'lambda-function', +} + /** * Options for specifying the s3 location that stores the data of each canary run. The artifacts bucket location **cannot** * be updated once the canary is created. @@ -314,13 +328,13 @@ export interface CanaryProps { * * To have the tags that you apply to this canary also be applied to the Lambda * function that the canary uses, specify this property with the value - * 'lambda-function'. If you do this, CloudWatch Synthetics will keep the tags of the canary and the + * ResourceToReplicateTags.LAMBDA_FUNCTION. If you do this, CloudWatch Synthetics will keep the tags of the canary and the * Lambda function synchronized. Any future changes you make to the canary's tags * will also be applied to the function. * * @default - No resources will have their tags replicated to this canary */ - readonly resourcesToReplicateTags?: string[]; + readonly resourcesToReplicateTags?: ResourceToReplicateTags[]; } /** diff --git a/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts b/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts index f680c325e4eed..f81002769a14c 100644 --- a/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts +++ b/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts @@ -1203,7 +1203,7 @@ test('can configure resourcesToReplicateTags', () => { code: synthetics.Code.fromInline('/* Synthetics handler code */'), }), runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_8_0, - resourcesToReplicateTags: ['lambda-function'], + resourcesToReplicateTags: [synthetics.ResourceToReplicateTags.LAMBDA_FUNCTION], }); // THEN From 8078da7c713501b7bf704f06379efc75c6219bb5 Mon Sep 17 00:00:00 2001 From: Lorenz Vanthillo Date: Tue, 8 Jul 2025 22:00:55 +0200 Subject: [PATCH 4/6] feat: add extra tests for backward compatibility + add assertion --- ...nteg.canary-resources-to-replicate-tags.ts | 23 ++++++++- .../aws-synthetics/test/canary.test.ts | 47 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts index d44c4cc6f8e0d..8fcf8bffff9e6 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts @@ -1,6 +1,6 @@ import * as path from 'node:path'; import { App, Duration, Size, Stack, StackProps, Tags } from 'aws-cdk-lib/core'; -import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests-alpha'; import { Construct } from 'constructs'; import * as synthetics from 'aws-cdk-lib/aws-synthetics'; @@ -31,6 +31,25 @@ class TestStack extends Stack { const app = new App(); const testStack = new TestStack(app, 'SyntheticsCanaryResourcesToReplicateTagsStack'); -new IntegTest(app, 'SyntheticsCanaryResourcesToReplicateTags', { +const integ = new IntegTest(app, 'SyntheticsCanaryResourcesToReplicateTags', { testCases: [testStack], }); + +const getCanary = integ.assertions.awsApiCall('Synthetics', 'getCanary', { + Name: 'tag-replication', +}); + +const getLambdaTags = integ.assertions.awsApiCall('Lambda', 'listTags', { + Resource: getCanary.getAttString('Canary.EngineArn'), +}); + +getLambdaTags.expect(ExpectedResult.objectLike({ + Tags: { + Environment: 'test', + Owner: 'cdk-team', + Project: 'synthetics-tag-replication', + }, +})).waitForAssertions({ + totalTimeout: Duration.minutes(5), + interval: Duration.seconds(30), +}); diff --git a/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts b/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts index f81002769a14c..7b77acf0ac825 100644 --- a/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts +++ b/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts @@ -1212,3 +1212,50 @@ test('can configure resourcesToReplicateTags', () => { ResourcesToReplicateTags: ['lambda-function'], }); }); + +test('resourcesToReplicateTags is not included when not specified', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + canaryName: 'mycanary', + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_8_0, + }); + + // THEN + const template = Template.fromStack(stack); + template.hasResourceProperties('AWS::Synthetics::Canary', { + Name: 'mycanary', + }); + + const canaryResource = template.findResources('AWS::Synthetics::Canary'); + const canaryProps = canaryResource[Object.keys(canaryResource)[0]].Properties; + expect(canaryProps.ResourcesToReplicateTags).toBeUndefined(); +}); + +test('resourcesToReplicateTags can be empty array', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new synthetics.Canary(stack, 'Canary', { + canaryName: 'mycanary', + test: synthetics.Test.custom({ + handler: 'index.handler', + code: synthetics.Code.fromInline('/* Synthetics handler code */'), + }), + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_8_0, + resourcesToReplicateTags: [], + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Synthetics::Canary', { + Name: 'mycanary', + ResourcesToReplicateTags: [], + }); +}); From b066d3fed4adb7ab66258c91d85f614153fe9072 Mon Sep 17 00:00:00 2001 From: Lorenz Vanthillo Date: Wed, 9 Jul 2025 14:20:23 +0200 Subject: [PATCH 5/6] chore: remove assertion from integ test because the standard integ test functionality can not handle it for this specific case --- ...nteg.canary-resources-to-replicate-tags.ts | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts index 8fcf8bffff9e6..d44c4cc6f8e0d 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-resources-to-replicate-tags.ts @@ -1,6 +1,6 @@ import * as path from 'node:path'; import { App, Duration, Size, Stack, StackProps, Tags } from 'aws-cdk-lib/core'; -import { IntegTest, ExpectedResult } from '@aws-cdk/integ-tests-alpha'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; import { Construct } from 'constructs'; import * as synthetics from 'aws-cdk-lib/aws-synthetics'; @@ -31,25 +31,6 @@ class TestStack extends Stack { const app = new App(); const testStack = new TestStack(app, 'SyntheticsCanaryResourcesToReplicateTagsStack'); -const integ = new IntegTest(app, 'SyntheticsCanaryResourcesToReplicateTags', { +new IntegTest(app, 'SyntheticsCanaryResourcesToReplicateTags', { testCases: [testStack], }); - -const getCanary = integ.assertions.awsApiCall('Synthetics', 'getCanary', { - Name: 'tag-replication', -}); - -const getLambdaTags = integ.assertions.awsApiCall('Lambda', 'listTags', { - Resource: getCanary.getAttString('Canary.EngineArn'), -}); - -getLambdaTags.expect(ExpectedResult.objectLike({ - Tags: { - Environment: 'test', - Owner: 'cdk-team', - Project: 'synthetics-tag-replication', - }, -})).waitForAssertions({ - totalTimeout: Duration.minutes(5), - interval: Duration.seconds(30), -}); From ac8d4c27775eccfcad7792e1857a334e026b1914 Mon Sep 17 00:00:00 2001 From: Lorenz Vanthillo Date: Thu, 10 Jul 2025 13:23:45 +0200 Subject: [PATCH 6/6] chore: run linter --- packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts b/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts index abd64ddc0061d..283ddefc0d163 100644 --- a/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts +++ b/packages/aws-cdk-lib/aws-synthetics/test/canary.test.ts @@ -1233,7 +1233,7 @@ test('resourcesToReplicateTags is not included when not specified', () => { template.hasResourceProperties('AWS::Synthetics::Canary', { Name: 'mycanary', }); - + const canaryResource = template.findResources('AWS::Synthetics::Canary'); const canaryProps = canaryResource[Object.keys(canaryResource)[0]].Properties; expect(canaryProps.ResourcesToReplicateTags).toBeUndefined();