diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/canaries-runtime-validation/root-only/canary.js b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/canaries-runtime-validation/root-only/canary.js new file mode 100644 index 0000000000000..1e8db9444b6aa --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/canaries-runtime-validation/root-only/canary.js @@ -0,0 +1,10 @@ +const synthetics = require('Synthetics'); + +const handler = async () => { + return await synthetics.executeStep('step1', async function () { + // Test step for puppeteer 11.0+ root-level validation + console.log('Testing puppeteer 11.0+ with root-level JS file'); + }); +}; + +exports.handler = handler; \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.assets.json new file mode 100644 index 0000000000000..98e15983d17cf --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.assets.json @@ -0,0 +1,20 @@ +{ + "version": "48.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B Template", + "source": { + "path": "SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-d8d86b35": { + "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-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.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-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationStack.assets.json new file mode 100644 index 0000000000000..9ec912d88d059 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationStack.assets.json @@ -0,0 +1,48 @@ +{ + "version": "48.0.0", + "files": { + "77584a9a44c49f552de0f96e8ef234f3cb070837690cf1cd608fc6ba23aa4ece": { + "displayName": "Puppeteer11RootCanary/Code", + "source": { + "path": "asset.77584a9a44c49f552de0f96e8ef234f3cb070837690cf1cd608fc6ba23aa4ece", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region-0ae1da66": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "77584a9a44c49f552de0f96e8ef234f3cb070837690cf1cd608fc6ba23aa4ece.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f": { + "displayName": "Puppeteer11NodeModulesCanary/Code", + "source": { + "path": "asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region-d9b11485": { + "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}" + } + } + }, + "95138806a7dd99c00ce6b0e3fc0649800079593906e807412fd387f2a2221407": { + "displayName": "SyntheticsCanaryRuntimeValidationStack Template", + "source": { + "path": "SyntheticsCanaryRuntimeValidationStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-65dc2bff": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "95138806a7dd99c00ce6b0e3fc0649800079593906e807412fd387f2a2221407.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-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationStack.template.json new file mode 100644 index 0000000000000..ae37c3cbfa575 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/SyntheticsCanaryRuntimeValidationStack.template.json @@ -0,0 +1,440 @@ +{ + "Resources": { + "Puppeteer11RootCanaryArtifactsBucketC312253E": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms" + } + } + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "Puppeteer11RootCanaryArtifactsBucketPolicyBB7D5123": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "Puppeteer11RootCanaryArtifactsBucketC312253E" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "Puppeteer11RootCanaryArtifactsBucketC312253E", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Puppeteer11RootCanaryArtifactsBucketC312253E", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "Puppeteer11RootCanaryServiceRoleDB811CBF": { + "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": [ + "Puppeteer11RootCanaryArtifactsBucketC312253E", + "Arn" + ] + } + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Puppeteer11RootCanaryArtifactsBucketC312253E", + "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" + } + ] + } + }, + "Puppeteer11RootCanaryD368BA61": { + "Type": "AWS::Synthetics::Canary", + "Properties": { + "ArtifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "Puppeteer11RootCanaryArtifactsBucketC312253E" + } + ] + ] + }, + "Code": { + "Handler": "canary.handler", + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "77584a9a44c49f552de0f96e8ef234f3cb070837690cf1cd608fc6ba23aa4ece.zip" + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "Puppeteer11RootCanaryServiceRoleDB811CBF", + "Arn" + ] + }, + "Name": "syntheticscanar64c23c", + "RunConfig": { + "MemoryInMB": 1024, + "TimeoutInSeconds": 120 + }, + "RuntimeVersion": "syn-nodejs-puppeteer-11.0", + "Schedule": { + "DurationInSeconds": "0", + "Expression": "rate(5 minutes)" + }, + "StartCanaryAfterCreation": true + } + }, + "Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "SSEAlgorithm": "aws:kms" + } + } + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "Puppeteer11NodeModulesCanaryArtifactsBucketPolicy3496BDE6": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "Puppeteer11NodeModulesCanaryServiceRole86DFFB6D": { + "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": [ + "Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798", + "Arn" + ] + } + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798", + "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" + } + ] + } + }, + "Puppeteer11NodeModulesCanaryC1D05D54": { + "Type": "AWS::Synthetics::Canary", + "Properties": { + "ArtifactS3Location": { + "Fn::Join": [ + "", + [ + "s3://", + { + "Ref": "Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798" + } + ] + ] + }, + "Code": { + "Handler": "canary.handler", + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f.zip" + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "Puppeteer11NodeModulesCanaryServiceRole86DFFB6D", + "Arn" + ] + }, + "Name": "syntheticscanar10f1d9", + "RunConfig": { + "MemoryInMB": 1024, + "TimeoutInSeconds": 120 + }, + "RuntimeVersion": "syn-nodejs-puppeteer-11.0", + "Schedule": { + "DurationInSeconds": "0", + "Expression": "rate(5 minutes)" + }, + "StartCanaryAfterCreation": true + } + } + }, + "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-runtime-validation.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/canary.mjs b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.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-runtime-validation.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-runtime-validation.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/canary.js b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.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-runtime-validation.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-runtime-validation.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/nodejs/node_modules/folder/canary.js b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.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-runtime-validation.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-runtime-validation.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/playwright/canary.mjs b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.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-runtime-validation.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-runtime-validation.js.snapshot/asset.5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f/python/canary.py b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.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-runtime-validation.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-runtime-validation.js.snapshot/asset.77584a9a44c49f552de0f96e8ef234f3cb070837690cf1cd608fc6ba23aa4ece/canary.js b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/asset.77584a9a44c49f552de0f96e8ef234f3cb070837690cf1cd608fc6ba23aa4ece/canary.js new file mode 100644 index 0000000000000..1e8db9444b6aa --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/asset.77584a9a44c49f552de0f96e8ef234f3cb070837690cf1cd608fc6ba23aa4ece/canary.js @@ -0,0 +1,10 @@ +const synthetics = require('Synthetics'); + +const handler = async () => { + return await synthetics.executeStep('step1', async function () { + // Test step for puppeteer 11.0+ root-level validation + console.log('Testing puppeteer 11.0+ with root-level JS file'); + }); +}; + +exports.handler = handler; \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/cdk.out new file mode 100644 index 0000000000000..523a9aac37cbf --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"48.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/integ.json new file mode 100644 index 0000000000000..b93bd7b4a8c84 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "48.0.0", + "testCases": { + "SyntheticsCanaryRuntimeValidation/DefaultTest": { + "stacks": [ + "SyntheticsCanaryRuntimeValidationStack" + ], + "assertionStack": "SyntheticsCanaryRuntimeValidation/DefaultTest/DeployAssert", + "assertionStackName": "SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B" + } + }, + "minimumCliVersion": "2.1027.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/manifest.json new file mode 100644 index 0000000000000..51a7863d07d6a --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/manifest.json @@ -0,0 +1,730 @@ +{ + "version": "48.0.0", + "artifacts": { + "SyntheticsCanaryRuntimeValidationStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "SyntheticsCanaryRuntimeValidationStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "SyntheticsCanaryRuntimeValidationStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "SyntheticsCanaryRuntimeValidationStack.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}/95138806a7dd99c00ce6b0e3fc0649800079593906e807412fd387f2a2221407.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "SyntheticsCanaryRuntimeValidationStack.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": [ + "SyntheticsCanaryRuntimeValidationStack.assets" + ], + "metadata": { + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "runtime": "*", + "test": "*", + "memory": "*", + "timeout": "*" + } + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ArtifactsBucket": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "encryption": "KMS_MANAGED", + "enforceSSL": true, + "lifecycleRules": "*" + } + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ArtifactsBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Puppeteer11RootCanaryArtifactsBucketC312253E" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ArtifactsBucket/Policy": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "bucket": "*" + } + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ArtifactsBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Puppeteer11RootCanaryArtifactsBucketPolicyBB7D5123" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + }, + "inlinePolicies": "*", + "managedPolicies": [] + } + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ServiceRole/ImportServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Puppeteer11RootCanaryServiceRoleDB811CBF" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Puppeteer11RootCanaryD368BA61" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "runtime": "*", + "test": "*", + "memory": "*", + "timeout": "*" + } + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ArtifactsBucket": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "encryption": "KMS_MANAGED", + "enforceSSL": true, + "lifecycleRules": "*" + } + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ArtifactsBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ArtifactsBucket/Policy": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "bucket": "*" + } + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ArtifactsBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Puppeteer11NodeModulesCanaryArtifactsBucketPolicy3496BDE6" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + }, + "inlinePolicies": "*", + "managedPolicies": [] + } + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ServiceRole/ImportServiceRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Puppeteer11NodeModulesCanaryServiceRole86DFFB6D" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Puppeteer11NodeModulesCanaryC1D05D54" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/SyntheticsCanaryRuntimeValidationStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "SyntheticsCanaryRuntimeValidationStack" + }, + "SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.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": [ + "SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.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": [ + "SyntheticsCanaryRuntimeValidationDefaultTestDeployAssert9B35538B.assets" + ], + "metadata": { + "/SyntheticsCanaryRuntimeValidation/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/SyntheticsCanaryRuntimeValidation/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "SyntheticsCanaryRuntimeValidation/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/aws-signer:signingProfileNamePassedToCfn": { + "recommendedValue": true, + "explanation": "Pass signingProfileName to CfnSigningProfile" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": { + "recommendedValue": true, + "explanation": "Disable implicit openListener when custom security groups are provided" + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to by default create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:explicitStackTags": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, stack tags need to be assigned explicitly on a Stack." + }, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": { + "userValue": false, + "recommendedValue": false, + "explanation": "When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated commands will be added to UserData to block container accessing IMDS. **Applicable to Linux only. IMPORTANT: See [details.](#aws-cdkaws-ecsenableImdsBlockingDeprecatedFeature)**" + }, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, CDK synth will throw exception if canContainersAccessInstanceRole is false. **IMPORTANT: See [details.](#aws-cdkaws-ecsdisableEcsImdsBlocking)**" + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + } + } + } + } + }, + "minimumCliVersion": "2.1027.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.js.snapshot/tree.json new file mode 100644 index 0000000000000..81bbaaaec07c9 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.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":{"SyntheticsCanaryRuntimeValidationStack":{"id":"SyntheticsCanaryRuntimeValidationStack","path":"SyntheticsCanaryRuntimeValidationStack","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Puppeteer11RootCanary":{"id":"Puppeteer11RootCanary","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary","constructInfo":{"fqn":"aws-cdk-lib.aws_synthetics.Canary","version":"0.0.0","metadata":[{"runtime":"*","test":"*","memory":"*","timeout":"*"}]},"children":{"ArtifactsBucket":{"id":"ArtifactsBucket","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/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":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/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"}}]}}}},"Policy":{"id":"Policy","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ArtifactsBucket/Policy","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketPolicy","version":"0.0.0","metadata":[{"bucket":"*"}]},"children":{"Resource":{"id":"Resource","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/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":"Puppeteer11RootCanaryArtifactsBucketC312253E"},"policyDocument":{"Statement":[{"Action":"s3:*","Condition":{"Bool":{"aws:SecureTransport":"false"}},"Effect":"Deny","Principal":{"AWS":"*"},"Resource":[{"Fn::GetAtt":["Puppeteer11RootCanaryArtifactsBucketC312253E","Arn"]},{"Fn::Join":["",[{"Fn::GetAtt":["Puppeteer11RootCanaryArtifactsBucketC312253E","Arn"]},"/*"]]}]}],"Version":"2012-10-17"}}}}}}}},"ServiceRole":{"id":"ServiceRole","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"inlinePolicies":"*","managedPolicies":[]}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/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":["Puppeteer11RootCanaryArtifactsBucketC312253E","Arn"]}},{"Action":"s3:PutObject","Effect":"Allow","Resource":{"Fn::Join":["",[{"Fn::GetAtt":["Puppeteer11RootCanaryArtifactsBucketC312253E","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"}}]}}}}},"Code":{"id":"Code","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/Code","constructInfo":{"fqn":"aws-cdk-lib.aws_s3_assets.Asset","version":"0.0.0"},"children":{"Stage":{"id":"Stage","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/Code/Stage","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"AssetBucket":{"id":"AssetBucket","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/Code/AssetBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketBase","version":"0.0.0","metadata":[]}}}},"Resource":{"id":"Resource","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11RootCanary/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":"Puppeteer11RootCanaryArtifactsBucketC312253E"}]]},"code":{"handler":"canary.handler","s3Bucket":{"Fn::Sub":"cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"},"s3Key":"77584a9a44c49f552de0f96e8ef234f3cb070837690cf1cd608fc6ba23aa4ece.zip"},"executionRoleArn":{"Fn::GetAtt":["Puppeteer11RootCanaryServiceRoleDB811CBF","Arn"]},"name":"syntheticscanar64c23c","runConfig":{"memoryInMb":1024,"timeoutInSeconds":120},"runtimeVersion":"syn-nodejs-puppeteer-11.0","schedule":{"durationInSeconds":"0","expression":"rate(5 minutes)"},"startCanaryAfterCreation":true}}}}},"Puppeteer11NodeModulesCanary":{"id":"Puppeteer11NodeModulesCanary","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary","constructInfo":{"fqn":"aws-cdk-lib.aws_synthetics.Canary","version":"0.0.0","metadata":[{"runtime":"*","test":"*","memory":"*","timeout":"*"}]},"children":{"ArtifactsBucket":{"id":"ArtifactsBucket","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/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":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/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"}}]}}}},"Policy":{"id":"Policy","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ArtifactsBucket/Policy","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketPolicy","version":"0.0.0","metadata":[{"bucket":"*"}]},"children":{"Resource":{"id":"Resource","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/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":"Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798"},"policyDocument":{"Statement":[{"Action":"s3:*","Condition":{"Bool":{"aws:SecureTransport":"false"}},"Effect":"Deny","Principal":{"AWS":"*"},"Resource":[{"Fn::GetAtt":["Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798","Arn"]},{"Fn::Join":["",[{"Fn::GetAtt":["Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798","Arn"]},"/*"]]}]}],"Version":"2012-10-17"}}}}}}}},"ServiceRole":{"id":"ServiceRole","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ServiceRole","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"assumedBy":{"principalAccount":"*","assumeRoleAction":"*"},"inlinePolicies":"*","managedPolicies":[]}]},"children":{"ImportServiceRole":{"id":"ImportServiceRole","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/ServiceRole/ImportServiceRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/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":["Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798","Arn"]}},{"Action":"s3:PutObject","Effect":"Allow","Resource":{"Fn::Join":["",[{"Fn::GetAtt":["Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798","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"}}]}}}}},"Code":{"id":"Code","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/Code","constructInfo":{"fqn":"aws-cdk-lib.aws_s3_assets.Asset","version":"0.0.0"},"children":{"Stage":{"id":"Stage","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/Code/Stage","constructInfo":{"fqn":"aws-cdk-lib.AssetStaging","version":"0.0.0"}},"AssetBucket":{"id":"AssetBucket","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/Code/AssetBucket","constructInfo":{"fqn":"aws-cdk-lib.aws_s3.BucketBase","version":"0.0.0","metadata":[]}}}},"Resource":{"id":"Resource","path":"SyntheticsCanaryRuntimeValidationStack/Puppeteer11NodeModulesCanary/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":"Puppeteer11NodeModulesCanaryArtifactsBucket1E9D6798"}]]},"code":{"handler":"canary.handler","s3Bucket":{"Fn::Sub":"cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"},"s3Key":"5178413cfe8db00b2d5dcfa9be417e934c64601d0da3031d88c145c8293bc27f.zip"},"executionRoleArn":{"Fn::GetAtt":["Puppeteer11NodeModulesCanaryServiceRole86DFFB6D","Arn"]},"name":"syntheticscanar10f1d9","runConfig":{"memoryInMb":1024,"timeoutInSeconds":120},"runtimeVersion":"syn-nodejs-puppeteer-11.0","schedule":{"durationInSeconds":"0","expression":"rate(5 minutes)"},"startCanaryAfterCreation":true}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"SyntheticsCanaryRuntimeValidationStack/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"SyntheticsCanaryRuntimeValidationStack/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"SyntheticsCanaryRuntimeValidation":{"id":"SyntheticsCanaryRuntimeValidation","path":"SyntheticsCanaryRuntimeValidation","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"SyntheticsCanaryRuntimeValidation/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"SyntheticsCanaryRuntimeValidation/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"SyntheticsCanaryRuntimeValidation/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"SyntheticsCanaryRuntimeValidation/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"SyntheticsCanaryRuntimeValidation/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-runtime-validation.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.ts new file mode 100644 index 0000000000000..c08ea4c1ef129 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-synthetics/test/integ.canary-runtime-validation.ts @@ -0,0 +1,43 @@ +import * as path from 'node:path'; +import { App, Duration, Size, Stack, StackProps } 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 puppeteer11RootCanary: synthetics.Canary; + public puppeteer11NodeModulesCanary: synthetics.Canary; + + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + // Test puppeteer 11.0+ with root-level files only + this.puppeteer11RootCanary = new synthetics.Canary(this, 'Puppeteer11RootCanary', { + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_11_0, + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries-runtime-validation', 'root-only')), + }), + memory: Size.mebibytes(1024), + timeout: Duration.minutes(2), + }); + + // Test puppeteer 11.0+ also supports nodejs/node_modules structure + this.puppeteer11NodeModulesCanary = new synthetics.Canary(this, 'Puppeteer11NodeModulesCanary', { + runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_11_0, + test: synthetics.Test.custom({ + handler: 'canary.handler', + code: synthetics.Code.fromAsset(path.join(__dirname, 'canaries')), + }), + memory: Size.mebibytes(1024), + timeout: Duration.minutes(2), + }); + } +} + +const app = new App(); +const testStack = new TestStack(app, 'SyntheticsCanaryRuntimeValidationStack'); + +new IntegTest(app, 'SyntheticsCanaryRuntimeValidation', { + testCases: [testStack], +}); diff --git a/packages/aws-cdk-lib/aws-synthetics/README.md b/packages/aws-cdk-lib/aws-synthetics/README.md index 9f19605a35b53..95275a03720b7 100644 --- a/packages/aws-cdk-lib/aws-synthetics/README.md +++ b/packages/aws-cdk-lib/aws-synthetics/README.md @@ -267,7 +267,7 @@ new synthetics.Canary(this, 'Bucket Canary', { ``` > **Note:** Synthetics have a specified folder structure for canaries. -> For Node with puppeteer scripts supplied via `code.fromAsset()` or `code.fromBucket()`, the canary resource requires the following folder structure: +> For Node with puppeteer scripts supplied via `code.fromAsset()` or `code.fromBucket()`, the canary resource requires the following folder structure for runtime versions older than `syn-nodejs-puppeteer-11.0`: > > ```plaintext > canary/ @@ -276,6 +276,22 @@ new synthetics.Canary(this, 'Bucket Canary', { > ├── .js > ``` > +> For puppeteer based runtime versions newer than or equal to `syn-nodejs-puppeteer-11.0`, `nodjs/node_modules` is not necessary but supported. +> +> Both +> ```plaintext +> canary/ +> ├── nodejs/ +> ├── node_modules/ +> ├── .js +> ``` +> And +> ```plaintext +> canary/ +> ├── .js +> ``` +> are supported. +> > For Node with playwright scripts supplied via `code.fromAsset()` or `code.fromBucket()`, the canary resource requires the following folder structure: > > ```plaintext diff --git a/packages/aws-cdk-lib/aws-synthetics/lib/code.ts b/packages/aws-cdk-lib/aws-synthetics/lib/code.ts index 988c011c6675f..5469e87507d02 100644 --- a/packages/aws-cdk-lib/aws-synthetics/lib/code.ts +++ b/packages/aws-cdk-lib/aws-synthetics/lib/code.ts @@ -150,9 +150,34 @@ export class AssetCode extends Code { if (runtimeName && !Token.isUnresolved(runtimeName)) { const playwrightValidExtensions = ['.cjs', '.mjs', '.js']; const hasValidExtension = playwrightValidExtensions.some(ext => fs.existsSync(path.join(assetPath, `${filename}${ext}`))); - // Requires asset directory to have the structure 'nodejs/node_modules' for puppeteer runtime. - if (family === RuntimeFamily.NODEJS && runtimeName.includes('puppeteer') && !fs.existsSync(path.join(assetPath, 'nodejs', 'node_modules', nodeFilename))) { - throw new ValidationError(`The canary resource requires that the handler is present at "nodejs/node_modules/${nodeFilename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`, scope); + // Validate Puppeteer runtime file structure requirements + if (family === RuntimeFamily.NODEJS && runtimeName.includes('puppeteer')) { + // Extract version number from runtime name (e.g., "syn-nodejs-puppeteer-11.0" -> 11.0) + const versionMatch = runtimeName.match(/syn-nodejs-puppeteer-(\d+\.\d+)/); + const version = versionMatch ? parseFloat(versionMatch[1]) : 0; + + const rootPath = path.join(assetPath, nodeFilename); // Root-level: canary.js + const legacyPath = path.join(assetPath, 'nodejs', 'node_modules', nodeFilename); // Legacy: nodejs/node_modules/canary.js + + // Check if files exist at each location + const hasRootFile = fs.existsSync(rootPath); + const hasLegacyFile = fs.existsSync(legacyPath); + + const needsLegacyStructure = version < 11.0; + const isMissingRequiredFile = needsLegacyStructure + ? !hasLegacyFile // < 11.0: Only legacy structure allowed + : (!hasRootFile && !hasLegacyFile); // >= 11.0: Either root OR legacy structure allowed + + if (isMissingRequiredFile) { + const expectedLocation = needsLegacyStructure + ? `"nodejs/node_modules/${nodeFilename}"` // < 11.0: Only legacy path + : `"${nodeFilename}" or "nodejs/node_modules/${nodeFilename}"`; // >= 11.0: Root OR legacy path + + throw new ValidationError( + `The canary resource requires that the handler is present at ${expectedLocation} but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`, + scope, + ); + } } // Requires the canary handler file to have the extension '.js', '.mjs', or '.cjs' for the playwright runtime. if (family === RuntimeFamily.NODEJS && runtimeName.includes('playwright') && !hasValidExtension) { diff --git a/packages/aws-cdk-lib/aws-synthetics/test/canaries/puppeteer/canary.js b/packages/aws-cdk-lib/aws-synthetics/test/canaries/puppeteer/canary.js new file mode 100644 index 0000000000000..2ee399fbc633f --- /dev/null +++ b/packages/aws-cdk-lib/aws-synthetics/test/canaries/puppeteer/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-lib/aws-synthetics/test/code.test.ts b/packages/aws-cdk-lib/aws-synthetics/test/code.test.ts index a9191e44698ad..6172d7430e4d7 100644 --- a/packages/aws-cdk-lib/aws-synthetics/test/code.test.ts +++ b/packages/aws-cdk-lib/aws-synthetics/test/code.test.ts @@ -227,6 +227,46 @@ describe(synthetics.Code.fromAsset, () => { .toThrow(`The canary resource requires that the handler is present at "nodejs/node_modules/incorrect.js" but not found at ${assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`); }); + test('puppeteer runtime >= 11.0 allows JS files in root path', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + const assetPath = path.join(__dirname, 'canaries', 'puppeteer'); + + // WHEN/THEN - should not throw for puppeteer 11.0+ + expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_11_0.name)) + .not.toThrow(); + }); + + test('puppeteer runtime >= 11.0 also allows nodejs/node_modules structure', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + const assetPath = path.join(__dirname, 'canaries'); + + // WHEN/THEN - should not throw for puppeteer 11.0+ with nodejs structure (has both root canary.js and nodejs/node_modules/canary.js) + expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_11_0.name)) + .not.toThrow(); + }); + + test('puppeteer runtime < 11.0 requires nodejs/node_modules structure', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + const assetPath = path.join(__dirname, 'canaries'); + + // WHEN/THEN - should not throw for puppeteer < 11.0 since nodejs/node_modules/canary.js exists + expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_10_0.name)) + .not.toThrow(); + }); + + test('puppeteer runtime < 11.0 fails without nodejs/node_modules structure', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + const assetPath = path.join(__dirname, 'canaries', 'puppeteer'); // Only has root-level files + + // WHEN/THEN - should throw for puppeteer < 11.0 without nodejs/node_modules structure + expect(() => synthetics.Code.fromAsset(assetPath).bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_10_0.name)) + .toThrow(`The canary resource requires that the handler is present at "nodejs/node_modules/canary.js" but not found at ${assetPath}`); + }); + test('passes if bundling is specified', () => { // GIVEN const stack = new Stack(new App(), 'canaries'); @@ -253,6 +293,28 @@ describe(synthetics.Code.fromAsset, () => { .not.toThrow(); }); + test('puppeteer 11.0+ with bundling allows root-level JS files', () => { + // GIVEN + const stack = new Stack(new App(), 'canaries'); + const assetPath = path.join(__dirname, 'canaries', 'nodejs', 'node_modules'); + const code = synthetics.Code.fromAsset(assetPath, { + bundling: { + image: DockerImage.fromRegistry('dummy'), + local: { + tryBundle(outputDir) { + // Create JS file in root for puppeteer 11.0+ + fs.copyFileSync(path.join(assetPath, 'canary.js'), path.join(outputDir, 'canary.js')); + return true; + }, + }, + }, + }); + + // THEN - should not throw for puppeteer 11.0+ with root-level file + expect(() => code.bind(stack, 'canary.handler', synthetics.RuntimeFamily.NODEJS, synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_11_0.name)) + .not.toThrow(); + }); + test('fails if bundling is specified but folder structure is wrong', () => { // GIVEN const stack = new Stack(new App(), 'canaries');