diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets.json new file mode 100644 index 0000000000000..9963729af1964 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.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-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.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-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/cdk.out index 8ecc185e9dbee..1f0068d32659a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"21.0.0"} \ No newline at end of file +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json index 89f3532813157..986e5b5df4d9e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "36.0.0", "files": { - "aabdbe840e6768f96ec51dd87886969be769aeca5a21773e27cd16f1a90367fe": { + "6b60feae70a32f40a6973349f09261825e869adf03504811cee49c59d2691e3c": { "source": { "path": "integ-mock-websocket-integration.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "aabdbe840e6768f96ec51dd87886969be769aeca5a21773e27cd16f1a90367fe.json", + "objectKey": "6b60feae70a32f40a6973349f09261825e869adf03504811cee49c59d2691e3c.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json index 085584d49a05b..42bc9215b9975 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json @@ -24,8 +24,8 @@ "ApiId": { "Ref": "mywsapi32E6CE11" }, - "RouteKey": "$default", "AuthorizationType": "NONE", + "RouteKey": "$default", "Target": { "Fn::Join": [ "", @@ -39,14 +39,18 @@ } } }, - "mywsapisendmessageRouteSendMessageIntegrationD29E12F9": { + "mywsapisendmessageRouteDefaultIntegration702159AD": { "Type": "AWS::ApiGatewayV2::Integration", "Properties": { "ApiId": { "Ref": "mywsapi32E6CE11" }, "IntegrationType": "MOCK", - "IntegrationUri": "" + "IntegrationUri": "", + "RequestTemplates": { + "application/json": "{\"statusCode\":200}" + }, + "TemplateSelectionExpression": "\\$default" } }, "mywsapisendmessageRouteAE873328": { @@ -55,29 +59,42 @@ "ApiId": { "Ref": "mywsapi32E6CE11" }, - "RouteKey": "sendmessage", "AuthorizationType": "NONE", + "RouteKey": "sendmessage", + "RouteResponseSelectionExpression": "$default", "Target": { "Fn::Join": [ "", [ "integrations/", { - "Ref": "mywsapisendmessageRouteSendMessageIntegrationD29E12F9" + "Ref": "mywsapisendmessageRouteDefaultIntegration702159AD" } ] ] } } }, + "mywsapisendmessageRouteResponse2ED167D2": { + "Type": "AWS::ApiGatewayV2::RouteResponse", + "Properties": { + "ApiId": { + "Ref": "mywsapi32E6CE11" + }, + "RouteId": { + "Ref": "mywsapisendmessageRouteAE873328" + }, + "RouteResponseKey": "$default" + } + }, "mystage114C35EC": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { "ApiId": { "Ref": "mywsapi32E6CE11" }, - "StageName": "dev", - "AutoDeploy": true + "AutoDeploy": true, + "StageName": "dev" } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ.json index e38a89b873c9c..925cbd9e68810 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ.json @@ -1,14 +1,12 @@ { - "version": "20.0.0", + "version": "36.0.0", "testCases": { - "integ.mock": { + "apigatewayv2-mock-integration-integ-test/DefaultTest": { "stacks": [ "integ-mock-websocket-integration" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert", + "assertionStackName": "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json index cf17e2f2db70c..5f61ef3d162a9 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json @@ -1,12 +1,6 @@ { - "version": "20.0.0", + "version": "36.0.0", "artifacts": { - "Tree": { - "type": "cdk:tree", - "properties": { - "file": "tree.json" - } - }, "integ-mock-websocket-integration.assets": { "type": "cdk:asset-manifest", "properties": { @@ -20,10 +14,11 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "integ-mock-websocket-integration.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}/aabdbe840e6768f96ec51dd87886969be769aeca5a21773e27cd16f1a90367fe.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/6b60feae70a32f40a6973349f09261825e869adf03504811cee49c59d2691e3c.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -57,10 +52,10 @@ "data": "mywsapidefaultRouteE9382DF8" } ], - "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/SendMessageIntegration/Resource": [ + "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/DefaultIntegration/Resource": [ { "type": "aws:cdk:logicalId", - "data": "mywsapisendmessageRouteSendMessageIntegrationD29E12F9" + "data": "mywsapisendmessageRouteDefaultIntegration702159AD" } ], "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/Resource": [ @@ -69,6 +64,12 @@ "data": "mywsapisendmessageRouteAE873328" } ], + "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/Response": [ + { + "type": "aws:cdk:logicalId", + "data": "mywsapisendmessageRouteResponse2ED167D2" + } + ], "/integ-mock-websocket-integration/mystage/Resource": [ { "type": "aws:cdk:logicalId", @@ -92,9 +93,72 @@ "type": "aws:cdk:logicalId", "data": "CheckBootstrapVersion" } + ], + "mywsapisendmessageRouteSendMessageIntegrationD29E12F9": [ + { + "type": "aws:cdk:logicalId", + "data": "mywsapisendmessageRouteSendMessageIntegrationD29E12F9", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "integ-mock-websocket-integration" + }, + "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.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": [ + "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.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": [ + "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets" + ], + "metadata": { + "/apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json index 62c47b25cc7d3..603ad0a2c563c 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json @@ -4,14 +4,6 @@ "id": "App", "path": "", "children": { - "Tree": { - "id": "Tree", - "path": "Tree", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" - } - }, "integ-mock-websocket-integration": { "id": "integ-mock-websocket-integration", "path": "integ-mock-websocket-integration", @@ -32,7 +24,7 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnApi", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnApi", "version": "0.0.0" } }, @@ -58,13 +50,13 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnIntegration", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnIntegration", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketIntegration", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketIntegration", "version": "0.0.0" } }, @@ -77,8 +69,8 @@ "apiId": { "Ref": "mywsapi32E6CE11" }, - "routeKey": "$default", "authorizationType": "NONE", + "routeKey": "$default", "target": { "Fn::Join": [ "", @@ -93,13 +85,13 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnRoute", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRoute", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketRoute", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketRoute", "version": "0.0.0" } }, @@ -107,13 +99,13 @@ "id": "sendmessage-Route", "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route", "children": { - "SendMessageIntegration": { - "id": "SendMessageIntegration", - "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/SendMessageIntegration", + "DefaultIntegration": { + "id": "DefaultIntegration", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/DefaultIntegration", "children": { "Resource": { "id": "Resource", - "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/SendMessageIntegration/Resource", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/DefaultIntegration/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Integration", "aws:cdk:cloudformation:props": { @@ -121,17 +113,21 @@ "Ref": "mywsapi32E6CE11" }, "integrationType": "MOCK", - "integrationUri": "" + "integrationUri": "", + "requestTemplates": { + "application/json": "{\"statusCode\":200}" + }, + "templateSelectionExpression": "\\$default" } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnIntegration", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnIntegration", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketIntegration", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketIntegration", "version": "0.0.0" } }, @@ -144,15 +140,16 @@ "apiId": { "Ref": "mywsapi32E6CE11" }, - "routeKey": "sendmessage", "authorizationType": "NONE", + "routeKey": "sendmessage", + "routeResponseSelectionExpression": "$default", "target": { "Fn::Join": [ "", [ "integrations/", { - "Ref": "mywsapisendmessageRouteSendMessageIntegrationD29E12F9" + "Ref": "mywsapisendmessageRouteDefaultIntegration702159AD" } ] ] @@ -160,19 +157,39 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnRoute", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRoute", + "version": "0.0.0" + } + }, + "Response": { + "id": "Response", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/Response", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::RouteResponse", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "mywsapi32E6CE11" + }, + "routeId": { + "Ref": "mywsapisendmessageRouteAE873328" + }, + "routeResponseKey": "$default" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRouteResponse", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketRoute", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketRoute", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketApi", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketApi", "version": "0.0.0" } }, @@ -189,18 +206,18 @@ "apiId": { "Ref": "mywsapi32E6CE11" }, - "stageName": "dev", - "autoDeploy": true + "autoDeploy": true, + "stageName": "dev" } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnStage", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnStage", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketStage", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketStage", "version": "0.0.0" } }, @@ -208,20 +225,98 @@ "id": "ApiEndpoint", "path": "integ-mock-websocket-integration/ApiEndpoint", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-mock-websocket-integration/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-mock-websocket-integration/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" } } }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "apigatewayv2-mock-integration-integ-test": { + "id": "apigatewayv2-mock-integration-integ-test", + "path": "apigatewayv2-mock-integration-integ-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.3.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts index 5ae9e8d7556be..37e760937e3ac 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts @@ -1,6 +1,7 @@ import { WebSocketApi, WebSocketStage } from 'aws-cdk-lib/aws-apigatewayv2'; import { App, CfnOutput, Stack } from 'aws-cdk-lib'; import { WebSocketMockIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; /* * Stack verification steps: @@ -19,6 +20,16 @@ const stage = new WebSocketStage(stack, 'mystage', { autoDeploy: true, }); -webSocketApi.addRoute('sendmessage', { integration: new WebSocketMockIntegration('SendMessageIntegration') }); +webSocketApi.addRoute('sendmessage', { + integration: new WebSocketMockIntegration('DefaultIntegration', { + requestTemplates: { 'application/json': JSON.stringify({ statusCode: 200 }) }, + templateSelectionExpression: '\\$default', + }), + returnResponse: true, +}); new CfnOutput(stack, 'ApiEndpoint', { value: stage.url }); + +new IntegTest(app, 'apigatewayv2-mock-integration-integ-test', { + testCases: [stack], +}); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53-targets/test/integ.elastic-beanstalk-environment-target-assets/package-lock.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53-targets/test/integ.elastic-beanstalk-environment-target-assets/package-lock.json index f7b7b98ab1f09..9e2f7444fce2c 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-route53-targets/test/integ.elastic-beanstalk-environment-target-assets/package-lock.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53-targets/test/integ.elastic-beanstalk-environment-target-assets/package-lock.json @@ -196,9 +196,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -219,7 +219,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -234,6 +234,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/finalhandler": { @@ -487,9 +491,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/proxy-addr": { "version": "2.0.7", diff --git a/packages/@aws-cdk/aws-neptune-alpha/lib/cluster.ts b/packages/@aws-cdk/aws-neptune-alpha/lib/cluster.ts index 252ba0f923f40..1a137541ea056 100644 --- a/packages/@aws-cdk/aws-neptune-alpha/lib/cluster.ts +++ b/packages/@aws-cdk/aws-neptune-alpha/lib/cluster.ts @@ -732,7 +732,7 @@ export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseClu throw new Error(`ServerlessScalingConfiguration minCapacity must be greater or equal than 1, received ${serverlessScalingConfiguration.minCapacity}`); } if (serverlessScalingConfiguration.maxCapacity < 2.5 || serverlessScalingConfiguration.maxCapacity > 128) { - throw new Error(`ServerlessScalingConfiguration maxCapacity must be between 2.5 and 128, reveived ${serverlessScalingConfiguration.maxCapacity}`); + throw new Error(`ServerlessScalingConfiguration maxCapacity must be between 2.5 and 128, received ${serverlessScalingConfiguration.maxCapacity}`); } if (serverlessScalingConfiguration.minCapacity >= serverlessScalingConfiguration.maxCapacity) { throw new Error(`ServerlessScalingConfiguration minCapacity ${serverlessScalingConfiguration.minCapacity} ` + diff --git a/packages/@aws-cdk/aws-neptune-alpha/lib/parameter-group.ts b/packages/@aws-cdk/aws-neptune-alpha/lib/parameter-group.ts index 94ce5d5cbe7c2..12c4e88d1fd53 100644 --- a/packages/@aws-cdk/aws-neptune-alpha/lib/parameter-group.ts +++ b/packages/@aws-cdk/aws-neptune-alpha/lib/parameter-group.ts @@ -21,7 +21,7 @@ export class ParameterGroupFamily { public static readonly NEPTUNE_1_3 = new ParameterGroupFamily('neptune1.3'); /** - * Constructor for specifying a custom parameter group famil + * Constructor for specifying a custom parameter group family * @param family the family of the parameter group Neptune */ public constructor(public readonly family: string) {} diff --git a/packages/@aws-cdk/aws-neptune-alpha/test/cluster.test.ts b/packages/@aws-cdk/aws-neptune-alpha/test/cluster.test.ts index a84f8378b781b..df6db3487db4c 100644 --- a/packages/@aws-cdk/aws-neptune-alpha/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-neptune-alpha/test/cluster.test.ts @@ -835,7 +835,7 @@ describe('DatabaseCluster', () => { maxCapacity: 200, }, }); - }).toThrow(/ServerlessScalingConfiguration maxCapacity must be between 2.5 and 128, reveived 200/); + }).toThrow(/ServerlessScalingConfiguration maxCapacity must be between 2.5 and 128, received 200/); expect(() => { new DatabaseCluster(stack, 'Database3', { diff --git a/packages/@aws-cdk/aws-pipes-alpha/lib/logs.ts b/packages/@aws-cdk/aws-pipes-alpha/lib/logs.ts index b99b2da6e8e65..01a4c9999931f 100644 --- a/packages/@aws-cdk/aws-pipes-alpha/lib/logs.ts +++ b/packages/@aws-cdk/aws-pipes-alpha/lib/logs.ts @@ -68,7 +68,7 @@ export interface S3LogDestinationProps { * The S3 bucket to deliver the log records for the pipe. * * The bucket can be in the same or a different AWS Account. If the bucket is in - * a different acccount, specify `bucketOwner`. You must also allow access to the + * a different account, specify `bucketOwner`. You must also allow access to the * Pipes role in the bucket policy of the cross-account bucket. * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-pipes-pipe-s3logdestination.html#cfn-pipes-pipe-s3logdestination-bucketname diff --git a/packages/@aws-cdk/aws-scheduler-targets-alpha/lib/util.ts b/packages/@aws-cdk/aws-scheduler-targets-alpha/lib/util.ts index 3ea723c29ef2c..f21cea645a31a 100644 --- a/packages/@aws-cdk/aws-scheduler-targets-alpha/lib/util.ts +++ b/packages/@aws-cdk/aws-scheduler-targets-alpha/lib/util.ts @@ -4,7 +4,7 @@ import { Token, TokenComparison } from 'aws-cdk-lib'; * Whether two string probably contain the same environment dimension (region or account) * * Used to compare either accounts or regions, and also returns true if both - * are unresolved (in which case both are expted to be "current region" or "current account"). + * are unresolved (in which case both are expected to be "current region" or "current account"). */ export function sameEnvDimension(dim1: string, dim2: string) { return [TokenComparison.SAME, TokenComparison.BOTH_UNRESOLVED].includes(Token.compareStrings(dim1, dim2)); diff --git a/packages/@aws-cdk/cli-lib-alpha/lib/cli.ts b/packages/@aws-cdk/cli-lib-alpha/lib/cli.ts index 1b96fa146ec4c..2f50aa3fc7916 100644 --- a/packages/@aws-cdk/cli-lib-alpha/lib/cli.ts +++ b/packages/@aws-cdk/cli-lib-alpha/lib/cli.ts @@ -123,7 +123,7 @@ export class AwsCdkCli implements IAwsCdkCli { return new AwsCdkCli(async (args) => changeDir( () => runCli(args, async (sdk, config) => { const env = await prepareDefaultEnvironment(sdk); - const context = await prepareContext(config, env); + const context = await prepareContext(config.settings, config.context.all, env); return withEnv(async() => createAssembly(await producer.produce(context)), env); }), diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index 9fd07172268e6..f01b36111edbc 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -11,6 +11,7 @@ - [WebSocket APIs](#websocket-apis) - [Lambda WebSocket Integration](#lambda-websocket-integration) - [AWS WebSocket Integration](#aws-websocket-integration) + - [Mock WebSocket Integration](#mock-websocket-integration) - [Import Issues](#import-issues) - [DotNet Namespace](#dotnet-namespace) - [Java Package](#java-package) @@ -312,6 +313,30 @@ webSocketApi.addRoute('$connect', { You can also set additional properties to change the behavior of your integration, such as `contentHandling`. See [Working with binary media types for WebSocket APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop-binary-media-types.html). +### Mock WebSocket Integration + +API Gateway also allows the creation of mock integrations, allowing you to generate API responses without the need for an integration backend. These responses can range in complexity from a static message to a templated response with parameters extracted from the input request or the integration's context. See [Set up data mapping for WebSocket APIs in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html) and [WebSocket API mapping template reference for API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html) for more information. + +```ts +import { WebSocketMockIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; + +const webSocketApi = new apigwv2.WebSocketApi(this, 'mywsapi'); +new apigwv2.WebSocketStage(this, 'mystage', { + webSocketApi, + stageName: 'dev', + autoDeploy: true, +}); + + +webSocketApi.addRoute('sendMessage', { + integration: new WebSocketMockIntegration('DefaultIntegration', { + requestTemplates: { 'application/json': JSON.stringify({ statusCode: 200 }) }, + templateSelectionExpression: '\\$default', + }), + returnResponse: true, +}); +``` + ## Import Issues `jsiirc.json` file is missing during the stablization process of this module, which caused import issues for DotNet and Java users who attempt to use this module. Unfortunately, to guarantee backward compatibility, we cannot simply correct the namespace for DotNet or package for Java. The following outlines the workaround. diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts index 3ebf7411930c7..9248d97e17ea5 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts @@ -5,6 +5,31 @@ import { WebSocketRouteIntegrationBindOptions, } from '../../../aws-apigatewayv2'; +/** + * Props for Mock type integration for a WebSocket Api. + */ +export interface WebSocketMockIntegrationProps { + /** + * A map of Apache Velocity templates that are applied on the request + * payload. + * + * ``` + * { "application/json": "{ \"statusCode\": 200 }" } + * ``` + * + * @default - No request template provided to the integration. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html + */ + readonly requestTemplates?: { [contentType: string]: string }; + + /** + * The template selection expression for the integration. + * + * @default - No template selection expression provided. + */ + readonly templateSelectionExpression?: string; +} + /** * Mock WebSocket Integration */ @@ -13,7 +38,7 @@ export class WebSocketMockIntegration extends WebSocketRouteIntegration { /** * @param id id of the underlying integration construct */ - constructor(id: string) { + constructor(id: string, private readonly props: WebSocketMockIntegrationProps = {}) { super(id); } @@ -22,6 +47,8 @@ export class WebSocketMockIntegration extends WebSocketRouteIntegration { return { type: WebSocketIntegrationType.MOCK, uri: '', + requestTemplates: this.props.requestTemplates, + templateSelectionExpression: this.props.templateSelectionExpression, }; } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts index 46920f16c4b24..d92d131dc8ee1 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts @@ -19,4 +19,28 @@ describe('MockWebSocketIntegration', () => { IntegrationUri: '', }); }); + + test('can set custom properties', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new WebSocketApi(stack, 'Api', { + defaultRouteOptions: { + integration: new WebSocketMockIntegration('DefaultIntegration', { + requestTemplates: { 'application/json': '{ "statusCode": 200 }' }, + templateSelectionExpression: '\\$default', + }), + returnResponse: true, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'MOCK', + IntegrationUri: '', + RequestTemplates: { 'application/json': '{ "statusCode": 200 }' }, + TemplateSelectionExpression: '\\$default', + }); + }); }); diff --git a/packages/aws-cdk-lib/aws-rds/lib/instance-engine.ts b/packages/aws-cdk-lib/aws-rds/lib/instance-engine.ts index cd0e6986fe1d5..7fd956629630d 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/instance-engine.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/instance-engine.ts @@ -959,7 +959,7 @@ export class MysqlEngineVersion { /** Version "8.0.40". */ public static readonly VER_8_0_40 = MysqlEngineVersion.of('8.0.40', '8.0'); /** Version "8.4.3". */ - public static readonly VER_8_4_3 = MysqlEngineVersion.of('8.4.3', '8.0'); + public static readonly VER_8_4_3 = MysqlEngineVersion.of('8.4.3', '8.4'); /** * Create a new MysqlEngineVersion with an arbitrary version. diff --git a/packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts b/packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts index c66ededbdaff0..a81ddb4282e57 100644 --- a/packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts +++ b/packages/aws-cdk-lib/testhelpers/jest-bufferedconsole.ts @@ -13,7 +13,10 @@ interface ConsoleMessage { export default class TestEnvironment extends NodeEnvironment implements JestEnvironment { private log = new Array(); + private originalConsole!: typeof console; + private originalStdoutWrite!: typeof process.stdout.write; + private originalStderrWrite!: typeof process.stderr.write; constructor(config: JestEnvironmentConfig, context: EnvironmentContext) { super(config, context); @@ -41,6 +44,8 @@ export default class TestEnvironment extends NodeEnvironment implements JestEnvi this.log = []; this.originalConsole = console; + this.originalStdoutWrite = process.stdout.write; + this.originalStderrWrite = process.stderr.write; this.global.console = { ...console, @@ -50,10 +55,30 @@ export default class TestEnvironment extends NodeEnvironment implements JestEnvi info: (message) => this.log.push({ type: 'info', message }), debug: (message) => this.log.push({ type: 'debug', message }), }; + + const self = this; + process.stdout.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void { + const encoding = typeof enccb === 'string' ? enccb : 'utf-8'; + const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk; + self.log.push({ type: 'log', message: message.replace(/\n$/, '') }); + if (typeof enccb === 'function') { + enccb(); + } + } as any; + process.stderr.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void { + const encoding = typeof enccb === 'string' ? enccb : 'utf-8'; + const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk; + self.log.push({ type: 'error', message: message.replace(/\n$/, '') }); + if (typeof enccb === 'function') { + enccb(); + } + } as any; } async teardown() { this.global.console = this.originalConsole; + process.stdout.write = this.originalStdoutWrite; + process.stderr.write = this.originalStderrWrite; await super.teardown(); } } @@ -72,4 +97,3 @@ function fullTestName(test: TestDescription) { } return ret; } - diff --git a/packages/aws-cdk/jest.config.js b/packages/aws-cdk/jest.config.js index 23f1a71b38590..03917ee50eb84 100644 --- a/packages/aws-cdk/jest.config.js +++ b/packages/aws-cdk/jest.config.js @@ -31,6 +31,7 @@ const config = { // fail because they rely on shared mutable state left by other tests // (files on disk, global mocks, etc). randomize: true, + testEnvironment: './test/jest-bufferedconsole.ts', }; // Disable coverage running in the VSCode debug terminal: we never want coverage @@ -40,4 +41,4 @@ if (process.env.VSCODE_INJECTION) { config.collectCoverage = false; } -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts index 079a0322b73e5..f32608e627a2f 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-props.ts @@ -1,5 +1,5 @@ import { BootstrapSource } from './bootstrap-environment'; -import { Tag } from '../../cdk-toolkit'; +import { Tag } from '../../tags'; import { StringWithoutPlaceholders } from '../util/placeholders'; export const BUCKET_NAME_OUTPUT = 'BucketName'; diff --git a/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts b/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts index 4f0fa296ec123..3bc37117162d6 100644 --- a/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts +++ b/packages/aws-cdk/lib/api/cxapp/cloud-assembly.ts @@ -2,8 +2,8 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as chalk from 'chalk'; import { minimatch } from 'minimatch'; import * as semver from 'semver'; -import { error, info, warning } from '../../logging'; -import { ToolkitError } from '../../toolkit/error'; +import { info } from '../../logging'; +import { AssemblyError, ToolkitError } from '../../toolkit/error'; import { flatten } from '../../util'; export enum DefaultSelection { @@ -134,7 +134,7 @@ export class CloudAssembly { } } - private selectMatchingStacks( + protected selectMatchingStacks( stacks: cxapi.CloudFormationStackArtifact[], patterns: string[], extend: ExtendedStackSelection = ExtendedStackSelection.None, @@ -170,7 +170,7 @@ export class CloudAssembly { } } - private extendStacks( + protected extendStacks( matched: cxapi.CloudFormationStackArtifact[], all: cxapi.CloudFormationStackArtifact[], extend: ExtendedStackSelection = ExtendedStackSelection.None, @@ -231,6 +231,10 @@ export class StackCollection { return this.stackArtifacts.map(s => s.id); } + public get hierarchicalIds(): string[] { + return this.stackArtifacts.map(s => s.hierarchicalId); + } + public reversed() { const arts = [...this.stackArtifacts]; arts.reverse(); @@ -241,14 +245,17 @@ export class StackCollection { return new StackCollection(this.assembly, this.stackArtifacts.filter(predicate)); } - public concat(other: StackCollection): StackCollection { - return new StackCollection(this.assembly, this.stackArtifacts.concat(other.stackArtifacts)); + public concat(...others: StackCollection[]): StackCollection { + return new StackCollection(this.assembly, this.stackArtifacts.concat(...others.map(o => o.stackArtifacts))); } /** * Extracts 'aws:cdk:warning|info|error' metadata entries from the stack synthesis */ - public processMetadataMessages(options: MetadataMessageOptions = {}) { + public async validateMetadata( + failAt: 'warn' | 'error' | 'none' = 'error', + logger: (level: 'info' | 'error' | 'warn', msg: cxapi.SynthesisMessage) => Promise = async () => {}, + ) { let warnings = false; let errors = false; @@ -257,33 +264,25 @@ export class StackCollection { switch (message.level) { case cxapi.SynthesisMessageLevel.WARNING: warnings = true; - printMessage(warning, 'Warning', message.id, message.entry); + await logger('warn', message); break; case cxapi.SynthesisMessageLevel.ERROR: errors = true; - printMessage(error, 'Error', message.id, message.entry); + await logger('error', message); break; case cxapi.SynthesisMessageLevel.INFO: - printMessage(info, 'Info', message.id, message.entry); + await logger('info', message); break; } } } - if (errors && !options.ignoreErrors) { - throw new ToolkitError('Found errors'); + if (errors && failAt != 'none') { + throw new AssemblyError('Found errors'); } - if (options.strict && warnings) { - throw new ToolkitError('Found warnings (--strict mode)'); - } - - function printMessage(logFn: (s: string) => void, prefix: string, id: string, entry: cxapi.MetadataEntry) { - logFn(`[${prefix} at ${id}] ${entry.data}`); - - if (options.verbose && entry.trace) { - logFn(` ${entry.trace.join('\n ')}`); - } + if (warnings && failAt === 'warn') { + throw new AssemblyError('Found warnings (--strict mode)'); } } } @@ -380,7 +379,7 @@ function includeUpstreamStacks( } } -function sanitizePatterns(patterns: string[]): string[] { +export function sanitizePatterns(patterns: string[]): string[] { let sanitized = patterns.filter(s => s != null); // filter null/undefined sanitized = [...new Set(sanitized)]; // make them unique return sanitized; diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index b02ad38445a07..f17d0c11453df 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -6,7 +6,7 @@ import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; import * as semver from 'semver'; import { debug, warning } from '../../logging'; -import { Configuration, PROJECT_CONFIG, USER_DEFAULTS } from '../../settings'; +import { Configuration, PROJECT_CONFIG, Settings, USER_DEFAULTS } from '../../settings'; import { ToolkitError } from '../../toolkit/error'; import { loadTree, some } from '../../tree'; import { splitBySize } from '../../util/objects'; @@ -22,7 +22,7 @@ export interface ExecProgramResult { /** Invokes the cloud executable and returns JSON output */ export async function execProgram(aws: SdkProvider, config: Configuration): Promise { const env = await prepareDefaultEnvironment(aws); - const context = await prepareContext(config, env); + const context = await prepareContext(config.settings, config.context.all, env); const build = config.settings.get(['build']); if (build) { @@ -44,7 +44,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom return { assembly: createAssembly(app), lock }; } - const commandLine = await guessExecutable(appToArray(app)); + const commandLine = await guessExecutable(app); const outdir = config.settings.get(['output']); if (!outdir) { @@ -166,16 +166,19 @@ export function createAssembly(appDir: string) { * * @param context The context key/value bash. */ -export async function prepareDefaultEnvironment(aws: SdkProvider): Promise<{ [key: string]: string }> { +export async function prepareDefaultEnvironment( + aws: SdkProvider, + logFn: (msg: string, ...args: any) => any = debug, +): Promise<{ [key: string]: string }> { const env: { [key: string]: string } = { }; env[cxapi.DEFAULT_REGION_ENV] = aws.defaultRegion; - debug(`Setting "${cxapi.DEFAULT_REGION_ENV}" environment variable to`, env[cxapi.DEFAULT_REGION_ENV]); + await logFn(`Setting "${cxapi.DEFAULT_REGION_ENV}" environment variable to`, env[cxapi.DEFAULT_REGION_ENV]); const accountId = (await aws.defaultAccount())?.accountId; if (accountId) { env[cxapi.DEFAULT_ACCOUNT_ENV] = accountId; - debug(`Setting "${cxapi.DEFAULT_ACCOUNT_ENV}" environment variable to`, env[cxapi.DEFAULT_ACCOUNT_ENV]); + await logFn(`Setting "${cxapi.DEFAULT_ACCOUNT_ENV}" environment variable to`, env[cxapi.DEFAULT_ACCOUNT_ENV]); } return env; @@ -186,35 +189,33 @@ export async function prepareDefaultEnvironment(aws: SdkProvider): Promise<{ [ke * The merging of various configuration sources like cli args or cdk.json has already happened. * We now need to set the final values to the context. */ -export async function prepareContext(config: Configuration, env: { [key: string]: string | undefined}) { - const context = config.context.all; - - const debugMode: boolean = config.settings.get(['debug']) ?? true; +export async function prepareContext(settings: Settings, context: {[key: string]: any}, env: { [key: string]: string | undefined}) { + const debugMode: boolean = settings.get(['debug']) ?? true; if (debugMode) { env.CDK_DEBUG = 'true'; } - const pathMetadata: boolean = config.settings.get(['pathMetadata']) ?? true; + const pathMetadata: boolean = settings.get(['pathMetadata']) ?? true; if (pathMetadata) { context[cxapi.PATH_METADATA_ENABLE_CONTEXT] = true; } - const assetMetadata: boolean = config.settings.get(['assetMetadata']) ?? true; + const assetMetadata: boolean = settings.get(['assetMetadata']) ?? true; if (assetMetadata) { context[cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT] = true; } - const versionReporting: boolean = config.settings.get(['versionReporting']) ?? true; + const versionReporting: boolean = settings.get(['versionReporting']) ?? true; if (versionReporting) { context[cxapi.ANALYTICS_REPORTING_ENABLED_CONTEXT] = true; } // We need to keep on doing this for framework version from before this flag was deprecated. if (!versionReporting) { context['aws:cdk:disable-version-reporting'] = true; } - const stagingEnabled = config.settings.get(['staging']) ?? true; + const stagingEnabled = settings.get(['staging']) ?? true; if (!stagingEnabled) { context[cxapi.DISABLE_ASSET_STAGING_CONTEXT] = true; } - const bundlingStacks = config.settings.get(['bundlingStacks']) ?? ['**']; + const bundlingStacks = settings.get(['bundlingStacks']) ?? ['**']; context[cxapi.BUNDLING_STACKS] = bundlingStacks; debug('context:', context); @@ -257,7 +258,8 @@ const EXTENSION_MAP = new Map([ * verify if registry associations have or have not been set up for this * file type, so we'll assume the worst and take control. */ -async function guessExecutable(commandLine: string[]) { +export async function guessExecutable(app: string) { + const commandLine = appToArray(app); if (commandLine.length === 1) { let fstat; @@ -300,7 +302,7 @@ function contextOverflowCleanup(location: string | undefined, assembly: cxapi.Cl } } -function spaceAvailableForContext(env: { [key: string]: string }, limit: number) { +export function spaceAvailableForContext(env: { [key: string]: string }, limit: number) { const size = (value: string) => value != null ? Buffer.byteLength(value) : 0; const usedSpace = Object.entries(env) diff --git a/packages/aws-cdk/lib/api/deployments.ts b/packages/aws-cdk/lib/api/deployments.ts index 32650ac7a3549..32633db0a122c 100644 --- a/packages/aws-cdk/lib/api/deployments.ts +++ b/packages/aws-cdk/lib/api/deployments.ts @@ -5,10 +5,10 @@ import { AssetManifest, IManifestEntry } from 'cdk-assets'; import * as chalk from 'chalk'; import type { SdkProvider } from './aws-auth/sdk-provider'; import { type DeploymentMethod, deployStack, DeployStackResult, destroyStack } from './deploy-stack'; +import { EnvironmentAccess } from './environment-access'; import { type EnvironmentResources } from './environment-resources'; -import type { Tag } from '../cdk-toolkit'; import { debug, warning } from '../logging'; -import { EnvironmentAccess } from './environment-access'; +import type { Tag } from '../tags'; import { HotswapMode, HotswapPropertyOverrides } from './hotswap/common'; import { loadCurrentTemplate, @@ -25,10 +25,6 @@ import { Template, uploadStackTemplateAssets, } from './util/cloudformation'; -import { StackActivityMonitor, StackActivityProgress } from './util/cloudformation/stack-activity-monitor'; -import { StackEventPoller } from './util/cloudformation/stack-event-poller'; -import { RollbackChoice } from './util/cloudformation/stack-status'; -import { makeBodyParameter } from './util/template-body-parameter'; import { AssetManifestBuilder } from '../util/asset-manifest-builder'; import { buildAssets, @@ -38,6 +34,10 @@ import { type PublishAssetsOptions, PublishingAws, } from '../util/asset-publishing'; +import { StackActivityMonitor, StackActivityProgress } from './util/cloudformation/stack-activity-monitor'; +import { StackEventPoller } from './util/cloudformation/stack-event-poller'; +import { RollbackChoice } from './util/cloudformation/stack-status'; +import { makeBodyParameter } from './util/template-body-parameter'; import { formatErrorMessage } from '../util/error'; const BOOTSTRAP_STACK_VERSION_FOR_ROLLBACK = 23; diff --git a/packages/aws-cdk/lib/api/util/string-manipulation.ts b/packages/aws-cdk/lib/api/util/string-manipulation.ts index 46ea54f42f422..87f6453be95b8 100644 --- a/packages/aws-cdk/lib/api/util/string-manipulation.ts +++ b/packages/aws-cdk/lib/api/util/string-manipulation.ts @@ -5,3 +5,27 @@ export function leftPad(s: string, n: number, char: string) { const padding = Math.max(0, n - s.length); return char.repeat(padding) + s; } + +/** + * Formats time in milliseconds (which we get from 'Date.getTime()') + * to a human-readable time; returns time in seconds rounded to 2 + * decimal places. + */ +export function formatTime(num: number): number { + return roundPercentage(millisecondsToSeconds(num)); +} + +/** + * Rounds a decimal number to two decimal points. + * The function is useful for fractions that need to be outputted as percentages. + */ +function roundPercentage(num: number): number { + return Math.round(100 * num) / 100; +} + +/** + * Given a time in milliseconds, return an equivalent amount in seconds. + */ +function millisecondsToSeconds(num: number): number { + return num / 1000; +} diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index 9556e9c0d70a9..0381c77b64971 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -22,8 +22,9 @@ import { GarbageCollector } from './api/garbage-collection/garbage-collector'; import { HotswapMode, HotswapPropertyOverrides, EcsHotswapProperties } from './api/hotswap/common'; import { findCloudWatchLogGroups } from './api/logs/find-cloudwatch-logs'; import { CloudWatchLogEventMonitor } from './api/logs/logs-monitor'; -import { createDiffChangeSet, ResourcesToImport } from './api/util/cloudformation'; +import { createDiffChangeSet } from './api/util/cloudformation'; import { StackActivityProgress } from './api/util/cloudformation/stack-activity-monitor'; +import { formatTime } from './api/util/string-manipulation'; import { generateCdkApp, generateStack, @@ -46,8 +47,10 @@ import { printSecurityDiff, printStackDiff, RequireApproval } from './diff'; import { ResourceImporter, removeNonImportResources } from './import'; import { listStacks } from './list-stacks'; import { data, debug, error, highlight, info, success, warning, withCorkedLogging } from './logging'; -import { deserializeStructure, serializeStructure } from './serialize'; +import { ResourceMigrator } from './migrator'; +import { deserializeStructure, obscureTemplate, serializeStructure } from './serialize'; import { Configuration, PROJECT_CONFIG } from './settings'; +import { Tag, tagsForStack } from './tags'; import { ToolkitError } from './toolkit/error'; import { numberFromBool, partition } from './util'; import { formatErrorMessage } from './util/error'; @@ -186,7 +189,10 @@ export class CdkToolkit { const currentTemplate = templateWithNestedStacks.deployedRootTemplate; const nestedStacks = templateWithNestedStacks.nestedStacks; - const resourcesToImport = await this.tryGetResources(await this.props.deployments.resolveEnvironment(stack)); + const migrator = new ResourceMigrator({ + deployments: this.props.deployments, + }); + const resourcesToImport = await migrator.tryGetResources(await this.props.deployments.resolveEnvironment(stack)); if (resourcesToImport) { removeNonImportResources(stack); } @@ -296,7 +302,10 @@ export class CdkToolkit { return; } - await this.tryMigrateResources(stackCollection, options); + const migrator = new ResourceMigrator({ + deployments: this.props.deployments, + }); + await migrator.tryMigrateResources(stackCollection, options); const requireApproval = options.requireApproval ?? RequireApproval.Broadening; @@ -1132,7 +1141,7 @@ export class CdkToolkit { }); this.validateStacksSelected(stacks, selector.patterns); - this.validateStacks(stacks); + await this.validateStacks(stacks); return stacks; } @@ -1158,7 +1167,7 @@ export class CdkToolkit { : new StackCollection(assembly, []); this.validateStacksSelected(selectedForDiff.concat(autoValidateStacks), stackNames); - this.validateStacks(selectedForDiff.concat(autoValidateStacks)); + await this.validateStacks(selectedForDiff.concat(autoValidateStacks)); return selectedForDiff; } @@ -1178,12 +1187,12 @@ export class CdkToolkit { /** * Validate the stacks for errors and warnings according to the CLI's current settings */ - private validateStacks(stacks: StackCollection) { - stacks.processMetadataMessages({ - ignoreErrors: this.props.ignoreErrors, - strict: this.props.strict, - verbose: this.props.verbose, - }); + private async validateStacks(stacks: StackCollection) { + let failAt: 'warn' | 'error' | 'none' = 'error'; + if (this.props.ignoreErrors) { failAt = 'none'; } + if (this.props.strict) { failAt = 'warn'; } + + await stacks.validateMetadata(failAt, stackMetadataLogger(this.props.verbose)); } /** @@ -1264,72 +1273,6 @@ export class CdkToolkit { stackName: assetNode.parentStack.stackName, })); } - - /** - * Checks to see if a migrate.json file exists. If it does and the source is either `filepath` or - * is in the same environment as the stack deployment, a new stack is created and the resources are - * migrated to the stack using an IMPORT changeset. The normal deployment will resume after this is complete - * to add back in any outputs and the CDKMetadata. - */ - private async tryMigrateResources(stacks: StackCollection, options: DeployOptions): Promise { - const stack = stacks.stackArtifacts[0]; - const migrateDeployment = new ResourceImporter(stack, this.props.deployments); - const resourcesToImport = await this.tryGetResources(await migrateDeployment.resolveEnvironment()); - - if (resourcesToImport) { - info('%s: creating stack for resource migration...', chalk.bold(stack.displayName)); - info('%s: importing resources into stack...', chalk.bold(stack.displayName)); - - await this.performResourceMigration(migrateDeployment, resourcesToImport, options); - - fs.rmSync('migrate.json'); - info('%s: applying CDKMetadata and Outputs to stack (if applicable)...', chalk.bold(stack.displayName)); - } - } - - /** - * Creates a new stack with just the resources to be migrated - */ - private async performResourceMigration( - migrateDeployment: ResourceImporter, - resourcesToImport: ResourcesToImport, - options: DeployOptions, - ) { - const startDeployTime = new Date().getTime(); - let elapsedDeployTime = 0; - - // Initial Deployment - await migrateDeployment.importResourcesFromMigrate(resourcesToImport, { - roleArn: options.roleArn, - toolkitStackName: options.toolkitStackName, - deploymentMethod: options.deploymentMethod, - usePreviousParameters: true, - progress: options.progress, - rollback: options.rollback, - }); - - elapsedDeployTime = new Date().getTime() - startDeployTime; - info('\n✨ Resource migration time: %ss\n', formatTime(elapsedDeployTime)); - } - - private async tryGetResources(environment: cxapi.Environment): Promise { - try { - const migrateFile = fs.readJsonSync('migrate.json', { - encoding: 'utf-8', - }); - const sourceEnv = (migrateFile.Source as string).split(':'); - if ( - sourceEnv[0] === 'localfile' || - (sourceEnv[4] === environment.account && sourceEnv[3] === environment.region) - ) { - return migrateFile.Resources; - } - } catch (e) { - // Nothing to do - } - - return undefined; - } } /** @@ -1868,42 +1811,6 @@ export interface MigrateOptions { readonly compress?: boolean; } -/** - * @returns an array with the tags available in the stack metadata. - */ -function tagsForStack(stack: cxapi.CloudFormationStackArtifact): Tag[] { - return Object.entries(stack.tags).map(([Key, Value]) => ({ Key, Value })); -} - -export interface Tag { - readonly Key: string; - readonly Value: string; -} - -/** - * Formats time in milliseconds (which we get from 'Date.getTime()') - * to a human-readable time; returns time in seconds rounded to 2 - * decimal places. - */ -function formatTime(num: number): number { - return roundPercentage(millisecondsToSeconds(num)); -} - -/** - * Rounds a decimal number to two decimal points. - * The function is useful for fractions that need to be outputted as percentages. - */ -function roundPercentage(num: number): number { - return Math.round(100 * num) / 100; -} - -/** - * Given a time in milliseconds, return an equivalent amount in seconds. - */ -function millisecondsToSeconds(num: number): number { - return num / 1000; -} - function buildParameterMap( parameters: | { @@ -1931,24 +1838,6 @@ function buildParameterMap( return parameterMap; } -/** - * Remove any template elements that we don't want to show users. - */ -function obscureTemplate(template: any = {}) { - if (template.Rules) { - // see https://github.com/aws/aws-cdk/issues/17942 - if (template.Rules.CheckBootstrapVersion) { - if (Object.keys(template.Rules).length > 1) { - delete template.Rules.CheckBootstrapVersion; - } else { - delete template.Rules; - } - } - } - - return template; -} - /** * Ask the user for a yes/no confirmation * @@ -1975,3 +1864,28 @@ async function askUserConfirmation( if (!confirmed) { throw new ToolkitError('Aborted by user'); } }); } + +/** + * Logger for processing stack metadata + */ +function stackMetadataLogger(verbose?: boolean): (level: 'info' | 'error' | 'warn', msg: cxapi.SynthesisMessage) => Promise { + const makeLogger = (level: string): [logger: (m: string) => void, prefix: string] => { + switch (level) { + case 'error': + return [error, 'Error']; + case 'warn': + return [warning, 'Warning']; + default: + return [info, 'Info']; + } + }; + + return async (level, msg) => { + const [logFn, prefix] = makeLogger(level); + logFn(`[${prefix} at ${msg.id}] ${msg.entry.data}`); + + if (verbose && msg.entry.trace) { + logFn(` ${msg.entry.trace.join('\n ')}`); + } + }; +} diff --git a/packages/aws-cdk/lib/cli.ts b/packages/aws-cdk/lib/cli.ts index 0ec08eaa89444..7ba9f5a3211c5 100644 --- a/packages/aws-cdk/lib/cli.ts +++ b/packages/aws-cdk/lib/cli.ts @@ -175,6 +175,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise { + CliIoHost.currentAction = command as any; const toolkitStackName: string = ToolkitInfo.determineName(configuration.settings.get(['toolkitStackName'])); debug(`Toolkit stack: ${chalk.bold(toolkitStackName)}`); diff --git a/packages/aws-cdk/lib/import.ts b/packages/aws-cdk/lib/import.ts index 47451ce76d874..bbfe268cecab6 100644 --- a/packages/aws-cdk/lib/import.ts +++ b/packages/aws-cdk/lib/import.ts @@ -10,8 +10,8 @@ import { assertIsSuccessfulDeployStackResult } from './api/deploy-stack'; import { Deployments } from './api/deployments'; import { ResourceIdentifierProperties, ResourcesToImport } from './api/util/cloudformation'; import { StackActivityProgress } from './api/util/cloudformation/stack-activity-monitor'; -import { Tag } from './cdk-toolkit'; import { error, info, success, warning } from './logging'; +import { Tag } from './tags'; import { ToolkitError } from './toolkit/error'; export interface ImportDeploymentOptions extends DeployOptions { diff --git a/packages/aws-cdk/lib/init-templates/app/csharp/src/%name.PascalCased%/%name.PascalCased%.template.csproj b/packages/aws-cdk/lib/init-templates/app/csharp/src/%name.PascalCased%/%name.PascalCased%.template.csproj index e82ee424f2b6c..b9c30f09b3c5b 100644 --- a/packages/aws-cdk/lib/init-templates/app/csharp/src/%name.PascalCased%/%name.PascalCased%.template.csproj +++ b/packages/aws-cdk/lib/init-templates/app/csharp/src/%name.PascalCased%/%name.PascalCased%.template.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 Major diff --git a/packages/aws-cdk/lib/init-templates/app/fsharp/src/%name.PascalCased%/%name.PascalCased%.template.fsproj b/packages/aws-cdk/lib/init-templates/app/fsharp/src/%name.PascalCased%/%name.PascalCased%.template.fsproj index 07755869f787b..c9cb096e6dd7b 100644 --- a/packages/aws-cdk/lib/init-templates/app/fsharp/src/%name.PascalCased%/%name.PascalCased%.template.fsproj +++ b/packages/aws-cdk/lib/init-templates/app/fsharp/src/%name.PascalCased%/%name.PascalCased%.template.fsproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 Major diff --git a/packages/aws-cdk/lib/init-templates/sample-app/csharp/src/%name.PascalCased%/%name.PascalCased%.template.csproj b/packages/aws-cdk/lib/init-templates/sample-app/csharp/src/%name.PascalCased%/%name.PascalCased%.template.csproj index e82ee424f2b6c..b9c30f09b3c5b 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/csharp/src/%name.PascalCased%/%name.PascalCased%.template.csproj +++ b/packages/aws-cdk/lib/init-templates/sample-app/csharp/src/%name.PascalCased%/%name.PascalCased%.template.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 Major diff --git a/packages/aws-cdk/lib/init-templates/sample-app/fsharp/src/%name.PascalCased%/%name.PascalCased%.template.fsproj b/packages/aws-cdk/lib/init-templates/sample-app/fsharp/src/%name.PascalCased%/%name.PascalCased%.template.fsproj index 07755869f787b..c9cb096e6dd7b 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/fsharp/src/%name.PascalCased%/%name.PascalCased%.template.fsproj +++ b/packages/aws-cdk/lib/init-templates/sample-app/fsharp/src/%name.PascalCased%/%name.PascalCased%.template.fsproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 Major diff --git a/packages/aws-cdk/lib/logging.ts b/packages/aws-cdk/lib/logging.ts index 2df85c308708b..9262e94ea6f4a 100644 --- a/packages/aws-cdk/lib/logging.ts +++ b/packages/aws-cdk/lib/logging.ts @@ -4,7 +4,7 @@ import { IoMessageLevel, IoMessage, CliIoHost, IoMessageSpecificCode, IoMessageC // Corking mechanism let CORK_COUNTER = 0; -const logBuffer: IoMessage[] = []; +const logBuffer: IoMessage[] = []; const levelPriority: Record = { error: 0, @@ -86,12 +86,12 @@ function log(options: LogOptions) { return; } - const ioMessage: IoMessage = { + const ioMessage: IoMessage = { level: options.level, message: options.message, forceStdout: options.forceStdout, time: new Date(), - action: CliIoHost.currentAction ?? 'none', + action: CliIoHost.currentAction, code: options.code, }; diff --git a/packages/aws-cdk/lib/migrator.ts b/packages/aws-cdk/lib/migrator.ts new file mode 100644 index 0000000000000..5bc82061456af --- /dev/null +++ b/packages/aws-cdk/lib/migrator.ts @@ -0,0 +1,87 @@ +import type * as cxapi from '@aws-cdk/cx-api'; +import * as chalk from 'chalk'; +import * as fs from 'fs-extra'; +import { StackCollection } from './api/cxapp/cloud-assembly'; +import { Deployments } from './api/deployments'; +import { ResourcesToImport } from './api/util/cloudformation'; +import { formatTime } from './api/util/string-manipulation'; +import { DeployOptions } from './cdk-toolkit'; +import { ResourceImporter } from './import'; +import { info } from './logging'; + +export interface ResourceMigratorProps { + deployments: Deployments; +} + +type ResourceMigratorOptions = Pick + +export class ResourceMigrator { + public constructor(private readonly props: ResourceMigratorProps) {} + + /** + * Checks to see if a migrate.json file exists. If it does and the source is either `filepath` or + * is in the same environment as the stack deployment, a new stack is created and the resources are + * migrated to the stack using an IMPORT changeset. The normal deployment will resume after this is complete + * to add back in any outputs and the CDKMetadata. + */ + public async tryMigrateResources(stacks: StackCollection, options: ResourceMigratorOptions): Promise { + const stack = stacks.stackArtifacts[0]; + const migrateDeployment = new ResourceImporter(stack, this.props.deployments); + const resourcesToImport = await this.tryGetResources(await migrateDeployment.resolveEnvironment()); + + if (resourcesToImport) { + info('%s: creating stack for resource migration...', chalk.bold(stack.displayName)); + info('%s: importing resources into stack...', chalk.bold(stack.displayName)); + + await this.performResourceMigration(migrateDeployment, resourcesToImport, options); + + fs.rmSync('migrate.json'); + info('%s: applying CDKMetadata and Outputs to stack (if applicable)...', chalk.bold(stack.displayName)); + } + } + + /** + * Creates a new stack with just the resources to be migrated + */ + private async performResourceMigration( + migrateDeployment: ResourceImporter, + resourcesToImport: ResourcesToImport, + options: ResourceMigratorOptions, + ) { + const startDeployTime = new Date().getTime(); + let elapsedDeployTime = 0; + + // Initial Deployment + await migrateDeployment.importResourcesFromMigrate(resourcesToImport, { + roleArn: options.roleArn, + toolkitStackName: options.toolkitStackName, + deploymentMethod: options.deploymentMethod, + usePreviousParameters: true, + progress: options.progress, + rollback: options.rollback, + }); + + elapsedDeployTime = new Date().getTime() - startDeployTime; + info('\n✨ Resource migration time: %ss\n', formatTime(elapsedDeployTime)); + } + + public async tryGetResources(environment: cxapi.Environment): Promise { + try { + const migrateFile = fs.readJsonSync('migrate.json', { + encoding: 'utf-8', + }); + const sourceEnv = (migrateFile.Source as string).split(':'); + if ( + sourceEnv[0] === 'localfile' || + (sourceEnv[4] === environment.account && sourceEnv[3] === environment.region) + ) { + return migrateFile.Resources; + } + } catch (e) { + // Nothing to do + } + + return undefined; + } +} + diff --git a/packages/aws-cdk/lib/serialize.ts b/packages/aws-cdk/lib/serialize.ts index ea7ea30c3b89c..9f310c95b3f78 100644 --- a/packages/aws-cdk/lib/serialize.ts +++ b/packages/aws-cdk/lib/serialize.ts @@ -33,3 +33,21 @@ export async function loadStructuredFile(fileName: string) { const contents = await fs.readFile(fileName, { encoding: 'utf-8' }); return deserializeStructure(contents); } + +/** + * Remove any template elements that we don't want to show users. + */ +export function obscureTemplate(template: any = {}) { + if (template.Rules) { + // see https://github.com/aws/aws-cdk/issues/17942 + if (template.Rules.CheckBootstrapVersion) { + if (Object.keys(template.Rules).length > 1) { + delete template.Rules.CheckBootstrapVersion; + } else { + delete template.Rules; + } + } + } + + return template; +} diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index 9c6e680e6c8d8..a14b712f05a3a 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -1,8 +1,8 @@ import * as os from 'os'; import * as fs_path from 'path'; import * as fs from 'fs-extra'; -import { Tag } from './cdk-toolkit'; import { debug, warning } from './logging'; +import { Tag } from './tags'; import { ToolkitError } from './toolkit/error'; import * as util from './util'; @@ -126,6 +126,7 @@ export class Configuration { this._projectConfig = await loadAndLog(PROJECT_CONFIG); this._projectContext = await loadAndLog(PROJECT_CONTEXT); + // @todo cannot currently be disabled by cli users const readUserContext = this.props.readUserContext ?? true; if (userConfig.get(['build'])) { diff --git a/packages/aws-cdk/lib/tags.ts b/packages/aws-cdk/lib/tags.ts new file mode 100644 index 0000000000000..df6ed884c1cca --- /dev/null +++ b/packages/aws-cdk/lib/tags.ts @@ -0,0 +1,13 @@ +import * as cxapi from '@aws-cdk/cx-api'; + +/** + * @returns an array with the tags available in the stack metadata. + */ +export function tagsForStack(stack: cxapi.CloudFormationStackArtifact): Tag[] { + return Object.entries(stack.tags).map(([Key, Value]) => ({ Key, Value })); +} + +export interface Tag { + readonly Key: string; + readonly Value: string; +} diff --git a/packages/aws-cdk/lib/toolkit/cli-io-host.ts b/packages/aws-cdk/lib/toolkit/cli-io-host.ts index 41038734d9c25..d55c394a9efc6 100644 --- a/packages/aws-cdk/lib/toolkit/cli-io-host.ts +++ b/packages/aws-cdk/lib/toolkit/cli-io-host.ts @@ -9,7 +9,7 @@ export type IoMessageCode = IoMessageSpecificCode; * Basic message structure for toolkit notifications. * Messages are emitted by the toolkit and handled by the IoHost. */ -export interface IoMessage { +export interface IoMessage { /** * The time the message was emitted. */ @@ -55,6 +55,18 @@ export interface IoMessage { * @default false */ readonly forceStdout?: boolean; + + /** + * The data attached to the message. + */ + readonly data?: T; +} + +export interface IoRequest extends IoMessage { + /** + * The default response that will be used if no data is returned. + */ + readonly defaultResponse: U; } export type IoMessageLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace'; @@ -62,7 +74,15 @@ export type IoMessageLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace'; /** * The current action being performed by the CLI. 'none' represents the absence of an action. */ -export type ToolkitAction = 'synth' | 'list' | 'deploy' | 'destroy' | 'none'; +export type ToolkitAction = +| 'bootstrap' +| 'synth' +| 'list' +| 'diff' +| 'deploy' +| 'rollback' +| 'watch' +| 'destroy'; /** * A simple IO host for the CLI that writes messages to the console. @@ -116,14 +136,14 @@ export class CliIoHost { /** * the current {@link ToolkitAction} set by the CLI. */ - private currentAction: ToolkitAction | undefined; + private currentAction: ToolkitAction = 'synth'; private constructor() { this.isTTY = process.stdout.isTTY ?? false; this.ci = false; } - public static get currentAction(): ToolkitAction | undefined { + public static get currentAction(): ToolkitAction { return CliIoHost.getIoHost().currentAction; } @@ -151,7 +171,7 @@ export class CliIoHost { * Notifies the host of a message. * The caller waits until the notification completes. */ - async notify(msg: IoMessage): Promise { + async notify(msg: IoMessage): Promise { const output = this.formatMessage(msg); const stream = CliIoHost.getStream(msg.level, msg.forceStdout ?? false); @@ -167,10 +187,21 @@ export class CliIoHost { }); } + /** + * Notifies the host of a message that requires a response. + * + * If the host does not return a response the suggested + * default response from the input message will be used. + */ + async requestResponse(msg: IoRequest): Promise { + await this.notify(msg); + return msg.defaultResponse; + } + /** * Formats a message for console output with optional color support */ - private formatMessage(msg: IoMessage): string { + private formatMessage(msg: IoMessage): string { // apply provided style or a default style if we're in TTY mode let message_text = this.isTTY ? styleMap[msg.level](msg.message) diff --git a/packages/aws-cdk/lib/toolkit/error.ts b/packages/aws-cdk/lib/toolkit/error.ts index 3fc628f0555d4..f61a4245e66fb 100644 --- a/packages/aws-cdk/lib/toolkit/error.ts +++ b/packages/aws-cdk/lib/toolkit/error.ts @@ -1,10 +1,11 @@ -const TOOLKIT_ERROR_SYMBOL = Symbol.for('@aws-cdk/core.ToolkitError'); -const AUTHENTICATION_ERROR_SYMBOL = Symbol.for('@aws-cdk/core.AuthenticationError'); +const TOOLKIT_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit.ToolkitError'); +const AUTHENTICATION_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit.AuthenticationError'); +const ASSEMBLY_ERROR_SYMBOL = Symbol.for('@aws-cdk/toolkit.AssemblyError'); /** * Represents a general toolkit error in the AWS CDK Toolkit. */ -class ToolkitError extends Error { +export class ToolkitError extends Error { /** * Determines if a given error is an instance of ToolkitError. */ @@ -19,6 +20,13 @@ class ToolkitError extends Error { return this.isToolkitError(x) && AUTHENTICATION_ERROR_SYMBOL in x; } + /** + * Determines if a given error is an instance of AssemblyError. + */ + public static isAssemblyError(x: any): x is AssemblyError { + return this.isToolkitError(x) && ASSEMBLY_ERROR_SYMBOL in x; + } + /** * The type of the error, defaults to "toolkit". */ @@ -36,7 +44,7 @@ class ToolkitError extends Error { /** * Represents an authentication-specific error in the AWS CDK Toolkit. */ -class AuthenticationError extends ToolkitError { +export class AuthenticationError extends ToolkitError { constructor(message: string) { super(message, 'authentication'); Object.setPrototypeOf(this, AuthenticationError.prototype); @@ -44,5 +52,13 @@ class AuthenticationError extends ToolkitError { } } -// Export classes for internal usage only -export { ToolkitError, AuthenticationError }; +/** + * Represents an authentication-specific error in the AWS CDK Toolkit. + */ +export class AssemblyError extends ToolkitError { + constructor(message: string) { + super(message, 'assembly'); + Object.setPrototypeOf(this, AssemblyError.prototype); + Object.defineProperty(this, ASSEMBLY_ERROR_SYMBOL, { value: true }); + } +} diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index e77fbb90d7ea2..b6cdedea62629 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -92,6 +92,7 @@ "constructs": "^10.0.0", "fast-check": "^3.22.0", "jest": "^29.7.0", + "jest-environment-node": "^29.7.0", "jest-mock": "^29.7.0", "madge": "^5.0.2", "make-runnable": "^1.4.1", diff --git a/packages/aws-cdk/test/api/cloud-assembly.test.ts b/packages/aws-cdk/test/api/cloud-assembly.test.ts index c97e6f4925d76..3000646a15deb 100644 --- a/packages/aws-cdk/test/api/cloud-assembly.test.ts +++ b/packages/aws-cdk/test/api/cloud-assembly.test.ts @@ -4,33 +4,6 @@ import { DefaultSelection } from '../../lib/api/cxapp/cloud-assembly'; import { MockCloudExecutable } from '../util'; import { cliAssemblyWithForcedVersion } from './assembly-versions'; -test('do not throw when selecting stack without errors', async () => { - // GIVEN - const cxasm = await testCloudAssembly(); - - // WHEN - const selected = await cxasm.selectStacks( { patterns: ['withouterrorsNODEPATH'] }, { - defaultBehavior: DefaultSelection.AllStacks, - }); - selected.processMetadataMessages(); - - // THEN - expect(selected.firstStack.template.resource).toBe('noerrorresource'); -}); - -test('do throw when selecting stack with errors', async () => { - // GIVEN - const cxasm = await testCloudAssembly(); - - // WHEN - const selected = await cxasm.selectStacks({ patterns: ['witherrors'] }, { - defaultBehavior: DefaultSelection.AllStacks, - }); - - // THEN - expect(() => selected.processMetadataMessages()).toThrow(/Found errors/); -}); - test('select all top level stacks in the presence of nested assemblies', async () => { // GIVEN const cxasm = await testNestedCloudAssembly(); @@ -52,7 +25,7 @@ test('select stacks by glob pattern', async () => { const x = await cxasm.selectStacks({ patterns: ['with*'] }, { defaultBehavior: DefaultSelection.AllStacks }); // THEN - expect(x.stackCount).toBe(2); + expect(x.stackCount).toBe(3); expect(x.stackIds).toContain('witherrors'); expect(x.stackIds).toContain('withouterrors'); }); @@ -65,7 +38,7 @@ test('select behavior: all', async () => { const x = await cxasm.selectStacks({ patterns: [] }, { defaultBehavior: DefaultSelection.AllStacks }); // THEN - expect(x.stackCount).toBe(2); + expect(x.stackCount).toBe(3); }); test('select behavior: none', async () => { @@ -186,6 +159,95 @@ test('select behavior with no stacks and default ignore stacks options (false)', .rejects.toThrow('This app contains no stacks'); }); +describe('StackCollection', () => { + test('returns hierarchicalIds', async () => { + // GIVEN + const cxasm = await testNestedCloudAssembly(); + + // WHEN + const x = await cxasm.selectStacks({ allTopLevel: true, patterns: [] }, { defaultBehavior: DefaultSelection.AllStacks }); + + // THEN + expect(x.stackCount).toBe(2); + expect(x.hierarchicalIds).toEqual(['witherrors', 'deeply/hidden/withouterrors']); + }); + + describe('validateMetadata', () => { + test('do not throw when selecting stack without errors', async () => { + // GIVEN + const cxasm = await testCloudAssembly(); + + // WHEN + const selected = await cxasm.selectStacks( { patterns: ['withouterrorsNODEPATH'] }, { + defaultBehavior: DefaultSelection.AllStacks, + }); + await selected.validateMetadata(); + + // THEN + expect(selected.stackCount).toBe(1); + expect(selected.firstStack.template.resource).toBe('noerrorresource'); + }); + + test('do not throw when selecting stack with warnings', async () => { + // GIVEN + const cxasm = await testCloudAssembly(); + + // WHEN + const selected = await cxasm.selectStacks( { patterns: ['withwarns'] }, { + defaultBehavior: DefaultSelection.AllStacks, + }); + await selected.validateMetadata(); + + // THEN + expect(selected.stackCount).toBe(1); + expect(selected.firstStack.template.resource).toBe('warnresource'); + }); + + test('do not throw when selecting stack with errors but errors are ignored', async () => { + // GIVEN + const cxasm = await testCloudAssembly(); + + // WHEN + const selected = await cxasm.selectStacks({ patterns: ['witherrors'] }, { + defaultBehavior: DefaultSelection.AllStacks, + }); + await selected.validateMetadata('none'); + + // THEN + expect(selected.stackCount).toBe(1); + expect(selected.firstStack.template.resource).toBe('errorresource'); + }); + + test('do throw when selecting stack with errors', async () => { + // GIVEN + const cxasm = await testCloudAssembly(); + + // WHEN + const selected = await cxasm.selectStacks({ patterns: ['witherrors'] }, { + defaultBehavior: DefaultSelection.AllStacks, + }); + + // THEN + expect(selected.stackCount).toBe(1); + await expect(async () => selected.validateMetadata()).rejects.toThrow(/Found errors/); + }); + + test('do throw when selecting stack with warnings and we are on strict mode', async () => { + // GIVEN + const cxasm = await testCloudAssembly(); + + // WHEN + const selected = await cxasm.selectStacks( { patterns: ['withwarns'] }, { + defaultBehavior: DefaultSelection.AllStacks, + }); + + // THEN + expect(selected.stackCount).toBe(1); + await expect(async () => selected.validateMetadata('warn')).rejects.toThrow(/Found warnings/); + }); + }); +}); + async function testCloudAssembly({ env }: { env?: string; versionReporting?: boolean } = {}) { const cloudExec = new MockCloudExecutable({ stacks: [{ @@ -206,6 +268,19 @@ async function testCloudAssembly({ env }: { env?: string; versionReporting?: boo }, ], }, + }, + { + stackName: 'withwarns', + env, + template: { resource: 'warnresource' }, + metadata: { + '/resource': [ + { + type: cxschema.ArtifactMetadataEntryType.WARN, + data: 'this is a warning', + }, + ], + }, }], }); diff --git a/packages/aws-cdk/test/cdk-toolkit.test.ts b/packages/aws-cdk/test/cdk-toolkit.test.ts index 8dad7142baea7..4f51ec2ec1885 100644 --- a/packages/aws-cdk/test/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cdk-toolkit.test.ts @@ -88,9 +88,10 @@ import { import { HotswapMode } from '../lib/api/hotswap/common'; import { Mode } from '../lib/api/plugin/mode'; import { Template } from '../lib/api/util/cloudformation'; -import { CdkToolkit, markTesting, Tag } from '../lib/cdk-toolkit'; +import { CdkToolkit, markTesting } from '../lib/cdk-toolkit'; import { RequireApproval } from '../lib/diff'; import { Configuration } from '../lib/settings'; +import { Tag } from '../lib/tags'; import { flatten } from '../lib/util'; markTesting(); diff --git a/packages/aws-cdk/test/init.test.ts b/packages/aws-cdk/test/init.test.ts index 2ef2582f1b63c..08764756beda2 100644 --- a/packages/aws-cdk/test/init.test.ts +++ b/packages/aws-cdk/test/init.test.ts @@ -97,7 +97,7 @@ describe('constructs version', () => { const sln = (await fs.readFile(slnFile, 'utf8')).split(/\r?\n/); expect(csproj).toContainEqual(expect.stringMatching(/\net6.0<\/TargetFramework>/)); + expect(csproj).toContainEqual(expect.stringMatching(/\net8.0<\/TargetFramework>/)); expect(sln).toContainEqual(expect.stringMatching(/\"AwsCdkTest[a-zA-Z0-9]{6}\\AwsCdkTest[a-zA-Z0-9]{6}.csproj\"/)); }); @@ -119,7 +119,7 @@ describe('constructs version', () => { const sln = (await fs.readFile(slnFile, 'utf8')).split(/\r?\n/); expect(fsproj).toContainEqual(expect.stringMatching(/\net6.0<\/TargetFramework>/)); + expect(fsproj).toContainEqual(expect.stringMatching(/\net8.0<\/TargetFramework>/)); expect(sln).toContainEqual(expect.stringMatching(/\"AwsCdkTest[a-zA-Z0-9]{6}\\AwsCdkTest[a-zA-Z0-9]{6}.fsproj\"/)); }); @@ -138,7 +138,7 @@ describe('constructs version', () => { const csproj = (await fs.readFile(csprojFile, 'utf8')).split(/\r?\n/); expect(csproj).toContainEqual(expect.stringMatching(/\net6.0<\/TargetFramework>/)); + expect(csproj).toContainEqual(expect.stringMatching(/\net8.0<\/TargetFramework>/)); }); cliTestWithDirSpaces('fsharp app with spaces', async (workDir) => { @@ -156,7 +156,7 @@ describe('constructs version', () => { const fsproj = (await fs.readFile(fsprojFile, 'utf8')).split(/\r?\n/); expect(fsproj).toContainEqual(expect.stringMatching(/\net6.0<\/TargetFramework>/)); + expect(fsproj).toContainEqual(expect.stringMatching(/\net8.0<\/TargetFramework>/)); }); cliTest('create a Python app project', async (workDir) => { diff --git a/packages/aws-cdk/test/jest-bufferedconsole.ts b/packages/aws-cdk/test/jest-bufferedconsole.ts new file mode 100644 index 0000000000000..a81ddb4282e57 --- /dev/null +++ b/packages/aws-cdk/test/jest-bufferedconsole.ts @@ -0,0 +1,99 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/** + * A Jest environment that buffers outputs to `console.log()` and only shows it for failing tests. + */ +import type { EnvironmentContext, JestEnvironment, JestEnvironmentConfig } from '@jest/environment'; +import { Circus } from '@jest/types'; +import { TestEnvironment as NodeEnvironment } from 'jest-environment-node'; + +interface ConsoleMessage { + type: 'log' | 'error' | 'warn' | 'info' | 'debug'; + message: string; +} + +export default class TestEnvironment extends NodeEnvironment implements JestEnvironment { + private log = new Array(); + + private originalConsole!: typeof console; + private originalStdoutWrite!: typeof process.stdout.write; + private originalStderrWrite!: typeof process.stderr.write; + + constructor(config: JestEnvironmentConfig, context: EnvironmentContext) { + super(config, context); + + // We need to set the event handler by assignment in the constructor, + // because if we declare it as an async member TypeScript's type derivation + // doesn't work properly. + (this as JestEnvironment).handleTestEvent = (async (event, _state) => { + if (event.name === 'test_done' && event.test.errors.length > 0 && this.log.length > 0) { + this.originalConsole.log(`[Console output] ${fullTestName(event.test)}\n`); + for (const item of this.log) { + this.originalConsole[item.type](' ' + item.message); + } + this.originalConsole.log('\n'); + } + + if (event.name === 'test_done') { + this.log = []; + } + }) satisfies Circus.EventHandler; + } + + async setup() { + await super.setup(); + + this.log = []; + this.originalConsole = console; + this.originalStdoutWrite = process.stdout.write; + this.originalStderrWrite = process.stderr.write; + + this.global.console = { + ...console, + log: (message) => this.log.push({ type: 'log', message }), + error: (message) => this.log.push({ type: 'error', message }), + warn: (message) => this.log.push({ type: 'warn', message }), + info: (message) => this.log.push({ type: 'info', message }), + debug: (message) => this.log.push({ type: 'debug', message }), + }; + + const self = this; + process.stdout.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void { + const encoding = typeof enccb === 'string' ? enccb : 'utf-8'; + const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk; + self.log.push({ type: 'log', message: message.replace(/\n$/, '') }); + if (typeof enccb === 'function') { + enccb(); + } + } as any; + process.stderr.write = function (chunk: Buffer | string, enccb?: BufferEncoding | ((error?: Error | null) => void)): void { + const encoding = typeof enccb === 'string' ? enccb : 'utf-8'; + const message = Buffer.isBuffer(chunk) ? chunk.toString(encoding) : chunk; + self.log.push({ type: 'error', message: message.replace(/\n$/, '') }); + if (typeof enccb === 'function') { + enccb(); + } + } as any; + } + + async teardown() { + this.global.console = this.originalConsole; + process.stdout.write = this.originalStdoutWrite; + process.stderr.write = this.originalStderrWrite; + await super.teardown(); + } +} + +// DescribeBlock is not exported from `@jest/types`, so we need to build the parts we are interested in +type TestDescription = PartialBy, 'parent'>; + +// Utility type to make specific fields optional +type PartialBy = Omit & Partial> + +function fullTestName(test: TestDescription) { + let ret = test.name; + while (test.parent != null && test.parent.name !== 'ROOT_DESCRIBE_BLOCK') { + ret = test.parent.name + ' › ' + fullTestName; + test = test.parent; + } + return ret; +} diff --git a/packages/aws-cdk/test/serialize.test.ts b/packages/aws-cdk/test/serialize.test.ts index c30e3caec2715..e465f1c8f8630 100644 --- a/packages/aws-cdk/test/serialize.test.ts +++ b/packages/aws-cdk/test/serialize.test.ts @@ -1,5 +1,5 @@ /* eslint-disable import/order */ -import { toYAML } from '../lib/serialize'; +import { toYAML, obscureTemplate } from '../lib/serialize'; describe(toYAML, () => { test('does not wrap lines', () => { @@ -7,3 +7,30 @@ describe(toYAML, () => { expect(toYAML({ longString })).toEqual(`longString: ${longString}\n`); }); }); + +describe(obscureTemplate, () => { + test('removes CheckBootstrapVersion rule only', () => { + const template = { + Rules: { + CheckBootstrapVersion: { Assertions: [{ AssertDescription: 'bootstrap' }] }, + MyOtherRule: { Assertions: [{ AssertDescription: 'other' }] }, + }, + }; + + const obscured = obscureTemplate(template); + expect(obscured).not.toHaveProperty('Rules.CheckBootstrapVersion'); + expect(obscured).toHaveProperty('Rules.MyOtherRule.Assertions.0.AssertDescription', 'other'); + }); + + test('removes all rules when CheckBootstrapVersion is the only rule', () => { + const template = { + Rules: { + CheckBootstrapVersion: { Assertions: [{ AssertDescription: 'bootstrap' }] }, + }, + }; + + const obscured = obscureTemplate(template); + expect(obscured).not.toHaveProperty('Rules.CheckBootstrapVersion'); + expect(obscured).not.toHaveProperty('Rules'); + }); +}); diff --git a/packages/aws-cdk/test/settings.test.ts b/packages/aws-cdk/test/settings.test.ts index 7edc2e9b487a2..4e2053e8e2848 100644 --- a/packages/aws-cdk/test/settings.test.ts +++ b/packages/aws-cdk/test/settings.test.ts @@ -1,6 +1,6 @@ /* eslint-disable import/order */ import { Command, Context, Settings } from '../lib/settings'; -import { Tag } from '../lib/cdk-toolkit'; +import { Tag } from '../lib/tags'; test('can delete values from Context object', () => { // GIVEN diff --git a/packages/aws-cdk/test/toolkit-error.test.ts b/packages/aws-cdk/test/toolkit-error.test.ts index 1aef772e186a5..5f2ff259300f1 100644 --- a/packages/aws-cdk/test/toolkit-error.test.ts +++ b/packages/aws-cdk/test/toolkit-error.test.ts @@ -1,17 +1,30 @@ -import { AuthenticationError, ToolkitError } from '../lib/toolkit/error'; +import { AssemblyError, AuthenticationError, ToolkitError } from '../lib/toolkit/error'; describe('toolkit error', () => { let toolkitError = new ToolkitError('Test toolkit error'); let authError = new AuthenticationError('Test authentication error'); + let assemblyError = new AssemblyError('Test authentication error'); + test('types are correctly assigned', async () => { expect(toolkitError.type).toBe('toolkit'); expect(authError.type).toBe('authentication'); + expect(assemblyError.type).toBe('assembly'); }); - test('isToolkitError and isAuthenticationError functions work', () => { + test('isToolkitError works', () => { expect(ToolkitError.isToolkitError(toolkitError)).toBe(true); expect(ToolkitError.isToolkitError(authError)).toBe(true); + expect(ToolkitError.isToolkitError(assemblyError)).toBe(true); + }); + + test('isAuthenticationError works', () => { expect(ToolkitError.isAuthenticationError(toolkitError)).toBe(false); expect(ToolkitError.isAuthenticationError(authError)).toBe(true); }); + + test('isAssemblyError works', () => { + expect(ToolkitError.isAssemblyError(assemblyError)).toBe(true); + expect(ToolkitError.isAssemblyError(toolkitError)).toBe(false); + expect(ToolkitError.isAssemblyError(authError)).toBe(false); + }); }); diff --git a/packages/aws-cdk/test/toolkit/cli-io-host.test.ts b/packages/aws-cdk/test/toolkit/cli-io-host.test.ts index 7ea32efcf6c2e..210a9a310a95e 100644 --- a/packages/aws-cdk/test/toolkit/cli-io-host.test.ts +++ b/packages/aws-cdk/test/toolkit/cli-io-host.test.ts @@ -4,7 +4,7 @@ import { CliIoHost, IoMessage } from '../../lib/toolkit/cli-io-host'; describe('CliIoHost', () => { let mockStdout: jest.Mock; let mockStderr: jest.Mock; - let defaultMessage: IoMessage; + let defaultMessage: IoMessage; beforeEach(() => { mockStdout = jest.fn(); @@ -13,7 +13,7 @@ describe('CliIoHost', () => { // Reset singleton state CliIoHost.isTTY = process.stdout.isTTY ?? false; CliIoHost.ci = false; - CliIoHost.currentAction = 'none'; + CliIoHost.currentAction = 'synth'; defaultMessage = { time: new Date('2024-01-01T12:00:00'), @@ -239,4 +239,21 @@ describe('CliIoHost', () => { })).rejects.toThrow('Write failed'); }); }); + + describe('requestResponse', () => { + test('logs messages and returns default', async () => { + CliIoHost.isTTY = true; + const response = await CliIoHost.getIoHost().requestResponse({ + time: new Date(), + level: 'info', + action: 'synth', + code: 'CDK_TOOLKIT_I0001', + message: 'test message', + defaultResponse: 'default response', + }); + + expect(mockStderr).toHaveBeenCalledWith(chalk.white('test message') + '\n'); + expect(response).toBe('default response'); + }); + }); });