diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..2dd42f3245ba7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,22 @@ +--- +name: Bug report +about: The software behaves in an unexpected way +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Command line used or code snippet + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Version:** + - OS + - Programming Language + - CDK Version diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..420c748d7867a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,10 @@ +--- +name: Feature request +about: Suggest an idea +title: '' +labels: feature-request +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/report-a-gap-in-the-aws-construct-library.md b/.github/ISSUE_TEMPLATE/report-a-gap-in-the-aws-construct-library.md new file mode 100644 index 0000000000000..71e3cfeaaf39f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/report-a-gap-in-the-aws-construct-library.md @@ -0,0 +1,10 @@ +--- +name: Report a gap in the AWS Construct Library +about: Missing feature in the AWS Construct Library +title: '' +labels: gap +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/request-sample-or-ask-a-question.md b/.github/ISSUE_TEMPLATE/request-sample-or-ask-a-question.md new file mode 100644 index 0000000000000..a0af80bd9e1be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/request-sample-or-ask-a-question.md @@ -0,0 +1,15 @@ +--- +name: Request sample or ask a question +about: Not sure how +title: '' +labels: '' +assignees: '' + +--- + +Check out the [CDK User Guide][1] or ask a question on [Stack Overflow][2] + +--thanks, The CDK Team + +[1]: https://docs.aws.amazon.com/CDK/latest/userguide +[2]: https://stackoverflow.com/questions/ask?tags=aws-cdk diff --git a/CHANGELOG.md b/CHANGELOG.md index 46647d23a3663..1a97d0b09546d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,100 @@ 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. +## [0.28.0](https://github.com/awslabs/aws-cdk/compare/v0.27.0...v0.28.0) (2019-04-04) + + +### Bug Fixes + +* **aws-ecs:** use executionRole for event rule target ([#2165](https://github.com/awslabs/aws-cdk/issues/2165)) ([aa6f7bc](https://github.com/awslabs/aws-cdk/commit/aa6f7bc)), closes [#2015](https://github.com/awslabs/aws-cdk/issues/2015) +* **core:** remove cdk.Secret ([#2068](https://github.com/awslabs/aws-cdk/issues/2068)) ([b53d04d](https://github.com/awslabs/aws-cdk/commit/b53d04d)), closes [#2064](https://github.com/awslabs/aws-cdk/issues/2064) + + +* feat(aws-iam): refactor grants, add OrganizationPrincipal (#1623) ([1bb8ca9](https://github.com/awslabs/aws-cdk/commit/1bb8ca9)), closes [#1623](https://github.com/awslabs/aws-cdk/issues/1623) [#236](https://github.com/awslabs/aws-cdk/issues/236) + + +### Code Refactoring + +* **cdk:** introduce SecretValue to represent secrets ([#2161](https://github.com/awslabs/aws-cdk/issues/2161)) ([a3d9f2e](https://github.com/awslabs/aws-cdk/commit/a3d9f2e)) + +### Features + +* **codepipeline:** move all of the Pipeline Actions to their dedicated package. ([#2098](https://github.com/awslabs/aws-cdk/issues/2098)) ([b314ecf](https://github.com/awslabs/aws-cdk/commit/b314ecf)) +* **codepipeline:** re-factor the CodePipeline Action `bind` method to take a Role separately from the Pipeline. ([#2085](https://github.com/awslabs/aws-cdk/issues/2085)) ([ffe0046](https://github.com/awslabs/aws-cdk/commit/ffe0046)) +* **ec2:** support reserving IP space in VPCs ([#2090](https://github.com/awslabs/aws-cdk/issues/2090)) ([4819ff4](https://github.com/awslabs/aws-cdk/commit/4819ff4)) +* Add python support to cdk init ([#2130](https://github.com/awslabs/aws-cdk/issues/2130)) ([997dbcc](https://github.com/awslabs/aws-cdk/commit/997dbcc)) +* **ecs:** support AWS Cloud Map (service discovery) ([#2065](https://github.com/awslabs/aws-cdk/issues/2065)) ([4864cc8](https://github.com/awslabs/aws-cdk/commit/4864cc8)), closes [#1554](https://github.com/awslabs/aws-cdk/issues/1554) +* **lambda:** add a `newVersion` method. ([#2099](https://github.com/awslabs/aws-cdk/issues/2099)) ([6fc179a](https://github.com/awslabs/aws-cdk/commit/6fc179a)) +* update CloudFormation resource spec to v2.29.0 ([#2170](https://github.com/awslabs/aws-cdk/issues/2170)) ([ebc490d](https://github.com/awslabs/aws-cdk/commit/ebc490d)) + + +### BREAKING CHANGES + +* The `secretsmanager.SecretString` class has been removed in favor of `cdk.SecretValue.secretsManager(id[, options])` +* The following prop types have been changed from `string` to `cdk.SecretValue`: `codepipeline-actions.AlexaSkillDeployAction.clientSecret`, `codepipeline-actions.AlexaSkillDeployAction.refreshToken`, `codepipeline-actions.GitHubSourceAction.oauthToken`, `iam.User.password` +* `secretsmanager.Secret.stringValue` and `jsonFieldValue` have been removed. Use `secretsmanage.Secret.secretValue` and `secretJsonValue` instead. +* `secretsmanager.Secret.secretString` have been removed. Use `cdk.SecretValue.secretsManager()` or `secretsmanager.Secret.import(..).secretValue`. +* The class `cdk.Secret` has been removed. Use `cdk.SecretValue` instead. +* The class `cdk.DynamicReference` is no longer a construct, and it's constructor signature was changed and was renamed `cdk.CfnDynamicReference`. +* `grant(function.role)` and `grant(project.role)` are now `grant(function)` and `grant(role)`. +* **core:** Replace use of `cdk.Secret` with `secretsmanager.SecretString` (preferred) or `ssm.ParameterStoreSecureString`. +* **codepipeline:** this changes the package of all CodePipeline Actions to be aws-codepipeline-actions. +* **codepipeline:** this moves all classes from the aws-codepipeline-api package to the aws-codepipeline package. +* **codepipeline:** this changes the CodePipeline Action naming scheme from .PipelineAction (s3.PipelineSourceAction) to codepipeline_actions.Action (codepipeline_actions.S3SourceAction). + + + +## [0.27.0](https://github.com/awslabs/aws-cdk/compare/v0.26.0...v0.27.0) (2019-03-28) + +### Highlights + +* Python support (experimental) +* You can now run the CLI through `npx cdk` +* Make sure to go through the BREAKING CHANGES section below + +### Bug Fixes + +* **autoscaling:** verify public subnets for associatePublicIpAddress ([#2077](https://github.com/awslabs/aws-cdk/issues/2077)) ([1e3d41e](https://github.com/awslabs/aws-cdk/commit/1e3d41e)) +* **ec2:** descriptive error message when selecting 0 subnets ([#2025](https://github.com/awslabs/aws-cdk/issues/2025)) ([0de2206](https://github.com/awslabs/aws-cdk/commit/0de2206)), closes [#2011](https://github.com/awslabs/aws-cdk/issues/2011) +* **lambda:** use Alias ARN directly ([#2091](https://github.com/awslabs/aws-cdk/issues/2091)) ([bc40494](https://github.com/awslabs/aws-cdk/commit/bc40494)) +* **rds:** remove Instance class ([#2081](https://github.com/awslabs/aws-cdk/issues/2081)) ([6699fed](https://github.com/awslabs/aws-cdk/commit/6699fed)) +* **secretsmanager:** allow templated string creation ([#2010](https://github.com/awslabs/aws-cdk/issues/2010)) ([4e105a3](https://github.com/awslabs/aws-cdk/commit/4e105a3)) +* **secretsmanager/ssm:** verify presence of parameter name ([#2066](https://github.com/awslabs/aws-cdk/issues/2066)) ([b93350f](https://github.com/awslabs/aws-cdk/commit/b93350f)) +* **serverless:** rename aws-serverless to aws-sam ([#2074](https://github.com/awslabs/aws-cdk/issues/2074)) ([4a82f13](https://github.com/awslabs/aws-cdk/commit/4a82f13)) +* **stepfunctions:** make Fail.error optional ([#2042](https://github.com/awslabs/aws-cdk/issues/2042)) ([86e9d03](https://github.com/awslabs/aws-cdk/commit/86e9d03)) + + +### Code Refactoring + +* readonly struct properties and hide internals ([#2106](https://github.com/awslabs/aws-cdk/issues/2106)) ([66dd228](https://github.com/awslabs/aws-cdk/commit/66dd228)), closes [awslabs/cdk-ops#321](https://github.com/awslabs/cdk-ops/issues/321) + + +### Features + +* **toolkit:**: new 'cdk' package to allow executing the cli through `npx cdk` ([#2113](https://github.com/awslabs/aws-cdk/issues/2113)) ([32bca05](https://github.com/awslabs/aws-cdk/commit/32bca05)) +* Python Support ([#2009](https://github.com/awslabs/aws-cdk/issues/2009)) ([e6083fa](https://github.com/awslabs/aws-cdk/commit/e6083fa)) +* **core:** present reason for cyclic references ([#2061](https://github.com/awslabs/aws-cdk/issues/2061)) ([e82e208](https://github.com/awslabs/aws-cdk/commit/e82e208)) +* **lambda:** add support for log retention ([#2067](https://github.com/awslabs/aws-cdk/issues/2067)) ([63132ec](https://github.com/awslabs/aws-cdk/commit/63132ec)), closes [#667](https://github.com/awslabs/aws-cdk/issues/667) [#667](https://github.com/awslabs/aws-cdk/issues/667) +* **rds:** cluster retention, reference KMS key by object ([#2063](https://github.com/awslabs/aws-cdk/issues/2063)) ([99ab46d](https://github.com/awslabs/aws-cdk/commit/99ab46d)) +* **secretsmanager/rds:** support credential rotation ([#2052](https://github.com/awslabs/aws-cdk/issues/2052)) ([bf79c82](https://github.com/awslabs/aws-cdk/commit/bf79c82)) +* **toolkit:** introduce the concept of auto-deployed Stacks. ([#2046](https://github.com/awslabs/aws-cdk/issues/2046)) ([abacc66](https://github.com/awslabs/aws-cdk/commit/abacc66)) + + +### BREAKING CHANGES + +* **lambda:** `cloudWatchLogsRetentionTimeDays` in `@aws-cdk/aws-cloudtrail` +now uses a `logs.RetentionDays` instead of a `LogRetention`. +* **core:** `stack._toCloudFormation` method is now unavailable and is replaced by `@aws-cdk/assert.SynthUtils.toCloudFormation(stack)`. +* **rds:** replaced `kmsKeyArn: string` by `kmsKey: kms.IEncryptionKey` in `DatabaseClusterProps` +* **autoscaling:** `VpcNetwork.isPublicSubnet()` has been renamed to +`VpcNetwork.isPublicSubnetIds()`. +* **serverless:** renamed `aws-serverless` to `aws-sam` +* **ec2:** `vpcPlacement` has been renamed to `vpcSubnets` +on all objects, `subnetsToUse` has been renamed to `subnetType`. +`natGatewayPlacement` has been renamed to `natGatewaySubnets`. +* All properties of all structs (interfaces that do not begin with an "I") are now readonly since it is passed by-value and not by-ref (Python is the first language to require that). This may impact code in all languages that assumed it is possible to mutate these structs. Let us know if this blocks you in any way. + + ## [0.26.0](https://github.com/awslabs/aws-cdk/compare/v0.25.3...v0.26.0) (2019-03-20) ### Bug Fixes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index adc2e67ac93ea..e570c7d5041d0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,39 +7,65 @@ and let us know if it's not up-to-date (even better, submit a PR with your corr ## Pull Requests -1. If there isn't one already, open an issue describing what you intend to contribute. It's useful to communicate in - advance, because sometimes, someone is already working in this space, so maybe it's worth collaborating with them - instead of duplicating the efforts. -1. Work your magic. Here are some guidelines: - * Every change requires a unit test - * If you change APIs, make sure to update the module's README file - * Try to maintain a single feature/bugfix per pull request. It's okay to introduce a little bit of housekeeping - changes along the way, but try to avoid conflating multiple features. Eventually all these are going to go into a - single commit, so you can use that to frame your scope. -1. Create a commit with the proposed change changes: - * Commit title and message (and PR title and description) must adhere to [conventionalcommits]. - * The title must begin with `feat(module): title`, `fix(module): title`, `refactor(module): title` or - `chore(module): title`. - * Title should be lowercase. - * No period at the end of the title. - * Commit message should describe _motivation_. Think about your code reviewers and what information they need in - order to understand what you did. If it's a big commit (hopefully not), try to provide some good entry points so - it will be easier to follow. - * Commit message should indicate which issues are fixed: `fixes #` or `closes #`. - * Shout out to collaborators. - * If not obvious (i.e. from unit tests), describe how you verified that your change works. - * If this commit includes a breaking change, the commit message must end with a single paragraph - `BREAKING CHANGE: description of what broke and how to achieve this behavior now`. -2. Push to a fork or to a branch (naming convention: `/`) -3. Submit a Pull Requests on GitHub and assign the PR for a review to the "awslabs/aws-cdk" team. -5. Discuss review comments and iterate until you get at least one “Approve”. When iterating, push new commits to the - same branch. Usually all these are going to be squashed when you merge to master. The commit messages should be hints - for you when you finalize your merge commit message. -7. Make sure to update the PR title/description if things change. The PR title/description are going to be used as the - commit title/message and will appear in the CHANGELOG, so maintain them all the way throughout the process. -6. Make sure your PR builds successfully (we have CodeBuild setup to automatically build all PRs) -7. Once approved and tested, a maintainer will squash-merge to master and will use your PR title/description as the - commit message. +### Step 1: Open Issue + +If there isn't one already, open an issue describing what you intend to contribute. It's useful to communicate in +advance, because sometimes, someone is already working in this space, so maybe it's worth collaborating with them +instead of duplicating the efforts. + +### Step 2: Work your Magic + +Work your magic. Here are some guidelines: + +* Every change requires a unit test +* If you change APIs, make sure to update the module's README file +* Try to maintain a single feature/bugfix per pull request. It's okay to introduce a little bit of housekeeping + changes along the way, but try to avoid conflating multiple features. Eventually all these are going to go into a + single commit, so you can use that to frame your scope. + +### Step 3: Commit + +Create a commit with the proposed change changes: + +* Commit title and message (and PR title and description) must adhere to [conventionalcommits]. + * The title must begin with `feat(module): title`, `fix(module): title`, `refactor(module): title` or + `chore(module): title`. + * Title should be lowercase. + * No period at the end of the title. + +* Commit message should describe _motivation_. Think about your code reviewers and what information they need in + order to understand what you did. If it's a big commit (hopefully not), try to provide some good entry points so + it will be easier to follow. + +* Commit message should indicate which issues are fixed: `fixes #` or `closes #`. + +* Shout out to collaborators. + +* If not obvious (i.e. from unit tests), describe how you verified that your change works. + +* If this commit includes breaking changes, they must be listed at the end in the following format (notice how multiple breaking changes should be formatted): + +``` +BREAKING CHANGE: Description of what broke and how to achieve this behavior now +* Another breaking change +* Yet another breaking change +``` + +### Pull Request + +* Push to a GitHub fork or to a branch (naming convention: `/`) +* Submit a Pull Requests on GitHub and assign the PR for a review to the "awslabs/aws-cdk" team. +* Discuss review comments and iterate until you get at least one “Approve”. When iterating, push new commits to the + same branch. Usually all these are going to be squashed when you merge to master. The commit messages should be hints + for you when you finalize your merge commit message. +* Make sure to update the PR title/description if things change. The PR title/description are going to be used as the + commit title/message and will appear in the CHANGELOG, so maintain them all the way throughout the process. + +### Merge + +* Make sure your PR builds successfully (we have CodeBuild setup to automatically build all PRs) +* Once approved and tested, a maintainer will squash-merge to master and will use your PR title/description as the + commit message. ## Design Process @@ -355,18 +381,12 @@ Cycle: @aws-cdk/aws-sns => @aws-cdk/aws-lambda => @aws-cdk/aws-codecommit => @aw ### Updating all Dependencies -We use `npm update` to - -1. Obtain a fresh clone from “master” -2. Run `./install.sh` and `./build.sh` to make sure the current HEAD is not broken (should never be...). -3. Once build succeeded, run: - ```shell - $ npm update # to update the root deps - $ lerna exec npm update # to update deps in all modules - ``` -4. This will probably install some new versions and update `package.json` and `package-lock.json` files. -5. Now, run `./build.sh` again to verify all tests pass. -6. Submit a Pull Request. +To update all dependencies (without bumping major versions): + +1. Obtain a fresh clone from "master". +2. Run `./install.sh` +2. Run `./scripts/update-dependencies.sh --mode full` (use `--mode semver` to avoid bumping major versions) +3. Submit a Pull Request. ### Troubleshooting common issues diff --git a/buildspec.yaml b/buildspec.yaml index b23384f617e95..129f6ecab5ce0 100644 --- a/buildspec.yaml +++ b/buildspec.yaml @@ -12,7 +12,7 @@ phases: - /bin/bash ./build.sh post_build: commands: - - "[ -f .BUILD_COMPLETED ] && /bin/bash ./pack.sh" + - "[ -f .BUILD_COMPLETED ] && /bin/bash ./pack.sh -v" artifacts: files: - "**/*" diff --git a/design/aws-ecs-service-discovery-integration.md b/design/aws-ecs-service-discovery-integration.md new file mode 100644 index 0000000000000..63ce8414af939 --- /dev/null +++ b/design/aws-ecs-service-discovery-integration.md @@ -0,0 +1,216 @@ +# AWS ECS - Support for AWS Cloud Map (Service Discovery) Integration + +To address issue [#1554](https://github.com/awslabs/aws-cdk/issues/1554), the +ECS construct library should provide a way to set +[`serviceRegistries`](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_CreateService.html#ECS-CreateService-request-serviceRegistries) +on their ECS service at the L1 level. + + +## General approach +Rather than having the customer instantiate Cloud Map constructs directly and +thus be forced to learn the API of a separate construct library, we should +allow the customer to pass in the domain name and other configuration minimally +needed to instantiate a Cloud Map namespace within an ECS cluster and create a +Cloud Map service for each ECS service. + +The current proposal is to add a method on the ECS Cluster construct, +`addNamespace` that would take a set of properties that include the namespace +type (Public DNS, Private DNS or Http) and namespace name (domain name). + +While it is possible to use more than one namespace for services in an ECS +cluster, realistically, we would expect ECS customers to only have one +namespace per given cluster. A Cloud Map service within that namespace would +then be created for each ECS service using service discovery in the cluster, +and would be discoverable by service name within that namespace, e.g. +frontend.mydomain.com, backend.mydomain.com, etc. + +ECS will automatically register service discovery instances that are accessible +by IP address (IPv4 only) on the createService API call. + +// FIXME public namespace needs to be imported? + +## Code changes + +The following are the new methods/interfaces that would be required for the proposed approach: + +#### Cluster#addNamespace(options: NamespaceOptions) + +This will allow adding a Cloud Map namespace, which will be accessible as a +member on the cluster. In the case of a Private DNS Namespace, a Route53 hosted +zone will be created for the customer. + +```ts +export interface NamespaceOptions { + /** + * The domain name for the namespace, such as foo.com + */ + name: string; + + /** + * The type of CloudMap Namespace to create in your cluster + * + * @default PrivateDns + */ + type?: cloudmap.NamespaceType; + + /** + * The Amazon VPC that you want to associate the namespace with. Required for Private DNS namespaces + * + * @default VPC of the cluster for Private DNS Namespace, otherwise none + */ + vpc?: ec2.IVpcNetwork; +} +``` + +#### service#enableServiceDiscovery(options: ServiceDiscoveryOptions) + +This method would create a Cloud Map service, whose arn would then be passed as the serviceRegistry when the ECS service is created. + +Other fields in the service registry are optionally needed depending on the +network mode of the task definition used by the ECS service. + +- If the task definition uses the bridge or host network mode, a containerName + and containerPort combination are needed. These will be taken from the +defaultContainer on the task definition. + +- If the task definition uses the awsvpc network mode and a type SRV DNS record + is used, you must specify either a containerName and containerPort +combination. These will be taken from the defaultContainer on the task definition. +NOTE: In this case, the API also allows you to simply pass in "port" at the +mutual exclusion of the `containerName` and `containerPort` combination, but +for simplicity we are only including `containerName` and `containerPort` and +not `port`. + +NOTE: warn about creating service with public namespace? + +If the customer wishes to have maximum configurability for their service, we will also add + +```ts +export interface ServiceDiscoveryOptions { + /** + * Name of the cloudmap service to attach to the ECS Service + * + * @default CloudFormation-generated name + */ + name?: string, + + /** + * The DNS type of the record that you want AWS Cloud Map to create. Supported record types + * include A or SRV. + + * @default: A + */ + dnsRecordType?: cloudmap.DnsRecordType.A | cloudmap.DnsRecordType.SRV, + + /** + * The amount of time, in seconds, that you want DNS resolvers to cache the settings for this + * record. + * + * @default 60 + */ + dnsTtlSec?: number; + + /** + * The number of 30-second intervals that you want Cloud Map to wait after receiving an + * UpdateInstanceCustomHealthStatus request before it changes the health status of a service instance. + * NOTE: This is used for a Custom HealthCheckCustomConfig + */ + failureThreshold?: number, +} +``` + +A full example would look like the following: + +``` +const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 2 }); + +// Cloud Map Namespace +const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'MyNamespace', { + name: 'mydomain.com', + vpc, +}); + +// Cloud Map Service + +const cloudMapService = namespace.createService('MyCloudMapService', { + dnsRecordType: servicediscovery.DnsRecordType.A, + dnsTtlSec: 300, + customHealthCheck: { + failureThreshold = 1 + } +}); + +// ECS Cluster +const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + +cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro') +}); + +cluster.addNamespace({ name: "foo.com" }) + +const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + +const container = taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + memoryLimitMiB: 256, +}); + +container.addPortMappings({ + containerPort: 80, + hostPort: 8080, + protocol: ecs.Protocol.Tcp +}); + +const ecsService = new ecs.Ec2Service(stack, "MyECSService", { + cluster, + taskDefinition, +}); + +ecsService.enableServiceDiscovery( + dnsRecordType: servicediscovery.DnsRecordType.A, + dnsTtlSec: 300, + customHealthCheck: { + failureThreshold = 1 + } +) + +``` +#### Service Discovery Considerations +##### See: (https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-discovery.html) + +The following should be considered when using service discovery: + +Service discovery is supported for tasks using the Fargate launch type if they are using platform version v1.1.0 or later. For more information, see AWS Fargate Platform Versions. + +The Create Service workflow in the Amazon ECS console only supports registering services into private DNS namespaces. When a AWS Cloud Map private DNS namespace is created, a Route 53 private hosted zone will be created automatically. + +Amazon ECS does not support registering services into public DNS namespaces. + +The DNS records created for a service discovery service always register with the private IP address for the task, rather than the public IP address, even when public namespaces are used. + +Service discovery requires that tasks specify either the awsvpc, bridge, or host network mode (none is not supported). + +If the task definition your service task specifies uses the awsvpc network mode, you can create any combination of A or SRV records for each service task. If you use SRV records, a port is required. + +If the task definition that your service task specifies uses the bridge or host network mode, an SRV record is the only supported DNS record type. Create an SRV record for each service task. The SRV record must specify a container name and container port combination from the task definition. + +DNS records for a service discovery service can be queried within your VPC. They use the following format: .. For more information, see Step 3: Verify Service Discovery. + +When doing a DNS query on the service name, A records return a set of IP addresses that correspond to your tasks. SRV records return a set of IP addresses and ports per task. + +You can configure service discovery for an ECS service that is behind a load balancer, but service discovery traffic is always routed to the task and not the load balancer. + +Service discovery does not support the use of Classic Load Balancers. + +It is recommended to use container-level health checks managed by Amazon ECS for your service discovery service. + +HealthCheckCustomConfig—Amazon ECS manages health checks on your behalf. Amazon ECS uses information from container and health checks, as well as your task state, to update the health with AWS Cloud Map. This is specified using the --health-check-custom-config parameter when creating your service discovery service. For more information, see HealthCheckCustomConfig in the AWS Cloud Map API Reference. + +If you are using the Amazon ECS console, the workflow creates one service discovery service per ECS service. It maps all of the task IP addresses as A records, or task IP addresses and port as SRV records. + +Service discovery can only be configured when first creating a service. Updating existing services to configure service discovery for the first time or change the current configuration is not supported. + +The AWS Cloud Map resources created when service discovery is used must be cleaned up manually. For more information, see Step 4: Clean Up in the Tutorial: Creating a Service Using Service Discovery topic. + + diff --git a/design/aws-guidelines.md b/design/aws-guidelines.md index dfb1a27ee8458..c634976f4ad0e 100644 --- a/design/aws-guidelines.md +++ b/design/aws-guidelines.md @@ -326,9 +326,9 @@ export interface IFoo extends cdk.IConstruct, ISomething { readonly connections: ec2.Connections; // permission grants (adds statements to the principal's policy) - grant(principal?: iam.IPrincipal, ...actions: string[]): void; - grantFoo(principal?: iam.IPrincipal): void; - grantBar(principal?: iam.IPrincipal): void; + grant(grantee?: iam.IGrantable, ...actions: string[]): void; + grantFoo(grantee?: iam.IGrantable): void; + grantBar(grantee?: iam.IGrantable): void; // resource policy (if applicable) addToResourcePolicy(statement: iam.PolicyStatement): void; @@ -364,7 +364,7 @@ export abstract class FooBase extends cdk.Construct implements IFoo { public abstract export(): FooAttributes; // grants can usually be shared - public grantYyy(principal?: iam.IPrincipal) { + public grantYyy(grantee?: iam.IGrantable) { // ... } diff --git a/lerna.json b/lerna.json index 6fe045c84a269..98782e7389984 100644 --- a/lerna.json +++ b/lerna.json @@ -7,5 +7,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "0.26.0" + "version": "0.28.0" } diff --git a/package.json b/package.json index 1d7a4c13b30aa..ad3efdffe367f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "include": "dependencies/node-version" }, "scripts": { - "pkglint": "tools/pkglint/bin/pkglint -f ." + "pkglint": "tools/pkglint/bin/pkglint -f .", + "build-all": "tsc -b" }, "devDependencies": { "@types/node": "8.10.40", diff --git a/packages/@aws-cdk/alexa-ask/README.md b/packages/@aws-cdk/alexa-ask/README.md index ce3247e40160e..1c6c442e64100 100644 --- a/packages/@aws-cdk/alexa-ask/README.md +++ b/packages/@aws-cdk/alexa-ask/README.md @@ -2,54 +2,4 @@ ```ts const alexaAsk = require('@aws-cdk/alexa-ask'); -``` - -### Alexa as deploy target for CodePipeline - -You can deploy to Alexa using CodePipeline with the following DeployAction. - -```ts -// Read the secrets from ParameterStore -const clientId = new cdk.SecretParameter(this, 'AlexaClientId', { ssmParameter: '/Alexa/ClientId' }); -const clientSecret = new cdk.SecretParameter(this, 'AlexaClientSecret', { ssmParameter: '/Alexa/ClientSecret' }); -const refreshToken = new cdk.SecretParameter(this, 'AlexaRefreshToken', { ssmParameter: '/Alexa/RefreshToken' }); - -// Add deploy action -new alexaAsk.AlexaSkillDeployAction({ - actionName: 'DeploySkill', - runOrder: 1, - inputArtifact: sourceAction.outputArtifact, - clientId: clientId.value, - clientSecret: clientSecret.value, - refreshToken: refreshToken.value, - skillId: 'amzn1.ask.skill.12345678-1234-1234-1234-123456789012', -}); -``` - -If you need manifest overrides you can specify them as `parameterOverridesArtifact` in the action: - -```ts -const cloudformation = require('@aws-cdk/aws-cloudformation'); - -// Deploy some CFN change set and store output -const executeChangeSetAction = new cloudformation.PipelineExecuteChangeSetAction({ - actionName: 'ExecuteChangesTest', - runOrder: 2, - stackName, - changeSetName, - outputFileName: 'overrides.json', - outputArtifactName: 'CloudFormation', -}); - -// Provide CFN output as manifest overrides -new alexaAsk.AlexaSkillDeployAction({ - actionName: 'DeploySkill', - runOrder: 1, - inputArtifact: sourceAction.outputArtifact, - parameterOverridesArtifact: executeChangeSetAction.outputArtifact, - clientId: clientId.value, - clientSecret: clientSecret.value, - refreshToken: refreshToken.value, - skillId: 'amzn1.ask.skill.12345678-1234-1234-1234-123456789012', -}); -``` +``` \ No newline at end of file diff --git a/packages/@aws-cdk/alexa-ask/lib/index.ts b/packages/@aws-cdk/alexa-ask/lib/index.ts index a691e2ef6e3cb..59194e547cc5a 100644 --- a/packages/@aws-cdk/alexa-ask/lib/index.ts +++ b/packages/@aws-cdk/alexa-ask/lib/index.ts @@ -1,3 +1,2 @@ // Alexa::ASK CloudFormation Resources: export * from './ask.generated'; -export * from './pipeline-actions'; diff --git a/packages/@aws-cdk/alexa-ask/package.json b/packages/@aws-cdk/alexa-ask/package.json index 4aa230c8bca1d..71867be19d174 100644 --- a/packages/@aws-cdk/alexa-ask/package.json +++ b/packages/@aws-cdk/alexa-ask/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/alexa-ask", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for Alexa::ASK", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "alexa-ask" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.alexa-ask", + "module": "aws_cdk.alexa_ask" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/alexa-ask" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -55,20 +60,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/app-delivery/README.md b/packages/@aws-cdk/app-delivery/README.md index a7e877b954f25..c19886892a86c 100644 --- a/packages/@aws-cdk/app-delivery/README.md +++ b/packages/@aws-cdk/app-delivery/README.md @@ -29,9 +29,11 @@ The example below defines a *CDK App* that contains 3 stacks: ``` #### `index.ts` + ```typescript import codebuild = require('@aws-cdk/aws-codebuild'); import codepipeline = require('@aws-cdk/aws-codepipeline'); +import codepipeline_actions = require('@aws-cdk/aws-codepipeline-actions'); import cdk = require('@aws-cdk/cdk'); import cicd = require('@aws-cdk/cicd'); @@ -48,7 +50,7 @@ const pipeline = new codepipeline.Pipeline(pipelineStack, 'CodePipeline', { }); // Configure the CodePipeline source - where your CDK App's source code is hosted -const source = new codepipeline.GitHubSourceAction({ +const source = new codepipeline_actions.GitHubSourceAction({ actionName: 'GitHub', /* ... */ }); @@ -67,8 +69,9 @@ const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild', { * }, */ }); -const buildAction = project.toCodePipelineBuildAction({ +const buildAction = new codepipeline_actions.CodeBuildBuildAction({ actionName: 'CodeBuild', + project, inputArtifact: source.outputArtifact, }); pipeline.addStage({ diff --git a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts index 9e4632ae4c9c1..e215739318cd4 100644 --- a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts @@ -1,5 +1,6 @@ import cfn = require('@aws-cdk/aws-cloudformation'); -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import cpactions = require('@aws-cdk/aws-codepipeline-actions'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); import cxapi = require('@aws-cdk/cx-api'); @@ -8,39 +9,39 @@ export interface PipelineDeployStackActionProps { /** * The CDK stack to be deployed. */ - stack: cdk.Stack; + readonly stack: cdk.Stack; /** * The CodePipeline stage in which to perform the deployment. */ - stage: codepipeline.IStage; + readonly stage: codepipeline.IStage; /** * The CodePipeline artifact that holds the synthesized app, which is the * contents of the ```` when running ``cdk synth -o ``. */ - inputArtifact: codepipeline.Artifact; + readonly inputArtifact: codepipeline.Artifact; /** * The name to use when creating a ChangeSet for the stack. * * @default CDK-CodePipeline-ChangeSet */ - changeSetName?: string; + readonly changeSetName?: string; /** * The runOrder for the CodePipeline action creating the ChangeSet. * * @default 1 */ - createChangeSetRunOrder?: number; + readonly createChangeSetRunOrder?: number; /** * The runOrder for the CodePipeline action executing the ChangeSet. * * @default ``createChangeSetRunOrder + 1`` */ - executeChangeSetRunOrder?: number; + readonly executeChangeSetRunOrder?: number; /** * IAM role to assume when deploying changes. @@ -51,7 +52,7 @@ export interface PipelineDeployStackActionProps { * * @default A fresh role with admin or no permissions (depending on the value of `adminPermissions`). */ - role?: iam.IRole; + readonly role?: iam.IRole; /** * Acknowledge certain changes made as part of deployment @@ -64,7 +65,7 @@ export interface PipelineDeployStackActionProps { * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities * @default AnonymousIAM, unless `adminPermissions` is true */ - capabilities?: cfn.CloudFormationCapabilities; + readonly capabilities?: cfn.CloudFormationCapabilities; /** * Whether to grant admin permissions to CloudFormation while deploying this template. @@ -81,7 +82,7 @@ export interface PipelineDeployStackActionProps { * use `addToRolePolicy` and `capabilities` to control what the CloudFormation * deployment is allowed to do. */ - adminPermissions: boolean; + readonly adminPermissions: boolean; } /** @@ -120,7 +121,7 @@ export class PipelineDeployStackAction extends cdk.Construct { const changeSetName = props.changeSetName || 'CDK-CodePipeline-ChangeSet'; const capabilities = cfnCapabilities(props.adminPermissions, props.capabilities); - const changeSetAction = new cfn.PipelineCreateReplaceChangeSetAction({ + const changeSetAction = new cpactions.CloudFormationCreateReplaceChangeSetAction({ actionName: 'ChangeSet', changeSetName, runOrder: createChangeSetRunOrder, @@ -131,7 +132,7 @@ export class PipelineDeployStackAction extends cdk.Construct { capabilities, }); props.stage.addAction(changeSetAction); - props.stage.addAction(new cfn.PipelineExecuteChangeSetAction({ + props.stage.addAction(new cpactions.CloudFormationExecuteChangeSetAction({ actionName: 'Execute', changeSetName, runOrder: executeChangeSetRunOrder, diff --git a/packages/@aws-cdk/app-delivery/package-lock.json b/packages/@aws-cdk/app-delivery/package-lock.json index 09a0bb9abe6e0..b42160748caf2 100644 --- a/packages/@aws-cdk/app-delivery/package-lock.json +++ b/packages/@aws-cdk/app-delivery/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/app-delivery", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/app-delivery/package.json b/packages/@aws-cdk/app-delivery/package.json index 550e97324f1a4..df47462c5b26a 100644 --- a/packages/@aws-cdk/app-delivery/package.json +++ b/packages/@aws-cdk/app-delivery/package.json @@ -1,7 +1,7 @@ { "name": "@aws-cdk/app-delivery", "description": "Continuous Integration / Continuous Delivery for CDK Applications", - "version": "0.26.0", + "version": "0.28.0", "main": "lib/index.js", "types": "lib/index.d.ts", "jsii": { @@ -19,6 +19,10 @@ "packageId": "Amazon.CDK.AppDelivery", "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" + }, + "python": { + "distName": "aws-cdk.app-delivery", + "module": "aws_cdk.app_delivery" } }, "outdir": "dist" @@ -33,25 +37,26 @@ "awslint": "cdk-awslint" }, "dependencies": { - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-codebuild": "^0.26.0", - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-codebuild": "^0.28.0", + "@aws-cdk/aws-codepipeline": "^0.28.0", + "@aws-cdk/aws-codepipeline-actions": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0" }, "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-codepipeline": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", "fast-check": "^1.7.0", - "pkglint": "^0.26.0" + "pkglint": "^0.28.0" }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/app-delivery" }, "homepage": "https://github.com/awslabs/aws-cdk", "license": "Apache-2.0", @@ -65,12 +70,13 @@ "cdk" ], "peerDependencies": { - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-codepipeline": "^0.28.0", + "@aws-cdk/aws-codepipeline-actions": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/app-delivery/test/integ.cicd.ts b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts index 261cd071307b0..874bf12864126 100644 --- a/packages/@aws-cdk/app-delivery/test/integ.cicd.ts +++ b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts @@ -1,5 +1,6 @@ import cfn = require('@aws-cdk/aws-cloudformation'); -import code = require('@aws-cdk/aws-codepipeline'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import cpactions = require('@aws-cdk/aws-codepipeline-actions'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import cicd = require('../lib'); @@ -7,16 +8,16 @@ import cicd = require('../lib'); const app = new cdk.App(); const stack = new cdk.Stack(app, 'CICD'); -const pipeline = new code.Pipeline(stack, 'CodePipeline', { +const pipeline = new codepipeline.Pipeline(stack, 'CodePipeline', { artifactBucket: new s3.Bucket(stack, 'ArtifactBucket', { removalPolicy: cdk.RemovalPolicy.Destroy }) }); -const source = new code.GitHubSourceAction({ +const source = new cpactions.GitHubSourceAction({ actionName: 'GitHub', owner: 'awslabs', repo: 'aws-cdk', - oauthToken: new cdk.Secret('DummyToken'), + oauthToken: cdk.SecretValue.plainText('DummyToken'), pollForSourceChanges: true, outputArtifactName: 'Artifact_CICDGitHubF8BA7ADD', }); diff --git a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts index b72c64234feb2..1ecffba85f323 100644 --- a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts @@ -1,7 +1,7 @@ import cfn = require('@aws-cdk/aws-cloudformation'); import codebuild = require('@aws-cdk/aws-codebuild'); -import code = require('@aws-cdk/aws-codepipeline'); -import api = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import cpactions = require('@aws-cdk/aws-codepipeline-actions'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); @@ -13,8 +13,8 @@ import { countResources, expect, haveResource, isSuperObject } from '@aws-cdk/as import { PipelineDeployStackAction } from '../lib/pipeline-deploy-stack-action'; interface SelfUpdatingPipeline { - synthesizedApp: api.Artifact; - pipeline: code.Pipeline; + synthesizedApp: codepipeline.Artifact; + pipeline: codepipeline.Pipeline; } const accountId = fc.array(fc.integer(0, 9), 12, 12).map(arr => arr.join()); @@ -28,7 +28,7 @@ export = nodeunit.testCase({ test.throws(() => { const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test', { env: { account: pipelineAccount } }); - const pipeline = new code.Pipeline(stack, 'Pipeline'); + const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); const fakeAction = new FakeAction('Fake'); pipeline.addStage({ name: 'FakeStage', @@ -57,7 +57,7 @@ export = nodeunit.testCase({ test.throws(() => { const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); - const pipeline = new code.Pipeline(stack, 'Pipeline'); + const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); const fakeAction = new FakeAction('Fake'); pipeline.addStage({ name: 'FakeStage', @@ -272,7 +272,7 @@ export = nodeunit.testCase({ (assetCount) => { const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); - const pipeline = new code.Pipeline(stack, 'Pipeline'); + const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); const fakeAction = new FakeAction('Fake'); pipeline.addStage({ name: 'FakeStage', @@ -299,21 +299,21 @@ export = nodeunit.testCase({ } }); -class FakeAction extends api.Action { - public readonly outputArtifact: api.Artifact; +class FakeAction extends codepipeline.Action { + public readonly outputArtifact: codepipeline.Artifact; constructor(actionName: string) { super({ actionName, - artifactBounds: api.defaultBounds(), - category: api.ActionCategory.Test, + artifactBounds: codepipeline.defaultBounds(), + category: codepipeline.ActionCategory.Test, provider: 'Test', }); - this.outputArtifact = new api.Artifact('OutputArtifact'); + this.outputArtifact = new codepipeline.Artifact('OutputArtifact'); } - protected bind(_stage: api.IStage, _scope: cdk.Construct): void { + protected bind(_info: codepipeline.ActionBind): void { // do nothing } } @@ -323,13 +323,13 @@ function getTestStack(): cdk.Stack { } function createSelfUpdatingStack(pipelineStack: cdk.Stack): SelfUpdatingPipeline { - const pipeline = new code.Pipeline(pipelineStack, 'CodePipeline', { + const pipeline = new codepipeline.Pipeline(pipelineStack, 'CodePipeline', { restartExecutionOnUpdate: true, }); // simple source const bucket = s3.Bucket.import( pipeline, 'PatternBucket', { bucketArn: 'arn:aws:s3:::totally-fake-bucket' }); - const sourceAction = new s3.PipelineSourceAction({ + const sourceAction = new cpactions.S3SourceAction({ actionName: 'S3Source', bucket, bucketKey: 'the-great-key', @@ -340,8 +340,9 @@ function createSelfUpdatingStack(pipelineStack: cdk.Stack): SelfUpdatingPipeline }); const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild'); - const buildAction = project.toCodePipelineBuildAction({ + const buildAction = new cpactions.CodeBuildBuildAction({ actionName: 'CodeBuild', + project, inputArtifact: sourceAction.outputArtifact, }); pipeline.addStage({ diff --git a/packages/@aws-cdk/applet-js/package-lock.json b/packages/@aws-cdk/applet-js/package-lock.json index 1e25d4a689835..d8a818655f886 100644 --- a/packages/@aws-cdk/applet-js/package-lock.json +++ b/packages/@aws-cdk/applet-js/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/applet-js", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/applet-js/package.json b/packages/@aws-cdk/applet-js/package.json index eb6d0e6e78eb8..b7b9848cc394b 100644 --- a/packages/@aws-cdk/applet-js/package.json +++ b/packages/@aws-cdk/applet-js/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/applet-js", - "version": "0.26.0", + "version": "0.28.0", "description": "Javascript CDK applet host program", "main": "bin/cdk-applet-js.js", "types": "bin/cdk-applet-js.d.ts", @@ -24,18 +24,19 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/yaml": "^1.0.0", - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0", + "@aws-cdk/cdk": "^0.28.0", "fs-extra": "^7.0.0", "source-map-support": "^0.5.6", "yaml": "^1.1.0" }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", - "type": "git" + "type": "git", + "directory": "packages/@aws-cdk/applet-js" }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/assert/lib/expect.ts b/packages/@aws-cdk/assert/lib/expect.ts index f1c444a425caa..9fca81ff8e2f3 100644 --- a/packages/@aws-cdk/assert/lib/expect.ts +++ b/packages/@aws-cdk/assert/lib/expect.ts @@ -1,26 +1,22 @@ import cdk = require('@aws-cdk/cdk'); +import { ConstructNode, ConstructOrder } from '@aws-cdk/cdk'; import api = require('@aws-cdk/cx-api'); import { StackInspector } from './inspector'; +import { SynthUtils } from './synth-utils'; export function expect(stack: api.SynthesizedStack | cdk.Stack, skipValidation = false): StackInspector { // Can't use 'instanceof' here, that breaks if we have multiple copies // of this library. let sstack: api.SynthesizedStack; - if (isStackClassInstance(stack)) { - if (!skipValidation) { - // Do a prepare-and-validate run over the given stack - stack.node.prepareTree(); - - const errors = stack.node.validateTree(); - if (errors.length > 0) { - throw new Error(`Stack validation failed:\n${errors.map(e => `${e.message} at: ${e.source.node.scope}`).join('\n')}`); - } - } + if (cdk.Stack.isStack(stack)) { + const session = SynthUtils.synthesize(stack, { + skipValidation + }); sstack = { name: stack.name, - template: stack._toCloudFormation(), + template: SynthUtils.templateForStackName(session, stack.name), metadata: collectStackMetadata(stack.node), environment: { name: 'test', @@ -35,13 +31,9 @@ export function expect(stack: api.SynthesizedStack | cdk.Stack, skipValidation = return new StackInspector(sstack); } -function isStackClassInstance(x: api.SynthesizedStack | cdk.Stack): x is cdk.Stack { - return '_toCloudFormation' in x; -} - -function collectStackMetadata(root: cdk.ConstructNode): api.StackMetadata { +function collectStackMetadata(root: ConstructNode): api.StackMetadata { const result: api.StackMetadata = {}; - for (const construct of root.findAll(cdk.ConstructOrder.PreOrder)) { + for (const construct of root.findAll(ConstructOrder.PreOrder)) { const path = `/${root.id}/${construct.node.path}`; for (const entry of construct.node.metadata) { result[path] = result[path] || []; diff --git a/packages/@aws-cdk/assert/lib/index.ts b/packages/@aws-cdk/assert/lib/index.ts index e811cd2e0bb0d..b79d93592affc 100644 --- a/packages/@aws-cdk/assert/lib/index.ts +++ b/packages/@aws-cdk/assert/lib/index.ts @@ -1,6 +1,7 @@ export * from './assertion'; export * from './expect'; export * from './inspector'; +export * from './synth-utils'; export * from './assertions/exist'; export * from './assertions/have-resource'; diff --git a/packages/@aws-cdk/assert/lib/synth-utils.ts b/packages/@aws-cdk/assert/lib/synth-utils.ts new file mode 100644 index 0000000000000..2d3e1d237dfff --- /dev/null +++ b/packages/@aws-cdk/assert/lib/synth-utils.ts @@ -0,0 +1,17 @@ +import { ISynthesisSession, Stack, SynthesisOptions, Synthesizer } from '@aws-cdk/cdk'; + +export class SynthUtils { + public static toCloudFormation(stack: Stack, options: SynthesisOptions = { }): any { + const session = this.synthesize(stack, options); + return this.templateForStackName(session, stack.name); + } + + public static templateForStackName(session: ISynthesisSession, stackName: string): any { + return session.store.readJson(session.getArtifact(stackName).properties!.templateFile); + } + + public static synthesize(stack: Stack, options: SynthesisOptions): ISynthesisSession { + const synth = new Synthesizer(); + return synth.synthesize(stack, options); + } +} diff --git a/packages/@aws-cdk/assert/package-lock.json b/packages/@aws-cdk/assert/package-lock.json index 9de1c314a3aad..1dde198c79e55 100644 --- a/packages/@aws-cdk/assert/package-lock.json +++ b/packages/@aws-cdk/assert/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assert", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index 146798a0d28aa..b33bdb9c5f9cb 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assert", - "version": "0.26.0", + "version": "0.28.0", "description": "An assertion library for use with CDK Apps", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -24,18 +24,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cloudformation-diff": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cloudformation-diff": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0", "source-map-support": "^0.5.6" }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", - "type": "git" + "type": "git", + "directory": "packages/@aws-cdk/assert" }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/assets-docker/lib/image-asset.ts b/packages/@aws-cdk/assets-docker/lib/image-asset.ts index 2bc8cc1850876..7d939dfca904d 100644 --- a/packages/@aws-cdk/assets-docker/lib/image-asset.ts +++ b/packages/@aws-cdk/assets-docker/lib/image-asset.ts @@ -1,3 +1,4 @@ +import assets = require('@aws-cdk/assets'); import ecr = require('@aws-cdk/aws-ecr'); import cdk = require('@aws-cdk/cdk'); import cxapi = require('@aws-cdk/cx-api'); @@ -9,7 +10,7 @@ export interface DockerImageAssetProps { /** * The directory where the Dockerfile is stored */ - directory: string; + readonly directory: string; /** * ECR repository name @@ -20,7 +21,7 @@ export interface DockerImageAssetProps { * * @default automatically derived from the asset's ID. */ - repositoryName?: string; + readonly repositoryName?: string; } /** @@ -49,14 +50,20 @@ export class DockerImageAsset extends cdk.Construct { super(scope, id); // resolve full path - this.directory = path.resolve(props.directory); - if (!fs.existsSync(this.directory)) { - throw new Error(`Cannot find image directory at ${this.directory}`); + const dir = path.resolve(props.directory); + if (!fs.existsSync(dir)) { + throw new Error(`Cannot find image directory at ${dir}`); } - if (!fs.existsSync(path.join(this.directory, 'Dockerfile'))) { - throw new Error(`No 'Dockerfile' found in ${this.directory}`); + if (!fs.existsSync(path.join(dir, 'Dockerfile'))) { + throw new Error(`No 'Dockerfile' found in ${dir}`); } + const staging = new assets.Staging(this, 'Staging', { + sourcePath: dir + }); + + this.directory = staging.stagedPath; + const imageNameParameter = new cdk.CfnParameter(this, 'ImageName', { type: 'String', description: `ECR repository name and tag asset "${this.node.path}"`, diff --git a/packages/@aws-cdk/assets-docker/package-lock.json b/packages/@aws-cdk/assets-docker/package-lock.json index 90f3cd63db2fd..137155b2c29d2 100644 --- a/packages/@aws-cdk/assets-docker/package-lock.json +++ b/packages/@aws-cdk/assets-docker/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assets-docker", - "version": "0.25.3", + "version": "0.28.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/assets-docker/package.json b/packages/@aws-cdk/assets-docker/package.json index 703bd3f409f13..3026076d33fa1 100644 --- a/packages/@aws-cdk/assets-docker/package.json +++ b/packages/@aws-cdk/assets-docker/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assets-docker", - "version": "0.26.0", + "version": "0.28.0", "description": "Docker image assets", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.assets-docker", + "module": "aws_cdk.assets_docker" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/assets-docker" }, "scripts": { "build": "cdk-build", @@ -51,31 +56,33 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", "@types/proxyquire": "^1.3.28", - "aws-cdk": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "pkglint": "^0.26.0", + "aws-cdk": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "pkglint": "^0.28.0", "proxyquire": "^2.1.0" }, "dependencies": { - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-ecr": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-ecr": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/assets": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-ecr": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-ecr": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/assets": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/assets-docker/test/test.image-asset.ts b/packages/@aws-cdk/assets-docker/test/test.image-asset.ts index 42aacd7a565fc..43cc5c5705989 100644 --- a/packages/@aws-cdk/assets-docker/test/test.image-asset.ts +++ b/packages/@aws-cdk/assets-docker/test/test.image-asset.ts @@ -1,7 +1,10 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { expect, haveResource, SynthUtils } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); +import cxapi = require('@aws-cdk/cx-api'); +import fs = require('fs'); import { Test } from 'nodeunit'; +import os = require('os'); import path = require('path'); import { DockerImageAsset } from '../lib'; @@ -18,7 +21,7 @@ export = { }); // THEN - const template = stack._toCloudFormation(); + const template = SynthUtils.toCloudFormation(stack); test.deepEqual(template.Parameters.ImageImageName5E684353, { Type: 'String', @@ -61,22 +64,13 @@ export = { ":", { "Ref": "AWS::AccountId" }, ":repository/", - { - "Fn::GetAtt": [ - "ImageAdoptRepositoryE1E84E35", - "RepositoryName" - ] - } + { "Fn::GetAtt": [ "ImageAdoptRepositoryE1E84E35", "RepositoryName" ] } ] ] } }, { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], + "Action": "ecr:GetAuthorizationToken", "Effect": "Allow", "Resource": "*" } @@ -152,5 +146,30 @@ export = { }); }, /No 'Dockerfile' found in/); test.done(); + }, + + 'docker directory is staged if asset staging is enabled'(test: Test) { + const workdir = mkdtempSync(); + process.chdir(workdir); + + const app = new cdk.App({ + context: { [cxapi.ASSET_STAGING_DIR_CONTEXT]: '.stage-me' } + }); + + const stack = new cdk.Stack(app, 'stack'); + + new DockerImageAsset(stack, 'MyAsset', { + directory: path.join(__dirname, 'demo-image') + }); + + app.run(); + + test.ok(fs.existsSync('.stage-me/96e3ffe92a19cbaa6c558942f7a60246/Dockerfile')); + test.ok(fs.existsSync('.stage-me/96e3ffe92a19cbaa6c558942f7a60246/index.py')); + test.done(); } }; + +function mkdtempSync() { + return fs.mkdtempSync(path.join(os.tmpdir(), 'test.assets')); +} diff --git a/packages/@aws-cdk/assets/lib/asset.ts b/packages/@aws-cdk/assets/lib/asset.ts index e7d230aa60be4..e601fd73e2625 100644 --- a/packages/@aws-cdk/assets/lib/asset.ts +++ b/packages/@aws-cdk/assets/lib/asset.ts @@ -4,6 +4,7 @@ import cdk = require('@aws-cdk/cdk'); import cxapi = require('@aws-cdk/cx-api'); import fs = require('fs'); import path = require('path'); +import { Staging } from './staging'; /** * Defines the way an asset is packaged before it is uploaded to S3. @@ -25,18 +26,18 @@ export interface GenericAssetProps { /** * The disk location of the asset. */ - path: string; + readonly path: string; /** * The packaging type for this asset. */ - packaging: AssetPackaging; + readonly packaging: AssetPackaging; /** * A list of principals that should be able to read this asset from S3. * You can use `asset.grantRead(principal)` to grant read permissions later. */ - readers?: iam.IPrincipal[]; + readonly readers?: iam.IGrantable[]; } /** @@ -61,7 +62,10 @@ export class Asset extends cdk.Construct { public readonly s3Url: string; /** - * Resolved full-path location of this asset. + * The path to the asset (stringinfied token). + * + * If asset staging is disabled, this will just be the original path. + * If asset staging is enabled it will be the staged path. */ public readonly assetPath: string; @@ -84,16 +88,20 @@ export class Asset extends cdk.Construct { constructor(scope: cdk.Construct, id: string, props: GenericAssetProps) { super(scope, id); - // resolve full path - this.assetPath = path.resolve(props.path); + // stage the asset source (conditionally). + const staging = new Staging(this, 'Stage', { + sourcePath: path.resolve(props.path) + }); + + this.assetPath = staging.stagedPath; // sets isZipArchive based on the type of packaging and file extension const allowedExtensions: string[] = ['.jar', '.zip']; this.isZipArchive = props.packaging === AssetPackaging.ZipDirectory ? true - : allowedExtensions.some(ext => this.assetPath.toLowerCase().endsWith(ext)); + : allowedExtensions.some(ext => staging.sourcePath.toLowerCase().endsWith(ext)); - validateAssetOnDisk(this.assetPath, props.packaging); + validateAssetOnDisk(staging.sourcePath, props.packaging); // add parameters for s3 bucket and s3 key. those will be set by // the toolkit or by CI/CD when the stack is deployed and will include @@ -171,12 +179,12 @@ export class Asset extends cdk.Construct { /** * Grants read permissions to the principal on the asset's S3 object. */ - public grantRead(principal?: iam.IPrincipal) { + public grantRead(grantee: iam.IGrantable) { // We give permissions on all files with the same prefix. Presumably // different versions of the same file will have the same prefix // and we don't want to accidentally revoke permission on old versions // when deploying a new version. - this.bucket.grantRead(principal, `${this.s3Prefix}*`); + this.bucket.grantRead(grantee, `${this.s3Prefix}*`); } } @@ -184,13 +192,13 @@ export interface FileAssetProps { /** * File path. */ - path: string; + readonly path: string; /** * A list of principals that should be able to read this file asset from S3. * You can use `asset.grantRead(principal)` to grant read permissions later. */ - readers?: iam.IPrincipal[]; + readonly readers?: iam.IGrantable[]; } /** @@ -206,13 +214,13 @@ export interface ZipDirectoryAssetProps { /** * Path of the directory. */ - path: string; + readonly path: string; /** * A list of principals that should be able to read this ZIP file from S3. * You can use `asset.grantRead(principal)` to grant read permissions later. */ - readers?: iam.IPrincipal[]; + readonly readers?: iam.IGrantable[]; } /** diff --git a/packages/@aws-cdk/assets/lib/fs/copy.ts b/packages/@aws-cdk/assets/lib/fs/copy.ts new file mode 100644 index 0000000000000..6ea1f2a6e5f8c --- /dev/null +++ b/packages/@aws-cdk/assets/lib/fs/copy.ts @@ -0,0 +1,89 @@ +import fs = require('fs'); +import minimatch = require('minimatch'); +import path = require('path'); +import { FollowMode } from './follow-mode'; + +export interface CopyOptions { + /** + * @default External only follows symlinks that are external to the source directory + */ + follow?: FollowMode; + + /** + * glob patterns to exclude from the copy. + */ + exclude?: string[]; +} + +export function copyDirectory(srcDir: string, destDir: string, options: CopyOptions = { }, rootDir?: string) { + const follow = options.follow !== undefined ? options.follow : FollowMode.External; + const exclude = options.exclude || []; + + rootDir = rootDir || srcDir; + + if (!fs.statSync(srcDir).isDirectory()) { + throw new Error(`${srcDir} is not a directory`); + } + + const files = fs.readdirSync(srcDir); + for (const file of files) { + const sourceFilePath = path.join(srcDir, file); + + if (shouldExclude(path.relative(rootDir, sourceFilePath))) { + continue; + } + + const destFilePath = path.join(destDir, file); + + let stat: fs.Stats | undefined = follow === FollowMode.Always + ? fs.statSync(sourceFilePath) + : fs.lstatSync(sourceFilePath); + + if (stat && stat.isSymbolicLink()) { + const target = fs.readlinkSync(sourceFilePath); + + // determine if this is an external link (i.e. the target's absolute path + // is outside of the root directory). + const targetPath = path.normalize(path.resolve(srcDir, target)); + const rootPath = path.normalize(rootDir); + const external = !targetPath.startsWith(rootPath); + + if (follow === FollowMode.External && external) { + stat = fs.statSync(sourceFilePath); + } else { + fs.symlinkSync(target, destFilePath); + stat = undefined; + } + } + + if (stat && stat.isDirectory()) { + fs.mkdirSync(destFilePath); + copyDirectory(sourceFilePath, destFilePath, options, rootDir); + stat = undefined; + } + + if (stat && stat.isFile()) { + fs.copyFileSync(sourceFilePath, destFilePath); + stat = undefined; + } + } + + function shouldExclude(filePath: string): boolean { + let excludeOutput = false; + + for (const pattern of exclude) { + const negate = pattern.startsWith('!'); + const match = minimatch(filePath, pattern, { matchBase: true, flipNegate: true }); + + if (!negate && match) { + excludeOutput = true; + } + + if (negate && match) { + excludeOutput = false; + } + } + + return excludeOutput; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/assets/lib/fs/fingerprint.ts b/packages/@aws-cdk/assets/lib/fs/fingerprint.ts new file mode 100644 index 0000000000000..06cdb6a0ed2aa --- /dev/null +++ b/packages/@aws-cdk/assets/lib/fs/fingerprint.ts @@ -0,0 +1,86 @@ +import crypto = require('crypto'); +import fs = require('fs'); +import path = require('path'); +import { FollowMode } from './follow-mode'; + +const BUFFER_SIZE = 8 * 1024; + +export interface FingerprintOptions { + /** + * Extra information to encode into the fingerprint (e.g. build instructions + * and other inputs) + */ + extra?: string; + + /** + * List of exclude patterns (see `CopyOptions`) + * @default include all files + */ + exclude?: string[]; + + /** + * What to do when we encounter symlinks. + * @default External only follows symlinks that are external to the source + * directory + */ + follow?: FollowMode; +} + +/** + * Produces fingerprint based on the contents of a single file or an entire directory tree. + * + * The fingerprint will also include: + * 1. An extra string if defined in `options.extra`. + * 2. The set of exclude patterns, if defined in `options.exclude` + * 3. The symlink follow mode value. + * + * @param fileOrDirectory The directory or file to fingerprint + * @param options Fingerprinting options + */ +export function fingerprint(fileOrDirectory: string, options: FingerprintOptions = { }) { + const follow = options.follow !== undefined ? options.follow : FollowMode.External; + const hash = crypto.createHash('md5'); + addToHash(fileOrDirectory); + + hash.update(`==follow==${follow}==\n\n`); + + if (options.extra) { + hash.update(`==extra==${options.extra}==\n\n`); + } + + for (const ex of options.exclude || []) { + hash.update(`==exclude==${ex}==\n\n`); + } + + return hash.digest('hex'); + + function addToHash(pathToAdd: string) { + hash.update('==\n'); + const relativePath = path.relative(fileOrDirectory, pathToAdd); + hash.update(relativePath + '\n'); + hash.update('~~~~~~~~~~~~~~~~~~\n'); + const stat = fs.statSync(pathToAdd); + + if (stat.isSymbolicLink()) { + const target = fs.readlinkSync(pathToAdd); + hash.update(target); + } else if (stat.isDirectory()) { + for (const file of fs.readdirSync(pathToAdd)) { + addToHash(path.join(pathToAdd, file)); + } + } else { + const file = fs.openSync(pathToAdd, 'r'); + const buffer = Buffer.alloc(BUFFER_SIZE); + + try { + let bytesRead; + do { + bytesRead = fs.readSync(file, buffer, 0, BUFFER_SIZE, null); + hash.update(buffer.slice(0, bytesRead)); + } while (bytesRead === BUFFER_SIZE); + } finally { + fs.closeSync(file); + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/assets/lib/fs/follow-mode.ts b/packages/@aws-cdk/assets/lib/fs/follow-mode.ts new file mode 100644 index 0000000000000..02ecebfaaa0a7 --- /dev/null +++ b/packages/@aws-cdk/assets/lib/fs/follow-mode.ts @@ -0,0 +1,29 @@ +export enum FollowMode { + /** + * Never follow symlinks. + */ + Never = 'never', + + /** + * Materialize all symlinks, whether they are internal or external to the source directory. + */ + Always = 'always', + + /** + * Only follows symlinks that are external to the source directory. + */ + External = 'external', + + // ----------------- TODO:::::::::::::::::::::::::::::::::::::::::::: + /** + * Forbids source from having any symlinks pointing outside of the source + * tree. + * + * This is the safest mode of operation as it ensures that copy operations + * won't materialize files from the user's file system. Internal symlinks are + * not followed. + * + * If the copy operation runs into an external symlink, it will fail. + */ + BlockExternal = 'internal-only', +} \ No newline at end of file diff --git a/packages/@aws-cdk/assets/lib/fs/index.ts b/packages/@aws-cdk/assets/lib/fs/index.ts new file mode 100644 index 0000000000000..31b1f468bbdfc --- /dev/null +++ b/packages/@aws-cdk/assets/lib/fs/index.ts @@ -0,0 +1,3 @@ +export * from './fingerprint'; +export * from './follow-mode'; +export * from './copy'; \ No newline at end of file diff --git a/packages/@aws-cdk/assets/lib/index.ts b/packages/@aws-cdk/assets/lib/index.ts index ea2719dd83bd3..24ddffa892f0e 100644 --- a/packages/@aws-cdk/assets/lib/index.ts +++ b/packages/@aws-cdk/assets/lib/index.ts @@ -1 +1,2 @@ export * from './asset'; +export * from './staging'; diff --git a/packages/@aws-cdk/assets/lib/staging.ts b/packages/@aws-cdk/assets/lib/staging.ts new file mode 100644 index 0000000000000..5315d602be6a0 --- /dev/null +++ b/packages/@aws-cdk/assets/lib/staging.ts @@ -0,0 +1,91 @@ +import { Construct, Token } from '@aws-cdk/cdk'; +import cxapi = require('@aws-cdk/cx-api'); +import fs = require('fs'); +import path = require('path'); +import { copyDirectory, fingerprint } from './fs'; + +export interface StageProps { + readonly sourcePath: string; +} + +/** + * Stages a file or directory from a location on the file system into a staging + * directory. + * + * This is controlled by the context key 'aws:cdk:asset-staging-dir' and enabled + * by the CLI by default in order to ensure that when the CDK app exists, all + * assets are available for deployment. Otherwise, if an app references assets + * in temporary locations, those will not be available when it exists (see + * https://github.com/awslabs/aws-cdk/issues/1716). + * + * The `stagedPath` property is a stringified token that represents the location + * of the file or directory after staging. It will be resolved only during the + * "prepare" stage and may be either the original path or the staged path + * depending on the context setting. + * + * The file/directory are staged based on their content hash (fingerprint). This + * means that only if content was changed, copy will happen. + */ +export class Staging extends Construct { + + /** + * The path to the asset (stringinfied token). + * + * If asset staging is disabled, this will just be the original path. + * If asset staging is enabled it will be the staged path. + */ + public readonly stagedPath: string; + + /** + * The path of the asset as it was referenced by the user. + */ + public readonly sourcePath: string; + + /** + * The asset path after "prepare" is called. + * + * If staging is disabled, this will just be the original path. + * If staging is enabled it will be the staged path. + */ + private _preparedAssetPath?: string; + + constructor(scope: Construct, id: string, props: StageProps) { + super(scope, id); + + this.sourcePath = props.sourcePath; + this.stagedPath = new Token(() => this._preparedAssetPath).toString(); + } + + protected prepare() { + const stagingDir = this.node.getContext(cxapi.ASSET_STAGING_DIR_CONTEXT); + if (!stagingDir) { + this._preparedAssetPath = this.sourcePath; + return; + } + + if (!fs.existsSync(stagingDir)) { + fs.mkdirSync(stagingDir); + } + + const hash = fingerprint(this.sourcePath); + const targetPath = path.join(stagingDir, hash + path.extname(this.sourcePath)); + + this._preparedAssetPath = targetPath; + + // asset already staged + if (fs.existsSync(targetPath)) { + return; + } + + // copy file/directory to staging directory + const stat = fs.statSync(this.sourcePath); + if (stat.isFile()) { + fs.copyFileSync(this.sourcePath, targetPath); + } else if (stat.isDirectory()) { + fs.mkdirSync(targetPath); + copyDirectory(this.sourcePath, targetPath); + } else { + throw new Error(`Unknown file type: ${this.sourcePath}`); + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/assets/package-lock.json b/packages/@aws-cdk/assets/package-lock.json new file mode 100644 index 0000000000000..d3b41e065d7a8 --- /dev/null +++ b/packages/@aws-cdk/assets/package-lock.json @@ -0,0 +1,41 @@ +{ + "name": "@aws-cdk/assets", + "version": "0.28.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + } + } +} diff --git a/packages/@aws-cdk/assets/package.json b/packages/@aws-cdk/assets/package.json index 0361f863fbae4..a2e2d66613d07 100644 --- a/packages/@aws-cdk/assets/package.json +++ b/packages/@aws-cdk/assets/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/assets", - "version": "0.26.0", + "version": "0.28.0", "description": "Integration of CDK apps with local assets", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.assets", + "module": "aws_cdk.assets" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/assets" }, "scripts": { "build": "cdk-build", @@ -37,6 +42,11 @@ "package": "cdk-package", "awslint": "cdk-awslint" }, + "cdk-build": { + "pre": [ + "cd test/fs && tar -xzvf fixtures.tar.gz" + ] + }, "keywords": [ "aws", "cdk", @@ -50,25 +60,30 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "aws-cdk": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "@types/minimatch": "^3.0.3", + "aws-cdk": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0", + "minimatch": "^3.0.4" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" - } -} \ No newline at end of file + }, + "bundledDependencies": [ + "minimatch" + ] +} diff --git a/packages/@aws-cdk/assets/test/fs/.gitignore b/packages/@aws-cdk/assets/test/fs/.gitignore new file mode 100644 index 0000000000000..b13de5858771b --- /dev/null +++ b/packages/@aws-cdk/assets/test/fs/.gitignore @@ -0,0 +1,3 @@ +# codepipeline looses symlinks so we bundled fixtures into a tarball +# and unpack in pre-build script (yak) +fixtures/** diff --git a/packages/@aws-cdk/assets/test/fs/fixtures.tar.gz b/packages/@aws-cdk/assets/test/fs/fixtures.tar.gz new file mode 100644 index 0000000000000..50f5ba0f4a259 Binary files /dev/null and b/packages/@aws-cdk/assets/test/fs/fixtures.tar.gz differ diff --git a/packages/@aws-cdk/assets/test/fs/test.fs-copy.ts b/packages/@aws-cdk/assets/test/fs/test.fs-copy.ts new file mode 100644 index 0000000000000..ac0693d70d361 --- /dev/null +++ b/packages/@aws-cdk/assets/test/fs/test.fs-copy.ts @@ -0,0 +1,146 @@ +import fs = require('fs'); +import { Test } from 'nodeunit'; +import os = require('os'); +import path = require('path'); +import { copyDirectory } from '../../lib/fs/copy'; +import { FollowMode } from '../../lib/fs/follow-mode'; + +export = { + 'Default: copies all files and subdirectories, with default follow mode is "External"'(test: Test) { + // GIVEN + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); + + // WHEN + copyDirectory(path.join(__dirname, 'fixtures', 'test1'), outdir); + + // THEN + test.deepEqual(tree(outdir), [ + 'external-link.txt', + 'file1.txt', + 'local-link.txt => file1.txt', + 'subdir (D)', + ' file2.txt', + 'subdir2 (D)', + ' empty-subdir (D)', + ' .hidden', + ' subdir3 (D)', + ' file3.txt' + ]); + test.done(); + }, + + 'Always: follow all symlinks'(test: Test) { + // GIVEN + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); + + // WHEN + copyDirectory(path.join(__dirname, 'fixtures', 'symlinks'), outdir, { + follow: FollowMode.Always + }); + + // THEN + test.deepEqual(tree(outdir), [ + 'external-dir-link (D)', + ' file2.txt', + 'external-link.txt', + 'indirect-external-link.txt', + 'local-dir-link (D)', + ' file-in-subdir.txt', + 'local-link.txt', + 'normal-dir (D)', + ' file-in-subdir.txt', + 'normal-file.txt' + ]); + test.done(); + }, + + 'Never: do not follow all symlinks'(test: Test) { + // GIVEN + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); + + // WHEN + copyDirectory(path.join(__dirname, 'fixtures', 'symlinks'), outdir, { + follow: FollowMode.Never + }); + + // THEN + test.deepEqual(tree(outdir), [ + 'external-dir-link => ../test1/subdir', + 'external-link.txt => ../test1/subdir2/subdir3/file3.txt', + 'indirect-external-link.txt => external-link.txt', + 'local-dir-link => normal-dir', + 'local-link.txt => normal-file.txt', + 'normal-dir (D)', + ' file-in-subdir.txt', + 'normal-file.txt' + ]); + test.done(); + }, + + 'External: follow only external symlinks'(test: Test) { + // GIVEN + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); + + // WHEN + copyDirectory(path.join(__dirname, 'fixtures', 'symlinks'), outdir, { + follow: FollowMode.External + }); + + // THEN + test.deepEqual(tree(outdir), [ + 'external-dir-link (D)', + ' file2.txt', + 'external-link.txt', + 'indirect-external-link.txt => external-link.txt', + 'local-dir-link => normal-dir', + 'local-link.txt => normal-file.txt', + 'normal-dir (D)', + ' file-in-subdir.txt', + 'normal-file.txt' + ]); + + test.done(); + }, + + 'exclude'(test: Test) { + // GIVEN + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); + + // WHEN + copyDirectory(path.join(__dirname, 'fixtures', 'test1'), outdir, { + exclude: [ + '*', + '!subdir2', + '!subdir2/**/*', + '.*' + ] + }); + + // THEN + test.deepEqual(tree(outdir), [ + 'subdir2 (D)', + ' empty-subdir (D)', + ' subdir3 (D)', + ' file3.txt' + ]); + test.done(); + }, +}; + +function tree(dir: string, depth = ''): string[] { + const lines = []; + for (const file of fs.readdirSync(dir).sort()) { + const filePath = path.join(dir, file); + const stat = fs.lstatSync(filePath); + if (stat.isSymbolicLink()) { + const linkDest = fs.readlinkSync(filePath); + lines.push(depth + file + ' => ' + linkDest); + } else if (stat.isDirectory()) { + lines.push(depth + file + ' (D)'); + lines.push(...tree(filePath, depth + ' ')); + } else { + lines.push(depth + file); + } + } + return lines; +} \ No newline at end of file diff --git a/packages/@aws-cdk/assets/test/fs/test.fs-fingerprint.ts b/packages/@aws-cdk/assets/test/fs/test.fs-fingerprint.ts new file mode 100644 index 0000000000000..87cf001562055 --- /dev/null +++ b/packages/@aws-cdk/assets/test/fs/test.fs-fingerprint.ts @@ -0,0 +1,108 @@ +import fs = require('fs'); +import { Test } from 'nodeunit'; +import os = require('os'); +import path = require('path'); +import { copyDirectory } from '../../lib/fs/copy'; +import { fingerprint } from '../../lib/fs/fingerprint'; + +export = { + 'single file'(test: Test) { + // GIVEN + const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'hash-tests')); + const content = 'Hello, world!'; + const input1 = path.join(workdir, 'input1.txt'); + const input2 = path.join(workdir, 'input2.txt'); + const input3 = path.join(workdir, 'input3.txt'); + fs.writeFileSync(input1, content); + fs.writeFileSync(input2, content); + fs.writeFileSync(input3, content + '.'); // add one character, hash should be different + + // WHEN + const hash1 = fingerprint(input1); + const hash2 = fingerprint(input2); + const hash3 = fingerprint(input3); + + // THEN + test.deepEqual(hash1, hash2); + test.notDeepEqual(hash3, hash1); + test.done(); + }, + + 'empty file'(test: Test) { + // GIVEN + const workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'hash-tests')); + const input1 = path.join(workdir, 'empty'); + const input2 = path.join(workdir, 'empty'); + fs.writeFileSync(input1, ''); + fs.writeFileSync(input2, ''); + + // WHEN + const hash1 = fingerprint(input1); + const hash2 = fingerprint(input2); + + // THEN + test.deepEqual(hash1, hash2); + test.done(); + }, + + 'directory'(test: Test) { + // GIVEN + const srcdir = path.join(__dirname, 'fixtures', 'symlinks'); + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); + copyDirectory(srcdir, outdir); + + // WHEN + const hashSrc = fingerprint(srcdir); + const hashCopy = fingerprint(outdir); + + // THEN + test.deepEqual(hashSrc, hashCopy); + test.done(); + }, + + 'directory, rename files (fingerprint should change)'(test: Test) { + // GIVEN + const srcdir = path.join(__dirname, 'fixtures', 'symlinks'); + const cpydir = fs.mkdtempSync(path.join(os.tmpdir(), 'fingerprint-tests')); + copyDirectory(srcdir, cpydir); + + // be careful not to break a symlink + fs.renameSync(path.join(cpydir, 'normal-dir', 'file-in-subdir.txt'), path.join(cpydir, 'move-me.txt')); + + // WHEN + const hashSrc = fingerprint(srcdir); + const hashCopy = fingerprint(cpydir); + + // THEN + test.notDeepEqual(hashSrc, hashCopy); + test.done(); + }, + + 'external symlink content changes (fingerprint should change)'(test: Test) { + // GIVEN + const dir1 = fs.mkdtempSync(path.join(os.tmpdir(), 'fingerprint-tests')); + const dir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'fingerprint-tests')); + const target = path.join(dir1, 'boom.txt'); + const content = 'boom'; + fs.writeFileSync(target, content); + fs.symlinkSync(target, path.join(dir2, 'link-to-boom.txt')); + + // now dir2 contains a symlink to a file in dir1 + + // WHEN + const original = fingerprint(dir2); + + // now change the contents of the target + fs.writeFileSync(target, 'changning you!'); + const afterChange = fingerprint(dir2); + + // revert the content to original and expect hash to be reverted + fs.writeFileSync(target, content); + const afterRevert = fingerprint(dir2); + + // THEN + test.notDeepEqual(original, afterChange); + test.deepEqual(afterRevert, original); + test.done(); + } +}; diff --git a/packages/@aws-cdk/assets/test/test.asset.ts b/packages/@aws-cdk/assets/test/test.asset.ts index 1c28ec9ef4d04..1d8ad7d56777d 100644 --- a/packages/@aws-cdk/assets/test/test.asset.ts +++ b/packages/@aws-cdk/assets/test/test.asset.ts @@ -1,17 +1,21 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import { expect, haveResource, ResourcePart, SynthUtils } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); +import { App, Stack } from '@aws-cdk/cdk'; import cxapi = require('@aws-cdk/cx-api'); +import fs = require('fs'); import { Test } from 'nodeunit'; +import os = require('os'); import path = require('path'); import { FileAsset, ZipDirectoryAsset } from '../lib/asset'; +const SAMPLE_ASSET_DIR = path.join(__dirname, 'sample-asset-directory'); + export = { 'simple use case'(test: Test) { const stack = new cdk.Stack(); - const dirPath = path.join(__dirname, 'sample-asset-directory'); const asset = new ZipDirectoryAsset(stack, 'MyAsset', { - path: dirPath + path: SAMPLE_ASSET_DIR }); // verify that metadata contains an "aws:cdk:asset" entry with @@ -19,18 +23,17 @@ export = { const entry = asset.node.metadata.find(m => m.type === 'aws:cdk:asset'); test.ok(entry, 'found metadata entry'); - // console.error(JSON.stringify(stack.node.resolve(entry!.data))); + // verify that now the template contains parameters for this asset + const template = SynthUtils.toCloudFormation(stack); test.deepEqual(stack.node.resolve(entry!.data), { - path: dirPath, + path: SAMPLE_ASSET_DIR, id: 'MyAsset', packaging: 'zip', s3BucketParameter: 'MyAssetS3Bucket68C9B344', s3KeyParameter: 'MyAssetS3VersionKey68E1A45D', }); - // verify that now the template contains parameters for this asset - const template = stack._toCloudFormation(); test.equal(template.Parameters.MyAssetS3Bucket68C9B344.Type, 'String'); test.equal(template.Parameters.MyAssetS3VersionKey68E1A45D.Type, 'String'); @@ -65,6 +68,10 @@ export = { const asset = new FileAsset(stack, 'MyAsset', { path: filePath }); const entry = asset.node.metadata.find(m => m.type === 'aws:cdk:asset'); test.ok(entry, 'found metadata entry'); + + // synthesize first so "prepare" is called + const template = SynthUtils.toCloudFormation(stack); + test.deepEqual(stack.node.resolve(entry!.data), { path: filePath, packaging: 'file', @@ -74,7 +81,6 @@ export = { }); // verify that now the template contains parameters for this asset - const template = stack._toCloudFormation(); test.equal(template.Parameters.MyAssetS3Bucket68C9B344.Type, 'String'); test.equal(template.Parameters.MyAssetS3VersionKey68E1A45D.Type, 'String'); @@ -194,9 +200,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const location = path.join(__dirname, 'sample-asset-directory'); const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); - const asset = new ZipDirectoryAsset(stack, 'MyAsset', { path: location }); + const asset = new ZipDirectoryAsset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); // WHEN asset.addResourceMetadata(resource, 'PropName'); @@ -204,11 +209,155 @@ export = { // THEN expect(stack).notTo(haveResource('My::Resource::Type', { Metadata: { - "aws:asset:path": location, + "aws:asset:path": SAMPLE_ASSET_DIR, "aws:asset:property": "PropName" } }, ResourcePart.CompleteDefinition)); test.done(); + }, + + 'staging': { + + 'copy file assets under .assets/fingerprint.ext'(test: Test) { + const tempdir = mkdtempSync(); + process.chdir(tempdir); // change current directory to somewhere in /tmp + + // GIVEN + const app = new App({ + context: { [cxapi.ASSET_STAGING_DIR_CONTEXT]: '.assets' } + }); + const stack = new Stack(app, 'stack'); + + // WHEN + new FileAsset(stack, 'ZipFile', { + path: path.join(SAMPLE_ASSET_DIR, 'sample-zip-asset.zip') + }); + + new FileAsset(stack, 'TextFile', { + path: path.join(SAMPLE_ASSET_DIR, 'sample-asset-file.txt') + }); + + // THEN + app.run(); + test.ok(fs.existsSync(path.join(tempdir, '.assets'))); + test.ok(fs.existsSync(path.join(tempdir, '.assets', 'fdb4701ff6c99e676018ee2c24a3119b.zip'))); + fs.readdirSync(path.join(tempdir, '.assets')); + test.done(); + }, + + 'copy directory under .assets/fingerprint/**'(test: Test) { + const tempdir = mkdtempSync(); + process.chdir(tempdir); // change current directory to somewhere in /tmp + + // GIVEN + const app = new App({ + context: { [cxapi.ASSET_STAGING_DIR_CONTEXT]: '.assets' } + }); + const stack = new Stack(app, 'stack'); + + // WHEN + new ZipDirectoryAsset(stack, 'ZipDirectory', { + path: SAMPLE_ASSET_DIR + }); + + // THEN + app.run(); + test.ok(fs.existsSync(path.join(tempdir, '.assets'))); + test.ok(fs.existsSync(path.join(tempdir, '.assets', 'b550524e103eb4cf257c594fba5b9fe8', 'sample-asset-file.txt'))); + test.ok(fs.existsSync(path.join(tempdir, '.assets', 'b550524e103eb4cf257c594fba5b9fe8', 'sample-jar-asset.jar'))); + fs.readdirSync(path.join(tempdir, '.assets')); + test.done(); + }, + + 'staging path is relative if the dir is below the working directory'(test: Test) { + // GIVEN + const tempdir = mkdtempSync(); + process.chdir(tempdir); // change current directory to somewhere in /tmp + + const staging = '.my-awesome-staging-directory'; + const app = new App({ + context: { + [cxapi.ASSET_STAGING_DIR_CONTEXT]: staging, + [cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true', + } + }); + + const stack = new Stack(app, 'stack'); + + const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); + const asset = new ZipDirectoryAsset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); + + // WHEN + asset.addResourceMetadata(resource, 'PropName'); + + const session = app.run(); + const template = SynthUtils.templateForStackName(session, stack.name); + + test.deepEqual(template.Resources.MyResource.Metadata, { + "aws:asset:path": `.my-awesome-staging-directory/b550524e103eb4cf257c594fba5b9fe8`, + "aws:asset:property": "PropName" + }); + test.done(); + }, + + 'if staging directory is absolute, asset path is absolute'(test: Test) { + // GIVEN + const staging = path.resolve(mkdtempSync()); + const app = new App({ + context: { + [cxapi.ASSET_STAGING_DIR_CONTEXT]: staging, + [cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true', + } + }); + + const stack = new Stack(app, 'stack'); + + const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); + const asset = new ZipDirectoryAsset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); + + // WHEN + asset.addResourceMetadata(resource, 'PropName'); + + const session = app.run(); + const template = SynthUtils.templateForStackName(session, stack.name); + + test.deepEqual(template.Resources.MyResource.Metadata, { + "aws:asset:path": `${staging}/b550524e103eb4cf257c594fba5b9fe8`, + "aws:asset:property": "PropName" + }); + test.done(); + }, + + 'cdk metadata points to staged asset'(test: Test) { + // GIVEN + const tempdir = mkdtempSync(); + process.chdir(tempdir); // change current directory to somewhere in /tmp + + const staging = '.stageme'; + + const app = new App({ + context: { + [cxapi.ASSET_STAGING_DIR_CONTEXT]: staging, + } + }); + + const stack = new Stack(app, 'stack'); + + new ZipDirectoryAsset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); + + // WHEN + const session = app.run(); + const artifact = session.getArtifact(stack.name); + + const md = Object.values(artifact.metadata || {})[0][0].data; + test.deepEqual(md.path, '.stageme/b550524e103eb4cf257c594fba5b9fe8'); + test.done(); + } + } }; + +function mkdtempSync() { + return fs.mkdtempSync(path.join(os.tmpdir(), 'test.assets')); +} diff --git a/packages/@aws-cdk/aws-amazonmq/package.json b/packages/@aws-cdk/aws-amazonmq/package.json index 6e61ca7ca62e6..46162267c092c 100644 --- a/packages/@aws-cdk/aws-amazonmq/package.json +++ b/packages/@aws-cdk/aws-amazonmq/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-amazonmq", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::AmazonMQ", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "amazonmq" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-amazonmq", + "module": "aws_cdk.aws_amazonmq" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-amazonmq" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -55,18 +60,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts index 49c31a5a0804b..74c0dfda90afb 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/deployment.ts @@ -7,12 +7,12 @@ export interface DeploymentProps { /** * The Rest API to deploy. */ - api: IRestApi; + readonly api: IRestApi; /** * A description of the purpose of the API Gateway deployment. */ - description?: string; + readonly description?: string; /** * When an API Gateway model is updated, a new deployment will automatically be created. @@ -21,7 +21,7 @@ export interface DeploymentProps { * * @default false */ - retainDeployments?: boolean; + readonly retainDeployments?: boolean; } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/integration.ts b/packages/@aws-cdk/aws-apigateway/lib/integration.ts index 1f47868e186f1..5883555d69698 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integration.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integration.ts @@ -7,12 +7,12 @@ export interface IntegrationOptions { * A list of request parameters whose values are to be cached. It determines * request parameters that will make it into the cache key. */ - cacheKeyParameters?: string[]; + readonly cacheKeyParameters?: string[]; /** * An API-specific tag group of related cached parameters. */ - cacheNamespace?: string; + readonly cacheNamespace?: string; /** * Specifies how to handle request payload content type conversions. @@ -22,7 +22,7 @@ export interface IntegrationOptions { * modification, provided that the `passthroughBehaviors` property is * configured to support payload pass-through. */ - contentHandling?: ContentHandling; + readonly contentHandling?: ContentHandling; /** * An IAM role that API Gateway assumes. @@ -31,14 +31,14 @@ export interface IntegrationOptions { * * @default A role is not assumed */ - credentialsRole?: iam.Role; + readonly credentialsRole?: iam.Role; /** * Requires that the caller's identity be passed through from the request. * * @default Caller identity is not passed through */ - credentialsPassthrough?: boolean; + readonly credentialsPassthrough?: boolean; /** * Specifies the pass-through behavior for incoming requests based on the @@ -47,7 +47,7 @@ export interface IntegrationOptions { * There are three valid values: WHEN_NO_MATCH, WHEN_NO_TEMPLATES, and * NEVER. */ - passthroughBehavior?: PassthroughBehavior + readonly passthroughBehavior?: PassthroughBehavior /** * The request parameters that API Gateway sends with the backend request. @@ -62,7 +62,7 @@ export interface IntegrationOptions { * value. You must enclose static values in single quotation marks and * pre-encode these values based on their destination in the request. */ - requestParameters?: { [dest: string]: string }; + readonly requestParameters?: { [dest: string]: string }; /** * A map of Apache Velocity templates that are applied on the request @@ -75,7 +75,7 @@ export interface IntegrationOptions { * * @see http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html */ - requestTemplates?: { [contentType: string]: string }; + readonly requestTemplates?: { [contentType: string]: string }; /** * The response that API Gateway provides after a method's backend completes @@ -84,26 +84,26 @@ export interface IntegrationOptions { * responses. For example, you can map the backend status codes to codes * that you define. */ - integrationResponses?: IntegrationResponse[]; + readonly integrationResponses?: IntegrationResponse[]; /** * The type of network connection to the integration endpoint. * @default ConnectionType.Internet */ - connectionType?: ConnectionType; + readonly connectionType?: ConnectionType; /** * The VpcLink used for the integration. * Required if connectionType is VPC_LINK */ - vpcLink?: VpcLink; + readonly vpcLink?: VpcLink; } export interface IntegrationProps { /** * Specifies an API method integration type. */ - type: IntegrationType; + readonly type: IntegrationType; /** * The Uniform Resource Identifier (URI) for the integration. @@ -118,18 +118,18 @@ export interface IntegrationProps { * * @see https://docs.aws.amazon.com/apigateway/api-reference/resource/integration/#uri */ - uri?: any; + readonly uri?: any; /** * The integration's HTTP method type. * Required unless you use a MOCK integration. */ - integrationHttpMethod?: string; + readonly integrationHttpMethod?: string; /** * Integration options. */ - options?: IntegrationOptions; + readonly options?: IntegrationOptions; } /** @@ -243,13 +243,13 @@ export interface IntegrationResponse { * * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-integration-settings-integration-response.html */ - selectionPattern?: string; + readonly selectionPattern?: string; /** * The status code that API Gateway uses to map the integration response to * a MethodResponse status code. */ - statusCode: string; + readonly statusCode: string; /** * Specifies how to handle request payload content type conversions. @@ -257,7 +257,7 @@ export interface IntegrationResponse { * @default none the request payload is passed through from the method * request to the integration request without modification. */ - contentHandling?: ContentHandling; + readonly contentHandling?: ContentHandling; /** * The response parameters from the backend response that API Gateway sends @@ -274,7 +274,7 @@ export interface IntegrationResponse { * * @see http://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html */ - responseParameters?: { [destination: string]: string }; + readonly responseParameters?: { [destination: string]: string }; /** * The templates that are used to transform the integration response body. @@ -283,5 +283,5 @@ export interface IntegrationResponse { * * @see http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html */ - responseTemplates?: { [contentType: string]: string }; + readonly responseTemplates?: { [contentType: string]: string }; } diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts index a4fe394384033..8ccb4d11cc654 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/aws.ts @@ -9,18 +9,18 @@ export interface AwsIntegrationProps { * * @default false */ - proxy?: boolean; + readonly proxy?: boolean; /** * The name of the integrated AWS service (e.g. `s3`) */ - service: string; + readonly service: string; /** * A designated subdomain supported by certain AWS service for fast * host-name lookup. */ - subdomain?: string; + readonly subdomain?: string; /** * The path to use for path-base APIs. @@ -30,7 +30,7 @@ export interface AwsIntegrationProps { * * Mutually exclusive with the `action` options. */ - path?: string; + readonly path?: string; /** * The AWS action to perform in the integration. @@ -39,7 +39,7 @@ export interface AwsIntegrationProps { * * Mutually exclusive with `path`. */ - action?: string; + readonly action?: string; /** * Parameters for the action. @@ -47,12 +47,19 @@ export interface AwsIntegrationProps { * `action` must be set, and `path` must be undefined. * The action params will be URL encoded. */ - actionParameters?: { [key: string]: string }; + readonly actionParameters?: { [key: string]: string }; + + /** + * The integration's HTTP method type. + * + * @default POST + */ + readonly integrationHttpMethod?: string; /** * Integration options, such as content handling, request/response mapping, etc. */ - options?: IntegrationOptions + readonly options?: IntegrationOptions } /** @@ -70,7 +77,7 @@ export class AwsIntegration extends Integration { const { apiType, apiValue } = parseAwsApiCall(props.path, props.action, props.actionParameters); super({ type, - integrationHttpMethod: 'POST', + integrationHttpMethod: props.integrationHttpMethod || 'POST', uri: new cdk.Token(() => { if (!this.scope) { throw new Error('AwsIntegration must be used in API'); } return this.scope.node.stack.formatArn({ diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts index faed63c8f6371..d98f0b7853075 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/http.ts @@ -6,13 +6,13 @@ export interface HttpIntegrationProps { * * @default true */ - proxy?: boolean; + readonly proxy?: boolean; /** * HTTP method to use when invoking the backend URL. * @default GET */ - httpMethod?: string; + readonly httpMethod?: string; /** * Integration options, such as request/resopnse mapping, content handling, @@ -20,7 +20,7 @@ export interface HttpIntegrationProps { * * @default defaults based on `IntegrationOptions` defaults */ - options?: IntegrationOptions; + readonly options?: IntegrationOptions; } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts index 10ba40547523d..26c2560c12d1e 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/integrations/lambda.ts @@ -9,7 +9,7 @@ export interface LambdaIntegrationOptions extends IntegrationOptions { * Use proxy integration or normal (request/response mapping) integration. * @default true */ - proxy?: boolean; + readonly proxy?: boolean; /** * Allow invoking method from AWS Console UI (for testing purposes). @@ -21,7 +21,7 @@ export interface LambdaIntegrationOptions extends IntegrationOptions { * * @default true */ - allowTestInvoke?: boolean; + readonly allowTestInvoke?: boolean; } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts b/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts index 74a2811eaacf8..553a955fbc57d 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/lambda-api.ts @@ -12,7 +12,7 @@ export interface LambdaRestApiProps { * This handler will be used as a the default integration for all methods in * this API, unless specified otherwise in `addMethod`. */ - handler: lambda.IFunction; + readonly handler: lambda.IFunction; /** * If true, route all requests to the Lambda Function @@ -22,14 +22,14 @@ export interface LambdaRestApiProps { * * @default true */ - proxy?: boolean; + readonly proxy?: boolean; /** * Further customization of the REST API. * * @default defaults */ - options?: RestApiProps; + readonly options?: RestApiProps; } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 7375c8b51cf08..e492cb573c722 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -12,13 +12,13 @@ export interface MethodOptions { * A friendly operation name for the method. For example, you can assign the * OperationName of ListPets for the GET /pets method. */ - operationName?: string; + readonly operationName?: string; /** * Method authorization. * @default None open access */ - authorizationType?: AuthorizationType; + readonly authorizationType?: AuthorizationType; /** * If `authorizationType` is `Custom`, this specifies the ID of the method @@ -27,13 +27,13 @@ export interface MethodOptions { * NOTE: in the future this will be replaced with an `IAuthorizer` * construct. */ - authorizerId?: string; + readonly authorizerId?: string; /** * Indicates whether the method requires clients to submit a valid API key. * @default false */ - apiKeyRequired?: boolean; + readonly apiKeyRequired?: boolean; /** * The responses that can be sent to the client who calls the method. @@ -44,7 +44,7 @@ export interface MethodOptions { * for the integration response to be correctly mapped to a response to the client. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-method-settings-method-response.html */ - methodResponses?: MethodResponse[] + readonly methodResponses?: MethodResponse[] /** * The request parameters that API Gateway accepts. Specify request parameters @@ -54,7 +54,7 @@ export interface MethodOptions { * is querystring, path, or header, and name is a valid, unique parameter name. * @default None */ - requestParameters?: { [param: string]: boolean }; + readonly requestParameters?: { [param: string]: boolean }; // TODO: // - RequestValidatorId @@ -66,22 +66,22 @@ export interface MethodProps { * The resource this method is associated with. For root resource methods, * specify the `RestApi` object. */ - resource: IRestApiResource; + readonly resource: IRestApiResource; /** * The HTTP method ("GET", "POST", "PUT", ...) that clients use to call this method. */ - httpMethod: string; + readonly httpMethod: string; /** * The backend system that the method calls when it receives a request. */ - integration?: Integration; + readonly integration?: Integration; /** * Method options. */ - options?: MethodOptions; + readonly options?: MethodOptions; } export class Method extends cdk.Construct { diff --git a/packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts b/packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts index 59032a06d860f..a3b504ae4cbba 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts @@ -6,7 +6,7 @@ export interface MethodResponse { * The method response's status code, which you map to an IntegrationResponse. * Required. */ - statusCode: string; + readonly statusCode: string; /** * Response parameters that API Gateway sends to the client that called a method. @@ -16,7 +16,7 @@ export interface MethodResponse { * valid, unique header name. The Boolean specifies whether a parameter is required. * @default None */ - responseParameters?: { [destination: string]: boolean }; + readonly responseParameters?: { [destination: string]: boolean }; /** * The resources used for the response's content type. Specify response models as @@ -24,5 +24,5 @@ export interface MethodResponse { * resource name as the value. * @default None */ - responseModels?: { [contentType: string]: IModel }; + readonly responseModels?: { [contentType: string]: IModel }; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index 573dceaac83d4..4ba58d7fbbee2 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -105,12 +105,12 @@ export interface ResourceProps extends ResourceOptions { * The parent resource of this resource. You can either pass another * `Resource` object or a `RestApi` object here. */ - parent: IRestApiResource; + readonly parent: IRestApiResource; /** * A path name for the resource. */ - pathPart: string; + readonly pathPart: string; } export abstract class ResourceBase extends cdk.Construct implements IRestApiResource { @@ -231,7 +231,7 @@ export interface ProxyResourceProps extends ResourceOptions { * The parent resource of this resource. You can either pass another * `Resource` object or a `RestApi` object here. */ - parent: IRestApiResource; + readonly parent: IRestApiResource; /** * Adds an "ANY" method to this resource. If set to `false`, you will have to explicitly @@ -239,7 +239,7 @@ export interface ProxyResourceProps extends ResourceOptions { * * @default true */ - anyMethod?: boolean; + readonly anyMethod?: boolean; } /** diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index 2a979fd50a178..9c2d19be0f204 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -11,7 +11,7 @@ export interface RestApiImportProps { /** * The REST API ID of an existing REST API resource. */ - restApiId: string; + readonly restApiId: string; } export interface IRestApi extends cdk.IConstruct { @@ -49,7 +49,7 @@ export interface RestApiProps extends ResourceOptions { * * @default true */ - deploy?: boolean; + readonly deploy?: boolean; /** * Options for the API Gateway stage that will always point to the latest @@ -58,7 +58,7 @@ export interface RestApiProps extends ResourceOptions { * * @default defaults based on defaults of `StageOptions` */ - deployOptions?: StageOptions; + readonly deployOptions?: StageOptions; /** * Retains old deployment resources when the API changes. This allows @@ -67,38 +67,38 @@ export interface RestApiProps extends ResourceOptions { * * @default false */ - retainDeployments?: boolean; + readonly retainDeployments?: boolean; /** * A name for the API Gateway RestApi resource. * * @default construct-id defaults to the id of the RestApi construct */ - restApiName?: string; + readonly restApiName?: string; /** * Custom header parameters for the request. * @see https://docs.aws.amazon.com/cli/latest/reference/apigateway/import-rest-api.html */ - parameters?: { [key: string]: string }; + readonly parameters?: { [key: string]: string }; /** * A policy document that contains the permissions for this RestApi */ - policy?: iam.PolicyDocument; + readonly policy?: iam.PolicyDocument; /** * A description of the purpose of this API Gateway RestApi resource. * @default No description */ - description?: string; + readonly description?: string; /** * The source of the API key for metering requests according to a usage * plan. * @default undefined metering is disabled */ - apiKeySourceType?: ApiKeySourceType; + readonly apiKeySourceType?: ApiKeySourceType; /** * The list of binary media mine-types that are supported by the RestApi @@ -106,13 +106,13 @@ export interface RestApiProps extends ResourceOptions { * * @default By default, RestApi supports only UTF-8-encoded text payloads */ - binaryMediaTypes?: string[]; + readonly binaryMediaTypes?: string[]; /** * A list of the endpoint types of the API. Use this property when creating * an API. */ - endpointTypes?: EndpointType[]; + readonly endpointTypes?: EndpointType[]; /** * Indicates whether to roll back the resource if a warning occurs while API @@ -120,7 +120,7 @@ export interface RestApiProps extends ResourceOptions { * * @default false */ - failOnWarnings?: boolean; + readonly failOnWarnings?: boolean; /** * A nullable integer that is used to enable compression (with non-negative @@ -132,18 +132,18 @@ export interface RestApiProps extends ResourceOptions { * * @default undefined compression is disabled */ - minimumCompressionSize?: number; + readonly minimumCompressionSize?: number; /** * The ID of the API Gateway RestApi resource that you want to clone. */ - cloneFrom?: IRestApi; + readonly cloneFrom?: IRestApi; /** * Automatically configure an AWS CloudWatch role for API Gateway. * @default true */ - cloudWatchRole?: boolean; + readonly cloudWatchRole?: boolean; } /** @@ -282,6 +282,8 @@ export class RestApi extends cdk.Construct implements IRestApi { /** * Internal API used by `Method` to keep an inventory of methods at the API * level for validation purposes. + * + * @internal */ public _attachMethod(method: Method) { this.methods.push(method); diff --git a/packages/@aws-cdk/aws-apigateway/lib/stage.ts b/packages/@aws-cdk/aws-apigateway/lib/stage.ts index 26a1cef90447d..f870b40da7511 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/stage.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/stage.ts @@ -11,24 +11,24 @@ export interface StageOptions extends MethodDeploymentOptions { * * @default "prod" */ - stageName?: string; + readonly stageName?: string; /** * Specifies whether Amazon X-Ray tracing is enabled for this method. * @default false */ - tracingEnabled?: boolean; + readonly tracingEnabled?: boolean; /** * Indicates whether cache clustering is enabled for the stage. */ - cacheClusterEnabled?: boolean; + readonly cacheClusterEnabled?: boolean; /** * The stage's cache cluster size. * @default 0.5 */ - cacheClusterSize?: string; + readonly cacheClusterSize?: string; /** * The identifier of the client certificate that API Gateway uses to call @@ -36,24 +36,24 @@ export interface StageOptions extends MethodDeploymentOptions { * * @default None */ - clientCertificateId?: string; + readonly clientCertificateId?: string; /** * A description of the purpose of the stage. */ - description?: string; + readonly description?: string; /** * The version identifier of the API documentation snapshot. */ - documentationVersion?: string; + readonly documentationVersion?: string; /** * A map that defines the stage variables. Variable names must consist of * alphanumeric characters, and the values must match the following regular * expression: [A-Za-z0-9-._~:/?#&=,]+. */ - variables?: { [key: string]: string }; + readonly variables?: { [key: string]: string }; /** * Method deployment options for specific resources/methods. These will @@ -64,14 +64,14 @@ export interface StageOptions extends MethodDeploymentOptions { * to define options for all methods/resources. */ - methodOptions?: { [path: string]: MethodDeploymentOptions }; + readonly methodOptions?: { [path: string]: MethodDeploymentOptions }; } export interface StageProps extends StageOptions { /** * The deployment that this stage points to. */ - deployment: Deployment; + readonly deployment: Deployment; } export enum MethodLoggingLevel { @@ -85,76 +85,79 @@ export interface MethodDeploymentOptions { * Specifies whether Amazon CloudWatch metrics are enabled for this method. * @default false */ - metricsEnabled?: boolean; + readonly metricsEnabled?: boolean; /** * Specifies the logging level for this method, which effects the log * entries pushed to Amazon CloudWatch Logs. * @default Off */ - loggingLevel?: MethodLoggingLevel; + readonly loggingLevel?: MethodLoggingLevel; /** * Specifies whether data trace logging is enabled for this method, which * effects the log entries pushed to Amazon CloudWatch Logs. * @default false */ - dataTraceEnabled?: boolean; + readonly dataTraceEnabled?: boolean; /** * Specifies the throttling burst limit. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html */ - throttlingBurstLimit?: number; + readonly throttlingBurstLimit?: number; /** * Specifies the throttling rate limit. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html */ - throttlingRateLimit?: number; + readonly throttlingRateLimit?: number; /** * Specifies whether responses should be cached and returned for requests. A * cache cluster must be enabled on the stage for responses to be cached. */ - cachingEnabled?: boolean; + readonly cachingEnabled?: boolean; /** * Specifies the time to live (TTL), in seconds, for cached responses. The * higher the TTL, the longer the response will be cached. */ - cacheTtlSeconds?: number; + readonly cacheTtlSeconds?: number; /** * Indicates whether the cached responses are encrypted. * @default false */ - cacheDataEncrypted?: boolean; + readonly cacheDataEncrypted?: boolean; } export class Stage extends cdk.Construct { public readonly stageName: string; private readonly restApi: IRestApi; + private enableCacheCluster?: boolean; constructor(scope: cdk.Construct, id: string, props: StageProps) { super(scope, id); - const methodSettings = this.renderMethodSettings(props); + this.enableCacheCluster = props.cacheClusterEnabled; + + const methodSettings = this.renderMethodSettings(props); // this can mutate `this.cacheClusterEnabled` // enable cache cluster if cacheClusterSize is set if (props.cacheClusterSize !== undefined) { - if (props.cacheClusterEnabled === undefined) { - props.cacheClusterEnabled = true; - } else if (props.cacheClusterEnabled === false) { + if (this.enableCacheCluster === undefined) { + this.enableCacheCluster = true; + } else if (this.enableCacheCluster === false) { throw new Error(`Cannot set "cacheClusterSize" to ${props.cacheClusterSize} and "cacheClusterEnabled" to "false"`); } } - const cacheClusterSize = props.cacheClusterEnabled ? (props.cacheClusterSize || '0.5') : undefined; + const cacheClusterSize = this.enableCacheCluster ? (props.cacheClusterSize || '0.5') : undefined; const resource = new CfnStage(this, 'Resource', { stageName: props.stageName || 'prod', - cacheClusterEnabled: props.cacheClusterEnabled, + cacheClusterEnabled: this.enableCacheCluster, cacheClusterSize, clientCertificateId: props.clientCertificateId, deploymentId: props.deployment.deploymentId, @@ -183,6 +186,7 @@ export class Stage extends cdk.Construct { private renderMethodSettings(props: StageProps): CfnStage.MethodSettingProperty[] | undefined { const settings = new Array(); + const self = this; // extract common method options from the stage props const commonMethodOptions: MethodDeploymentOptions = { @@ -212,9 +216,9 @@ export class Stage extends cdk.Construct { function renderEntry(path: string, options: MethodDeploymentOptions): CfnStage.MethodSettingProperty { if (options.cachingEnabled) { - if (props.cacheClusterEnabled === undefined) { - props.cacheClusterEnabled = true; - } else if (props.cacheClusterEnabled === false) { + if (self.enableCacheCluster === undefined) { + self.enableCacheCluster = true; + } else if (self.enableCacheCluster === false) { throw new Error(`Cannot enable caching for method ${path} since cache cluster is disabled on stage`); } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts index 73584be5c5387..8c7c697f03044 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/vpc-link.ts @@ -10,19 +10,19 @@ export interface VpcLinkProps { * The name used to label and identify the VPC link. * @default automatically generated name */ - name?: string; + readonly name?: string; /** * The description of the VPC link. * @default no description */ - description?: string; + readonly description?: string; /** * The network load balancers of the VPC targeted by the VPC link. * The network load balancers must be owned by the same AWS account of the API owner. */ - targets: elbv2.INetworkLoadBalancer[]; + readonly targets: elbv2.INetworkLoadBalancer[]; } /** diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index 220b4ec7c4c88..b32a46863bd4a 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-apigateway", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::ApiGateway", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-apigateway", + "module": "aws_cdk.aws_apigateway" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-apigateway" }, "scripts": { "build": "cdk-build", @@ -57,25 +62,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -85,4 +90,4 @@ "resource-attribute:@aws-cdk/aws-apigateway.IRestApi.restApiRootResourceId" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json index 74e92fce8436e..0baf839a608ca 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.books.expected.json @@ -65,7 +65,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "BooksHandler3EB83358" + "Fn::GetAtt": [ + "BooksHandler3EB83358", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -103,7 +106,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "BooksHandler3EB83358" + "Fn::GetAtt": [ + "BooksHandler3EB83358", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -137,7 +143,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "BooksHandler3EB83358" + "Fn::GetAtt": [ + "BooksHandler3EB83358", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -175,7 +184,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "BooksHandler3EB83358" + "Fn::GetAtt": [ + "BooksHandler3EB83358", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -269,7 +281,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "BookHandlerF9638A7A" + "Fn::GetAtt": [ + "BookHandlerF9638A7A", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -307,7 +322,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "BookHandlerF9638A7A" + "Fn::GetAtt": [ + "BookHandlerF9638A7A", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -341,7 +359,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "BookHandlerF9638A7A" + "Fn::GetAtt": [ + "BookHandlerF9638A7A", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -379,7 +400,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "BookHandlerF9638A7A" + "Fn::GetAtt": [ + "BookHandlerF9638A7A", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -473,7 +497,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "Hello4A628BD4" + "Fn::GetAtt": [ + "Hello4A628BD4", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -511,7 +538,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "Hello4A628BD4" + "Fn::GetAtt": [ + "Hello4A628BD4", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -890,4 +920,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json index f00374b4201ea..c46e8005d766f 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.expected.json @@ -395,7 +395,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyHandler6B74D312" + "Fn::GetAtt": [ + "MyHandler6B74D312", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -433,7 +436,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyHandler6B74D312" + "Fn::GetAtt": [ + "MyHandler6B74D312", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -467,7 +473,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyHandler6B74D312" + "Fn::GetAtt": [ + "MyHandler6B74D312", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -505,7 +514,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyHandler6B74D312" + "Fn::GetAtt": [ + "MyHandler6B74D312", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -539,7 +551,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyHandler6B74D312" + "Fn::GetAtt": [ + "MyHandler6B74D312", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -577,7 +592,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyHandler6B74D312" + "Fn::GetAtt": [ + "MyHandler6B74D312", + "Arn" + ] }, "Principal": "apigateway.amazonaws.com", "SourceArn": { @@ -635,4 +653,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts index ac8db66cd4d25..13087303a8ba9 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import { expect, haveResource, ResourcePart, SynthUtils } from '@aws-cdk/assert'; import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import apigateway = require('../lib'); @@ -153,7 +153,7 @@ export = { function synthesize() { stack.node.prepareTree(); - return stack._toCloudFormation(); + return SynthUtils.toCloudFormation(stack); } }, diff --git a/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts b/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts index 723718c4f8720..5a16869d06149 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.lambda-api.ts @@ -126,12 +126,9 @@ export = { }, ":lambda:path/2015-03-31/functions/", { - "Fn::GetAtt": [ - "handlerE1533BD5", - "Arn" - ] + "Ref": "alias68BF17F5" }, - ":my-alias/invocations" + "/invocations" ] ] } diff --git a/packages/@aws-cdk/aws-apigateway/test/test.method.ts b/packages/@aws-cdk/aws-apigateway/test/test.method.ts index b74b6f1241baf..d1d2919ee28f0 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.method.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.method.ts @@ -87,6 +87,28 @@ export = { test.done(); }, + 'integration with a custom http method can be set via a property'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.RestApi(stack, 'test-api', { cloudWatchRole: false, deploy: false }); + + // WHEN + new apigateway.Method(stack, 'my-method', { + httpMethod: 'POST', + resource: api.root, + integration: new apigateway.AwsIntegration({ service: 's3', path: 'bucket/key', integrationHttpMethod: 'GET' }) + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ApiGateway::Method', { + Integration: { + IntegrationHttpMethod: "GET" + } + })); + + test.done(); + }, + 'use default integration from api'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts index 5b7de4b605e30..50d9286cc6c21 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike, ResourcePart, SynthUtils } from '@aws-cdk/assert'; import cdk = require('@aws-cdk/cdk'); import { App, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; @@ -344,7 +344,7 @@ export = { // THEN stack.node.prepareTree(); - test.deepEqual(stack._toCloudFormation().Outputs.MyRestApiRestApiIdB93C5C2D, { + test.deepEqual(SynthUtils.toCloudFormation(stack).Outputs.MyRestApiRestApiIdB93C5C2D, { Value: { Ref: 'MyRestApi2D1F47A9' }, Export: { Name: 'Stack:MyRestApiRestApiIdB93C5C2D' } }); diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts index 446cae58f42b5..205503b235e6d 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/base-scalable-attribute.ts @@ -11,22 +11,22 @@ export interface BaseScalableAttributeProps extends EnableScalingProps { /** * Service namespace of the scalable attribute */ - serviceNamespace: ServiceNamespace; + readonly serviceNamespace: ServiceNamespace; /** * Resource ID of the attribute */ - resourceId: string; + readonly resourceId: string; /** * Scalable dimension of the attribute */ - dimension: string; + readonly dimension: string; /** * Role to use for scaling */ - role: iam.IRole; + readonly role: iam.IRole; } /** @@ -90,10 +90,10 @@ export interface EnableScalingProps { * * @default 1 */ - minCapacity?: number; + readonly minCapacity?: number; /** * Maximum capacity to scale to */ - maxCapacity: number; + readonly maxCapacity: number; } diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts index eaedba2e01fbd..597c7dca15c0f 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts @@ -11,19 +11,19 @@ export interface ScalableTargetProps { /** * The minimum value that Application Auto Scaling can use to scale a target during a scaling activity. */ - minCapacity: number; + readonly minCapacity: number; /** * The maximum value that Application Auto Scaling can use to scale a target during a scaling activity. */ - maxCapacity: number; + readonly maxCapacity: number; /** * Role that allows Application Auto Scaling to modify your scalable target. * * @default A role is automatically created */ - role?: iam.IRole; + readonly role?: iam.IRole; /** * The resource identifier to associate with this scalable target. @@ -33,7 +33,7 @@ export interface ScalableTargetProps { * @example service/ecsStack-MyECSCluster-AB12CDE3F4GH/ecsStack-MyECSService-AB12CDE3F4GH * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_RegisterScalableTarget.html */ - resourceId: string; + readonly resourceId: string; /** * The scalable dimension that's associated with the scalable target. @@ -43,7 +43,7 @@ export interface ScalableTargetProps { * @example ecs:service:DesiredCount * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_ScalingPolicy.html */ - scalableDimension: string; + readonly scalableDimension: string; /** * The namespace of the AWS service that provides the resource or @@ -55,7 +55,7 @@ export interface ScalableTargetProps { * * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_RegisterScalableTarget.html */ - serviceNamespace: ServiceNamespace; + readonly serviceNamespace: ServiceNamespace; } /** @@ -169,21 +169,21 @@ export interface ScalingSchedule { * * @example rate(12 hours) */ - schedule: string; + readonly schedule: string; /** * When this scheduled action becomes active. * * @default The rule is activate immediately */ - startTime?: Date + readonly startTime?: Date /** * When this scheduled action expires. * * @default The rule never expires. */ - endTime?: Date; + readonly endTime?: Date; /** * The new minimum capacity. @@ -195,7 +195,7 @@ export interface ScalingSchedule { * * @default No new minimum capacity */ - minCapacity?: number; + readonly minCapacity?: number; /** * The new maximum capacity. @@ -207,7 +207,7 @@ export interface ScalingSchedule { * * @default No new maximum capacity */ - maxCapacity?: number; + readonly maxCapacity?: number; } /** 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 c825577ab21d3..5499ddcda4b41 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts @@ -10,21 +10,21 @@ export interface StepScalingActionProps { /** * The scalable target */ - scalingTarget: ScalableTarget; + readonly scalingTarget: ScalableTarget; /** * A name for the scaling policy * * @default Automatically generated name */ - policyName?: string; + readonly policyName?: string; /** * How the adjustment numbers are interpreted * * @default ChangeInCapacity */ - adjustmentType?: AdjustmentType; + readonly adjustmentType?: AdjustmentType; /** * Grace period after scaling activity. @@ -38,7 +38,7 @@ export interface StepScalingActionProps { * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_StepScalingPolicyConfiguration.html * @default No cooldown period */ - cooldownSec?: number; + readonly cooldownSec?: number; /** * Minimum absolute number to adjust capacity with as result of percentage scaling. @@ -48,14 +48,14 @@ export interface StepScalingActionProps { * * @default No minimum scaling effect */ - minAdjustmentMagnitude?: number; + readonly minAdjustmentMagnitude?: number; /** * The aggregation type for the CloudWatch metrics. * * @default Average */ - metricAggregationType?: MetricAggregationType; + readonly metricAggregationType?: MetricAggregationType; } /** @@ -172,7 +172,7 @@ export interface AdjustmentTier { * * Can be positive or negative. */ - adjustment: number; + readonly adjustment: number; /** * Lower bound where this scaling tier applies. @@ -182,7 +182,7 @@ export interface AdjustmentTier { * * @default -Infinity if this is the first tier, otherwise the upperBound of the previous tier */ - lowerBound?: number; + readonly lowerBound?: number; /** * Upper bound where this scaling tier applies @@ -192,5 +192,5 @@ export interface AdjustmentTier { * * @default +Infinity */ - upperBound?: number; + readonly upperBound?: number; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts index 1ee9f9324732a..5cbe98b95e0d2 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts @@ -8,21 +8,21 @@ export interface BasicStepScalingPolicyProps { /** * Metric to scale on. */ - metric: cloudwatch.Metric; + readonly metric: cloudwatch.Metric; /** * The intervals for scaling. * * Maps a range of metric values to a particular scaling behavior. */ - scalingSteps: ScalingInterval[]; + readonly scalingSteps: ScalingInterval[]; /** * How the adjustment numbers inside 'intervals' are interpreted. * * @default ChangeInCapacity */ - adjustmentType?: AdjustmentType; + readonly adjustmentType?: AdjustmentType; /** * Grace period after scaling activity. @@ -35,7 +35,7 @@ export interface BasicStepScalingPolicyProps { * @see https://docs.aws.amazon.com/autoscaling/application/APIReference/API_StepScalingPolicyConfiguration.html * @default No cooldown period */ - cooldownSec?: number; + readonly cooldownSec?: number; /** * Minimum absolute number to adjust capacity with as result of percentage scaling. @@ -45,14 +45,14 @@ export interface BasicStepScalingPolicyProps { * * @default No minimum scaling effect */ - minAdjustmentMagnitude?: number; + readonly minAdjustmentMagnitude?: number; } export interface StepScalingPolicyProps extends BasicStepScalingPolicyProps { /** * The scaling target */ - scalingTarget: ScalableTarget; + readonly scalingTarget: ScalableTarget; } /** @@ -154,7 +154,7 @@ export interface ScalingInterval { * * @default Threshold automatically derived from neighbouring intervals */ - lower?: number; + readonly lower?: number; /** * The upper bound of the interval. @@ -163,7 +163,7 @@ export interface ScalingInterval { * * @default Threshold automatically derived from neighbouring intervals */ - upper?: number; + readonly upper?: number; /** * The capacity adjustment to apply in this interval @@ -177,7 +177,7 @@ export interface ScalingInterval { * - ExactCapacity: set the capacity to this number. The number must * be positive. */ - change: number; + readonly change: number; } function aggregationTypeFromMetric(metric: cloudwatch.Metric): MetricAggregationType { 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 59d10805f3956..bea6f15f0d7a9 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 @@ -18,7 +18,7 @@ export interface BaseTargetTrackingProps { * * @default Automatically generated name */ - policyName?: string; + readonly policyName?: string; /** * Indicates whether scale in by the target tracking policy is disabled. @@ -30,21 +30,21 @@ export interface BaseTargetTrackingProps { * * @default false */ - disableScaleIn?: boolean; + readonly disableScaleIn?: boolean; /** * Period after a scale in activity completes before another scale in activity can start. * * @default No scale in cooldown */ - scaleInCooldownSec?: number; + readonly scaleInCooldownSec?: number; /** * Period after a scale out activity completes before another scale out activity can start. * * @default No scale out cooldown */ - scaleOutCooldownSec?: number; + readonly scaleOutCooldownSec?: number; } /** @@ -54,7 +54,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin /** * The target value for the metric. */ - targetValue: number; + readonly targetValue: number; /** * A predefined metric for application autoscaling @@ -64,7 +64,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin * * Exactly one of customMetric or predefinedMetric must be specified. */ - predefinedMetric?: PredefinedMetric; + readonly predefinedMetric?: PredefinedMetric; /** * Identify the resource associated with the metric type. @@ -73,7 +73,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin * * @example app///targetgroup// */ - resourceLabel?: string; + readonly resourceLabel?: string; /** * A custom metric for application autoscaling @@ -83,7 +83,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin * * Exactly one of customMetric or predefinedMetric must be specified. */ - customMetric?: cloudwatch.Metric; + readonly customMetric?: cloudwatch.Metric; } /** @@ -95,7 +95,7 @@ export interface TargetTrackingScalingPolicyProps extends BasicTargetTrackingSca /* * The scalable target */ - scalingTarget: ScalableTarget; + readonly scalingTarget: ScalableTarget; } export class TargetTrackingScalingPolicy extends cdk.Construct { diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json b/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json index 04b8cb6035dbd..a96643c8f2f4f 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-applicationautoscaling", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-applicationautoscaling/package.json b/packages/@aws-cdk/aws-applicationautoscaling/package.json index 1df5cdbd74191..a584c26fd9e3e 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/package.json +++ b/packages/@aws-cdk/aws-applicationautoscaling/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-applicationautoscaling", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::ApplicationAutoScaling", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-applicationautoscaling", + "module": "aws_cdk.aws_applicationautoscaling" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-applicationautoscaling" }, "scripts": { "build": "cdk-build", @@ -54,25 +59,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", "fast-check": "^1.7.0", - "pkglint": "^0.26.0" + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling-common": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling-common": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts index 71772bcf7e639..6e60c3cbd6067 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts @@ -1,3 +1,4 @@ +import { SynthUtils } from '@aws-cdk/assert'; import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import fc = require('fast-check'); @@ -129,7 +130,7 @@ function setupStepScaling(intervals: appscaling.ScalingInterval[]) { scalingSteps: intervals }); - return new ScalingStackTemplate(stack._toCloudFormation()); + return new ScalingStackTemplate(SynthUtils.toCloudFormation(stack)); } class ScalingStackTemplate { diff --git a/packages/@aws-cdk/aws-appmesh/.gitignore b/packages/@aws-cdk/aws-appmesh/.gitignore new file mode 100644 index 0000000000000..7b20ed5f53385 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/.gitignore @@ -0,0 +1,14 @@ +*.d.ts +*.generated.ts +*.js +*.js.map +*.snk +.jsii +.LAST_BUILD +.LAST_PACKAGE +.nycrc +.nyc_output +coverage +dist +tsconfig.json +tslint.json diff --git a/packages/@aws-cdk/aws-appmesh/.npmignore b/packages/@aws-cdk/aws-appmesh/.npmignore new file mode 100644 index 0000000000000..0ed32f4a4a755 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/.npmignore @@ -0,0 +1,17 @@ +# The basics +*.ts +*.tgz +*.snk +!*.d.ts +!*.js + +# Coverage +coverage +.nyc_output +.nycrc + +# Build gear +dist +.LAST_BUILD +.LAST_PACKAGE +.jsii diff --git a/packages/@aws-cdk/aws-serverless/LICENSE b/packages/@aws-cdk/aws-appmesh/LICENSE similarity index 100% rename from packages/@aws-cdk/aws-serverless/LICENSE rename to packages/@aws-cdk/aws-appmesh/LICENSE diff --git a/packages/@aws-cdk/aws-serverless/NOTICE b/packages/@aws-cdk/aws-appmesh/NOTICE similarity index 100% rename from packages/@aws-cdk/aws-serverless/NOTICE rename to packages/@aws-cdk/aws-appmesh/NOTICE diff --git a/packages/@aws-cdk/aws-appmesh/README.md b/packages/@aws-cdk/aws-appmesh/README.md new file mode 100644 index 0000000000000..57c26bc1be9c1 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/README.md @@ -0,0 +1,7 @@ +## AWS::AppMesh Construct Library + +This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. + +```ts +import appmesh = require('@aws-cdk/aws-appmesh'); +``` diff --git a/packages/@aws-cdk/aws-appmesh/lib/index.ts b/packages/@aws-cdk/aws-appmesh/lib/index.ts new file mode 100644 index 0000000000000..4a71ecbe9cd17 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::AppMesh CloudFormation Resources: +export * from './appmesh.generated'; diff --git a/packages/@aws-cdk/aws-appmesh/package.json b/packages/@aws-cdk/aws-appmesh/package.json new file mode 100644 index 0000000000000..387d857396235 --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/package.json @@ -0,0 +1,78 @@ +{ + "name": "@aws-cdk/aws-appmesh", + "version": "0.28.0", + "description": "The CDK Construct Library for AWS::AppMesh", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.AppMesh", + "packageId": "Amazon.CDK.AWS.AppMesh", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk" + }, + "java": { + "package": "software.amazon.awscdk.services.appmesh", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "appmesh" + } + }, + "python": { + "distName": "aws-cdk.aws-appmesh", + "module": "aws_cdk.aws_appmesh" + }, + "sphinx": {} + } + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-appmesh" + }, + "homepage": "https://github.com/awslabs/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", + "cfn2ts": "cfn2ts" + }, + "cdk-build": { + "cloudformation": "AWS::AppMesh" + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "AWS::AppMesh", + "aws-appmesh" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" + }, + "dependencies": { + "@aws-cdk/cdk": "^0.28.0" + }, + "peerDependencies": { + "@aws-cdk/cdk": "^0.28.0" + }, + "engines": { + "node": ">= 8.10.0" + } +} diff --git a/packages/@aws-cdk/aws-appmesh/test/test.appmesh.ts b/packages/@aws-cdk/aws-appmesh/test/test.appmesh.ts new file mode 100644 index 0000000000000..51db772aeb78f --- /dev/null +++ b/packages/@aws-cdk/aws-appmesh/test/test.appmesh.ts @@ -0,0 +1,9 @@ +import { Test, testCase } from 'nodeunit'; +import {} from '../lib'; + +export = testCase({ + notTested(test: Test) { + test.ok(true, 'No tests are specified for this package.'); + test.done(); + } +}); diff --git a/packages/@aws-cdk/aws-appstream/package.json b/packages/@aws-cdk/aws-appstream/package.json index 02080ecad0a5b..8d5ebc8fe8f84 100644 --- a/packages/@aws-cdk/aws-appstream/package.json +++ b/packages/@aws-cdk/aws-appstream/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-appstream", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::AppStream", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "appstream" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-appstream", + "module": "aws_cdk.aws_appstream" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-appstream" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -55,18 +60,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-appsync/package.json b/packages/@aws-cdk/aws-appsync/package.json index a1e2cdc353eb6..284407087b1ba 100644 --- a/packages/@aws-cdk/aws-appsync/package.json +++ b/packages/@aws-cdk/aws-appsync/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-appsync", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::AppSync", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-appsync", + "module": "aws_cdk.aws_appsync" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-appsync" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-athena/package.json b/packages/@aws-cdk/aws-athena/package.json index 27983d63e9090..f77d65d1bcc38 100644 --- a/packages/@aws-cdk/aws-athena/package.json +++ b/packages/@aws-cdk/aws-athena/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-athena", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Athena", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,7 +20,11 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-athena", + "module": "aws_cdk.aws_athena" + } } }, "cdk-build": { @@ -28,7 +32,8 @@ }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-athena" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling-api/lib/lifecycle-hook-target.ts b/packages/@aws-cdk/aws-autoscaling-api/lib/lifecycle-hook-target.ts index 72d79007ee6b8..ba6a2f661128f 100644 --- a/packages/@aws-cdk/aws-autoscaling-api/lib/lifecycle-hook-target.ts +++ b/packages/@aws-cdk/aws-autoscaling-api/lib/lifecycle-hook-target.ts @@ -27,5 +27,5 @@ export interface LifecycleHookTargetProps { /** * The ARN to use as the notification target */ - notificationTargetArn: string; + readonly notificationTargetArn: string; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-api/package.json b/packages/@aws-cdk/aws-autoscaling-api/package.json index e2b817f92fc09..8158fd9e0d877 100644 --- a/packages/@aws-cdk/aws-autoscaling-api/package.json +++ b/packages/@aws-cdk/aws-autoscaling-api/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscaling-api", - "version": "0.26.0", + "version": "0.28.0", "description": "API package for @aws-cdk/aws-autoscaling", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-autoscaling-api", + "module": "aws_cdk.aws_autoscaling_api" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-autoscaling-api" }, "scripts": { "build": "cdk-build", @@ -60,21 +65,21 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling-common/lib/interval-utils.ts b/packages/@aws-cdk/aws-autoscaling-common/lib/interval-utils.ts index df78370991852..3c60a3d277a8d 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/lib/interval-utils.ts +++ b/packages/@aws-cdk/aws-autoscaling-common/lib/interval-utils.ts @@ -1,9 +1,9 @@ import { ScalingInterval } from "./types"; export interface CompleteScalingInterval { - lower: number; - upper: number; - change?: number; + readonly lower: number; + readonly upper: number; + readonly change?: number; } /** @@ -48,9 +48,11 @@ function orderAndCompleteIntervals(intervals: ScalingInterval[]): CompleteScalin // Propagate boundaries until no more change while (propagateBounds(intervals)) { /* Repeat */ } + const lastIndex = intervals.length - 1; + // Validate that no intervals have undefined bounds now, which must mean they're complete. - if (intervals[0].lower === undefined) { intervals[0].lower = 0; } - if (last(intervals).upper === undefined) { last(intervals).upper = Infinity; } + if (intervals[0].lower === undefined) { intervals[0] = { ...intervals[0], lower: 0 }; } + if (intervals[lastIndex].upper === undefined) { intervals[lastIndex] = { ...intervals[lastIndex], upper: Infinity }; } for (const interval of intervals) { if (interval.lower === undefined || interval.upper === undefined) { throw new Error(`Could not determine the lower and upper bounds for ${JSON.stringify(interval)}`); @@ -120,9 +122,10 @@ function makeGapsUndefined(intervals: CompleteScalingInterval[]) { * Turn zero changes into undefined, in-place */ function makeZerosUndefined(intervals: CompleteScalingInterval[]) { - for (const interval of intervals) { + for (let i = 0; i < intervals.length; ++i) { + const interval = intervals[i]; if (interval.change === 0) { - interval.change = undefined; + intervals[i] = { ...interval, change: undefined }; } } } @@ -134,7 +137,7 @@ function combineUndefineds(intervals: CompleteScalingInterval[]) { let i = 0; while (i < intervals.length - 1) { if (intervals[i].change === undefined && intervals[i + 1].change === undefined) { - intervals[i].upper = intervals[i + 1].upper; + intervals[i] = { ...intervals[i], upper: intervals[i + 1].upper }; intervals.splice(i + 1, 1); } else { i++; @@ -166,7 +169,7 @@ function propagateBounds(intervals: ScalingInterval[]) { // Propagate upper bounds upwards for (let i = 0; i < intervals.length - 1; i++) { if (intervals[i].upper !== undefined && intervals[i + 1].lower === undefined) { - intervals[i + 1].lower = intervals[i].upper; + intervals[i + 1] = { ...intervals[i + 1], lower: intervals[i].upper }; ret = true; } } @@ -174,7 +177,7 @@ function propagateBounds(intervals: ScalingInterval[]) { // Propagate lower bounds downwards for (let i = intervals.length - 1; i >= 1; i--) { if (intervals[i].lower !== undefined && intervals[i - 1].upper === undefined) { - intervals[i - 1].upper = intervals[i].lower; + intervals[i - 1] = { ...intervals[i - 1], upper: intervals[i].lower }; ret = true; } } @@ -194,8 +197,8 @@ function last(xs: T[]) { } export interface Alarms { - lowerAlarmIntervalIndex?: number; - upperAlarmIntervalIndex?: number; + readonly lowerAlarmIntervalIndex?: number; + readonly upperAlarmIntervalIndex?: number; } /** diff --git a/packages/@aws-cdk/aws-autoscaling-common/lib/test-utils.ts b/packages/@aws-cdk/aws-autoscaling-common/lib/test-utils.ts index 915d18bbf2426..a98c109eb5b5d 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/lib/test-utils.ts +++ b/packages/@aws-cdk/aws-autoscaling-common/lib/test-utils.ts @@ -37,14 +37,14 @@ export function generateArbitraryIntervals(mrng: IRandomGenerator): ArbitraryInt if (mrng.nextBoolean()) { ret.splice(ret.indexOf(noChanges[0]), 1); } else { - noChanges[0].change = -1 * factor + bias; + noChanges[0] = { ...noChanges[0], change: -1 * factor + bias }; } } if (mrng.nextBoolean()) { if (mrng.nextBoolean()) { ret.splice(ret.indexOf(noChanges[1]), 1); } else { - noChanges[1].change = 1 * factor + bias; + noChanges[1] = { ...noChanges[1], change: 1 * factor + bias }; } } } else { @@ -54,8 +54,8 @@ export function generateArbitraryIntervals(mrng: IRandomGenerator): ArbitraryInt ret.splice(ret.indexOf(noChanges[0]), 1); ret.splice(ret.indexOf(noChanges[1]), 1); } else { - noChanges[0].change = -1 * factor + bias; - noChanges[1].change = 1 * factor + bias; + noChanges[0] = { ...noChanges[0], change: -1 * factor + bias }; + noChanges[1] = { ...noChanges[1], change: 1 * factor + bias }; } } @@ -77,13 +77,13 @@ export function generateArbitraryIntervals(mrng: IRandomGenerator): ArbitraryInt // scrap lower bound // okay if current interval has an upper bound AND the preceding interval has an upper bound if (ret[i].upper !== undefined && (i === 0 || ret[i - 1].upper !== undefined)) { - ret[i].lower = undefined; + ret[i] = { ...ret[i], lower: undefined }; } } else { // scrap upper bound // okay if current interval has a lower bound AND the succeeding interval has a lower bound if (ret[i].lower !== undefined && (i === ret.length - 1 || ret[i + 1].lower !== undefined)) { - ret[i].upper = undefined; + ret[i] = { ...ret[i], upper: undefined }; } } } @@ -97,6 +97,6 @@ export interface IRandomGenerator { } export interface ArbitraryIntervals { - absolute: boolean; - intervals: appscaling.ScalingInterval[]; + readonly absolute: boolean; + readonly intervals: appscaling.ScalingInterval[]; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-common/lib/types.ts b/packages/@aws-cdk/aws-autoscaling-common/lib/types.ts index b3bf0e9c9e9d9..efd2826db12c8 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/lib/types.ts +++ b/packages/@aws-cdk/aws-autoscaling-common/lib/types.ts @@ -9,7 +9,7 @@ export interface ScalingInterval { * * @default Threshold automatically derived from neighbouring intervals */ - lower?: number; + readonly lower?: number; /** * The upper bound of the interval. @@ -18,7 +18,7 @@ export interface ScalingInterval { * * @default Threshold automatically derived from neighbouring intervals */ - upper?: number; + readonly upper?: number; /** * The capacity adjustment to apply in this interval @@ -32,5 +32,5 @@ export interface ScalingInterval { * - ExactCapacity: set the capacity to this number. The number must * be positive. */ - change: number; + readonly change: number; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling-common/package-lock.json b/packages/@aws-cdk/aws-autoscaling-common/package-lock.json index ddf7d8ac18165..54917ccc204a9 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package-lock.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscaling-common", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-autoscaling-common/package.json b/packages/@aws-cdk/aws-autoscaling-common/package.json index 911ac190341d6..9f41e29648c26 100644 --- a/packages/@aws-cdk/aws-autoscaling-common/package.json +++ b/packages/@aws-cdk/aws-autoscaling-common/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscaling-common", - "version": "0.26.0", + "version": "0.28.0", "description": "Common implementation package for @aws-cdk/aws-autoscaling and @aws-cdk/aws-applicationautoscaling", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-autoscaling-common", + "module": "aws_cdk.aws_autoscaling_common" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-autoscaling-common" }, "scripts": { "build": "cdk-build", @@ -50,22 +55,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", "fast-check": "^1.7.0", - "pkglint": "^0.26.0" + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts index 3b5962c1dcb9a..efc1209770f8b 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts @@ -29,44 +29,44 @@ export interface CommonAutoScalingGroupProps { * * @default 1 */ - minCapacity?: number; + readonly minCapacity?: number; /** * Maximum number of instances in the fleet * * @default desiredCapacity */ - maxCapacity?: number; + readonly maxCapacity?: number; /** * Initial amount of instances in the fleet * @default 1 */ - desiredCapacity?: number; + readonly desiredCapacity?: number; /** * Name of SSH keypair to grant access to instances * @default No SSH access will be possible */ - keyName?: string; + readonly keyName?: string; /** * Where to place instances within the VPC */ - vpcPlacement?: ec2.VpcPlacementStrategy; + readonly vpcSubnets?: ec2.SubnetSelection; /** * SNS topic to send notifications about fleet changes * @default No fleet change notifications will be sent. */ - notificationsTopic?: sns.ITopic; + readonly notificationsTopic?: sns.ITopic; /** * Whether the instances can initiate connections to anywhere by default * * @default true */ - allowAllOutbound?: boolean; + readonly allowAllOutbound?: boolean; /** * What to do when an AutoScalingGroup's instance configuration is changed @@ -79,14 +79,14 @@ export interface CommonAutoScalingGroupProps { * * @default UpdateType.None */ - updateType?: UpdateType; + readonly updateType?: UpdateType; /** * Configuration for rolling updates * * Only used if updateType == UpdateType.RollingUpdate. */ - rollingUpdateConfiguration?: RollingUpdateConfiguration; + readonly rollingUpdateConfiguration?: RollingUpdateConfiguration; /** * Configuration for replacing updates. @@ -94,7 +94,7 @@ export interface CommonAutoScalingGroupProps { * Only used if updateType == UpdateType.ReplacingUpdate. Specifies how * many instances must signal success for the update to succeed. */ - replacingUpdateMinSuccessfulInstancesPercent?: number; + readonly replacingUpdateMinSuccessfulInstancesPercent?: number; /** * If the ASG has scheduled actions, don't reset unchanged group sizes @@ -107,14 +107,14 @@ export interface CommonAutoScalingGroupProps { * * @default true */ - ignoreUnmodifiedSizeProperties?: boolean; + readonly ignoreUnmodifiedSizeProperties?: boolean; /** * How many ResourceSignal calls CloudFormation expects before the resource is considered created * * @default 1 */ - resourceSignalCount?: number; + readonly resourceSignalCount?: number; /** * The length of time to wait for the resourceSignalCount @@ -123,14 +123,14 @@ export interface CommonAutoScalingGroupProps { * * @default 300 (5 minutes) */ - resourceSignalTimeoutSec?: number; + readonly resourceSignalTimeoutSec?: number; /** * Default scaling cooldown for this AutoScalingGroup * * @default 300 (5 minutes) */ - cooldownSeconds?: number; + readonly cooldownSeconds?: number; /** * Whether instances in the Auto Scaling Group should have public @@ -138,7 +138,7 @@ export interface CommonAutoScalingGroupProps { * * @default Use subnet setting */ - associatePublicIpAddress?: boolean; + readonly associatePublicIpAddress?: boolean; } /** @@ -148,17 +148,17 @@ export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps { /** * VPC to launch these instances in. */ - vpc: ec2.IVpcNetwork; + readonly vpc: ec2.IVpcNetwork; /** * Type of instance to launch */ - instanceType: ec2.InstanceType; + readonly instanceType: ec2.InstanceType; /** * AMI to launch */ - machineImage: ec2.IMachineImageSource; + readonly machineImage: ec2.IMachineImageSource; /** * An IAM role to associate with the instance profile assigned to this Auto Scaling Group. @@ -173,7 +173,7 @@ export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps { * * @default A role will automatically be created, it can be accessed via the `role` property */ - role?: iam.IRole; + readonly role?: iam.IRole; } /** @@ -268,6 +268,7 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup throw new Error(`Should have minCapacity (${minCapacity}) <= desiredCapacity (${desiredCapacity}) <= maxCapacity (${maxCapacity})`); } + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); const asgProps: CfnAutoScalingGroupProps = { cooldown: props.cooldownSeconds !== undefined ? `${props.cooldownSeconds}` : undefined, minSize: minCapacity.toString(), @@ -276,24 +277,24 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup launchConfigurationName: launchConfig.ref, loadBalancerNames: new cdk.Token(() => this.loadBalancerNames.length > 0 ? this.loadBalancerNames : undefined).toList(), targetGroupArns: new cdk.Token(() => this.targetGroupArns.length > 0 ? this.targetGroupArns : undefined).toList(), + notificationConfigurations: !props.notificationsTopic ? undefined : [ + { + topicArn: props.notificationsTopic.topicArn, + notificationTypes: [ + "autoscaling:EC2_INSTANCE_LAUNCH", + "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", + "autoscaling:EC2_INSTANCE_TERMINATE", + "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" + ], + } + ], + vpcZoneIdentifier: subnetIds }; - if (props.notificationsTopic) { - asgProps.notificationConfigurations = []; - asgProps.notificationConfigurations.push({ - topicArn: props.notificationsTopic.topicArn, - notificationTypes: [ - "autoscaling:EC2_INSTANCE_LAUNCH", - "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", - "autoscaling:EC2_INSTANCE_TERMINATE", - "autoscaling:EC2_INSTANCE_TERMINATE_ERROR" - ], - }); + if (!props.vpc.isPublicSubnets(subnetIds) && props.associatePublicIpAddress) { + throw new Error("To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.Public })"); } - const subnets = props.vpc.subnets(props.vpcPlacement); - asgProps.vpcZoneIdentifier = subnets.map(n => n.subnetId); - this.autoScalingGroup = new CfnAutoScalingGroup(this, 'ASG', asgProps); this.osType = machineImage.os.type; this.autoScalingGroupName = this.autoScalingGroup.autoScalingGroupName; @@ -455,7 +456,12 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup */ private applyUpdatePolicies(props: AutoScalingGroupProps) { if (props.updateType === UpdateType.ReplacingUpdate) { - this.asgUpdatePolicy.autoScalingReplacingUpdate = { willReplace: true }; + this.autoScalingGroup.options.updatePolicy = { + ...this.autoScalingGroup.options.updatePolicy, + autoScalingReplacingUpdate: { + willReplace: true + } + }; if (props.replacingUpdateMinSuccessfulInstancesPercent !== undefined) { // Yes, this goes on CreationPolicy, not as a process parameter to ReplacingUpdate. @@ -463,46 +469,38 @@ export class AutoScalingGroup extends cdk.Construct implements IAutoScalingGroup // during the update? // // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-creationpolicy.html - this.asgCreationPolicy.autoScalingCreationPolicy = { - minSuccessfulInstancesPercent: validatePercentage(props.replacingUpdateMinSuccessfulInstancesPercent) + this.autoScalingGroup.options.creationPolicy = { + ...this.autoScalingGroup.options.creationPolicy, + autoScalingCreationPolicy: { + minSuccessfulInstancesPercent: validatePercentage(props.replacingUpdateMinSuccessfulInstancesPercent) + } }; } } else if (props.updateType === UpdateType.RollingUpdate) { - this.asgUpdatePolicy.autoScalingRollingUpdate = renderRollingUpdateConfig(props.rollingUpdateConfiguration); + this.autoScalingGroup.options.updatePolicy = { + ...this.autoScalingGroup.options.updatePolicy, + autoScalingRollingUpdate: renderRollingUpdateConfig(props.rollingUpdateConfiguration) + }; } // undefined is treated as 'true' if (props.ignoreUnmodifiedSizeProperties !== false) { - this.asgUpdatePolicy.autoScalingScheduledAction = { ignoreUnmodifiedGroupSizeProperties: true }; + this.autoScalingGroup.options.updatePolicy = { + ...this.autoScalingGroup.options.updatePolicy, + autoScalingScheduledAction: { ignoreUnmodifiedGroupSizeProperties: true } + }; } if (props.resourceSignalCount !== undefined || props.resourceSignalTimeoutSec !== undefined) { - this.asgCreationPolicy.resourceSignal = { - count: props.resourceSignalCount, - timeout: props.resourceSignalTimeoutSec !== undefined ? renderIsoDuration(props.resourceSignalTimeoutSec) : undefined, + this.autoScalingGroup.options.creationPolicy = { + ...this.autoScalingGroup.options.creationPolicy, + resourceSignal: { + count: props.resourceSignalCount, + timeout: props.resourceSignalTimeoutSec !== undefined ? renderIsoDuration(props.resourceSignalTimeoutSec) : undefined, + } }; } } - - /** - * Create and return the ASG update policy - */ - private get asgUpdatePolicy() { - if (this.autoScalingGroup.options.updatePolicy === undefined) { - this.autoScalingGroup.options.updatePolicy = {}; - } - return this.autoScalingGroup.options.updatePolicy; - } - - /** - * Create and return the ASG creation policy - */ - private get asgCreationPolicy() { - if (this.autoScalingGroup.options.creationPolicy === undefined) { - this.autoScalingGroup.options.creationPolicy = {}; - } - return this.autoScalingGroup.options.creationPolicy; - } } /** @@ -536,7 +534,7 @@ export interface RollingUpdateConfiguration { * * @default 1 */ - maxBatchSize?: number; + readonly maxBatchSize?: number; /** * The minimum number of instances that must be in service before more instances are replaced. @@ -545,7 +543,7 @@ export interface RollingUpdateConfiguration { * * @default 0 */ - minInstancesInService?: number; + readonly minInstancesInService?: number; /** * The percentage of instances that must signal success for an update to succeed. @@ -561,7 +559,7 @@ export interface RollingUpdateConfiguration { * * @default 100 */ - minSuccessfulInstancesPercent?: number; + readonly minSuccessfulInstancesPercent?: number; /** * The pause time after making a change to a batch of instances. @@ -574,7 +572,7 @@ export interface RollingUpdateConfiguration { * * @default 300 if the waitOnResourceSignals property is true, otherwise 0 */ - pauseTimeSec?: number; + readonly pauseTimeSec?: number; /** * Specifies whether the Auto Scaling group waits on signals from new instances during an update. @@ -589,7 +587,7 @@ export interface RollingUpdateConfiguration { * * @default true if you specified the minSuccessfulInstancesPercent property, false otherwise */ - waitOnResourceSignals?: boolean; + readonly waitOnResourceSignals?: boolean; /** * Specifies the Auto Scaling processes to suspend during a stack update. @@ -599,7 +597,7 @@ export interface RollingUpdateConfiguration { * * @default HealthCheck, ReplaceUnhealthy, AZRebalance, AlarmNotification, ScheduledActions. */ - suspendProcesses?: ScalingProcess[]; + readonly suspendProcesses?: ScalingProcess[]; } export enum ScalingProcess { @@ -716,7 +714,7 @@ export interface CpuUtilizationScalingProps extends BaseTargetTrackingProps { /** * Target average CPU utilization across the task */ - targetUtilizationPercent: number; + readonly targetUtilizationPercent: number; } /** @@ -726,7 +724,7 @@ export interface NetworkUtilizationScalingProps extends BaseTargetTrackingProps /** * Target average bytes/seconds on each instance */ - targetBytesPerSecond: number; + readonly targetBytesPerSecond: number; } /** @@ -736,7 +734,7 @@ export interface RequestCountScalingProps extends BaseTargetTrackingProps { /** * Target average requests/seconds on each instance */ - targetRequestsPerSecond: number; + readonly targetRequestsPerSecond: number; } /** @@ -750,10 +748,10 @@ export interface MetricTargetTrackingProps extends BaseTargetTrackingProps { * target value, your ASG should scale out, and if it's lower it should * scale in. */ - metric: cloudwatch.Metric; + readonly metric: cloudwatch.Metric; /** * Value to keep the metric around */ - targetValue: number; + readonly targetValue: number; } diff --git a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts index 8db23f124ce66..0c6223552c5d6 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/lifecycle-hook.ts @@ -13,45 +13,45 @@ export interface BasicLifecycleHookProps { * * @default Automatically generated name */ - lifecycleHookName?: string; + readonly lifecycleHookName?: string; /** * The action the Auto Scaling group takes when the lifecycle hook timeout elapses or if an unexpected failure occurs. * * @default Continue */ - defaultResult?: DefaultResult; + readonly defaultResult?: DefaultResult; /** * Maximum time between calls to RecordLifecycleActionHeartbeat for the hook * * If the lifecycle hook times out, perform the action in DefaultResult. */ - heartbeatTimeoutSec?: number; + readonly heartbeatTimeoutSec?: number; /** * The state of the Amazon EC2 instance to which you want to attach the lifecycle hook. */ - lifecycleTransition: LifecycleTransition; + readonly lifecycleTransition: LifecycleTransition; /** * Additional data to pass to the lifecycle hook target * * @default No metadata */ - notificationMetadata?: string; + readonly notificationMetadata?: string; /** * The target of the lifecycle hook */ - notificationTarget: api.ILifecycleHookTarget; + readonly notificationTarget: api.ILifecycleHookTarget; /** * The role that allows publishing to the notification target * * @default A role is automatically created */ - role?: iam.IRole; + readonly role?: iam.IRole; } /** @@ -61,7 +61,7 @@ export interface LifecycleHookProps extends BasicLifecycleHookProps { /** * The AutoScalingGroup to add the lifecycle hook to */ - autoScalingGroup: IAutoScalingGroup; + readonly autoScalingGroup: IAutoScalingGroup; } export class LifecycleHook extends cdk.Construct implements api.ILifecycleHook { diff --git a/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts b/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts index 4f04b79969055..81d1b3187b6bf 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/scheduled-action.ts @@ -16,21 +16,21 @@ export interface BasicScheduledActionProps { * * @example 0 8 * * ? */ - schedule: string; + readonly schedule: string; /** * When this scheduled action becomes active. * * @default The rule is activate immediately */ - startTime?: Date + readonly startTime?: Date /** * When this scheduled action expires. * * @default The rule never expires. */ - endTime?: Date; + readonly endTime?: Date; /** * The new minimum capacity. @@ -41,7 +41,7 @@ export interface BasicScheduledActionProps { * * @default No new minimum capacity */ - minCapacity?: number; + readonly minCapacity?: number; /** * The new maximum capacity. @@ -52,7 +52,7 @@ export interface BasicScheduledActionProps { * * @default No new maximum capacity */ - maxCapacity?: number; + readonly maxCapacity?: number; /** * The new desired capacity. @@ -61,7 +61,7 @@ export interface BasicScheduledActionProps { * * At least one of maxCapacity, minCapacity, or desiredCapacity must be supplied. */ - desiredCapacity?: number; + readonly desiredCapacity?: number; } /** @@ -71,7 +71,7 @@ export interface ScheduledActionProps extends BasicScheduledActionProps { /** * The AutoScalingGroup to apply the scheduled actions to */ - autoScalingGroup: IAutoScalingGroup; + readonly autoScalingGroup: IAutoScalingGroup; } const CRON_PART = '(\\*|\\?|[0-9]+)'; diff --git a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts index 158a08b0fb1be..2588626593bb9 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-action.ts @@ -10,28 +10,28 @@ export interface StepScalingActionProps { /** * The auto scaling group */ - autoScalingGroup: IAutoScalingGroup; + readonly autoScalingGroup: IAutoScalingGroup; /** * Period after a scaling completes before another scaling activity can start. * * @default The default cooldown configured on the AutoScalingGroup */ - cooldownSeconds?: number; + readonly cooldownSeconds?: number; /** * Estimated time until a newly launched instance can send metrics to CloudWatch. * * @default Same as the cooldown */ - estimatedInstanceWarmupSeconds?: number; + readonly estimatedInstanceWarmupSeconds?: number; /** * How the adjustment numbers are interpreted * * @default ChangeInCapacity */ - adjustmentType?: AdjustmentType; + readonly adjustmentType?: AdjustmentType; /** * Minimum absolute number to adjust capacity with as result of percentage scaling. @@ -41,14 +41,14 @@ export interface StepScalingActionProps { * * @default No minimum scaling effect */ - minAdjustmentMagnitude?: number; + readonly minAdjustmentMagnitude?: number; /** * The aggregation type for the CloudWatch metrics. * * @default Average */ - metricAggregationType?: MetricAggregationType; + readonly metricAggregationType?: MetricAggregationType; } /** @@ -164,7 +164,7 @@ export interface AdjustmentTier { * * Can be positive or negative. */ - adjustment: number; + readonly adjustment: number; /** * Lower bound where this scaling tier applies. @@ -174,7 +174,7 @@ export interface AdjustmentTier { * * @default -Infinity if this is the first tier, otherwise the upperBound of the previous tier */ - lowerBound?: number; + readonly lowerBound?: number; /** * Upper bound where this scaling tier applies @@ -184,5 +184,5 @@ export interface AdjustmentTier { * * @default +Infinity */ - upperBound?: number; + readonly upperBound?: number; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts index 063212910493a..e85e6f3546107 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/step-scaling-policy.ts @@ -8,35 +8,35 @@ export interface BasicStepScalingPolicyProps { /** * Metric to scale on. */ - metric: cloudwatch.Metric; + readonly metric: cloudwatch.Metric; /** * The intervals for scaling. * * Maps a range of metric values to a particular scaling behavior. */ - scalingSteps: ScalingInterval[]; + readonly scalingSteps: ScalingInterval[]; /** * How the adjustment numbers inside 'intervals' are interpreted. * * @default ChangeInCapacity */ - adjustmentType?: AdjustmentType; + readonly adjustmentType?: AdjustmentType; /** * Grace period after scaling activity. * * @default Default cooldown period on your AutoScalingGroup */ - cooldownSeconds?: number; + readonly cooldownSeconds?: number; /** * Estimated time until a newly launched instance can send metrics to CloudWatch. * * @default Same as the cooldown */ - estimatedInstanceWarmupSeconds?: number; + readonly estimatedInstanceWarmupSeconds?: number; /** * Minimum absolute number to adjust capacity with as result of percentage scaling. @@ -46,14 +46,14 @@ export interface BasicStepScalingPolicyProps { * * @default No minimum scaling effect */ - minAdjustmentMagnitude?: number; + readonly minAdjustmentMagnitude?: number; } export interface StepScalingPolicyProps extends BasicStepScalingPolicyProps { /** * The auto scaling group */ - autoScalingGroup: IAutoScalingGroup; + readonly autoScalingGroup: IAutoScalingGroup; } /** @@ -168,7 +168,7 @@ export interface ScalingInterval { * * @default Threshold automatically derived from neighbouring intervals */ - lower?: number; + readonly lower?: number; /** * The upper bound of the interval. @@ -177,7 +177,7 @@ export interface ScalingInterval { * * @default Threshold automatically derived from neighbouring intervals */ - upper?: number; + readonly upper?: number; /** * The capacity adjustment to apply in this interval @@ -191,5 +191,5 @@ export interface ScalingInterval { * - ExactCapacity: set the capacity to this number. The number must * be positive. */ - change: number; + readonly change: number; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts index 7c9be49f4133d..6e8b54a9b059d 100644 --- a/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-autoscaling/lib/target-tracking-scaling-policy.ts @@ -22,21 +22,21 @@ export interface BaseTargetTrackingProps { * * @default false */ - disableScaleIn?: boolean; + readonly disableScaleIn?: boolean; /** * Period after a scaling completes before another scaling activity can start. * * @default The default cooldown configured on the AutoScalingGroup */ - cooldownSeconds?: number; + readonly cooldownSeconds?: number; /** * Estimated time until a newly launched instance can send metrics to CloudWatch. * * @default Same as the cooldown */ - estimatedInstanceWarmupSeconds?: number; + readonly estimatedInstanceWarmupSeconds?: number; } /** @@ -46,7 +46,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin /** * The target value for the metric. */ - targetValue: number; + readonly targetValue: number; /** * A predefined metric for application autoscaling @@ -56,7 +56,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin * * Exactly one of customMetric or predefinedMetric must be specified. */ - predefinedMetric?: PredefinedMetric; + readonly predefinedMetric?: PredefinedMetric; /** * A custom metric for application autoscaling @@ -66,7 +66,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin * * Exactly one of customMetric or predefinedMetric must be specified. */ - customMetric?: cloudwatch.Metric; + readonly customMetric?: cloudwatch.Metric; /** * The resource label associated with the predefined metric @@ -78,7 +78,7 @@ export interface BasicTargetTrackingScalingPolicyProps extends BaseTargetTrackin * * @default No resource label */ - resourceLabel?: string; + readonly resourceLabel?: string; } /** @@ -90,7 +90,7 @@ export interface TargetTrackingScalingPolicyProps extends BasicTargetTrackingSca /* * The auto scaling group */ - autoScalingGroup: IAutoScalingGroup; + readonly autoScalingGroup: IAutoScalingGroup; } export class TargetTrackingScalingPolicy extends cdk.Construct { diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index c8f430febac73..18174f95c5626 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscaling", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::AutoScaling", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-autoscaling", + "module": "aws_cdk.aws_autoscaling" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-autoscaling" }, "scripts": { "build": "cdk-build", @@ -54,33 +59,33 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.26.0", - "@aws-cdk/aws-autoscaling-common": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling-api": "^0.28.0", + "@aws-cdk/aws-autoscaling-common": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling-api": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -92,4 +97,4 @@ "export:@aws-cdk/aws-autoscaling.IAutoScalingGroup" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts index 8871e86c958ad..f833fe4eec5b7 100644 --- a/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts +++ b/packages/@aws-cdk/aws-autoscaling/test/test.auto-scaling-group.ts @@ -418,6 +418,8 @@ export = { minCapacity: 0, maxCapacity: 0, desiredCapacity: 0, + + vpcSubnets: { subnetType: ec2.SubnetType.Public }, associatePublicIpAddress: true, }); @@ -428,6 +430,25 @@ export = { )); test.done(); }, + 'association of public IP address requires public subnet'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = mockVpc(stack); + + // WHEN + test.throws(() => { + new autoscaling.AutoScalingGroup(stack, 'MyStack', { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.M4, ec2.InstanceSize.Micro), + machineImage: new ec2.AmazonLinuxImage(), + vpc, + minCapacity: 0, + maxCapacity: 0, + desiredCapacity: 0, + associatePublicIpAddress: true, + }); + }); + test.done(); + }, 'allows disassociation of public IP address'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-autoscalingplans/package.json b/packages/@aws-cdk/aws-autoscalingplans/package.json index f807956afc1d7..41cb8aebf2caa 100644 --- a/packages/@aws-cdk/aws-autoscalingplans/package.json +++ b/packages/@aws-cdk/aws-autoscalingplans/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-autoscalingplans", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::AutoScalingPlans", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-autoscalingplans", + "module": "aws_cdk.aws_autoscalingplans" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-autoscalingplans" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-batch/package.json b/packages/@aws-cdk/aws-batch/package.json index c8f8d602066df..b9375a70d98d1 100644 --- a/packages/@aws-cdk/aws-batch/package.json +++ b/packages/@aws-cdk/aws-batch/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-batch", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Batch", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-batch", + "module": "aws_cdk.aws_batch" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-batch" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-budgets/package.json b/packages/@aws-cdk/aws-budgets/package.json index 9f1948b05c692..07909810526b1 100644 --- a/packages/@aws-cdk/aws-budgets/package.json +++ b/packages/@aws-cdk/aws-budgets/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-budgets", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Budgets", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-budgets", + "module": "aws_cdk.aws_budgets" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-budgets" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-certificatemanager/.npmignore b/packages/@aws-cdk/aws-certificatemanager/.npmignore index b757d55c46996..41a9c0cf03dfa 100644 --- a/packages/@aws-cdk/aws-certificatemanager/.npmignore +++ b/packages/@aws-cdk/aws-certificatemanager/.npmignore @@ -13,4 +13,7 @@ dist # Include .jsii !.jsii -*.snk \ No newline at end of file +*.snk + +# Don't pack node_modules directories of the lambdas in there. They should only be dev dependencies. +lambda-packages/dns_validated_certificate_handler/node_modules diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json index 659ed05dfc00a..22d07be2586a4 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/package-lock.json @@ -1,6 +1,6 @@ { "name": "dns_validated_certificate_handler", - "version": "0.25.3", + "version": "0.27.0", "lockfileVersion": 1, "requires": true, "dependencies": { 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 9cf63dcd868c0..d64ccdac72715 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 @@ -1,7 +1,7 @@ { "name": "dns_validated_certificate_handler", "private": true, - "version": "0.26.0", + "version": "0.28.0", "description": "This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project.", "main": "lib/index.js", "directories": { diff --git a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/test/handler.test.js b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/test/handler.test.js index 707553730568c..93a1de226f311 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/test/handler.test.js +++ b/packages/@aws-cdk/aws-certificatemanager/lambda-packages/dns_validated_certificate_handler/test/handler.test.js @@ -1,7 +1,7 @@ 'use strict'; const AWS = require('aws-sdk-mock'); -const LambdaTester = require('lambda-tester'); +const LambdaTester = require('lambda-tester').noVersionCheck(); const sinon = require('sinon'); const handler = require('..'); const nock = require('nock'); diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts index 8812049458687..5cd32e4e096ab 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/certificate.ts @@ -21,7 +21,7 @@ export interface CertificateImportProps { /** * The certificate's ARN */ - certificateArn: string; + readonly certificateArn: string; } /** @@ -33,14 +33,14 @@ export interface CertificateProps { * * May contain wildcards, such as ``*.domain.com``. */ - domainName: string; + readonly domainName: string; /** * Alternative domain names on your certificate. * * Use this to register alternative domain names that represent the same site. */ - subjectAlternativeNames?: string[]; + readonly subjectAlternativeNames?: string[]; /** * What validation domain to use for every requested domain. @@ -49,7 +49,7 @@ export interface CertificateProps { * * @default Apex domain is used for every domain that's not overridden. */ - validationDomains?: {[domainName: string]: string}; + readonly validationDomains?: {[domainName: string]: string}; } /** diff --git a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts index f1e0b265aa6a1..6fad959dc5c4b 100644 --- a/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/lib/dns-validated-certificate.ts @@ -11,7 +11,7 @@ export interface DnsValidatedCertificateProps extends CertificateProps { * Route 53 Hosted Zone used to perform DNS validation of the request. The zone * must be authoritative for the domain name specified in the Certificate Request. */ - hostedZone: route53.IHostedZone; + readonly hostedZone: route53.IHostedZone; } /** @@ -83,7 +83,7 @@ export class DnsValidatedCertificate extends cdk.Construct implements ICertifica protected validate(): string[] { const errors: string[] = []; // Ensure the zone name is a parent zone of the certificate domain name - if (!this.domainName.endsWith('.' + this.normalizedZoneName)) { + if (this.domainName !== this.normalizedZoneName && !this.domainName.endsWith('.' + this.normalizedZoneName)) { errors.push(`DNS zone ${this.normalizedZoneName} is not authoritative for certificate domain name ${this.domainName}`); } return errors; diff --git a/packages/@aws-cdk/aws-certificatemanager/package.json b/packages/@aws-cdk/aws-certificatemanager/package.json index 25a77408bc2c8..e72c9cae9230e 100644 --- a/packages/@aws-cdk/aws-certificatemanager/package.json +++ b/packages/@aws-cdk/aws-certificatemanager/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-certificatemanager", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::CertificateManager", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-certificatemanager", + "module": "aws_cdk.aws_certificatemanager" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-certificatemanager" }, "scripts": { "build": "cdk-build", @@ -54,27 +59,27 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-certificatemanager/test/test.dns-validated-certificate.ts b/packages/@aws-cdk/aws-certificatemanager/test/test.dns-validated-certificate.ts index 7920e0d41b9e9..8d5bbb720f6d4 100644 --- a/packages/@aws-cdk/aws-certificatemanager/test/test.dns-validated-certificate.ts +++ b/packages/@aws-cdk/aws-certificatemanager/test/test.dns-validated-certificate.ts @@ -112,4 +112,31 @@ export = { test.throws(() => expect(stack), /DNS zone hello.com is not authoritative for certificate domain name example.com/); test.done(); }, + + 'test root certificate'(test: Test) { + const stack = new Stack(); + + const exampleDotComZone = new PublicHostedZone(stack, 'ExampleDotCom', { + zoneName: 'example.com' + }); + + new DnsValidatedCertificate(stack, 'Cert', { + domainName: 'example.com', + hostedZone: exampleDotComZone, + }); + + expect(stack).to(haveResource('AWS::CloudFormation::CustomResource', { + ServiceToken: { + 'Fn::GetAtt': [ + 'CertCertificateRequestorFunction98FDF273', + 'Arn' + ] + }, + DomainName: 'example.com', + HostedZoneId: { + Ref: 'ExampleDotCom4D1B83AA' + } + })); + test.done(); + }, }; diff --git a/packages/@aws-cdk/aws-cloud9/package.json b/packages/@aws-cdk/aws-cloud9/package.json index 084b95029f858..dfaeff798084e 100644 --- a/packages/@aws-cdk/aws-cloud9/package.json +++ b/packages/@aws-cdk/aws-cloud9/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloud9", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Cloud9", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-cloud9", + "module": "aws_cdk.aws_cloud9" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-cloud9" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudformation/README.md b/packages/@aws-cdk/aws-cloudformation/README.md index 8ea59a126634c..20a82664f5aba 100644 --- a/packages/@aws-cdk/aws-cloudformation/README.md +++ b/packages/@aws-cdk/aws-cloudformation/README.md @@ -2,31 +2,6 @@ This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. -### CodePipeline Actions for CloudFormation - -This module contains Actions that allows you to deploy to CloudFormation from AWS CodePipeline. - -For example, the following code fragment defines a pipeline that automatically deploys a CloudFormation template -directly from a CodeCommit repository, with a manual approval step in between to confirm the changes: - -[example Pipeline to deploy CloudFormation](../aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts) - -See [the AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline.html) -for more details about using CloudFormation in CodePipeline. - -#### Actions defined by this package - -This package defines the following actions: - -* **PipelineCreateUpdateStackAction** - Deploy a CloudFormation template directly from the pipeline. The indicated stack is created, - or updated if it already exists. If the stack is in a failure state, deployment will fail (unless `replaceOnFailure` - is set to `true`, in which case it will be destroyed and recreated). -* **PipelineDeleteStackAction** - Delete the stack with the given name. -* **PipelineCreateReplaceChangeSetAction** - Prepare a change set to be applied later. You will typically use change sets if you want - to manually verify the changes that are being staged, or if you want to separate the people (or system) preparing the - changes from the people (or system) applying the changes. -* **PipelineExecuteChangeSetAction** - Execute a change set prepared previously. - ### Custom Resources Custom Resources are CloudFormation resources that are implemented by diff --git a/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts b/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts new file mode 100644 index 0000000000000..55a4cb1b29f75 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts @@ -0,0 +1,31 @@ +/** + * Capabilities that affect whether CloudFormation is allowed to change IAM resources + */ +export enum CloudFormationCapabilities { + /** + * No IAM Capabilities + * + * Pass this capability if you wish to block the creation IAM resources. + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities + */ + None = '', + + /** + * Capability to create anonymous IAM resources + * + * Pass this capability if you're only creating anonymous resources. + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities + */ + AnonymousIAM = 'CAPABILITY_IAM', + + /** + * Capability to create named IAM resources. + * + * Pass this capability if you're creating IAM resources that have physical + * names. + * + * `CloudFormationCapabilities.NamedIAM` implies `CloudFormationCapabilities.IAM`; you don't have to pass both. + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities + */ + NamedIAM = 'CAPABILITY_NAMED_IAM', +} diff --git a/packages/@aws-cdk/aws-cloudformation/lib/custom-resource.ts b/packages/@aws-cdk/aws-cloudformation/lib/custom-resource.ts index d204190d659e4..37a4b325725c1 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/custom-resource.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/custom-resource.ts @@ -19,19 +19,19 @@ export interface CustomResourceProps { * * Optional, exactly one of lamdaProvider or topicProvider must be set. */ - lambdaProvider?: lambda.IFunction; + readonly lambdaProvider?: lambda.IFunction; /** * The SNS Topic for the provider that implements this custom resource. * * Optional, exactly one of lamdaProvider or topicProvider must be set. */ - topicProvider?: sns.ITopic; + readonly topicProvider?: sns.ITopic; /** * Properties to pass to the Lambda */ - properties?: Properties; + readonly properties?: Properties; /** * For custom resources, you can specify AWS::CloudFormation::CustomResource @@ -52,7 +52,7 @@ export interface CustomResourceProps { * @see * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html#aws-cfn-resource-type-name */ - resourceType?: string; + readonly resourceType?: string; } /** diff --git a/packages/@aws-cdk/aws-cloudformation/lib/index.ts b/packages/@aws-cdk/aws-cloudformation/lib/index.ts index 2a9dc1178f2fe..3d4971d16542b 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/index.ts @@ -1,5 +1,5 @@ +export * from './cloud-formation-capabilities'; export * from './custom-resource'; -export * from './pipeline-actions'; // AWS::CloudFormation CloudFormation Resources: export * from './cloudformation.generated'; diff --git a/packages/@aws-cdk/aws-cloudformation/package.json b/packages/@aws-cdk/aws-cloudformation/package.json index 45abb74d4c033..42fd9cf838ca3 100644 --- a/packages/@aws-cdk/aws-cloudformation/package.json +++ b/packages/@aws-cdk/aws-cloudformation/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudformation", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS CloudFormation", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-cloudformation", + "module": "aws_cdk.aws_cloudformation" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-cloudformation" }, "scripts": { "build": "cdk-build", @@ -41,11 +46,6 @@ "cdk-build": { "cloudformation": "AWS::CloudFormation" }, - "nyc": { - "statements": 50, - "lines": 50, - "branches": 20 - }, "keywords": [ "aws", "cdk", @@ -59,29 +59,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@types/lodash": "^4.14.118", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "lodash": "^4.17.11", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -92,4 +88,4 @@ "construct-ctor:@aws-cdk/aws-cloudformation.PipelineCloudFormationDeployAction." ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index 1071ab4eda817..2a466978e6530 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -272,7 +272,7 @@ export interface Behavior { * * @default true */ - compress?: boolean; + readonly compress?: boolean; /** * If this behavior is the default behavior for the distribution. @@ -280,7 +280,7 @@ export interface Behavior { * You must specify exactly one default distribution per CloudFront distribution. * The default behavior is allowed to omit the "path" property. */ - isDefaultBehavior?: boolean; + readonly isDefaultBehavior?: boolean; /** * Trusted signers is how CloudFront allows you to serve private content. @@ -288,7 +288,7 @@ export interface Behavior { * * If you pass a non empty value, all requests for this behavior must be signed (no public access will be allowed) */ - trustedSigners?: string[]; + readonly trustedSigners?: string[]; /** * @@ -299,28 +299,28 @@ export interface Behavior { * @default 86400 (1 day) * */ - defaultTtlSeconds?: number; + readonly defaultTtlSeconds?: number; /** * The method this CloudFront distribution responds do. * * @default GET_HEAD */ - allowedMethods?: CloudFrontAllowedMethods; + readonly allowedMethods?: CloudFrontAllowedMethods; /** * The path this behavior responds to. * Required for all non-default behaviors. (The default behavior implicitly has "*" as the path pattern. ) * */ - pathPattern?: string; + readonly pathPattern?: string; /** * Which methods are cached by CloudFront by default. * * @default GET_HEAD */ - cachedMethods?: CloudFrontAllowedCachedMethods; + readonly cachedMethods?: CloudFrontAllowedCachedMethods; /** * The values CloudFront will forward to the origin when making a request. @@ -328,13 +328,13 @@ export interface Behavior { * @default none (no cookies - no headers) * */ - forwardedValues?: CfnDistribution.ForwardedValuesProperty; + readonly forwardedValues?: CfnDistribution.ForwardedValuesProperty; /** * The minimum amount of time that you want objects to stay in the cache * before CloudFront queries your origin. */ - minTtlSeconds?: number; + readonly minTtlSeconds?: number; /** * The max amount of time you want objects to stay in the cache @@ -342,7 +342,7 @@ export interface Behavior { * * @default 31536000 (one year) */ - maxTtlSeconds?: number; + readonly maxTtlSeconds?: number; } @@ -350,19 +350,19 @@ export interface ErrorConfiguration { /** * The error code matched from the origin */ - originErrorCode: number, + readonly originErrorCode: number; /** * The error code that is sent to the caller. */ - respondWithErrorCode: number, + readonly respondWithErrorCode: number; /** * The path to service instead */ - respondWithPage: string, + readonly respondWithPage: string; /** * How long before this error is retried. */ - cacheTtl?: number + readonly cacheTtl?: number; } export interface CloudFrontWebDistributionProps { @@ -372,52 +372,52 @@ export interface CloudFrontWebDistributionProps { * * @default none */ - aliasConfiguration?: AliasConfiguration; + readonly aliasConfiguration?: AliasConfiguration; /** * A comment for this distribution in the cloud front console. */ - comment?: string; + readonly comment?: string; /** * The default object to serve. * * @default "index.html" */ - defaultRootObject?: string; + readonly defaultRootObject?: string; /** * If your distribution should have IPv6 enabled. * * @default true */ - enableIpV6?: boolean; + readonly enableIpV6?: boolean; /** * The max supported HTTP Versions. * * @default HttpVersion.HTTP2 */ - httpVersion?: HttpVersion; + readonly httpVersion?: HttpVersion; /** * The price class for the distribution (this impacts how many locations CloudFront uses for your distribution, and billing) * * @default PriceClass_100: the cheapest option for CloudFront is picked by default. */ - priceClass?: PriceClass; + readonly priceClass?: PriceClass; /** * The default viewer policy for incoming clients. * * @default RedirectToHTTPs */ - viewerProtocolPolicy?: ViewerProtocolPolicy; + readonly viewerProtocolPolicy?: ViewerProtocolPolicy; /** * The origin configurations for this distribution. Behaviors are a part of the origin. */ - originConfigs: SourceConfiguration[]; + readonly originConfigs: SourceConfiguration[]; /** * Optional - if we should enable logging. @@ -426,17 +426,17 @@ export interface CloudFrontWebDistributionProps { * * @default: no logging is enabled by default. */ - loggingConfig?: LoggingConfiguration; + readonly loggingConfig?: LoggingConfiguration; /** * How CloudFront should handle requests that are no successful (eg PageNotFound) */ - errorConfigurations?: CfnDistribution.CustomErrorResponseProperty[]; + readonly errorConfigurations?: CfnDistribution.CustomErrorResponseProperty[]; /** * Optional AWS WAF WebACL to associate with this CloudFront distribution */ - webACLId?: string; + readonly webACLId?: string; } @@ -444,7 +444,7 @@ export interface CloudFrontWebDistributionProps { * Internal only - just adds the originId string to the Behavior */ interface BehaviorWithOrigin extends Behavior { - targetOriginId: string; + readonly targetOriginId: string; } /** @@ -526,7 +526,7 @@ export class CloudFrontWebDistribution extends cdk.Construct implements route53. constructor(scope: cdk.Construct, id: string, props: CloudFrontWebDistributionProps) { super(scope, id); - const distributionConfig: CfnDistribution.DistributionConfigProperty = { + let distributionConfig: CfnDistribution.DistributionConfigProperty = { comment: props.comment, enabled: true, defaultRootObject: props.defaultRootObject !== undefined ? props.defaultRootObject : "index.html", @@ -565,34 +565,32 @@ export class CloudFrontWebDistribution extends cdk.Construct implements route53. const originProperty: CfnDistribution.OriginProperty = { id: originId, - domainName: originConfig.s3OriginSource ? - originConfig.s3OriginSource.s3BucketSource.domainName : - originConfig.customOriginSource!.domainName, + domainName: originConfig.s3OriginSource + ? originConfig.s3OriginSource.s3BucketSource.domainName + : originConfig.customOriginSource!.domainName, originPath: originConfig.originPath, originCustomHeaders: originHeaders.length > 0 ? originHeaders : undefined, + s3OriginConfig: originConfig.s3OriginSource && originConfig.s3OriginSource.originAccessIdentity + ? { originAccessIdentity: `origin-access-identity/cloudfront/${originConfig.s3OriginSource.originAccessIdentity.ref}` } + : originConfig.s3OriginSource + ? { } + : undefined, + customOriginConfig: originConfig.customOriginSource + ? { + httpPort: originConfig.customOriginSource.httpPort || 80, + httpsPort: originConfig.customOriginSource.httpsPort || 443, + originKeepaliveTimeout: originConfig.customOriginSource.originKeepaliveTimeoutSeconds || 5, + originReadTimeout: originConfig.customOriginSource.originReadTimeoutSeconds || 30, + originProtocolPolicy: originConfig.customOriginSource.originProtocolPolicy || OriginProtocolPolicy.HttpsOnly, + originSslProtocols: originConfig.customOriginSource.allowedOriginSSLVersions || [OriginSslPolicy.TLSv1_2] + } + : undefined }; - if (originConfig.s3OriginSource && originConfig.s3OriginSource.originAccessIdentity) { - originProperty.s3OriginConfig = { - originAccessIdentity: `origin-access-identity/cloudfront/${originConfig.s3OriginSource.originAccessIdentity.ref}` - }; - } else if (originConfig.s3OriginSource) { - originProperty.s3OriginConfig = {}; - } - - if (originConfig.customOriginSource) { - originProperty.customOriginConfig = { - httpPort: originConfig.customOriginSource.httpPort || 80, - httpsPort: originConfig.customOriginSource.httpsPort || 443, - originKeepaliveTimeout: originConfig.customOriginSource.originKeepaliveTimeoutSeconds || 5, - originReadTimeout: originConfig.customOriginSource.originReadTimeoutSeconds || 30, - originProtocolPolicy: originConfig.customOriginSource.originProtocolPolicy || OriginProtocolPolicy.HttpsOnly, - originSslProtocols: originConfig.customOriginSource.allowedOriginSSLVersions || [OriginSslPolicy.TLSv1_2] - }; - } for (const behavior of originConfig.behaviors) { behaviors.push({ ...behavior, targetOriginId: originId }); } + origins.push(originProperty); originIndex++; } @@ -602,13 +600,18 @@ export class CloudFrontWebDistribution extends cdk.Construct implements route53. throw new Error(`Origin ${origin.domainName} is missing either S3OriginConfig or CustomOriginConfig. At least 1 must be specified.`); } }); - distributionConfig.origins = origins; + distributionConfig = { + ...distributionConfig, + origins + }; const defaultBehaviors = behaviors.filter(behavior => behavior.isDefaultBehavior); if (defaultBehaviors.length !== 1) { throw new Error("There can only be one default behavior across all sources. [ One default behavior per distribution ]."); } - distributionConfig.defaultCacheBehavior = this.toBehavior(defaultBehaviors[0], props.viewerProtocolPolicy); + + distributionConfig = { ...distributionConfig, defaultCacheBehavior: this.toBehavior(defaultBehaviors[0], props.viewerProtocolPolicy) }; + const otherBehaviors: CfnDistribution.CacheBehaviorProperty[] = []; for (const behavior of behaviors.filter(b => !b.isDefaultBehavior)) { if (!behavior.pathPattern) { @@ -616,40 +619,51 @@ export class CloudFrontWebDistribution extends cdk.Construct implements route53. } otherBehaviors.push(this.toBehavior(behavior, props.viewerProtocolPolicy) as CfnDistribution.CacheBehaviorProperty); } - distributionConfig.cacheBehaviors = otherBehaviors; + + distributionConfig = { ...distributionConfig, cacheBehaviors: otherBehaviors }; if (props.aliasConfiguration) { - distributionConfig.aliases = props.aliasConfiguration.names; - distributionConfig.viewerCertificate = { - acmCertificateArn: props.aliasConfiguration.acmCertRef, - sslSupportMethod: props.aliasConfiguration.sslMethod || SSLMethod.SNI, - minimumProtocolVersion: props.aliasConfiguration.securityPolicy + const minimumProtocolVersion = props.aliasConfiguration.securityPolicy; + const sslSupportMethod = props.aliasConfiguration.sslMethod || SSLMethod.SNI; + const acmCertificateArn = props.aliasConfiguration.acmCertRef; + + distributionConfig = { + ...distributionConfig, + aliases: props.aliasConfiguration.names, + viewerCertificate: { + acmCertificateArn, + sslSupportMethod, + minimumProtocolVersion + } }; - if (distributionConfig.viewerCertificate.minimumProtocolVersion !== undefined) { - const validProtocols = this.VALID_SSL_PROTOCOLS[distributionConfig.viewerCertificate.sslSupportMethod!.toString()]; + if (minimumProtocolVersion !== undefined) { + const validProtocols = this.VALID_SSL_PROTOCOLS[sslSupportMethod.toString()]; if (validProtocols === undefined) { - throw new Error(`Invalid sslMethod. ${distributionConfig.viewerCertificate.sslSupportMethod!.toString()} is not fully implemented yet.`); + throw new Error(`Invalid sslMethod. ${sslSupportMethod.toString()} is not fully implemented yet.`); } - if (validProtocols.indexOf(distributionConfig.viewerCertificate.minimumProtocolVersion.toString()) === -1) { + if (validProtocols.indexOf(minimumProtocolVersion.toString()) === -1) { // tslint:disable-next-line:max-line-length - throw new Error(`${distributionConfig.viewerCertificate.minimumProtocolVersion} is not compabtible with sslMethod ${distributionConfig.viewerCertificate.sslSupportMethod}.\n\tValid Protocols are: ${validProtocols.join(", ")}`); + throw new Error(`${minimumProtocolVersion} is not compabtible with sslMethod ${sslSupportMethod}.\n\tValid Protocols are: ${validProtocols.join(", ")}`); } } } else { - distributionConfig.viewerCertificate = { - cloudFrontDefaultCertificate: true + distributionConfig = { ...distributionConfig, + viewerCertificate: { cloudFrontDefaultCertificate: true } }; } if (props.loggingConfig) { this.loggingBucket = props.loggingConfig.bucket || new s3.Bucket(this, `LoggingBucket`); - distributionConfig.logging = { - bucket: this.loggingBucket.domainName, - includeCookies: props.loggingConfig.includeCookies || false, - prefix: props.loggingConfig.prefix + distributionConfig = { + ...distributionConfig, + logging: { + bucket: this.loggingBucket.domainName, + includeCookies: props.loggingConfig.includeCookies || false, + prefix: props.loggingConfig.prefix + } }; } diff --git a/packages/@aws-cdk/aws-cloudfront/package-lock.json b/packages/@aws-cdk/aws-cloudfront/package-lock.json index dea362a95d46c..6923e7bc7fa55 100644 --- a/packages/@aws-cdk/aws-cloudfront/package-lock.json +++ b/packages/@aws-cdk/aws-cloudfront/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudfront", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index 27d1d97999a17..2faba6030aeb2 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudfront", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS CloudFront", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-cloudfront", + "module": "aws_cdk.aws_cloudfront" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-cloudfront" }, "scripts": { "build": "cdk-build", @@ -54,28 +59,28 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-certificatemanager": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-certificatemanager": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts b/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts index 5109ae9284cb6..acb6d39b441b9 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/test.basic.ts @@ -15,81 +15,81 @@ export = { originConfigs: [ { originHeaders: { - "X-Custom-Header": "somevalue", + "X-Custom-Header": "somevalue", }, customOriginSource: { - domainName: "myorigin.com", + domainName: "myorigin.com", }, behaviors: [ - { - isDefaultBehavior: true, - } + { + isDefaultBehavior: true, + } ], } ] }); expect(stack).toMatch( - { - "Resources": { - "AnAmazingWebsiteProbablyCFDistribution47E3983B": { - "Type": "AWS::CloudFront::Distribution", - "Properties": { - "DistributionConfig": { - "CacheBehaviors": [], - "DefaultCacheBehavior": { - "AllowedMethods": [ - "GET", - "HEAD" - ], - "CachedMethods": [ - "GET", - "HEAD" - ], - "ForwardedValues": { - "Cookies": { - "Forward": "none" - }, - "QueryString": false - }, - "TargetOriginId": "origin1", - "ViewerProtocolPolicy": "redirect-to-https" - }, - "DefaultRootObject": "index.html", - "Enabled": true, - "HttpVersion": "http2", - "IPV6Enabled": true, - "Origins": [ - { - "CustomOriginConfig": { - "HTTPPort": 80, - "HTTPSPort": 443, - "OriginKeepaliveTimeout": 5, - "OriginProtocolPolicy": "https-only", - "OriginReadTimeout": 30, - "OriginSSLProtocols": [ - "TLSv1.2" - ] - }, - "DomainName": "myorigin.com", - "Id": "origin1", - "OriginCustomHeaders": [ - { - "HeaderName": "X-Custom-Header", - "HeaderValue": "somevalue" + { + "Resources": { + "AnAmazingWebsiteProbablyCFDistribution47E3983B": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "CacheBehaviors": [], + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "ForwardedValues": { + "Cookies": { + "Forward": "none" + }, + "QueryString": false + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "CustomOriginConfig": { + "HTTPPort": 80, + "HTTPSPort": 443, + "OriginKeepaliveTimeout": 5, + "OriginProtocolPolicy": "https-only", + "OriginReadTimeout": 30, + "OriginSSLProtocols": [ + "TLSv1.2" + ] + }, + "DomainName": "myorigin.com", + "Id": "origin1", + "OriginCustomHeaders": [ + { + "HeaderName": "X-Custom-Header", + "HeaderValue": "somevalue" + } + ] + } + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + } + } } - ] } - ], - "PriceClass": "PriceClass_100", - "ViewerCertificate": { - "CloudFrontDefaultCertificate": true } - } - } } - } - } ); test.done(); @@ -117,55 +117,55 @@ export = { expect(stack).toMatch({ "Resources": { "Bucket83908E77": { - "Type": "AWS::S3::Bucket", - "DeletionPolicy": "Retain", + "Type": "AWS::S3::Bucket", + "DeletionPolicy": "Retain", }, "AnAmazingWebsiteProbablyCFDistribution47E3983B": { - "Type": "AWS::CloudFront::Distribution", - "Properties": { - "DistributionConfig": { - "DefaultRootObject": "index.html", - "Origins": [ - { - "DomainName": { - "Fn::GetAtt": [ - "Bucket83908E77", - "DomainName" - ] - }, - "Id": "origin1", - "S3OriginConfig": {} - } - ], - "ViewerCertificate": { - "CloudFrontDefaultCertificate": true - }, - "PriceClass": "PriceClass_100", - "DefaultCacheBehavior": { - "AllowedMethods": [ - "GET", - "HEAD" - ], - "CachedMethods": [ - "GET", - "HEAD" - ], - "TargetOriginId": "origin1", - "ViewerProtocolPolicy": "redirect-to-https", - "ForwardedValues": { - "QueryString": false, - "Cookies" : { "Forward" : "none"} + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultRootObject": "index.html", + "Origins": [ + { + "DomainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "DomainName" + ] + }, + "Id": "origin1", + "S3OriginConfig": {} + } + ], + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + }, + "PriceClass": "PriceClass_100", + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https", + "ForwardedValues": { + "QueryString": false, + "Cookies": { "Forward": "none" } + } + }, + "Enabled": true, + "IPV6Enabled": true, + "HttpVersion": "http2", + "CacheBehaviors": [] } - }, - "Enabled": true, - "IPV6Enabled": true, - "HttpVersion": "http2", - "CacheBehaviors": [] } } - } } - }); + }); test.done(); }, @@ -182,7 +182,7 @@ export = { behaviors: [ { isDefaultBehavior: true, - trustedSigners: [ "1234" ], + trustedSigners: ["1234"], }, ] } @@ -192,58 +192,58 @@ export = { expect(stack).toMatch({ "Resources": { "Bucket83908E77": { - "Type": "AWS::S3::Bucket", - "DeletionPolicy": "Retain", + "Type": "AWS::S3::Bucket", + "DeletionPolicy": "Retain", }, "AnAmazingWebsiteProbablyCFDistribution47E3983B": { - "Type": "AWS::CloudFront::Distribution", - "Properties": { - "DistributionConfig": { - "DefaultRootObject": "index.html", - "Origins": [ - { - "DomainName": { - "Fn::GetAtt": [ - "Bucket83908E77", - "DomainName" - ] - }, - "Id": "origin1", - "S3OriginConfig": {} + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultRootObject": "index.html", + "Origins": [ + { + "DomainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "DomainName" + ] + }, + "Id": "origin1", + "S3OriginConfig": {} + } + ], + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + }, + "PriceClass": "PriceClass_100", + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https", + "ForwardedValues": { + "QueryString": false, + "Cookies": { "Forward": "none" } + }, + "TrustedSigners": [ + "1234" + ] + }, + "Enabled": true, + "IPV6Enabled": true, + "HttpVersion": "http2", + "CacheBehaviors": [] } - ], - "ViewerCertificate": { - "CloudFrontDefaultCertificate": true - }, - "PriceClass": "PriceClass_100", - "DefaultCacheBehavior": { - "AllowedMethods": [ - "GET", - "HEAD" - ], - "CachedMethods": [ - "GET", - "HEAD" - ], - "TargetOriginId": "origin1", - "ViewerProtocolPolicy": "redirect-to-https", - "ForwardedValues": { - "QueryString": false, - "Cookies" : { "Forward" : "none"} - }, - "TrustedSigners" : [ - "1234" - ] - }, - "Enabled": true, - "IPV6Enabled": true, - "HttpVersion": "http2", - "CacheBehaviors": [] } } - } } - }); + }); test.done(); }, diff --git a/packages/@aws-cdk/aws-cloudtrail/lib/index.ts b/packages/@aws-cdk/aws-cloudtrail/lib/index.ts index e6f0c2cd52744..398be10eb39be 100644 --- a/packages/@aws-cdk/aws-cloudtrail/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudtrail/lib/index.ts @@ -15,13 +15,13 @@ export interface CloudTrailProps { * events are delivered to any trail that includes global services, and are logged as occurring in US East (N. Virginia) Region. * @default true */ - includeGlobalServiceEvents?: boolean; + readonly includeGlobalServiceEvents?: boolean; /** * Whether or not this trail delivers log files from multiple regions to a single S3 bucket for a single account. * @default true */ - isMultiRegionTrail?: boolean; + readonly isMultiRegionTrail?: boolean; /** * When an event occurs in your account, CloudTrail evaluates whether the event matches the settings for your trails. @@ -37,7 +37,7 @@ export interface CloudTrailProps { * If managementEvents is undefined, we'll not log management events by default. * @param managementEvents the management configuration type to log */ - managementEvents?: ReadWriteType; + readonly managementEvents?: ReadWriteType; /** * To determine whether a log file was modified, deleted, or unchanged after CloudTrail delivered it, @@ -47,41 +47,41 @@ export interface CloudTrailProps { * You can use the AWS CLI to validate the files in the location where CloudTrail delivered them. * @default true */ - enableFileValidation?: boolean; + readonly enableFileValidation?: boolean; /** * If CloudTrail pushes logs to CloudWatch Logs in addition to S3. * Disabled for cost out of the box. * @default false */ - sendToCloudWatchLogs?: boolean; + readonly sendToCloudWatchLogs?: boolean; /** * How long to retain logs in CloudWatchLogs. Ignored if sendToCloudWatchLogs is false - * @default LogRetention.OneYear + * @default logs.RetentionDays.OneYear */ - cloudWatchLogsRetentionTimeDays?: LogRetention; + readonly cloudWatchLogsRetentionTimeDays?: logs.RetentionDays; /** The AWS Key Management Service (AWS KMS) key ID that you want to use to encrypt CloudTrail logs. * @default none */ - kmsKey?: kms.IEncryptionKey; + readonly kmsKey?: kms.IEncryptionKey; /** The name of an Amazon SNS topic that is notified when new log files are published. * @default none */ - snsTopic?: string; // TODO: fix to use L2 SNS + readonly snsTopic?: string; // TODO: fix to use L2 SNS /** * The name of the trail. We recoomend customers do not set an explicit name. * @default the CloudFormation generated neme */ - trailName?: string; + readonly trailName?: string; /** An Amazon S3 object key prefix that precedes the name of all log files. * @default none */ - s3KeyPrefix?: string; + readonly s3KeyPrefix?: string; } export enum ReadWriteType { @@ -90,26 +90,6 @@ export enum ReadWriteType { All = "All" } -// TODO: This belongs in a CWL L2 -export enum LogRetention { - OneDay = 1, - ThreeDays = 3, - FiveDays = 5, - OneWeek = 7, - TwoWeeks = 14, - OneMonth = 30, - TwoMonths = 60, - ThreeMonths = 90, - FourMonths = 120, - FiveMonths = 150, - HalfYear = 180, - OneYear = 365, - FourHundredDays = 400, - EighteenMonths = 545, - TwoYears = 731, - FiveYears = 1827, - TenYears = 3653 -} /** * Cloud trail allows you to log events that happen in your AWS account * For example: @@ -145,7 +125,7 @@ export class CloudTrail extends cdk.Construct { let logsRole: iam.IRole | undefined; if (props.sendToCloudWatchLogs) { logGroup = new logs.CfnLogGroup(this, "LogGroup", { - retentionInDays: props.cloudWatchLogsRetentionTimeDays || LogRetention.OneYear + retentionInDays: props.cloudWatchLogsRetentionTimeDays || logs.RetentionDays.OneYear }); logsRole = new iam.Role(this, 'LogsRole', { assumedBy: new iam.ServicePrincipal(cloudTrailPrincipal) }); @@ -228,14 +208,14 @@ export interface AddS3EventSelectorOptions { * * @default ReadWriteType.All */ - readWriteType?: ReadWriteType; + readonly readWriteType?: ReadWriteType; /** * Specifies whether the event selector includes management events for the trail. * * @default true */ - includeManagementEvents?: boolean; + readonly includeManagementEvents?: boolean; } interface EventSelector { diff --git a/packages/@aws-cdk/aws-cloudtrail/package-lock.json b/packages/@aws-cdk/aws-cloudtrail/package-lock.json index 9c2f0ace6cc3c..6055f554ff583 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package-lock.json +++ b/packages/@aws-cdk/aws-cloudtrail/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudtrail", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index 7704ad357fb4e..dda523a6fc725 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudtrail", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS CloudTrail", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-cloudtrail", + "module": "aws_cdk.aws_cloudtrail" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-cloudtrail" }, "scripts": { "build": "cdk-build", @@ -54,27 +59,28 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", "colors": "^1.2.1", - "pkglint": "^0.26.0" + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-logs": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts index 1197811a5f2bf..b9a25981ccc88 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts @@ -1,7 +1,8 @@ -import { expect, haveResource, not } from '@aws-cdk/assert'; +import { expect, haveResource, not, SynthUtils } from '@aws-cdk/assert'; +import { RetentionDays } from '@aws-cdk/aws-logs'; import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { CloudTrail, LogRetention, ReadWriteType } from '../lib'; +import { CloudTrail, ReadWriteType } from '../lib'; const ExpectedBucketPolicyProperties = { PolicyDocument: { @@ -66,7 +67,7 @@ export = { expect(stack).to(haveResource("AWS::S3::Bucket")); expect(stack).to(haveResource("AWS::S3::BucketPolicy", ExpectedBucketPolicyProperties)); expect(stack).to(not(haveResource("AWS::Logs::LogGroup"))); - const trail: any = stack._toCloudFormation().Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; test.deepEqual(trail.DependsOn, ['MyAmazingCloudTrailS3Policy39C120B0']); test.done(); }, @@ -97,7 +98,7 @@ export = { PolicyName: logsRolePolicyName, Roles: [{ Ref: 'MyAmazingCloudTrailLogsRoleF2CCF977' }], })); - const trail: any = stack._toCloudFormation().Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; test.deepEqual(trail.DependsOn, [logsRolePolicyName, logsRoleName, 'MyAmazingCloudTrailS3Policy39C120B0']); test.done(); }, @@ -105,7 +106,7 @@ export = { const stack = getTestStack(); new CloudTrail(stack, 'MyAmazingCloudTrail', { sendToCloudWatchLogs: true, - cloudWatchLogsRetentionTimeDays: LogRetention.OneWeek + cloudWatchLogsRetentionTimeDays: RetentionDays.OneWeek }); expect(stack).to(haveResource("AWS::CloudTrail::Trail")); @@ -116,7 +117,7 @@ export = { expect(stack).to(haveResource("AWS::Logs::LogGroup", { RetentionInDays: 7 })); - const trail: any = stack._toCloudFormation().Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; test.deepEqual(trail.DependsOn, [logsRolePolicyName, logsRoleName, 'MyAmazingCloudTrailS3Policy39C120B0']); test.done(); }, @@ -134,7 +135,7 @@ export = { expect(stack).to(not(haveResource("AWS::Logs::LogGroup"))); expect(stack).to(not(haveResource("AWS::IAM::Role"))); - const trail: any = stack._toCloudFormation().Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; test.equals(trail.Properties.EventSelectors.length, 1); const selector = trail.Properties.EventSelectors[0]; test.equals(selector.ReadWriteType, null, "Expected selector read write type to be undefined"); @@ -160,7 +161,7 @@ export = { expect(stack).to(not(haveResource("AWS::Logs::LogGroup"))); expect(stack).to(not(haveResource("AWS::IAM::Role"))); - const trail: any = stack._toCloudFormation().Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; test.equals(trail.Properties.EventSelectors.length, 1); const selector = trail.Properties.EventSelectors[0]; test.equals(selector.ReadWriteType, "ReadOnly", "Expected selector read write type to be Read"); @@ -179,7 +180,7 @@ export = { new CloudTrail(stack, 'MyAmazingCloudTrail', { managementEvents: ReadWriteType.WriteOnly }); - const trail: any = stack._toCloudFormation().Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; test.equals(trail.Properties.EventSelectors.length, 1); const selector = trail.Properties.EventSelectors[0]; test.equals(selector.ReadWriteType, "WriteOnly", "Expected selector read write type to be All"); diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index 009834d0682b8..ae35de6547665 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -14,7 +14,7 @@ export interface AlarmProps extends MetricAlarmProps { * Metric objects can be obtained from most resources, or you can construct * custom Metric objects by instantiating one. */ - metric: Metric; + readonly metric: Metric; } /** @@ -233,35 +233,35 @@ export interface AlarmMetricJson { /** * The dimensions to apply to the alarm */ - dimensions?: Dimension[]; + readonly dimensions?: Dimension[]; /** * Namespace of the metric */ - namespace: string; + readonly namespace: string; /** * Name of the metric */ - metricName: string; + readonly metricName: string; /** * How many seconds to aggregate over */ - period: number; + readonly period: number; /** * Simple aggregation function to use */ - statistic?: Statistic; + readonly statistic?: Statistic; /** * Percentile aggregation function to use */ - extendedStatistic?: string; + readonly extendedStatistic?: string; /** * The unit of the alarm */ - unit?: Unit; + readonly unit?: Unit; } diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts index 195d1babf382a..cb35768f65c78 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/dashboard.ts @@ -9,7 +9,7 @@ export interface DashboardProps { * * @default Automatically generated name */ - dashboardName?: string; + readonly dashboardName?: string; } /** diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts index a97fe451b1357..fb28630a4bae3 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/graph.ts @@ -11,28 +11,28 @@ export interface MetricWidgetProps { /** * Title for the graph */ - title?: string; + readonly title?: string; /** * The region the metrics of this graph should be taken from * * @default Current region */ - region?: string; + readonly region?: string; /** * Width of the widget, in a grid of 24 units wide * * @default 6 */ - width?: number; + readonly width?: number; /** * Height of the widget * * @default Depends on the type of widget */ - height?: number; + readonly height?: number; } /** @@ -42,14 +42,14 @@ export interface AlarmWidgetProps extends MetricWidgetProps { /** * The alarm to show */ - alarm: Alarm; + readonly alarm: Alarm; /** * Range of left Y axis * * @default 0..automatic */ - leftAxisRange?: YAxisRange; + readonly leftAxisRange?: YAxisRange; } /** @@ -92,41 +92,41 @@ export interface GraphWidgetProps extends MetricWidgetProps { /** * Metrics to display on left Y axis */ - left?: Metric[]; + readonly left?: Metric[]; /** * Metrics to display on right Y axis */ - right?: Metric[]; + readonly right?: Metric[]; /** * Annotations for the left Y axis */ - leftAnnotations?: HorizontalAnnotation[]; + readonly leftAnnotations?: HorizontalAnnotation[]; /** * Annotations for the right Y axis */ - rightAnnotations?: HorizontalAnnotation[]; + readonly rightAnnotations?: HorizontalAnnotation[]; /** * Whether the graph should be shown as stacked lines */ - stacked?: boolean; + readonly stacked?: boolean; /** * Range of left Y axis * * @default 0..automatic */ - leftAxisRange?: YAxisRange; + readonly leftAxisRange?: YAxisRange; /** * Range of right Y axis * * @default 0..automatic */ - rightAxisRange?: YAxisRange; + readonly rightAxisRange?: YAxisRange; } /** @@ -173,7 +173,7 @@ export interface SingleValueWidgetProps extends MetricWidgetProps { /** * Metrics to display */ - metrics: Metric[]; + readonly metrics: Metric[]; } /** @@ -213,14 +213,14 @@ export interface YAxisRange { * * @default Automatic */ - min?: number; + readonly min?: number; /** * The maximum value * * @default Automatic */ - max?: number; + readonly max?: number; } /** @@ -230,35 +230,35 @@ export interface HorizontalAnnotation { /** * The value of the annotation */ - value: number; + readonly value: number; /** * Label for the annotation * * @default No label */ - label?: string; + readonly label?: string; /** * Hex color code to be used for the annotation * * @default Automatic color */ - color?: string; + readonly color?: string; /** * Add shading above or below the annotation * * @default No shading */ - fill?: Shading; + readonly fill?: Shading; /** * Whether the annotation is visible * * @default true */ - visible?: boolean; + readonly visible?: boolean; } export enum Shading { diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/layout.ts b/packages/@aws-cdk/aws-cloudwatch/lib/layout.ts index d52de9a5758c0..f86d813bbddfa 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/layout.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/layout.ts @@ -106,14 +106,14 @@ export interface SpacerProps { * * @default 1 */ - width?: number; + readonly width?: number; /** * Height of the spacer * * @default: 1 */ - height?: number; + readonly height?: number; } /** diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts index 7d36a33148ce3..08e401470e925 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/metric.ts @@ -14,17 +14,17 @@ export interface MetricProps { * * @default No dimensions */ - dimensions?: DimensionHash; + readonly dimensions?: DimensionHash; /** * Namespace of the metric. */ - namespace: string; + readonly namespace: string; /** * Name of the metric. */ - metricName: string; + readonly metricName: string; /** * The period over which the specified statistic is applied. @@ -33,7 +33,7 @@ export interface MetricProps { * * @default 300 */ - periodSec?: number; + readonly periodSec?: number; /** * What function to use for aggregating. @@ -49,22 +49,22 @@ export interface MetricProps { * * @default Average */ - statistic?: string; + readonly statistic?: string; /** * Unit for the metric that is associated with the alarm */ - unit?: Unit; + readonly unit?: Unit; /** * Label for this metric when added to a Graph in a Dashboard */ - label?: string; + readonly label?: string; /** * Color for this metric when added to a Graph in a Dashboard */ - color?: string; + readonly color?: string; } /** @@ -85,14 +85,14 @@ export class Metric { /** * Grant permissions to the given identity to write metrics. * - * @param identity The IAM identity to give permissions to. + * @param grantee The IAM identity to give permissions to. */ - public static grantPutMetricData(identity?: iam.IPrincipal) { - if (!identity) { return; } - - identity.addToPolicy(new iam.PolicyStatement() - .addAllResources() - .addAction("cloudwatch:PutMetricData")); + public static grantPutMetricData(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['cloudwatch:PutMetricData'], + resourceArns: ['*'] + }); } public readonly dimensions?: DimensionHash; @@ -189,12 +189,12 @@ export interface Dimension { /** * Name of the dimension */ - name: string; + readonly name: string; /** * Value of the dimension */ - value: any; + readonly value: any; } /** @@ -250,7 +250,7 @@ export interface MetricCustomization { * * @default No dimensions */ - dimensions?: DimensionHash; + readonly dimensions?: DimensionHash; /** * The period over which the specified statistic is applied. @@ -259,7 +259,7 @@ export interface MetricCustomization { * * @default 300 */ - periodSec?: number; + readonly periodSec?: number; /** * What function to use for aggregating. @@ -275,22 +275,22 @@ export interface MetricCustomization { * * @default Average */ - statistic?: string; + readonly statistic?: string; /** * Unit for the metric that is associated with the alarm */ - unit?: Unit; + readonly unit?: Unit; /** * Label for this metric when added to a Graph in a Dashboard */ - label?: string; + readonly label?: string; /** * Color for this metric when added to a Graph in a Dashboard */ - color?: string; + readonly color?: string; } /** @@ -304,7 +304,7 @@ export interface MetricAlarmProps { * * @default 300 */ - periodSec?: number; + readonly periodSec?: number; /** * What function to use for aggregating. @@ -320,59 +320,59 @@ export interface MetricAlarmProps { * * @default Average */ - statistic?: string; + readonly statistic?: string; /** * Name of the alarm * * @default Automatically generated name */ - alarmName?: string; + readonly alarmName?: string; /** * Description for the alarm * * @default No description */ - alarmDescription?: string; + readonly alarmDescription?: string; /** * Comparison to use to check if metric is breaching * * @default GreaterThanOrEqualToThreshold */ - comparisonOperator?: ComparisonOperator; + readonly comparisonOperator?: ComparisonOperator; /** * The value against which the specified statistic is compared. */ - threshold: number; + readonly threshold: number; /** * The number of periods over which data is compared to the specified threshold. */ - evaluationPeriods: number; + readonly evaluationPeriods: number; /** * Specifies whether to evaluate the data and potentially change the alarm state if there are too few data points to be statistically significant. * * Used only for alarms that are based on percentiles. */ - evaluateLowSampleCountPercentile?: string; + readonly evaluateLowSampleCountPercentile?: string; /** * Sets how this alarm is to handle missing data points. * * @default TreatMissingData.Missing */ - treatMissingData?: TreatMissingData; + readonly treatMissingData?: TreatMissingData; /** * Whether the actions for this alarm are enabled * * @default true */ - actionsEnabled?: boolean; + readonly actionsEnabled?: boolean; /** * The number of datapoints that must be breaching to trigger the alarm. This is used only if you are setting an "M @@ -383,7 +383,7 @@ export interface MetricAlarmProps { * * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarm-evaluation */ - datapointsToAlarm?: number; + readonly datapointsToAlarm?: number; } function ifUndefined(x: T | undefined, def: T | undefined): T | undefined { diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/text.ts b/packages/@aws-cdk/aws-cloudwatch/lib/text.ts index 0e3e09c821211..b052ba1fbfe7d 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/text.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/text.ts @@ -7,21 +7,21 @@ export interface TextWidgetProps { /** * The text to display, in MarkDown format */ - markdown: string; + readonly markdown: string; /** * Width of the widget, in a grid of 24 units wide * * @default 6 */ - width?: number; + readonly width?: number; /** * Height of the widget * * @default 2 */ - height?: number; + readonly height?: number; } /** diff --git a/packages/@aws-cdk/aws-cloudwatch/package.json b/packages/@aws-cdk/aws-cloudwatch/package.json index b373aea53be13..282a4ee65f053 100644 --- a/packages/@aws-cdk/aws-cloudwatch/package.json +++ b/packages/@aws-cdk/aws-cloudwatch/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cloudwatch", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS CloudWatch", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-cloudwatch", + "module": "aws_cdk.aws_cloudwatch" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-cloudwatch" }, "scripts": { "build": "cdk-build", @@ -54,22 +59,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 54489fa39f39a..10a60f51bf0a0 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -90,9 +90,9 @@ Example: const gitHubSource = new codebuild.GitHubSource({ owner: 'awslabs', repo: 'aws-cdk', - oauthToken: new cdk.SecretParameter(this, 'GitHubOAuthToken', { - ssmParameter: 'my-github-token', - }).value, + oauthToken: new secretsmanager.SecretString(this, 'GitHubOAuthToken', { + secretId: 'my-github-token', + }).stringValue, webhook: true, // optional, default: false }); ``` @@ -165,87 +165,6 @@ const rule = project.onStateChange('BuildStateChange'); rule.addTarget(lambdaFunction); ``` -## Using a CodeBuild Project as an AWS CodePipeline action - -Example of a Project used in CodePipeline, alongside CodeCommit: - -```typescript -import codebuild = require('@aws-cdk/aws-codebuild'); -import codecommit = require('@aws-cdk/aws-codecommit'); -import codepipeline = require('@aws-cdk/aws-codepipeline'); - -const repository = new codecommit.Repository(this, 'MyRepository', { - repositoryName: 'MyRepository', -}); -const project = new codebuild.PipelineProject(this, 'MyProject'); - -const sourceAction = repository.toCodePipelineSourceAction({ actionName: 'CodeCommit' }); -const buildAction = new codebuild.PipelineBuildAction({ - actionName: 'CodeBuild', - project, - inputArtifact: sourceAction.outputArtifact, -}); - -new codepipeline.Pipeline(this, 'MyPipeline', { - stages: [ - { - name: 'Source', - actions: [sourceAction], - }, - { - name: 'Build', - actions: [buildAction], - }, - ], -}); -``` - -The `PipelineProject` utility class is a simple sugar around the `Project` -class, it's equivalent to: - -```ts -const project = new codebuild.Project(this, 'MyProject', { - source: new codebuild.CodePipelineSource(), - artifacts: new codebuild.CodePipelineBuildArtifacts(), - // rest of the properties from PipelineProject are passed unchanged... -} -``` - -You can also create the action from the Project directly: - -```ts -// equivalent to the code above: -const buildAction = project.toCodePipelineBuildAction({ - actionName: 'CodeBuild', - inputArtifact: sourceAction.outputArtifact, -}); -``` - -In addition to the build Action, there is also a test Action. It works very -similarly to the build Action, the only difference is that the test Action does -not always produce an output artifact. - -Examples: - -```typescript -const testAction = new codebuild.PipelineTestAction({ - actionName: 'IntegrationTest', - project, - inputArtifact: sourceAction.outputArtifact, - // outputArtifactName is optional - if you don't specify it, - // the Action will have an undefined `outputArtifact` property - outputArtifactName: 'IntegrationTestOutput', -}); - -// equivalent to the code above: -const testAction = project.toCodePipelineTestAction({ - actionName: 'IntegrationTest', - inputArtifact: sourceAction.outputArtifact, - // of course, this property is optional here as well - outputArtifactName: 'IntegrationTestOutput', -}); -``` - ## Secondary sources and artifacts CodeBuild Projects can get their sources from multiple places, and produce @@ -255,16 +174,16 @@ multiple outputs. For example: const project = new codebuild.Project(this, 'MyProject', { secondarySources: [ new codebuild.CodeCommitSource({ - identifier: 'source2', - repository: repo, + identifier: 'source2', + repository: repo, }), ], secondaryArtifacts: [ new codebuild.S3BucketBuildArtifacts({ - identifier: 'artifact2', - bucket: bucket, - path: 'some/path', - name: 'file.zip', + identifier: 'artifact2', + bucket: bucket, + path: 'some/path', + name: 'file.zip', }), ], // ... @@ -290,91 +209,71 @@ const project = new codebuild.Project(this, 'MyProject', { buildSpec: { version: '0.2', phases: { - build: { - commands: [ - 'cd $CODEBUILD_SRC_DIR_source2', - 'touch output2.txt', - ], - }, - }, - artifacts: { - 'secondary-artifacts': { - 'artifact2': { - 'base-directory': '$CODEBUILD_SRC_DIR_source2', - 'files': [ - 'output2.txt', + build: { + commands: [ + 'cd $CODEBUILD_SRC_DIR_source2', + 'touch output2.txt', ], }, }, + artifacts: { + 'secondary-artifacts': { + 'artifact2': { + 'base-directory': '$CODEBUILD_SRC_DIR_source2', + 'files': [ + 'output2.txt', + ], + }, + }, }, }, }); ``` -### Multiple inputs and outputs in CodePipeline +### Definition of VPC configuration in CodeBuild Project -When you want to have multiple inputs and/or outputs for a Project used in a -Pipeline, instead of using the `secondarySources` and `secondaryArtifacts` -properties, you need to use the `additionalInputArtifacts` and -`additionalOutputArtifactNames` properties of the CodeBuild CodePipeline -Actions. Example: +Typically, resources in an VPC are not accessible by AWS CodeBuild. To enable +access, you must provide additional VPC-specific configuration information as +part of your CodeBuild project configuration. This includes the VPC ID, the +VPC subnet IDs, and the VPC security group IDs. VPC-enabled builds are then +able to access resources inside your VPC. -```ts -const sourceAction1 = repository1.toCodePipelineSourceAction({ - actionName: 'Source1', -}); -const sourceAction2 = repository2.toCodePipelineSourceAction({ - actionName: 'Source2', - outputArtifactName: 'source2', -}); +For further Information see https://docs.aws.amazon.com/codebuild/latest/userguide/vpc-support.html -const buildAction = project.toCodePipelineBuildAction({ - actionName: 'Build', - inputArtifact: sourceAction1.outputArtifact, - outputArtifactName: 'artifact1', // for better buildspec readability - see below - additionalInputArtifacts: [ - sourceAction2.outputArtifact, // this is where 'source2' comes from - ], - additionalOutputArtifactNames: [ - 'artifact2', - ], -}); -``` +**Use Cases** +VPC connectivity from AWS CodeBuild builds makes it possible to: + +* Run integration tests from your build against data in an Amazon RDS database that's isolated on a private subnet. +* Query data in an Amazon ElastiCache cluster directly from tests. +* Interact with internal web services hosted on Amazon EC2, Amazon ECS, or services that use internal Elastic Load Balancing. +* Retrieve dependencies from self-hosted, internal artifact repositories, such as PyPI for Python, Maven for Java, and npm for Node.js. +* Access objects in an Amazon S3 bucket configured to allow access through an Amazon VPC endpoint only. +* Query external web services that require fixed IP addresses through the Elastic IP address of the NAT gateway or NAT instance associated with your subnet(s). + +Your builds can access any resource that's hosted in your VPC. + +**Enable Amazon VPC Access in your CodeBuild Projects** + +Include these settings in your VPC configuration: -**Note**: when a CodeBuild Action in a Pipeline has more than one output, it -only uses the `secondary-artifacts` field of the buildspec, never the -primary output specification directly under `artifacts`. Because of that, it -pays to name even your primary output artifact on the Pipeline, like we did -above, so that you know what name to use in the buildspec. +* For VPC ID, choose the VPC that CodeBuild uses. +* For Subnets, choose a private subnet SubnetSelection with NAT translation that includes or has routes to the resources used CodeBuild. +* For Security Groups, choose the security groups that CodeBuild uses to allow access to resources in the VPCs. -Example buildspec for the above project: +For example: ```ts -const project = new codebuild.PipelineProject(this, 'MyProject', { - buildSpec: { - version: '0.2', - phases: { - build: { - commands: [ - // By default, you're in a directory with the contents of the repository from sourceAction1. - // Use the CODEBUILD_SRC_DIR_source2 environment variable - // to get a path to the directory with the contents of the second input repository. - ], - }, - }, - artifacts: { - 'secondary-artifacts': { - 'artifact1': { - // primary Action output artifact, - // available as buildAction.outputArtifact - }, - 'artifact2': { - // additional output artifact, - // available as buildAction.additionalOutputArtifact('artifact2') - }, - }, - }, - }, - // ... +const stack = new cdk.Stack(app, 'aws-cdk-codebuild-project-vpc'); +const vpc = new ec2.VpcNetwork(stack, 'MyVPC'); +const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { + allowAllOutbound: true, + description: 'Example', + groupName: 'MySecurityGroup', + vpc: vpc, }); -``` +new Project(stack, 'MyProject', { + buildScriptAsset: new assets.ZipDirectoryAsset(stack, 'Bundle', { path: 'script_bundle' }), + securityGroups: [securityGroup], + vpc: vpc +}); +``` \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts b/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts index e8d44528bf015..53c9e835a2766 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/artifacts.ts @@ -10,7 +10,7 @@ export interface BuildArtifactsProps { * The artifact identifier. * This property is required on secondary artifacts. */ - identifier?: string; + readonly identifier?: string; } /** @@ -24,6 +24,9 @@ export abstract class BuildArtifacts { this.identifier = props.identifier; } + /** + * @internal + */ public _bind(_project: Project) { return; } @@ -79,14 +82,14 @@ export interface S3BucketBuildArtifactsProps extends BuildArtifactsProps { /** * The name of the output bucket. */ - bucket: s3.IBucket; + readonly bucket: s3.IBucket; /** * The path inside of the bucket for the build output .zip file or folder. * If a value is not specified, then build output will be stored at the root of the * bucket (or under the directory if `includeBuildId` is set to true). */ - path?: string; + readonly path?: string; /** * The name of the build output ZIP file or folder inside the bucket. @@ -94,7 +97,7 @@ export interface S3BucketBuildArtifactsProps extends BuildArtifactsProps { * The full S3 object key will be "//" or * "/" depending on whether `includeBuildId` is set to true. */ - name: string; + readonly name: string; /** * Indicates if the build ID should be included in the path. If this is set to true, @@ -102,7 +105,7 @@ export interface S3BucketBuildArtifactsProps extends BuildArtifactsProps { * * @default true */ - includeBuildId?: boolean; + readonly includeBuildId?: boolean; /** * If this is true, all build output will be packaged into a single .zip file. @@ -110,7 +113,7 @@ export interface S3BucketBuildArtifactsProps extends BuildArtifactsProps { * * @default true - files will be archived */ - packageZip?: boolean; + readonly packageZip?: boolean; } /** @@ -123,8 +126,11 @@ export class S3BucketBuildArtifacts extends BuildArtifacts { super(props); } + /** + * @internal + */ public _bind(project: Project) { - this.props.bucket.grantReadWrite(project.role); + this.props.bucket.grantReadWrite(project); } protected toArtifactsProperty(): any { diff --git a/packages/@aws-cdk/aws-codebuild/lib/index.ts b/packages/@aws-cdk/aws-codebuild/lib/index.ts index 77571a2a92c05..7fbe814d26429 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/index.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/index.ts @@ -1,4 +1,3 @@ -export * from './pipeline-actions'; export * from './pipeline-project'; export * from './project'; export * from './source'; diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 8f1ade3a851d0..e178ffd3287c9 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -1,6 +1,7 @@ import assets = require('@aws-cdk/assets'); import { DockerImageAsset, DockerImageAssetProps } from '@aws-cdk/assets-docker'; import cloudwatch = require('@aws-cdk/aws-cloudwatch'); +import ec2 = require('@aws-cdk/aws-ec2'); import ecr = require('@aws-cdk/aws-ecr'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); @@ -9,17 +10,13 @@ import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import { BuildArtifacts, CodePipelineBuildArtifacts, NoBuildArtifacts } from './artifacts'; import { CfnProject } from './codebuild.generated'; -import { - CommonPipelineBuildActionProps, CommonPipelineTestActionProps, - PipelineBuildAction, PipelineTestAction -} from './pipeline-actions'; import { BuildSource, NoSource, SourceType } from './source'; const CODEPIPELINE_TYPE = 'CODEPIPELINE'; const S3_BUCKET_ENV = 'SCRIPT_S3_BUCKET'; const S3_KEY_ENV = 'SCRIPT_S3_KEY'; -export interface IProject extends cdk.IConstruct, events.IEventRuleTarget { +export interface IProject extends cdk.IConstruct, events.IEventRuleTarget, iam.IGrantable { /** The ARN of this Project. */ readonly projectArn: string; @@ -29,22 +26,6 @@ export interface IProject extends cdk.IConstruct, events.IEventRuleTarget { /** The IAM service Role of this Project. Undefined for imported Projects. */ readonly role?: iam.IRole; - /** - * Convenience method for creating a new {@link PipelineBuildAction CodeBuild build Action}. - * - * @param props the construction properties of the new Action - * @returns the newly created {@link PipelineBuildAction CodeBuild build Action} - */ - toCodePipelineBuildAction(props: CommonPipelineBuildActionProps): PipelineBuildAction; - - /** - * Convenience method for creating a new {@link PipelineTestAction CodeBuild test Action}. - * - * @param props the construction properties of the new Action - * @returns the newly created {@link PipelineTestAction CodeBuild test Action} - */ - toCodePipelineTestAction(props: CommonPipelineTestActionProps): PipelineTestAction; - /** * Defines a CloudWatch event rule triggered when the build project state * changes. You can filter specific build status events using an event @@ -161,7 +142,7 @@ export interface ProjectImportProps { * The human-readable name of the CodeBuild Project we're referencing. * The Project must be in the same account and region as the root Stack. */ - projectName: string; + readonly projectName: string; } /** @@ -175,13 +156,15 @@ export interface ProjectImportProps { * use the {@link import} method. */ export abstract class ProjectBase extends cdk.Construct implements IProject { + public abstract readonly grantPrincipal: iam.IPrincipal; + /** The ARN of this Project. */ public abstract readonly projectArn: string; /** The human-visible name of this Project. */ public abstract readonly projectName: string; - /** The IAM service Role of this Project. Undefined for imported Projects. */ + /** The IAM service Role of this Project. */ public abstract readonly role?: iam.IRole; /** A role used by CloudWatch events to trigger a build */ @@ -189,20 +172,6 @@ export abstract class ProjectBase extends cdk.Construct implements IProject { public abstract export(): ProjectImportProps; - public toCodePipelineBuildAction(props: CommonPipelineBuildActionProps): PipelineBuildAction { - return new PipelineBuildAction({ - ...props, - project: this, - }); - } - - public toCodePipelineTestAction(props: CommonPipelineTestActionProps): PipelineTestAction { - return new PipelineTestAction({ - ...props, - project: this, - }); - } - /** * Defines a CloudWatch event rule triggered when the build project state * changes. You can filter specific build status events using an event @@ -229,8 +198,8 @@ export abstract class ProjectBase extends cdk.Construct implements IProject { const rule = new events.EventRule(this, name, options); rule.addTarget(target); rule.addEventPattern({ - source: [ 'aws.codebuild' ], - detailType: [ 'CodeBuild Build State Change' ], + source: ['aws.codebuild'], + detailType: ['CodeBuild Build State Change'], detail: { 'project-name': [ this.projectName @@ -250,8 +219,8 @@ export abstract class ProjectBase extends cdk.Construct implements IProject { const rule = new events.EventRule(this, name, options); rule.addTarget(target); rule.addEventPattern({ - source: [ 'aws.codebuild' ], - detailType: [ 'CodeBuild Build Phase Change' ], + source: ['aws.codebuild'], + detailType: ['CodeBuild Build Phase Change'], detail: { 'project-name': [ this.projectName @@ -268,7 +237,7 @@ export abstract class ProjectBase extends cdk.Construct implements IProject { const rule = this.onStateChange(name, target, options); rule.addEventPattern({ detail: { - 'build-status': [ 'IN_PROGRESS' ] + 'build-status': ['IN_PROGRESS'] } }); return rule; @@ -281,7 +250,7 @@ export abstract class ProjectBase extends cdk.Construct implements IProject { const rule = this.onStateChange(name, target, options); rule.addEventPattern({ detail: { - 'build-status': [ 'FAILED' ] + 'build-status': ['FAILED'] } }); return rule; @@ -294,7 +263,7 @@ export abstract class ProjectBase extends cdk.Construct implements IProject { const rule = this.onStateChange(name, target, options); rule.addEventPattern({ detail: { - 'build-status': [ 'SUCCEEDED' ] + 'build-status': ['SUCCEEDED'] } }); return rule; @@ -402,6 +371,7 @@ export abstract class ProjectBase extends cdk.Construct implements IProject { } class ImportedProject extends ProjectBase { + public readonly grantPrincipal: iam.IPrincipal; public readonly projectArn: string; public readonly projectName: string; public readonly role?: iam.Role = undefined; @@ -414,6 +384,7 @@ class ImportedProject extends ProjectBase { resource: 'project', resourceName: props.projectName, }); + this.grantPrincipal = new iam.ImportedResourcePrincipal({ resource: this }); this.projectName = props.projectName; } @@ -428,13 +399,13 @@ export interface CommonProjectProps { * A description of the project. Use the description to identify the purpose * of the project. */ - description?: string; + readonly description?: string; /** * Filename or contents of buildspec in JSON format. * @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html#build-spec-ref-example */ - buildSpec?: any; + readonly buildSpec?: any; /** * Run a script from an asset as build script @@ -447,68 +418,104 @@ export interface CommonProjectProps { * * @default No asset build script */ - buildScriptAsset?: assets.Asset; + readonly buildScriptAsset?: assets.Asset; /** * The script in the asset to run. * * @default build.sh */ - buildScriptAssetEntrypoint?: string; + readonly buildScriptAssetEntrypoint?: string; /** * Service Role to assume while running the build. * If not specified, a role will be created. */ - role?: iam.IRole; + readonly role?: iam.IRole; /** * Encryption key to use to read and write artifacts * If not specified, a role will be created. */ - encryptionKey?: kms.IEncryptionKey; + readonly encryptionKey?: kms.IEncryptionKey; /** * Bucket to store cached source artifacts * If not specified, source artifacts will not be cached. */ - cacheBucket?: s3.IBucket; + readonly cacheBucket?: s3.IBucket; /** * Subdirectory to store cached artifacts */ - cacheDir?: string; + readonly cacheDir?: string; /** * Build environment to use for the build. */ - environment?: BuildEnvironment; + readonly environment?: BuildEnvironment; /** * Indicates whether AWS CodeBuild generates a publicly accessible URL for * your project's build badge. For more information, see Build Badges Sample * in the AWS CodeBuild User Guide. */ - badge?: boolean; + readonly badge?: boolean; /** * The number of minutes after which AWS CodeBuild stops the build if it's * not complete. For valid values, see the timeoutInMinutes field in the AWS * CodeBuild User Guide. */ - timeout?: number; + readonly timeout?: number; /** * Additional environment variables to add to the build environment. */ - environmentVariables?: { [name: string]: BuildEnvironmentVariable }; + readonly environmentVariables?: { [name: string]: BuildEnvironmentVariable }; /** * The physical, human-readable name of the CodeBuild Project. */ - projectName?: string; -} + readonly projectName?: string; + + /** + * VPC network to place codebuild network interfaces + * + * Specify this if the codebuild project needs to access resources in a VPC. + */ + readonly vpc?: ec2.IVpcNetwork; + + /** + * Where to place the network interfaces within the VPC. + * + * Only used if 'vpc' is supplied. + * + * @default All private subnets + */ + readonly subnetSelection?: ec2.SubnetSelection; + /** + * What security group to associate with the codebuild project's network interfaces. + * If no security group is identified, one will be created automatically. + * + * Only used if 'vpc' is supplied. + * + */ + readonly securityGroups?: ec2.ISecurityGroup[]; + + /** + * Whether to allow the CodeBuild to send all network traffic + * + * If set to false, you must individually add traffic rules to allow the + * CodeBuild project to connect to network targets. + * + * Only used if 'vpc' is supplied. + * + * @default true + */ + readonly allowAllOutbound?: boolean; +} export interface ProjectProps extends CommonProjectProps { /** * The source of the build. @@ -517,7 +524,7 @@ export interface ProjectProps extends CommonProjectProps { * * @default NoSource */ - source?: BuildSource; + readonly source?: BuildSource; /** * Defines where build artifacts will be stored. @@ -525,7 +532,7 @@ export interface ProjectProps extends CommonProjectProps { * * @default NoBuildArtifacts */ - artifacts?: BuildArtifacts; + readonly artifacts?: BuildArtifacts; /** * The secondary sources for the Project. @@ -534,7 +541,7 @@ export interface ProjectProps extends CommonProjectProps { * @default [] * @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-multi-in-out.html */ - secondarySources?: BuildSource[]; + readonly secondarySources?: BuildSource[]; /** * The secondary artifacts for the Project. @@ -543,7 +550,7 @@ export interface ProjectProps extends CommonProjectProps { * @default [] * @see https://docs.aws.amazon.com/codebuild/latest/userguide/sample-multi-in-out.html */ - secondaryArtifacts?: BuildArtifacts[]; + readonly secondaryArtifacts?: BuildArtifacts[]; } /** @@ -569,6 +576,8 @@ export class Project extends ProjectBase { return new ImportedProject(scope, id, props); } + public readonly grantPrincipal: iam.IPrincipal; + /** * The IAM role for this project. */ @@ -588,6 +597,7 @@ export class Project extends ProjectBase { private readonly buildImage: IBuildImage; private readonly _secondarySources: BuildSource[]; private readonly _secondaryArtifacts: BuildArtifacts[]; + private _securityGroups: ec2.ISecurityGroup[] = []; constructor(scope: cdk.Construct, id: string, props: ProjectProps) { super(scope, id); @@ -599,10 +609,11 @@ export class Project extends ProjectBase { this.role = props.role || new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('codebuild.amazonaws.com') }); + this.grantPrincipal = this.role; let cache: CfnProject.ProjectCacheProperty | undefined; if (props.cacheBucket) { - const cacheDir = props.cacheDir != null ? props.cacheDir : new cdk.AwsNoValue().toString(); + const cacheDir = props.cacheDir != null ? props.cacheDir : cdk.Aws.noValue; cache = { type: 'S3', location: cdk.Fn.join('/', [props.cacheBucket.bucketName, cacheDir]), @@ -633,16 +644,27 @@ export class Project extends ProjectBase { } // Render the source and add in the buildspec - const sourceJson = this.source.toSourceJSON(); - if (typeof buildSpec === 'string') { - sourceJson.buildSpec = buildSpec; // Filename to buildspec file - } else if (Object.keys(buildSpec).length > 0) { - // We have to pretty-print the buildspec, otherwise - // CodeBuild will not recognize it as an inline buildspec. - sourceJson.buildSpec = JSON.stringify(buildSpec, undefined, 2); // Literal buildspec - } else if (this.source.type === SourceType.None) { - throw new Error("If the Project's source is NoSource, you need to provide a buildSpec"); - } + + const renderSource = () => { + const sourceJson = this.source.toSourceJSON(); + if (typeof buildSpec === 'string') { + return { + ...sourceJson, + buildSpec // Filename to buildspec file + }; + } else if (Object.keys(buildSpec).length > 0) { + // We have to pretty-print the buildspec, otherwise + // CodeBuild will not recognize it as an inline buildspec. + return { + ...sourceJson, + buildSpec: JSON.stringify(buildSpec, undefined, 2) + }; + } else if (this.source.type === SourceType.None) { + throw new Error("If the Project's source is NoSource, you need to provide a buildSpec"); + } else { + return sourceJson; + } + }; this._secondarySources = []; for (const secondarySource of props.secondarySources || []) { @@ -658,7 +680,7 @@ export class Project extends ProjectBase { const resource = new CfnProject(this, 'Resource', { description: props.description, - source: sourceJson, + source: renderSource(), artifacts: artifacts.toArtifactsJSON(), serviceRole: this.role.roleArn, environment: this.renderEnvironment(props.environment, environmentVariables), @@ -670,14 +692,19 @@ export class Project extends ProjectBase { secondarySources: new cdk.Token(() => this.renderSecondarySources()), secondaryArtifacts: new cdk.Token(() => this.renderSecondaryArtifacts()), triggers: this.source.buildTriggers(), + vpcConfig: this.configureVpc(props), }); this.projectArn = resource.projectArn; - this.projectName = resource.ref; + this.projectName = resource.projectName; this.addToRolePolicy(this.createLoggingPermission()); } + public get securityGroups(): ec2.ISecurityGroup[] { + return this._securityGroups.slice(); + } + /** * Export this Project. Allows referencing this Project in a different CDK Stack. */ @@ -697,6 +724,20 @@ export class Project extends ProjectBase { } } + /** + * Add a permission only if there's a policy attached. + * @param statement The permissions statement to add + */ + public addToRoleInlinePolicy(statement: iam.PolicyStatement) { + if (this.role) { + const policy = new iam.Policy(this, 'PolicyDocument', { + policyName: 'CodeBuildEC2Policy', + statements: [statement] + }); + this.role.attachInlinePolicy(policy); + } + } + /** * Adds a secondary source to the Project. * @@ -765,8 +806,7 @@ export class Project extends ProjectBase { } private renderEnvironment(env: BuildEnvironment = {}, - projectVars: { [name: string]: BuildEnvironmentVariable } = {}): - CfnProject.EnvironmentProperty { + projectVars: { [name: string]: BuildEnvironmentVariable } = {}): CfnProject.EnvironmentProperty { const vars: { [name: string]: BuildEnvironmentVariable } = {}; const containerVars = env.environmentVariables || {}; @@ -812,6 +852,60 @@ export class Project extends ProjectBase { : this._secondaryArtifacts.map((secondaryArtifact) => secondaryArtifact.toArtifactsJSON()); } + /** + * If configured, set up the VPC-related properties + * + * Returns the VpcConfig that should be added to the + * codebuild creation properties. + */ + private configureVpc(props: ProjectProps): CfnProject.VpcConfigProperty | undefined { + if ((props.securityGroups || props.allowAllOutbound !== undefined) && !props.vpc) { + throw new Error(`Cannot configure 'securityGroup' or 'allowAllOutbound' without configuring a VPC`); + } + + if (!props.vpc) { return undefined; } + + if ((props.securityGroups && props.securityGroups.length > 0) && props.allowAllOutbound !== undefined) { + throw new Error(`Configure 'allowAllOutbound' directly on the supplied SecurityGroup.`); + } + + if (props.securityGroups && props.securityGroups.length > 0) { + this._securityGroups = props.securityGroups.slice(); + } else { + const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { + vpc: props.vpc, + description: 'Automatic generated security group for CodeBuild ' + this.node.uniqueId, + allowAllOutbound: props.allowAllOutbound + }); + this._securityGroups = [securityGroup]; + } + this.addToRoleInlinePolicy(new iam.PolicyStatement() + .addAllResources() + .addActions( + 'ec2:CreateNetworkInterface', + 'ec2:DescribeNetworkInterfaces', + 'ec2:DeleteNetworkInterface', + 'ec2:DescribeSubnets', + 'ec2:DescribeSecurityGroups', + 'ec2:DescribeDhcpOptions', + 'ec2:DescribeVpcs' + )); + this.addToRolePolicy(new iam.PolicyStatement() + .addResource(`arn:aws:ec2:${cdk.Aws.region}:${cdk.Aws.accountId}:network-interface/*`) + .addCondition('StringEquals', { + "ec2:Subnet": [ + `arn:aws:ec2:${cdk.Aws.region}:${cdk.Aws.accountId}:subnet/[[subnets]]` + ], + "ec2:AuthorizedService": "codebuild.amazonaws.com" + }) + .addAction('ec2:CreateNetworkInterfacePermission')); + return { + vpcId: props.vpc.vpcId, + subnets: props.vpc.selectSubnets(props.subnetSelection).subnetIds, + securityGroupIds: this._securityGroups.map(s => s.securityGroupId) + }; + } + private parseArtifacts(props: ProjectProps) { if (props.artifacts) { return props.artifacts; @@ -829,7 +923,7 @@ export class Project extends ProjectBase { if ((sourceType === CODEPIPELINE_TYPE || artifactsType === CODEPIPELINE_TYPE) && (sourceType !== artifactsType)) { - throw new Error('Both source and artifacts must be set to CodePipeline'); + throw new Error('Both source and artifacts must be set to CodePipeline'); } } } @@ -838,9 +932,9 @@ export class Project extends ProjectBase { * Build machine compute type. */ export enum ComputeType { - Small = 'BUILD_GENERAL1_SMALL', + Small = 'BUILD_GENERAL1_SMALL', Medium = 'BUILD_GENERAL1_MEDIUM', - Large = 'BUILD_GENERAL1_LARGE' + Large = 'BUILD_GENERAL1_LARGE' } export interface BuildEnvironment { @@ -849,7 +943,7 @@ export interface BuildEnvironment { * * @default LinuxBuildImage.UBUNTU_14_04_BASE */ - buildImage?: IBuildImage; + readonly buildImage?: IBuildImage; /** * The type of compute to use for this build. @@ -857,7 +951,7 @@ export interface BuildEnvironment { * * @default taken from {@link #buildImage#defaultComputeType} */ - computeType?: ComputeType; + readonly computeType?: ComputeType; /** * Indicates how the project builds Docker images. Specify true to enable @@ -869,12 +963,12 @@ export interface BuildEnvironment { * * @default false */ - privileged?: boolean; + readonly privileged?: boolean; /** * The environment variables that your builds can use. */ - environmentVariables?: { [name: string]: BuildEnvironmentVariable }; + readonly environmentVariables?: { [name: string]: BuildEnvironmentVariable }; } /** @@ -1128,13 +1222,13 @@ export interface BuildEnvironmentVariable { * The type of environment variable. * @default PlainText */ - type?: BuildEnvironmentVariableType; + readonly type?: BuildEnvironmentVariableType; /** * The value of the environment variable (or the name of the parameter in * the SSM parameter store.) */ - value: any; + readonly value: any; } export enum BuildEnvironmentVariableType { diff --git a/packages/@aws-cdk/aws-codebuild/lib/source.ts b/packages/@aws-cdk/aws-codebuild/lib/source.ts index add210773f4d5..d25bd0db3f34d 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/source.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/source.ts @@ -1,7 +1,7 @@ import codecommit = require('@aws-cdk/aws-codecommit'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); -import cdk = require('@aws-cdk/cdk'); +import { SecretValue } from '@aws-cdk/cdk'; import { CfnProject } from './codebuild.generated'; import { Project } from './project'; @@ -13,7 +13,7 @@ export interface BuildSourceProps { * The source identifier. * This property is required on secondary sources. */ - identifier?: string; + readonly identifier?: string; } /** @@ -31,6 +31,8 @@ export abstract class BuildSource { * Called by the project when the source is added so that the source can perform * binding operations on the source. For example, it can grant permissions to the * code build project to read from the S3 bucket. + * + * @internal */ public _bind(_project: Project) { // by default, do nothing @@ -80,7 +82,7 @@ export interface GitBuildSourceProps extends BuildSourceProps { * If this value is 0, greater than 25, or not provided, * then the full history is downloaded with each build of the project. */ - cloneDepth?: number; + readonly cloneDepth?: number; } /** @@ -96,9 +98,10 @@ export abstract class GitBuildSource extends BuildSource { } public toSourceJSON(): CfnProject.SourceProperty { - const ret = super.toSourceJSON(); - ret.gitCloneDepth = this.cloneDepth; - return ret; + return { + ...super.toSourceJSON(), + gitCloneDepth: this.cloneDepth + }; } } @@ -106,7 +109,7 @@ export abstract class GitBuildSource extends BuildSource { * Construction properties for {@link CodeCommitSource}. */ export interface CodeCommitSourceProps extends GitBuildSourceProps { - repository: codecommit.IRepository; + readonly repository: codecommit.IRepository; } /** @@ -121,6 +124,9 @@ export class CodeCommitSource extends GitBuildSource { this.repo = props.repository; } + /** + * @internal + */ public _bind(project: Project) { // https://docs.aws.amazon.com/codebuild/latest/userguide/setting-up.html project.addToRolePolicy(new iam.PolicyStatement() @@ -139,8 +145,8 @@ export class CodeCommitSource extends GitBuildSource { * Construction properties for {@link S3BucketSource}. */ export interface S3BucketSourceProps extends BuildSourceProps { - bucket: s3.IBucket; - path: string; + readonly bucket: s3.IBucket; + readonly path: string; } /** @@ -157,8 +163,11 @@ export class S3BucketSource extends BuildSource { this.path = props.path; } + /** + * @internal + */ public _bind(project: Project) { - this.bucket.grantRead(project.role); + this.bucket.grantRead(project); } protected toSourceProperty(): any { @@ -190,35 +199,35 @@ export interface GitHubSourceProps extends GitBuildSourceProps { * * @example 'awslabs' */ - owner: string; + readonly owner: string; /** * The name of the repo (without the username). * * @example 'aws-cdk' */ - repo: string; + readonly repo: string; /** * The oAuthToken used to authenticate when cloning source git repo. * Note that you need to give CodeBuild permissions to your GitHub account in order for the token to work. * That is a one-time operation that can be done through the AWS Console for CodeBuild. */ - oauthToken: cdk.Secret; + readonly oauthToken: SecretValue; /** * Whether to create a webhook that will trigger a build every time a commit is pushed to the GitHub repository. * * @default false */ - webhook?: boolean; + readonly webhook?: boolean; /** * Whether to send GitHub notifications on your build's start and end. * * @default true */ - reportBuildStatus?: boolean; + readonly reportBuildStatus?: boolean; } /** @@ -227,7 +236,7 @@ export interface GitHubSourceProps extends GitBuildSourceProps { export class GitHubSource extends GitBuildSource { public readonly type: SourceType = SourceType.GitHub; private readonly httpsCloneUrl: string; - private readonly oauthToken: cdk.Secret; + private readonly oauthToken: SecretValue; private readonly reportBuildStatus: boolean; private readonly webhook?: boolean; @@ -263,19 +272,19 @@ export interface GitHubEnterpriseSourceProps extends GitBuildSourceProps { /** * The HTTPS URL of the repository in your GitHub Enterprise installation. */ - httpsCloneUrl: string; + readonly httpsCloneUrl: string; /** * The OAuth token used to authenticate when cloning the git repository. */ - oauthToken: cdk.Secret; + readonly oauthToken: SecretValue; /** * Whether to ignore SSL errors when connecting to the repository. * * @default false */ - ignoreSslErrors?: boolean; + readonly ignoreSslErrors?: boolean; } /** @@ -284,7 +293,7 @@ export interface GitHubEnterpriseSourceProps extends GitBuildSourceProps { export class GitHubEnterpriseSource extends GitBuildSource { public readonly type: SourceType = SourceType.GitHubEnterprise; private readonly httpsCloneUrl: string; - private readonly oauthToken: cdk.Secret; + private readonly oauthToken: SecretValue; private readonly ignoreSslErrors?: boolean; constructor(props: GitHubEnterpriseSourceProps) { @@ -312,14 +321,14 @@ export interface BitBucketSourceProps extends GitBuildSourceProps { * * @example 'awslabs' */ - owner: string; + readonly owner: string; /** * The name of the repo (without the username). * * @example 'aws-cdk' */ - repo: string; + readonly repo: string; } /** diff --git a/packages/@aws-cdk/aws-codebuild/package-lock.json b/packages/@aws-cdk/aws-codebuild/package-lock.json index 29cad125e8a0e..4ccdc4c0687aa 100644 --- a/packages/@aws-cdk/aws-codebuild/package-lock.json +++ b/packages/@aws-cdk/aws-codebuild/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codebuild", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index d35bad59b3fa9..a224d640bd513 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codebuild", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS CodeBuild", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-codebuild", + "module": "aws_cdk.aws_codebuild" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codebuild" }, "scripts": { "build": "cdk-build", @@ -58,43 +63,43 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/aws-sqs": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/assets": "^0.26.0", - "@aws-cdk/assets-docker": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-codecommit": "^0.26.0", - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-ecr": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/assets": "^0.28.0", + "@aws-cdk/assets-docker": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-codecommit": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-ecr": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/assets": "^0.26.0", - "@aws-cdk/assets-docker": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-codecommit": "^0.26.0", - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-ecr": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/assets": "^0.28.0", + "@aws-cdk/assets-docker": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-codecommit": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-ecr": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} 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 new file mode 100644 index 0000000000000..48d7151aeb05c --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.expected.json @@ -0,0 +1,537 @@ +{ + "Resources": { + "MyVPCAFB07A31": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codebuild-project-vpc/MyVPC" + } + ] + } + }, + "MyVPCPublicSubnet1Subnet0C75866A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/17", + "VpcId": { + "Ref": "MyVPCAFB07A31" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codebuild-project-vpc/MyVPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "MyVPCPublicSubnet1RouteTable538A9511": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVPCAFB07A31" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codebuild-project-vpc/MyVPC/PublicSubnet1" + } + ] + } + }, + "MyVPCPublicSubnet1RouteTableAssociation8A950D8E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVPCPublicSubnet1RouteTable538A9511" + }, + "SubnetId": { + "Ref": "MyVPCPublicSubnet1Subnet0C75866A" + } + } + }, + "MyVPCPublicSubnet1DefaultRouteAF81AA9B": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVPCPublicSubnet1RouteTable538A9511" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVPCIGW30AB6DD6" + } + }, + "DependsOn": [ + "MyVPCVPCGWE6F260E1" + ] + }, + "MyVPCPublicSubnet1EIP5EB6147D": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "MyVPCPublicSubnet1NATGateway838228A5": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVPCPublicSubnet1EIP5EB6147D", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVPCPublicSubnet1Subnet0C75866A" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codebuild-project-vpc/MyVPC/PublicSubnet1" + } + ] + } + }, + "MyVPCPrivateSubnet1Subnet641543F4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/17", + "VpcId": { + "Ref": "MyVPCAFB07A31" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codebuild-project-vpc/MyVPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "MyVPCPrivateSubnet1RouteTable133BD901": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVPCAFB07A31" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codebuild-project-vpc/MyVPC/PrivateSubnet1" + } + ] + } + }, + "MyVPCPrivateSubnet1RouteTableAssociation85DFBFBB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVPCPrivateSubnet1RouteTable133BD901" + }, + "SubnetId": { + "Ref": "MyVPCPrivateSubnet1Subnet641543F4" + } + } + }, + "MyVPCPrivateSubnet1DefaultRouteA8EE6636": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVPCPrivateSubnet1RouteTable133BD901" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVPCPublicSubnet1NATGateway838228A5" + } + } + }, + "MyVPCIGW30AB6DD6": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codebuild-project-vpc/MyVPC" + } + ] + } + }, + "MyVPCVPCGWE6F260E1": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "MyVPCAFB07A31" + }, + "InternetGatewayId": { + "Ref": "MyVPCIGW30AB6DD6" + } + } + }, + "SecurityGroup1F554B36F": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Example", + "GroupName": "Bob", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": { + "Ref": "MyVPCAFB07A31" + } + } + }, + "MyProjectRole9BBE5233": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "codebuild.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyProjectRoleDefaultPolicyB19B7C29": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "BundleS3Bucket0EFC11B0" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "BundleS3Bucket0EFC11B0" + }, + "/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "BundleS3VersionKey720F2199" + } + ] + } + ] + }, + "*" + ] + ] + } + ] + }, + { + "Action": "ec2:CreateNetworkInterfacePermission", + "Condition": { + "StringEquals": { + "ec2:Subnet": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:ec2:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":subnet/[[subnets]]" + ] + ] + } + ], + "ec2:AuthorizedService": "codebuild.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:ec2:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":network-interface/*" + ] + ] + } + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "MyProject39F7B0AE" + }, + ":*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyProjectRoleDefaultPolicyB19B7C29", + "Roles": [ + { + "Ref": "MyProjectRole9BBE5233" + } + ] + } + }, + "MyProjectPolicyDocument646EE0F2": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeDhcpOptions", + "ec2:DescribeVpcs" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CodeBuildEC2Policy", + "Roles": [ + { + "Ref": "MyProjectRole9BBE5233" + } + ] + } + }, + "MyProject39F7B0AE": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "EnvironmentVariables": [ + { + "Name": "SCRIPT_S3_BUCKET", + "Type": "PLAINTEXT", + "Value": { + "Ref": "BundleS3Bucket0EFC11B0" + } + }, + { + "Name": "SCRIPT_S3_KEY", + "Type": "PLAINTEXT", + "Value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "BundleS3VersionKey720F2199" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "BundleS3VersionKey720F2199" + } + ] + } + ] + } + ] + ] + } + } + ], + "Image": "aws/codebuild/ubuntu-base:14.04", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "MyProjectRole9BBE5233", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"pre_build\": {\n \"commands\": [\n \"echo \\\"Downloading scripts from s3://${SCRIPT_S3_BUCKET}/${SCRIPT_S3_KEY}\\\"\",\n \"aws s3 cp s3://${SCRIPT_S3_BUCKET}/${SCRIPT_S3_KEY} /tmp\",\n \"mkdir -p /tmp/scriptdir\",\n \"unzip /tmp/$(basename $SCRIPT_S3_KEY) -d /tmp/scriptdir\"\n ]\n },\n \"build\": {\n \"commands\": [\n \"export SCRIPT_DIR=/tmp/scriptdir\",\n \"echo \\\"Running build.sh\\\"\",\n \"chmod +x /tmp/scriptdir/build.sh\",\n \"/tmp/scriptdir/build.sh\"\n ]\n }\n }\n}", + "Type": "NO_SOURCE" + }, + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "SecurityGroup1F554B36F", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "MyVPCPrivateSubnet1Subnet641543F4" + } + ], + "VpcId": { + "Ref": "MyVPCAFB07A31" + } + } + } + } + }, + "Parameters": { + "BundleS3Bucket0EFC11B0": { + "Type": "String", + "Description": "S3 bucket for asset \"aws-cdk-codebuild-project-vpc/Bundle\"" + }, + "BundleS3VersionKey720F2199": { + "Type": "String", + "Description": "S3 key for asset version \"aws-cdk-codebuild-project-vpc/Bundle\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.ts b/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.ts new file mode 100644 index 0000000000000..35534b5992c33 --- /dev/null +++ b/packages/@aws-cdk/aws-codebuild/test/integ.project-vpc.ts @@ -0,0 +1,26 @@ +#!/usr/bin/env node +import assets = require('@aws-cdk/assets'); +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { Project } from '../lib'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-codebuild-project-vpc'); +const vpc = new ec2.VpcNetwork(stack, 'MyVPC', { + maxAZs: 1, + natGateways: 1, +}); +const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { + allowAllOutbound: true, + description: 'Example', + groupName: 'Bob', + vpc, +}); +new Project(stack, 'MyProject', { + buildScriptAsset: new assets.ZipDirectoryAsset(stack, 'Bundle', { path: 'script_bundle' }), + securityGroups: [securityGroup], + vpc +}); + +app.run(); diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 8e87cf2b65c08..8cd3dd1891e5e 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -1,5 +1,6 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import codecommit = require('@aws-cdk/aws-codecommit'); +import ec2 = require('@aws-cdk/aws-ec2'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; @@ -479,6 +480,99 @@ export = { test.done(); }, + 'with VPC configuration'(test: Test) { + const stack = new cdk.Stack(); + + const bucket = new s3.Bucket(stack, 'MyBucket'); + const vpc = new ec2.VpcNetwork(stack, 'MyVPC'); + const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { + groupName: 'Bob', + vpc, + allowAllOutbound: true, + description: 'Example', + }); + new codebuild.Project(stack, 'MyProject', { + source: new codebuild.S3BucketSource({ + bucket, + path: 'path/to/source.zip', + }), + vpc, + securityGroups: [securityGroup] + }); + expect(stack).to(haveResourceLike("AWS::CodeBuild::Project", { + "VpcConfig": { + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "SecurityGroup1F554B36F", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "MyVPCPrivateSubnet1Subnet641543F4" + }, + { + "Ref": "MyVPCPrivateSubnet2SubnetA420D3F0" + }, + { + "Ref": "MyVPCPrivateSubnet3SubnetE1B8B1B4" + } + ], + "VpcId": { + "Ref": "MyVPCAFB07A31" + } + } + })); + test.done(); + }, + 'without VPC configuration but security group identified'(test: Test) { + const stack = new cdk.Stack(); + + const bucket = new s3.Bucket(stack, 'MyBucket'); + const vpc = new ec2.VpcNetwork(stack, 'MyVPC'); + const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { + groupName: 'Bob', + vpc, + allowAllOutbound: true, + description: 'Example', + }); + + test.throws(() => + new codebuild.Project(stack, 'MyProject', { + source: new codebuild.S3BucketSource({ + bucket, + path: 'path/to/source.zip', + }), + securityGroups: [securityGroup] + }) + , /Cannot configure 'securityGroup' or 'allowAllOutbound' without configuring a VPC/); + test.done(); + }, + 'with VPC configuration but allowAllOutbound identified'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'MyBucket'); + const vpc = new ec2.VpcNetwork(stack, 'MyVPC'); + const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup1', { + groupName: 'Bob', + vpc, + allowAllOutbound: true, + description: 'Example', + }); + test.throws(() => + new codebuild.Project(stack, 'MyProject', { + source: new codebuild.S3BucketSource({ + bucket, + path: 'path/to/source.zip', + }), + vpc, + allowAllOutbound: true, + securityGroups: [securityGroup] + }) + , /Configure 'allowAllOutbound' directly on the supplied SecurityGroup/); + test.done(); + } }, 'using timeout and path in S3 artifacts sets it correctly'(test: Test) { @@ -680,7 +774,7 @@ export = { test.done(); }, - 'if sourcde is set to CodePipeline, and artifacts are not set, they are defaulted to CodePipeline'(test: Test) { + 'if source is set to CodePipeline, and artifacts are not set, they are defaulted to CodePipeline'(test: Test) { const stack = new cdk.Stack(); new codebuild.Project(stack, 'MyProject', { diff --git a/packages/@aws-cdk/aws-codebuild/test/test.project.ts b/packages/@aws-cdk/aws-codebuild/test/test.project.ts index 83622ca99df28..8e7728d71e463 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.project.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.project.ts @@ -1,6 +1,7 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import assets = require('@aws-cdk/assets'); import cdk = require('@aws-cdk/cdk'); +import { SecretValue } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import codebuild = require('../lib'); @@ -58,7 +59,7 @@ export = { owner: 'testowner', repo: 'testrepo', cloneDepth: 3, - oauthToken: new cdk.Secret("test_oauth_token") + oauthToken: SecretValue.plainText("test_oauth_token"), }) }); @@ -88,7 +89,7 @@ export = { source: new codebuild.GitHubSource({ owner: 'testowner', repo: 'testrepo', - oauthToken: new cdk.Secret('test_oauth_token'), + oauthToken: SecretValue.plainText("test_oauth_token"), reportBuildStatus: false, }) }); @@ -112,7 +113,7 @@ export = { source: new codebuild.GitHubSource({ owner: 'testowner', repo: 'testrepo', - oauthToken: new cdk.Secret('test_oauth_token'), + oauthToken: SecretValue.plainText("test_oauth_token"), webhook: true, }) }); @@ -138,7 +139,7 @@ export = { httpsCloneUrl: 'https://github.testcompany.com/testowner/testrepo', ignoreSslErrors: true, cloneDepth: 4, - oauthToken: new cdk.Secret("test_oauth_token") + oauthToken: SecretValue.plainText("test_oauth_token"), }) }); diff --git a/packages/@aws-cdk/aws-codecommit/README.md b/packages/@aws-cdk/aws-codecommit/README.md index 7bc6d9ff341d8..85165e9469dd8 100644 --- a/packages/@aws-cdk/aws-codecommit/README.md +++ b/packages/@aws-cdk/aws-codecommit/README.md @@ -23,33 +23,6 @@ To add an Amazon SNS trigger to your repository: repo.notify('arn:aws:sns:*:123456789012:my_topic'); ``` -## AWS CodePipeline - -To use a CodeCommit Repository in a CodePipeline: - -```ts -import codepipeline = require('@aws-cdk/aws-codepipeline'); - -const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { - pipelineName: 'MyPipeline', -}); -const sourceAction = new codecommit.PipelineSourceAction({ - actionName: 'CodeCommit', - repository: repo, -}); -pipeline.addStage({ - name: 'Source', - actions: [sourceAction], -}); -``` - -You can also create the action from the Repository directly: - -```ts -// equivalent to the code above: -const sourceAction = repo.toCodePipelineSourceAction({ actionName: 'CodeCommit' }); -``` - ## Events CodeCommit repositories emit Amazon CloudWatch events for certain activities. diff --git a/packages/@aws-cdk/aws-codecommit/lib/index.ts b/packages/@aws-cdk/aws-codecommit/lib/index.ts index d2e8bb4a6494c..2fa63e2e6ef94 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/index.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/index.ts @@ -1,4 +1,3 @@ -export * from './pipeline-action'; export * from './repository'; // AWS::CodeCommit CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index 201198a90cec3..a524c0580b677 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -1,7 +1,6 @@ import events = require('@aws-cdk/aws-events'); import cdk = require('@aws-cdk/cdk'); import { CfnRepository } from './codecommit.generated'; -import { CommonPipelineSourceActionProps, PipelineSourceAction } from './pipeline-action'; export interface IRepository extends cdk.IConstruct { /** The ARN of this Repository. */ @@ -16,14 +15,6 @@ export interface IRepository extends cdk.IConstruct { /** The SSH clone URL */ readonly repositoryCloneUrlSsh: string; - /** - * Convenience method for creating a new {@link PipelineSourceAction}. - * - * @param props the construction properties of the new Action - * @returns the newly created {@link PipelineSourceAction} - */ - toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction; - /** * Defines a CloudWatch event rule which triggers for repository events. Use * `rule.addEventPattern(pattern)` to specify a filter. @@ -92,7 +83,7 @@ export interface RepositoryImportProps { * The name of an existing CodeCommit Repository that we are referencing. * Must be in the same account and region as the root Stack. */ - repositoryName: string; + readonly repositoryName: string; } /** @@ -119,13 +110,6 @@ export abstract class RepositoryBase extends cdk.Construct implements IRepositor public abstract export(): RepositoryImportProps; - public toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction { - return new PipelineSourceAction({ - ...props, - repository: this, - }); - } - /** * Defines a CloudWatch event rule which triggers for repository events. Use * `rule.addEventPattern(pattern)` to specify a filter. @@ -258,13 +242,13 @@ export interface RepositoryProps { /** * Name of the repository. This property is required for all repositories. */ - repositoryName: string; + readonly repositoryName: string; /** * A description of the repository. Use the description to identify the * purpose of the repository. */ - description?: string; + readonly description?: string; } /** @@ -366,27 +350,27 @@ export interface RepositoryTriggerOptions { /** * A name for the trigger.Triggers on a repository must have unique names */ - name?: string; + readonly name?: string; /** * The repository events for which AWS CodeCommit sends information to the * target, which you specified in the DestinationArn property.If you don't * specify events, the trigger runs for all repository events. */ - events?: RepositoryEventTrigger[]; + readonly events?: RepositoryEventTrigger[]; /** * The names of the branches in the AWS CodeCommit repository that contain * events that you want to include in the trigger. If you don't specify at * least one branch, the trigger applies to all branches. */ - branches?: string[]; + readonly branches?: string[]; /** * When an event is triggered, additional information that AWS CodeCommit * includes when it sends information to the target. */ - customData?: string; + readonly customData?: string; } /** diff --git a/packages/@aws-cdk/aws-codecommit/package-lock.json b/packages/@aws-cdk/aws-codecommit/package-lock.json index 986c01651f110..ad54001d3feb4 100644 --- a/packages/@aws-cdk/aws-codecommit/package-lock.json +++ b/packages/@aws-cdk/aws-codecommit/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codecommit", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 33c77d465c210..8bcb1d20b810e 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codecommit", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS CodeCommit", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-codecommit", + "module": "aws_cdk.aws_codecommit" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codecommit" }, "scripts": { "build": "cdk-build", @@ -44,7 +49,7 @@ "nyc": { "statements": 30, "lines": 30, - "branches": 38 + "branches": 36 }, "keywords": [ "aws", @@ -59,27 +64,26 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codedeploy-api/package.json b/packages/@aws-cdk/aws-codedeploy-api/package.json index e3c5a94cc9563..ce357ea3140e0 100644 --- a/packages/@aws-cdk/aws-codedeploy-api/package.json +++ b/packages/@aws-cdk/aws-codedeploy-api/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codedeploy-api", - "version": "0.26.0", + "version": "0.28.0", "description": "Load Balancer API for AWS CodeDeploy", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-codedeploy-api", + "module": "aws_cdk.aws_codedeploy_api" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codedeploy-api" }, "scripts": { "build": "cdk-build", @@ -50,13 +55,13 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "engines": { diff --git a/packages/@aws-cdk/aws-codedeploy/README.md b/packages/@aws-cdk/aws-codedeploy/README.md index 565949185d053..5e8d3b9aa74be 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -160,42 +160,6 @@ const deploymentConfig = codedeploy.ServerDeploymentConfig.import(this, 'Existin }); ``` -### Use in CodePipeline - -This module also contains an Action that allows you to use CodeDeploy with AWS CodePipeline. - -Example: - -```ts -import codepipeline = require('@aws-cdk/aws-codepipeline'); - -const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { - pipelineName: 'MyPipeline', -}); - -// add the source and build Stages to the Pipeline... - -const deployAction = new codedeploy.PipelineDeployAction({ - actionName: 'CodeDeploy', - inputArtifact: buildAction.outputArtifact, - deploymentGroup, -}); -pipeline.addStage({ - name: 'Deploy', - actions: [deployAction], -}); -``` - -You can also create an action from the Deployment Group directly: - -```ts -// equivalent to the code above: -const deployAction = deploymentGroup.toCodePipelineDeployAction({ - actionName: 'CodeDeploy', - inputArtifact: buildAction.outputArtifact, -}); -``` - ### Lambda Applications To create a new CodeDeploy Application that deploys to a Lambda function: diff --git a/packages/@aws-cdk/aws-codedeploy/lib/index.ts b/packages/@aws-cdk/aws-codedeploy/lib/index.ts index d1d50615e1c8b..7b78d58755771 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/index.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/index.ts @@ -1,6 +1,5 @@ export * from './rollback-config'; export * from './lambda'; -export * from './pipeline-action'; export * from './server'; // AWS::CodeDeploy CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts index 2238b6ebe2ddc..dd1f2b72e9e56 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/application.ts @@ -28,7 +28,7 @@ export interface LambdaApplicationProps { * * @default an auto-generated name will be used */ - applicationName?: string; + readonly applicationName?: string; } /** @@ -81,7 +81,7 @@ export interface LambdaApplicationImportProps { * The physical, human-readable name of the Lambda Application we're referencing. * The Application must be in the same account and region as the root Stack. */ - applicationName: string; + readonly applicationName: string; } class ImportedLambdaApplication extends cdk.Construct implements ILambdaApplication { diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts index e128d29458458..416b1cbf77b82 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-config.ts @@ -38,7 +38,7 @@ export interface LambdaDeploymentConfigImportProps { * The physical, human-readable name of the custom CodeDeploy Lambda Deployment Configuration * that we are referencing. */ - deploymentConfigName: string; + readonly deploymentConfigName: string; } class ImportedLambdaDeploymentConfig extends cdk.Construct implements ILambdaDeploymentConfig { diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts index 9c259750dc77c..e661ad5bd5b2c 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts @@ -43,21 +43,21 @@ export interface LambdaDeploymentGroupProps { * * @default one will be created for you */ - application?: ILambdaApplication; + readonly application?: ILambdaApplication; /** * The physical, human-readable name of the CodeDeploy Deployment Group. * * @default an auto-generated name will be used */ - deploymentGroupName?: string; + readonly deploymentGroupName?: string; /** * The Deployment Configuration this Deployment Group uses. * * @default LambdaDeploymentConfig#AllAtOnce */ - deploymentConfig?: ILambdaDeploymentConfig; + readonly deploymentConfig?: ILambdaDeploymentConfig; /** * The CloudWatch alarms associated with this Deployment Group. @@ -69,42 +69,42 @@ export interface LambdaDeploymentGroupProps { * @default [] * @see https://docs.aws.amazon.com/codedeploy/latest/userguide/monitoring-create-alarms.html */ - alarms?: cloudwatch.Alarm[]; + readonly alarms?: cloudwatch.Alarm[]; /** * The service Role of this Deployment Group. * * @default a new Role will be created. */ - role?: iam.IRole; + readonly role?: iam.IRole; /** * Lambda Alias to shift traffic. Updating the version * of the alias will trigger a CodeDeploy deployment. */ - alias: lambda.Alias; + readonly alias: lambda.Alias; /** * The Lambda function to run before traffic routing starts. */ - preHook?: lambda.IFunction; + readonly preHook?: lambda.IFunction; /** * The Lambda function to run after traffic routing starts. */ - postHook?: lambda.IFunction; + readonly postHook?: lambda.IFunction; /** * Whether to continue a deployment even if fetching the alarm status from CloudWatch failed. * * @default false */ - ignorePollAlarmsFailure?: boolean; + readonly ignorePollAlarmsFailure?: boolean; /** * The auto-rollback configuration for this Deployment Group. */ - autoRollback?: AutoRollbackConfig; + readonly autoRollback?: AutoRollbackConfig; } export class LambdaDeploymentGroup extends cdk.Construct implements ILambdaDeploymentGroup { @@ -194,7 +194,7 @@ export class LambdaDeploymentGroup extends cdk.Construct implements ILambdaDeplo throw new Error('A pre-hook function is already defined for this deployment group'); } this.preHook = preHook; - this.grantPutLifecycleEventHookExecutionStatus(this.preHook.role); + this.grantPutLifecycleEventHookExecutionStatus(this.preHook); this.preHook.grantInvoke(this.role); } @@ -208,7 +208,7 @@ export class LambdaDeploymentGroup extends cdk.Construct implements ILambdaDeplo throw new Error('A post-hook function is already defined for this deployment group'); } this.postHook = postHook; - this.grantPutLifecycleEventHookExecutionStatus(this.postHook.role); + this.grantPutLifecycleEventHookExecutionStatus(this.postHook); this.postHook.grantInvoke(this.role); } @@ -217,12 +217,12 @@ export class LambdaDeploymentGroup extends cdk.Construct implements ILambdaDeplo * on this deployment group resource. * @param principal to grant permission to */ - public grantPutLifecycleEventHookExecutionStatus(principal?: iam.IPrincipal): void { - if (principal) { - principal.addToPolicy(new iam.PolicyStatement() - .addResource(this.deploymentGroupArn) - .addAction('codedeploy:PutLifecycleEventHookExecutionStatus')); - } + public grantPutLifecycleEventHookExecutionStatus(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + resourceArns: [this.deploymentGroupArn], + actions: ['codedeploy:PutLifecycleEventHookExecutionStatus'], + }); } public export(): LambdaDeploymentGroupImportProps { @@ -246,13 +246,13 @@ export interface LambdaDeploymentGroupImportProps { * The reference to the CodeDeploy Lambda Application * that this Deployment Group belongs to. */ - application: ILambdaApplication; + readonly application: ILambdaApplication; /** * The physical, human-readable name of the CodeDeploy Lambda Deployment Group * that we are referencing. */ - deploymentGroupName: string; + readonly deploymentGroupName: string; } class ImportedLambdaDeploymentGroup extends cdk.Construct implements ILambdaDeploymentGroup { diff --git a/packages/@aws-cdk/aws-codedeploy/lib/rollback-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/rollback-config.ts index eb399ae9fd522..f5865d3157068 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/rollback-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/rollback-config.ts @@ -7,14 +7,14 @@ export interface AutoRollbackConfig { * * @default true */ - failedDeployment?: boolean; + readonly failedDeployment?: boolean; /** * Whether to automatically roll back a deployment that was manually stopped. * * @default false */ - stoppedDeployment?: boolean; + readonly stoppedDeployment?: boolean; /** * Whether to automatically roll back a deployment during which one of the configured @@ -22,5 +22,5 @@ export interface AutoRollbackConfig { * * @default true if you've provided any Alarms with the `alarms` property, false otherwise */ - deploymentInAlarm?: boolean; + readonly deploymentInAlarm?: boolean; } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts index c786ccfe005dd..faa78f4bb7f6b 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/application.ts @@ -30,7 +30,7 @@ export interface ServerApplicationImportProps { * The physical, human-readable name of the CodeDeploy EC2/on-premise Application we're referencing. * The Application must be in the same account and region as the root Stack. */ - applicationName: string; + readonly applicationName: string; } class ImportedServerApplication extends cdk.Construct implements IServerApplication { @@ -58,7 +58,7 @@ export interface ServerApplicationProps { * * @default an auto-generated name will be used */ - applicationName?: string; + readonly applicationName?: string; } /** diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts index e2b39b5e676a5..8ce853ef0e5c6 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-config.ts @@ -26,7 +26,7 @@ export interface ServerDeploymentConfigImportProps { * The physical, human-readable name of the custom CodeDeploy EC2/on-premise Deployment Configuration * that we are referencing. */ - deploymentConfigName: string; + readonly deploymentConfigName: string; } class ImportedServerDeploymentConfig extends cdk.Construct implements IServerDeploymentConfig { @@ -74,7 +74,7 @@ export interface ServerDeploymentConfigProps { * * @default a name will be auto-generated */ - deploymentConfigName?: string; + readonly deploymentConfigName?: string; /** * The minimum healhty hosts threshold expressed as an absolute number. @@ -82,7 +82,7 @@ export interface ServerDeploymentConfigProps { * you can't specify {@link #minHealthyHostPercentage}, * however one of this or {@link #minHealthyHostPercentage} is required. */ - minHealthyHostCount?: number; + readonly minHealthyHostCount?: number; /** * The minmum healhty hosts threshold expressed as a percentage of the fleet. @@ -90,7 +90,7 @@ export interface ServerDeploymentConfigProps { * you can't specify {@link #minHealthyHostCount}, * however one of this or {@link #minHealthyHostCount} is required. */ - minHealthyHostPercentage?: number; + readonly minHealthyHostPercentage?: number; } /** diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index f7cf5469c84cc..0b0200220aba2 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -6,7 +6,6 @@ import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import { CfnDeploymentGroup } from '../codedeploy.generated'; -import { CommonPipelineDeployActionProps, PipelineDeployAction } from '../pipeline-action'; import { AutoRollbackConfig } from '../rollback-config'; import { deploymentGroupNameToArn, renderAlarmConfiguration, renderAutoRollbackConfiguration } from '../utils'; import { IServerApplication, ServerApplication } from './application'; @@ -20,14 +19,6 @@ export interface IServerDeploymentGroup extends cdk.IConstruct { readonly deploymentConfig: IServerDeploymentConfig; readonly autoScalingGroups?: autoscaling.AutoScalingGroup[]; export(): ServerDeploymentGroupImportProps; - - /** - * Convenience method for creating a new {@link PipelineDeployAction}. - * - * @param props the construction properties of the new Action - * @returns the newly created {@link PipelineDeployAction} - */ - toCodePipelineDeployAction(props: CommonPipelineDeployActionProps): PipelineDeployAction; } /** @@ -41,20 +32,20 @@ export interface ServerDeploymentGroupImportProps { * The reference to the CodeDeploy EC2/on-premise Application * that this Deployment Group belongs to. */ - application: IServerApplication; + readonly application: IServerApplication; /** * The physical, human-readable name of the CodeDeploy EC2/on-premise Deployment Group * that we are referencing. */ - deploymentGroupName: string; + readonly deploymentGroupName: string; /** * The Deployment Configuration this Deployment Group uses. * * @default ServerDeploymentConfig#OneAtATime */ - deploymentConfig?: IServerDeploymentConfig; + readonly deploymentConfig?: IServerDeploymentConfig; } /** @@ -81,14 +72,6 @@ export abstract class ServerDeploymentGroupBase extends cdk.Construct implements } public abstract export(): ServerDeploymentGroupImportProps; - - public toCodePipelineDeployAction(props: CommonPipelineDeployActionProps): - PipelineDeployAction { - return new PipelineDeployAction({ - ...props, - deploymentGroup: this, - }); - } } class ImportedServerDeploymentGroup extends ServerDeploymentGroupBase { @@ -155,27 +138,27 @@ export interface ServerDeploymentGroupProps { * The CodeDeploy EC2/on-premise Application this Deployment Group belongs to. * If you don't provide one, a new Application will be created. */ - application?: IServerApplication; + readonly application?: IServerApplication; /** * The service Role of this Deployment Group. * If you don't provide one, a new Role will be created. */ - role?: iam.Role; + readonly role?: iam.Role; /** * The physical, human-readable name of the CodeDeploy Deployment Group. * * @default an auto-generated name will be used */ - deploymentGroupName?: string; + readonly deploymentGroupName?: string; /** * The EC2/on-premise Deployment Configuration to use for this Deployment Group. * * @default ServerDeploymentConfig#OneAtATime */ - deploymentConfig?: IServerDeploymentConfig; + readonly deploymentConfig?: IServerDeploymentConfig; /** * The auto-scaling groups belonging to this Deployment Group. @@ -184,7 +167,7 @@ export interface ServerDeploymentGroupProps { * * @default [] */ - autoScalingGroups?: autoscaling.AutoScalingGroup[]; + readonly autoScalingGroups?: autoscaling.AutoScalingGroup[]; /** * If you've provided any auto-scaling groups with the {@link #autoScalingGroups} property, @@ -193,7 +176,7 @@ export interface ServerDeploymentGroupProps { * @default true * @see https://docs.aws.amazon.com/codedeploy/latest/userguide/codedeploy-agent-operations-install.html */ - installAgent?: boolean; + readonly installAgent?: boolean; /** * The load balancer to place in front of this Deployment Group. @@ -202,21 +185,21 @@ export interface ServerDeploymentGroupProps { * * @default the Deployment Group will not have a load balancer defined */ - loadBalancer?: codedeploylb.ILoadBalancer; + readonly loadBalancer?: codedeploylb.ILoadBalancer; /** * All EC2 instances matching the given set of tags when a deployment occurs will be added to this Deployment Group. * * @default no additional EC2 instances will be added to the Deployment Group */ - ec2InstanceTags?: InstanceTagSet; + readonly ec2InstanceTags?: InstanceTagSet; /** * All on-premise instances matching the given set of tags when a deployment occurs will be added to this Deployment Group. * * @default no additional on-premise instances will be added to the Deployment Group */ - onPremiseInstanceTags?: InstanceTagSet; + readonly onPremiseInstanceTags?: InstanceTagSet; /** * The CloudWatch alarms associated with this Deployment Group. @@ -228,19 +211,19 @@ export interface ServerDeploymentGroupProps { * @default [] * @see https://docs.aws.amazon.com/codedeploy/latest/userguide/monitoring-create-alarms.html */ - alarms?: cloudwatch.Alarm[]; + readonly alarms?: cloudwatch.Alarm[]; /** * Whether to continue a deployment even if fetching the alarm status from CloudWatch failed. * * @default false */ - ignorePollAlarmsFailure?: boolean; + readonly ignorePollAlarmsFailure?: boolean; /** * The auto-rollback configuration for this Deployment Group. */ - autoRollback?: AutoRollbackConfig; + readonly autoRollback?: AutoRollbackConfig; } /** diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index b46c0ed38caf6..46f5a91e3802a 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codedeploy", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::CodeDeploy", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-codedeploy", + "module": "aws_cdk.aws_codedeploy" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codedeploy" }, "scripts": { "build": "cdk-build", @@ -57,34 +62,32 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-codedeploy-api": "^0.26.0", - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-codedeploy-api": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-autoscaling": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-codedeploy-api": "^0.26.0", - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-codedeploy-api": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -94,4 +97,4 @@ "construct-ctor:@aws-cdk/aws-codedeploy.ServerDeploymentGroupBase..params[2]" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codedeploy/test/lambda/integ.deployment-group.expected.json b/packages/@aws-cdk/aws-codedeploy/test/lambda/integ.deployment-group.expected.json index fa1ff2aba0ce3..27491e271ad0b 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/lambda/integ.deployment-group.expected.json +++ b/packages/@aws-cdk/aws-codedeploy/test/lambda/integ.deployment-group.expected.json @@ -429,6 +429,12 @@ "Dimensions": [ { "Name": "FunctionName", + "Value": { + "Ref": "Handler886CB40B" + } + }, + { + "Name": "Resource", "Value": { "Fn::Join": [ "", @@ -590,4 +596,4 @@ "Description": "S3 key for asset version \"aws-cdk-codedeploy-lambda/PostHook/Code\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts index ac79e4efc9f6d..61aae5044cf74 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/server/test.deployment-group.ts @@ -1,4 +1,4 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { expect, haveResource, SynthUtils } from '@aws-cdk/assert'; import autoscaling = require('@aws-cdk/aws-autoscaling'); import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); @@ -354,10 +354,7 @@ export = { }, }); - test.throws(() => { - stack._toCloudFormation(); - }, /deploymentInAlarm/); - + test.throws(() => SynthUtils.toCloudFormation(stack), /deploymentInAlarm/); test.done(); }, }, diff --git a/packages/@aws-cdk/aws-codepipeline-actions/.gitignore b/packages/@aws-cdk/aws-codepipeline-actions/.gitignore new file mode 100644 index 0000000000000..76066ee170e9f --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/.gitignore @@ -0,0 +1,17 @@ +*.js +tsconfig.json +tslint.json +*.js.map +*.d.ts +*.generated.ts +dist +lib/generated/resources.ts +*.tgz +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/.npmignore b/packages/@aws-cdk/aws-codepipeline-actions/.npmignore new file mode 100644 index 0000000000000..b757d55c46996 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/.npmignore @@ -0,0 +1,16 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk \ No newline at end of file diff --git a/tools/y-npm/LICENSE b/packages/@aws-cdk/aws-codepipeline-actions/LICENSE similarity index 100% rename from tools/y-npm/LICENSE rename to packages/@aws-cdk/aws-codepipeline-actions/LICENSE diff --git a/tools/y-npm/NOTICE b/packages/@aws-cdk/aws-codepipeline-actions/NOTICE similarity index 100% rename from tools/y-npm/NOTICE rename to packages/@aws-cdk/aws-codepipeline-actions/NOTICE diff --git a/packages/@aws-cdk/aws-codepipeline-actions/README.md b/packages/@aws-cdk/aws-codepipeline-actions/README.md new file mode 100644 index 0000000000000..34c4ed0939ba8 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/README.md @@ -0,0 +1,554 @@ +## AWS CodePipeline Actions + +This package contains Actions that can be used in a CodePipeline. + +```typescript +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import codepipeline_actions = require('@aws-cdk/aws-codepipeline-actions'); +``` + +### Sources + +#### AWS CodeCommit + +To use a CodeCommit Repository in a CodePipeline: + +```ts +import codecommit = require('@aws-cdk/aws-codecommit'); + +const repo = new codecommit.Repository(this, 'Repo', { + // ... +}); + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { + pipelineName: 'MyPipeline', +}); +const sourceAction = new codepipeline_actions.CodeCommitSourceAction({ + actionName: 'CodeCommit', + repository: repo, +}); +pipeline.addStage({ + name: 'Source', + actions: [sourceAction], +}); +``` + +#### GitHub + +To use GitHub as the source of a CodePipeline: + +```typescript +// Read the secret from ParameterStore +const token = new cdk.SecretParameter(this, 'GitHubToken', { ssmParameter: 'my-github-token' }); +const sourceAction = new codepipeline_actions.GitHubSourceAction({ + actionName: 'GitHub_Source', + owner: 'awslabs', + repo: 'aws-cdk', + oauthToken: token.value, + outputArtifactName: 'SourceOutput', // this will be the name of the output artifact in the Pipeline + branch: 'develop', // default: 'master' +}); +pipeline.addStage({ + name: 'Source', + actions: [sourceAction], +}); +``` + +#### AWS S3 + +To use an S3 Bucket as a source in CodePipeline: + +```ts +import s3 = require('@aws-cdk/aws-s3'); + +const sourceBucket = new s3.Bucket(this, 'MyBucket', { + versioned: true, // a Bucket used as a source in CodePipeline must be versioned +}); + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const sourceAction = new codepipeline_actions.S3SourceAction({ + actionName: 'S3Source', + bucket: sourceBucket, + bucketKey: 'path/to/file.zip', +}); +pipeline.addStage({ + name: 'Source', + actions: [sourceAction], +}); +``` + +By default, the Pipeline will poll the Bucket to detect changes. +You can change that behavior to use CloudWatch Events by setting the `pollForSourceChanges` +property to `false` (it's `true` by default). +If you do that, make sure the source Bucket is part of an AWS CloudTrail Trail - +otherwise, the CloudWatch Events will not be emitted, +and your Pipeline will not react to changes in the Bucket. +You can do it through the CDK: + +```typescript +import cloudtrail = require('@aws-cdk/aws-cloudtrail'); + +const key = 'some/key.zip'; +const trail = new cloudtrail.CloudTrail(this, 'CloudTrail'); +trail.addS3EventSelector([sourceBucket.arnForObjects(key)], cloudtrail.ReadWriteType.WriteOnly); +const sourceAction = new codepipeline_actions.S3SourceAction({ + actionName: 'S3Source', + bucketKey: key, + bucket: sourceBucket, + pollForSourceChanges: false, // default: true +}); +``` + +#### AWS ECR + +To use an ECR Repository as a source in a Pipeline: + +```ts +import ecr = require('@aws-cdk/aws-ecr'); + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const sourceAction = new codepipeline_actions.EcrSourceAction({ + actionName: 'ECR', + repository: ecrRepository, + imageTag: 'some-tag', // optional, default: 'latest' + outputArtifactName: 'SomeName', // optional +}); +pipeline.addStage({ + actionName: 'Source', + actions: [sourceAction], +}); +``` + +### Build & test + +#### AWS CodeBuild + +Example of a CodeBuild Project used in a Pipeline, alongside CodeCommit: + +```typescript +import codebuild = require('@aws-cdk/aws-codebuild'); +import codecommit = require('@aws-cdk/aws-codecommit'); + +const repository = new codecommit.Repository(this, 'MyRepository', { + repositoryName: 'MyRepository', +}); +const project = new codebuild.PipelineProject(this, 'MyProject'); + +const sourceAction = new codepipeline_actions.CodeCommitSourceAction({ + actionName: 'CodeCommit', + repository, +}); +const buildAction = new codepipeline_actions.CodeBuildBuildAction({ + actionName: 'CodeBuild', + project, + inputArtifact: sourceAction.outputArtifact, +}); + +new codepipeline.Pipeline(this, 'MyPipeline', { + stages: [ + { + name: 'Source', + actions: [sourceAction], + }, + { + name: 'Build', + actions: [buildAction], + }, + ], +}); +``` + +The `PipelineProject` utility class is a simple sugar around the `Project` +class, it's equivalent to: + +```ts +const project = new codebuild.Project(this, 'MyProject', { + source: new codebuild.CodePipelineSource(), + artifacts: new codebuild.CodePipelineBuildArtifacts(), + // rest of the properties from PipelineProject are passed unchanged... +} +``` + +In addition to the build Action, there is also a test Action. It works very +similarly to the build Action, the only difference is that the test Action does +not always produce an output artifact. Example: + +```typescript +const testAction = new codepipeline_actions.CodeBuildTestAction({ + actionName: 'IntegrationTest', + project, + inputArtifact: sourceAction.outputArtifact, + // outputArtifactName is optional - if you don't specify it, + // the Action will have an undefined `outputArtifact` property + outputArtifactName: 'IntegrationTestOutput', +}); +``` + +##### Multiple inputs and outputs + +When you want to have multiple inputs and/or outputs for a Project used in a +Pipeline, instead of using the `secondarySources` and `secondaryArtifacts` +properties of the `Project` class, you need to use the `additionalInputArtifacts` and +`additionalOutputArtifactNames` properties of the CodeBuild CodePipeline +Actions. Example: + +```ts +const sourceAction1 = new codepipeline_actions.CodeCommitSourceAction({ + actionName: 'Source1', + repository: repository1, +}); +const sourceAction2 = new codepipeline_actions.CodeCommitSourceAction({ + actionName: 'Source2', + repository: repository2, + outputArtifactName: 'source2', +}); + +const buildAction = new codepipeline_actions.CodeBuildBuildAction({ + actionName: 'Build', + project, + inputArtifact: sourceAction1.outputArtifact, + outputArtifactName: 'artifact1', // for better buildspec readability - see below + additionalInputArtifacts: [ + sourceAction2.outputArtifact, // this is where 'source2' comes from + ], + additionalOutputArtifactNames: [ + 'artifact2', + ], +}); +``` + +**Note**: when a CodeBuild Action in a Pipeline has more than one output, it +only uses the `secondary-artifacts` field of the buildspec, never the +primary output specification directly under `artifacts`. Because of that, it +pays to name even your primary output artifact on the Pipeline, like we did +above, so that you know what name to use in the buildspec. + +Example buildspec for the above project: + +```ts +const project = new codebuild.PipelineProject(this, 'MyProject', { + buildSpec: { + version: '0.2', + phases: { + build: { + commands: [ + // By default, you're in a directory with the contents of the repository from sourceAction1. + // Use the CODEBUILD_SRC_DIR_source2 environment variable + // to get a path to the directory with the contents of the second input repository. + ], + }, + }, + artifacts: { + 'secondary-artifacts': { + 'artifact1': { + // primary Action output artifact, + // available as buildAction.outputArtifact + }, + 'artifact2': { + // additional output artifact, + // available as buildAction.additionalOutputArtifact('artifact2') + }, + }, + }, + }, + // ... +}); +``` + +#### Jenkins + +In order to use Jenkins Actions in the Pipeline, +you first need to create a `JenkinsProvider`: + +```ts +const jenkinsProvider = new codepipeline_actions.JenkinsProvider(this, 'JenkinsProvider', { + providerName: 'MyJenkinsProvider', + serverUrl: 'http://my-jenkins.com:8080', + version: '2', // optional, default: '1' +}); +``` + +If you've registered a Jenkins provider in a different CDK app, +or outside the CDK (in the CodePipeline AWS Console, for example), +you can import it: + +```ts +const jenkinsProvider = codepipeline_actions.JenkinsProvider.import(this, 'JenkinsProvider', { + providerName: 'MyJenkinsProvider', + serverUrl: 'http://my-jenkins.com:8080', + version: '2', // optional, default: '1' +}); +``` + +Note that a Jenkins provider +(identified by the provider name-category(build/test)-version tuple) +must always be registered in the given account, in the given AWS region, +before it can be used in CodePipeline. + +With a `JenkinsProvider`, +we can create a Jenkins Action: + +```ts +const buildAction = new codepipeline_actions.JenkinsBuildAction({ + actionName: 'JenkinsBuild', + jenkinsProvider: jenkinsProvider, + projectName: 'MyProject', +}); +// there's also a JenkinsTestAction that works identically +``` + +### Deploy + +#### AWS CloudFormation + +This module contains Actions that allows you to deploy to CloudFormation from AWS CodePipeline. + +For example, the following code fragment defines a pipeline that automatically deploys a CloudFormation template +directly from a CodeCommit repository, with a manual approval step in between to confirm the changes: + +[example Pipeline to deploy CloudFormation](test/integ.cfn-template-from-repo.lit.ts) + +See [the AWS documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline.html) +for more details about using CloudFormation in CodePipeline. + +##### Actions defined by this package + +This package defines the following actions: + +* **CloudFormationCreateUpdateStackAction** - Deploy a CloudFormation template directly from the pipeline. The indicated stack is created, + or updated if it already exists. If the stack is in a failure state, deployment will fail (unless `replaceOnFailure` + is set to `true`, in which case it will be destroyed and recreated). +* **CloudFormationDeleteStackAction** - Delete the stack with the given name. +* **CloudFormationCreateReplaceChangeSetAction** - Prepare a change set to be applied later. You will typically use change sets if you want + to manually verify the changes that are being staged, or if you want to separate the people (or system) preparing the + changes from the people (or system) applying the changes. +* **CloudFormationExecuteChangeSetAction** - Execute a change set prepared previously. + +##### Lambda deployed through CodePipeline + +If you want to deploy your Lambda through CodePipeline, +and you don't use assets (for example, because your CDK code and Lambda code are separate), +you can use a special Lambda `Code` class, `CfnParametersCode`. +Note that your Lambda must be in a different Stack than your Pipeline. +The Lambda itself will be deployed, alongside the entire Stack it belongs to, +using a CloudFormation CodePipeline Action. Example: + +[Example of deploying a Lambda through CodePipeline](test/integ.lambda-deployed-through-codepipeline.lit.ts) + +#### AWS CodeDeploy + +##### Server deployments + +To use CodeDeploy for EC2/on-premise deployments in a Pipeline: + +```ts +import codedeploy = require('@aws-cdk/aws-codedeploy'); + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { + pipelineName: 'MyPipeline', +}); + +// add the source and build Stages to the Pipeline... + +const deployAction = new codepipeline_actions.CodeDeployServerDeployAction({ + actionName: 'CodeDeploy', + inputArtifact: buildAction.outputArtifact, + deploymentGroup, +}); +pipeline.addStage({ + name: 'Deploy', + actions: [deployAction], +}); +``` + +##### Lambda deployments + +To use CodeDeploy for blue-green Lambda deployments in a Pipeline: + +```typescript +const lambdaCode = lambda.Code.cfnParameters(); +const func = new lambda.Function(lambdaStack, 'Lambda', { + code: lambdaCode, + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810, +}); +// used to make sure each CDK synthesis produces a different Version +const version = func.newVersion(); +const alias = new lambda.Alias(lambdaStack, 'LambdaAlias', { + aliasName: 'Prod', + version, +}); + +new codedeploy.LambdaDeploymentGroup(lambdaStack, 'DeploymentGroup', { + alias, + deploymentConfig: codedeploy.LambdaDeploymentConfig.Linear10PercentEvery1Minute, +}); +``` + +Then, you need to create your Pipeline Stack, +where you will define your Pipeline, +and deploy the `lambdaStack` using a CloudFormation CodePipeline Action +(see above for a complete example). + +#### ECS + +CodePipeline can deploy an ECS service. +The deploy Action receives one input Artifact which contains the [image definition file]: + +```typescript +const deployStage = pipeline.addStage({ + name: 'Deploy', + actions: [ + new codepipeline_actions.EcsDeployAction({ + actionName: 'DeployAction', + service, + // if your file is called imagedefinitions.json, + // use the `inputArtifact` property, + // and leave out the `imageFile` property + inputArtifact: buildAction.outputArtifact, + // if your file name is _not_ imagedefinitions.json, + // use the `imageFile` property, + // and leave out the `inputArtifact` property + imageFile: buildAction.outputArtifact.atPath('imageDef.json'), + }), + ], +}); +``` + +[image definition file]: https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create.html#pipelines-create-image-definitions + +#### AWS S3 + +To use an S3 Bucket as a deployment target in CodePipeline: + +```ts +const targetBucket = new s3.Bucket(this, 'MyBucket', {}); + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const deployAction = new codepipeline_actions.S3DeployAction({ + actionName: 'S3Deploy', + stage: deployStage, + bucket: targetBucket, + inputArtifact: sourceAction.outputArtifact, +}); +const deployStage = pipeline.addStage({ + name: 'Deploy', + actions: [deployAction], +}); +``` + +#### Alexa Skill + +You can deploy to Alexa using CodePipeline with the following Action: + +```ts +// Read the secrets from ParameterStore +const clientId = new cdk.SecretParameter(this, 'AlexaClientId', { ssmParameter: '/Alexa/ClientId' }); +const clientSecret = new cdk.SecretParameter(this, 'AlexaClientSecret', { ssmParameter: '/Alexa/ClientSecret' }); +const refreshToken = new cdk.SecretParameter(this, 'AlexaRefreshToken', { ssmParameter: '/Alexa/RefreshToken' }); + +// Add deploy action +new codepipeline_actions.AlexaSkillDeployAction({ + actionName: 'DeploySkill', + runOrder: 1, + inputArtifact: sourceAction.outputArtifact, + clientId: clientId.value, + clientSecret: clientSecret.value, + refreshToken: refreshToken.value, + skillId: 'amzn1.ask.skill.12345678-1234-1234-1234-123456789012', +}); +``` + +If you need manifest overrides you can specify them as `parameterOverridesArtifact` in the action: + +```ts +const cloudformation = require('@aws-cdk/aws-cloudformation'); + +// Deploy some CFN change set and store output +const executeChangeSetAction = new codepipeline_actions.CloudFormationExecuteChangeSetAction({ + actionName: 'ExecuteChangesTest', + runOrder: 2, + stackName, + changeSetName, + outputFileName: 'overrides.json', + outputArtifactName: 'CloudFormation', +}); + +// Provide CFN output as manifest overrides +new codepipeline_actions.AlexaSkillDeployAction({ + actionName: 'DeploySkill', + runOrder: 1, + inputArtifact: sourceAction.outputArtifact, + parameterOverridesArtifact: executeChangeSetAction.outputArtifact, + clientId: clientId.value, + clientSecret: clientSecret.value, + refreshToken: refreshToken.value, + skillId: 'amzn1.ask.skill.12345678-1234-1234-1234-123456789012', +}); +``` + +### Approve & invoke + +#### Manual approval Action + +This package contains an Action that stops the Pipeline until someone manually clicks the approve button: + +```typescript +const manualApprovalAction = new codepipeline.ManualApprovalAction({ + actionName: 'Approve', + notificationTopic: new sns.Topic(this, 'Topic'), // optional + notifyEmails: [ + 'some_email@example.com', + ], // optional + additionalInformation: 'additional info', // optional +}); +approveStage.addAction(manualApprovalAction); +// `manualApprovalAction.notificationTopic` can be used to access the Topic +// after the Action has been added to a Pipeline +``` + +If the `notificationTopic` has not been provided, +but `notifyEmails` were, +a new SNS Topic will be created +(and accessible through the `notificationTopic` property of the Action). + +#### AWS Lambda + +This module contains an Action that allows you to invoke a Lambda function in a Pipeline: + +```ts +import lambda = require('@aws-cdk/aws-lambda'); + +const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const lambdaAction = new codepipeline_actions.LambdaInvokeAction({ + actionName: 'Lambda', + lambda: fn, +}); +pipeline.addStage({ + actionName: 'Lambda', + actions: [lambdaAction], +}); +``` + +The Lambda Action can have up to 5 inputs, +and up to 5 outputs: + +```typescript +const lambdaAction = new codepipeline_actions.LambdaInvokeAction({ + actionName: 'Lambda', + inputArtifacts: [ + sourceAction.outputArtifact, + buildAction.outputArtifact, + ], + outputArtifactNames: [ + 'Out1', + 'Out2', + ], +}); + +lambdaAction.outputArtifacts(); // returns the list of output Artifacts +lambdaAction.outputArtifact('Out2'); // returns the named output Artifact, or throws an exception if not found +``` + +See [the AWS documentation](https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html) +on how to write a Lambda function invoked from CodePipeline. diff --git a/packages/@aws-cdk/alexa-ask/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts similarity index 76% rename from packages/@aws-cdk/alexa-ask/lib/pipeline-actions.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts index 83d1b5f20738d..0316382db7a9b 100644 --- a/packages/@aws-cdk/alexa-ask/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/alexa-ask/deploy-action.ts @@ -1,5 +1,5 @@ -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); -import cdk = require('@aws-cdk/cdk'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import { SecretValue } from '@aws-cdk/cdk'; /** * Construction properties of the {@link AlexaSkillDeployAction Alexa deploy Action}. @@ -8,32 +8,32 @@ export interface AlexaSkillDeployActionProps extends codepipeline.CommonActionPr /** * The client id of the developer console token */ - clientId: cdk.Secret; + readonly clientId: string; /** * The client secret of the developer console token */ - clientSecret: cdk.Secret; + readonly clientSecret: SecretValue; /** * The refresh token of the developer console token */ - refreshToken: cdk.Secret; + readonly refreshToken: SecretValue; /** * The Alexa skill id */ - skillId: string; + readonly skillId: string; /** * The source artifact containing the voice model and skill manifest */ - inputArtifact: codepipeline.Artifact; + readonly inputArtifact: codepipeline.Artifact; /** * An optional artifact containing overrides for the skill manifest */ - parameterOverridesArtifact?: codepipeline.Artifact; + readonly parameterOverridesArtifact?: codepipeline.Artifact; } /** @@ -64,7 +64,7 @@ export class AlexaSkillDeployAction extends codepipeline.DeployAction { } } - protected bind(_stage: codepipeline.IStage, _scope: cdk.Construct): void { + protected bind(_info: codepipeline.ActionBind): void { // nothing to do } } diff --git a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts similarity index 75% rename from packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts index 77ee1bd25b4b7..f6aa697bd6489 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts @@ -1,15 +1,16 @@ -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); +import cloudformation = require('@aws-cdk/aws-cloudformation'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); /** * Properties common to all CloudFormation actions */ -export interface PipelineCloudFormationActionProps extends codepipeline.CommonActionProps { +export interface CloudFormationActionProps extends codepipeline.CommonActionProps { /** * The name of the stack to apply this action to */ - stackName: string; + readonly stackName: string; /** * A name for the filename in the output artifact to store the AWS CloudFormation call's result. @@ -22,7 +23,7 @@ export interface PipelineCloudFormationActionProps extends codepipeline.CommonAc * * @default No output artifact generated */ - outputFileName?: string; + readonly outputFileName?: string; /** * The name of the output artifact to generate @@ -31,7 +32,7 @@ export interface PipelineCloudFormationActionProps extends codepipeline.CommonAc * * @default Automatically generated artifact name. */ - outputArtifactName?: string; + readonly outputArtifactName?: string; /** * The AWS region the given Action resides in. @@ -42,7 +43,7 @@ export interface PipelineCloudFormationActionProps extends codepipeline.CommonAc * * @default the Action resides in the same region as the Pipeline */ - region?: string; + readonly region?: string; /** * The service role that is assumed during execution of action. @@ -51,13 +52,13 @@ export interface PipelineCloudFormationActionProps extends codepipeline.CommonAc * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html */ - role?: iam.IRole; + readonly role?: iam.IRole; } /** * Base class for Actions that execute CloudFormation */ -export abstract class PipelineCloudFormationAction extends codepipeline.Action { +export abstract class CloudFormationAction extends codepipeline.Action { /** * CfnOutput artifact containing the CloudFormation call response * @@ -65,7 +66,7 @@ export abstract class PipelineCloudFormationAction extends codepipeline.Action { */ public outputArtifact?: codepipeline.Artifact; - constructor(props: PipelineCloudFormationActionProps, configuration?: any) { + constructor(props: CloudFormationActionProps, configuration?: any) { super({ ...props, region: props.region, @@ -92,22 +93,22 @@ export abstract class PipelineCloudFormationAction extends codepipeline.Action { } /** - * Properties for the PipelineExecuteChangeSetAction. + * Properties for the CloudFormationExecuteChangeSetAction. */ -export interface PipelineExecuteChangeSetActionProps extends PipelineCloudFormationActionProps { +export interface CloudFormationExecuteChangeSetActionProps extends CloudFormationActionProps { /** * Name of the change set to execute. */ - changeSetName: string; + readonly changeSetName: string; } /** * CodePipeline action to execute a prepared change set. */ -export class PipelineExecuteChangeSetAction extends PipelineCloudFormationAction { - private readonly props: PipelineExecuteChangeSetActionProps; +export class CloudFormationExecuteChangeSetAction extends CloudFormationAction { + private readonly props: CloudFormationExecuteChangeSetActionProps; - constructor(props: PipelineExecuteChangeSetActionProps) { + constructor(props: CloudFormationExecuteChangeSetActionProps) { super(props, { ActionMode: 'CHANGE_SET_EXECUTE', ChangeSetName: props.changeSetName, @@ -116,9 +117,8 @@ export class PipelineExecuteChangeSetAction extends PipelineCloudFormationAction this.props = props; } - protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { - SingletonPolicy.forRole(stage.pipeline.role) - .grantExecuteChangeSet(this.props); + protected bind(info: codepipeline.ActionBind): void { + SingletonPolicy.forRole(info.role).grantExecuteChangeSet(this.props); } } @@ -126,7 +126,7 @@ export class PipelineExecuteChangeSetAction extends PipelineCloudFormationAction /** * Properties common to CloudFormation actions that stage deployments */ -export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFormationActionProps { +export interface CloudFormationDeployActionProps extends CloudFormationActionProps { /** * IAM role to assume when deploying changes. * @@ -136,7 +136,7 @@ export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFo * * @default A fresh role with full or no permissions (depending on the value of `adminPermissions`). */ - deploymentRole?: iam.IRole; + readonly deploymentRole?: iam.IRole; /** * Acknowledge certain changes made as part of deployment @@ -149,7 +149,7 @@ export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFo * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities * @default None, unless `adminPermissions` is true */ - capabilities?: CloudFormationCapabilities; + readonly capabilities?: cloudformation.CloudFormationCapabilities; /** * Whether to grant full permissions to CloudFormation while deploying this template. @@ -166,7 +166,7 @@ export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFo * use `addToRolePolicy` and `capabilities` to control what the CloudFormation * deployment is allowed to do. */ - adminPermissions: boolean; + readonly adminPermissions: boolean; /** * Input artifact to use for template parameters values and stack policy. @@ -180,7 +180,7 @@ export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFo * * @default No template configuration based on input artifacts */ - templateConfiguration?: codepipeline.ArtifactPath; + readonly templateConfiguration?: codepipeline.ArtifactPath; /** * Additional template parameters. @@ -199,7 +199,7 @@ export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFo * * @default No overrides */ - parameterOverrides?: { [name: string]: any }; + readonly parameterOverrides?: { [name: string]: any }; /** * The list of additional input Artifacts for this Action. @@ -216,19 +216,21 @@ export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFo * you need to make sure to include them in the `additionalInputArtifacts` - * otherwise, you'll get an "unrecognized Artifact" error during your Pipeline's execution. */ - additionalInputArtifacts?: codepipeline.Artifact[]; + readonly additionalInputArtifacts?: codepipeline.Artifact[]; } // tslint:enable:max-line-length /** * Base class for all CloudFormation actions that execute or stage deployments. */ -export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFormationAction { +export abstract class CloudFormationDeployAction extends CloudFormationAction { private _deploymentRole?: iam.IRole; - private readonly props: PipelineCloudFormationDeployActionProps; + private readonly props: CloudFormationDeployActionProps; - constructor(props: PipelineCloudFormationDeployActionProps, configuration: any) { - const capabilities = props.adminPermissions && props.capabilities === undefined ? CloudFormationCapabilities.NamedIAM : props.capabilities; + constructor(props: CloudFormationDeployActionProps, configuration: any) { + const capabilities = props.adminPermissions && props.capabilities === undefined + ? cloudformation.CloudFormationCapabilities.NamedIAM + : props.capabilities; super(props, { ...configuration, // None evaluates to empty string which is falsey and results in undefined @@ -257,11 +259,11 @@ export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFo return this.getDeploymentRole('property role()'); } - protected bind(stage: codepipeline.IStage, scope: cdk.Construct): void { + protected bind(info: codepipeline.ActionBind): void { if (this.props.deploymentRole) { this._deploymentRole = this.props.deploymentRole; } else { - this._deploymentRole = new iam.Role(scope, 'Role', { + this._deploymentRole = new iam.Role(info.scope, 'Role', { assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com') }); @@ -270,7 +272,7 @@ export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFo } } - SingletonPolicy.forRole(stage.pipeline.role).grantPassRole(this._deploymentRole); + SingletonPolicy.forRole(info.role).grantPassRole(this._deploymentRole); } private getDeploymentRole(member: string): iam.IRole { @@ -283,18 +285,18 @@ export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFo } /** - * Properties for the PipelineCreateReplaceChangeSetAction. + * Properties for the CloudFormationCreateReplaceChangeSetAction. */ -export interface PipelineCreateReplaceChangeSetActionProps extends PipelineCloudFormationDeployActionProps { +export interface CloudFormationCreateReplaceChangeSetActionProps extends CloudFormationDeployActionProps { /** * Name of the change set to create or update. */ - changeSetName: string; + readonly changeSetName: string; /** * Input artifact with the ChangeSet's CloudFormation template */ - templatePath: codepipeline.ArtifactPath; + readonly templatePath: codepipeline.ArtifactPath; } /** @@ -303,10 +305,10 @@ export interface PipelineCreateReplaceChangeSetActionProps extends PipelineCloud * Creates the change set if it doesn't exist based on the stack name and template that you submit. * If the change set exists, AWS CloudFormation deletes it, and then creates a new one. */ -export class PipelineCreateReplaceChangeSetAction extends PipelineCloudFormationDeployAction { - private readonly props2: PipelineCreateReplaceChangeSetActionProps; +export class CloudFormationCreateReplaceChangeSetAction extends CloudFormationDeployAction { + private readonly props2: CloudFormationCreateReplaceChangeSetActionProps; - constructor(props: PipelineCreateReplaceChangeSetActionProps) { + constructor(props: CloudFormationCreateReplaceChangeSetActionProps) { super(props, { ActionMode: 'CHANGE_SET_REPLACE', ChangeSetName: props.changeSetName, @@ -321,21 +323,21 @@ export class PipelineCreateReplaceChangeSetAction extends PipelineCloudFormation this.props2 = props; } - protected bind(stage: codepipeline.IStage, scope: cdk.Construct): void { - super.bind(stage, scope); + protected bind(info: codepipeline.ActionBind): void { + super.bind(info); - SingletonPolicy.forRole(stage.pipeline.role).grantCreateReplaceChangeSet(this.props2); + SingletonPolicy.forRole(info.role).grantCreateReplaceChangeSet(this.props2); } } /** - * Properties for the PipelineCreateUpdateStackAction. + * Properties for the CloudFormationCreateUpdateStackAction. */ -export interface PipelineCreateUpdateStackActionProps extends PipelineCloudFormationDeployActionProps { +export interface CloudFormationCreateUpdateStackActionProps extends CloudFormationDeployActionProps { /** * Input artifact with the CloudFormation template to deploy */ - templatePath: codepipeline.ArtifactPath; + readonly templatePath: codepipeline.ArtifactPath; /** * Replace the stack if it's in a failed state. @@ -350,7 +352,7 @@ export interface PipelineCreateUpdateStackActionProps extends PipelineCloudForma * * @default false */ - replaceOnFailure?: boolean; + readonly replaceOnFailure?: boolean; } /** @@ -367,10 +369,10 @@ export interface PipelineCreateUpdateStackActionProps extends PipelineCloudForma * Use this action to automatically replace failed stacks without recovering or * troubleshooting them. You would typically choose this mode for testing. */ -export class PipelineCreateUpdateStackAction extends PipelineCloudFormationDeployAction { - private readonly props2: PipelineCreateUpdateStackActionProps; +export class CloudFormationCreateUpdateStackAction extends CloudFormationDeployAction { + private readonly props2: CloudFormationCreateUpdateStackActionProps; - constructor(props: PipelineCreateUpdateStackActionProps) { + constructor(props: CloudFormationCreateUpdateStackActionProps) { super(props, { ActionMode: props.replaceOnFailure ? 'REPLACE_ON_FAILURE' : 'CREATE_UPDATE', TemplatePath: props.templatePath.location @@ -384,18 +386,18 @@ export class PipelineCreateUpdateStackAction extends PipelineCloudFormationDeplo this.props2 = props; } - protected bind(stage: codepipeline.IStage, scope: cdk.Construct): void { - super.bind(stage, scope); + protected bind(info: codepipeline.ActionBind): void { + super.bind(info); - SingletonPolicy.forRole(stage.pipeline.role).grantCreateUpdateStack(this.props2); + SingletonPolicy.forRole(info.role).grantCreateUpdateStack(this.props2); } } /** - * Properties for the PipelineDeleteStackAction. + * Properties for the CloudFormationDeleteStackAction. */ // tslint:disable-next-line:no-empty-interface -export interface PipelineDeleteStackActionProps extends PipelineCloudFormationDeployActionProps { +export interface CloudFormationDeleteStackActionProps extends CloudFormationDeployActionProps { } /** @@ -404,10 +406,10 @@ export interface PipelineDeleteStackActionProps extends PipelineCloudFormationDe * Deletes a stack. If you specify a stack that doesn't exist, the action completes successfully * without deleting a stack. */ -export class PipelineDeleteStackAction extends PipelineCloudFormationDeployAction { - private readonly props2: PipelineDeleteStackActionProps; +export class CloudFormationDeleteStackAction extends CloudFormationDeployAction { + private readonly props2: CloudFormationDeleteStackActionProps; - constructor(props: PipelineDeleteStackActionProps) { + constructor(props: CloudFormationDeleteStackActionProps) { super(props, { ActionMode: 'DELETE_ONLY', }); @@ -415,45 +417,13 @@ export class PipelineDeleteStackAction extends PipelineCloudFormationDeployActio this.props2 = props; } - protected bind(stage: codepipeline.IStage, scope: cdk.Construct): void { - super.bind(stage, scope); + protected bind(info: codepipeline.ActionBind): void { + super.bind(info); - SingletonPolicy.forRole(stage.pipeline.role).grantDeleteStack(this.props2); + SingletonPolicy.forRole(info.role).grantDeleteStack(this.props2); } } -/** - * Capabilities that affect whether CloudFormation is allowed to change IAM resources - */ -export enum CloudFormationCapabilities { - /** - * No IAM Capabilities - * - * Pass this capability if you wish to block the creation IAM resources. - * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities - */ - None = '', - - /** - * Capability to create anonymous IAM resources - * - * Pass this capability if you're only creating anonymous resources. - * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities - */ - AnonymousIAM = 'CAPABILITY_IAM', - - /** - * Capability to create named IAM resources. - * - * Pass this capability if you're creating IAM resources that have physical - * names. - * - * `CloudFormationCapabilities.NamedIAM` implies `CloudFormationCapabilities.IAM`; you don't have to pass both. - * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities - */ - NamedIAM = 'CAPABILITY_NAMED_IAM', -} - /** * Manages a bunch of singleton-y statements on the policy of an IAM Role. * Dedicated methods can be used to add specific permissions to the role policy @@ -469,7 +439,7 @@ class SingletonPolicy extends cdk.Construct { * @param role the Role this policy is bound to. * @returns the SingletonPolicy for this role. */ - public static forRole(role: iam.Role): SingletonPolicy { + public static forRole(role: iam.IRole): SingletonPolicy { const found = role.node.tryFindChild(SingletonPolicy.UUID); return (found as SingletonPolicy) || new SingletonPolicy(role); } @@ -478,8 +448,8 @@ class SingletonPolicy extends cdk.Construct { private statements: { [key: string]: iam.PolicyStatement } = {}; - private constructor(private readonly role: iam.Role) { - super(role, SingletonPolicy.UUID); + private constructor(private readonly role: iam.IRole) { + super(role as unknown as cdk.Construct, SingletonPolicy.UUID); } public grantExecuteChangeSet(props: { stackName: string, changeSetName: string, region?: string }): void { diff --git a/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/pipeline-actions.ts similarity index 66% rename from packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/pipeline-actions.ts index 16589721b5738..1ff4ae6f53c87 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codebuild/pipeline-actions.ts @@ -1,62 +1,53 @@ -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); +import codebuild = require('@aws-cdk/aws-codebuild'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); -import cdk = require('@aws-cdk/cdk'); -import { IProject } from './project'; /** * Common construction properties of all CodeBuild Pipeline Actions. */ -export interface CommonCodeBuildActionProps { +export interface CommonCodeBuildActionProps extends codepipeline.CommonActionProps { + /** + * The source to use as input for this Action. + */ + readonly inputArtifact: codepipeline.Artifact; + /** * The list of additional input Artifacts for this Action. */ - additionalInputArtifacts?: codepipeline.Artifact[]; + readonly additionalInputArtifacts?: codepipeline.Artifact[]; /** * The list of names for additional output Artifacts for this Action. * The resulting output artifacts can be accessed with the `additionalOutputArtifacts` * method of the Action. */ - additionalOutputArtifactNames?: string[]; -} + readonly additionalOutputArtifactNames?: string[]; -/** - * Common properties for creating {@link PipelineBuildAction} - - * either directly, through its constructor, - * or through {@link IProject#toCodePipelineBuildAction}. - */ -export interface CommonPipelineBuildActionProps extends CommonCodeBuildActionProps, - codepipeline.CommonActionProps { /** - * The source to use as input for this build. - */ - inputArtifact: codepipeline.Artifact; - - /** - * The name of the build's output artifact. - * - * @default an auto-generated name will be used + * The Action's Project. */ - outputArtifactName?: string; + readonly project: codebuild.IProject; } /** - * Construction properties of the {@link PipelineBuildAction CodeBuild build CodePipeline Action}. + * Construction properties of the {@link CodeBuildBuildAction CodeBuild build CodePipeline Action}. */ -export interface PipelineBuildActionProps extends CommonPipelineBuildActionProps { +export interface CodeBuildBuildActionProps extends CommonCodeBuildActionProps { /** - * The build project + * The name of the build's output artifact. + * + * @default an auto-generated name will be used */ - project: IProject; + readonly outputArtifactName?: string; } /** * CodePipeline build Action that uses AWS CodeBuild. */ -export class PipelineBuildAction extends codepipeline.BuildAction { - private readonly props: PipelineBuildActionProps; +export class CodeBuildBuildAction extends codepipeline.BuildAction { + private readonly props: CodeBuildBuildActionProps; - constructor(props: PipelineBuildActionProps) { + constructor(props: CodeBuildBuildActionProps) { super({ ...props, provider: 'CodeBuild', @@ -71,6 +62,7 @@ export class PipelineBuildAction extends codepipeline.BuildAction { handleAdditionalInputOutputArtifacts(props, this, // pass functions to get around protected members + this.actionInputArtifacts, (artifact) => this.addInputArtifact(artifact), (artifactName) => this.addOutputArtifact(artifactName)); } @@ -84,7 +76,7 @@ export class PipelineBuildAction extends codepipeline.BuildAction { * @see #additionalOutputArtifact */ public additionalOutputArtifacts(): codepipeline.Artifact[] { - return this._outputArtifacts.slice(1); + return this.actionOutputArtifacts.slice(1); } /** @@ -102,23 +94,15 @@ export class PipelineBuildAction extends codepipeline.BuildAction { return findOutputArtifact(this.additionalOutputArtifacts(), name); } - protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { - setCodeBuildNeededPermissions(stage, this.props.project, true); + protected bind(info: codepipeline.ActionBind): void { + setCodeBuildNeededPermissions(info, this.props.project, true); } } /** - * Common properties for creating {@link PipelineTestAction} - - * either directly, through its constructor, - * or through {@link IProject#toCodePipelineTestAction}. + * Construction properties of the {@link CodeBuildTestAction CodeBuild test CodePipeline Action}. */ -export interface CommonPipelineTestActionProps extends CommonCodeBuildActionProps, - codepipeline.CommonActionProps { - /** - * The source to use as input for this test. - */ - inputArtifact: codepipeline.Artifact; - +export interface CodeBuildTestActionProps extends CommonCodeBuildActionProps { /** * The optional name of the primary output artifact. * If you provide a value here, @@ -127,23 +111,16 @@ export interface CommonPipelineTestActionProps extends CommonCodeBuildActionProp * * @default the Action will not have an output artifact */ - outputArtifactName?: string; + readonly outputArtifactName?: string; } /** - * Construction properties of the {@link PipelineTestAction CodeBuild test CodePipeline Action}. + * CodePipeline test Action that uses AWS CodeBuild. */ -export interface PipelineTestActionProps extends CommonPipelineTestActionProps { - /** - * The build Project. - */ - project: IProject; -} - -export class PipelineTestAction extends codepipeline.TestAction { - private readonly props: PipelineTestActionProps; +export class CodeBuildTestAction extends codepipeline.TestAction { + private readonly props: CodeBuildTestActionProps; - constructor(props: PipelineTestActionProps) { + constructor(props: CodeBuildTestActionProps) { super({ ...props, provider: 'CodeBuild', @@ -157,6 +134,7 @@ export class PipelineTestAction extends codepipeline.TestAction { handleAdditionalInputOutputArtifacts(props, this, // pass functions to get around protected members + this.actionInputArtifacts, (artifact) => this.addInputArtifact(artifact), (artifactName) => this.addOutputArtifact(artifactName)); } @@ -171,8 +149,8 @@ export class PipelineTestAction extends codepipeline.TestAction { */ public additionalOutputArtifacts(): codepipeline.Artifact[] { return this.outputArtifact === undefined - ? this._outputArtifacts - : this._outputArtifacts.slice(1); + ? this.actionOutputArtifacts + : this.actionOutputArtifacts.slice(1); } /** @@ -190,15 +168,15 @@ export class PipelineTestAction extends codepipeline.TestAction { return findOutputArtifact(this.additionalOutputArtifacts(), name); } - protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { - setCodeBuildNeededPermissions(stage, this.props.project, this._outputArtifacts.length > 0); + protected bind(info: codepipeline.ActionBind): void { + setCodeBuildNeededPermissions(info, this.props.project, this.actionOutputArtifacts.length > 0); } } -function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: IProject, - needsPipelineBucketWrite: boolean) { +function setCodeBuildNeededPermissions(info: codepipeline.ActionBind, project: codebuild.IProject, + needsPipelineBucketWrite: boolean): void { // grant the Pipeline role the required permissions to this Project - stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + info.role.addToPolicy(new iam.PolicyStatement() .addResource(project.projectArn) .addActions( 'codebuild:BatchGetBuilds', @@ -208,18 +186,19 @@ function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: IPro // allow the Project access to the Pipline's artifact Bucket if (needsPipelineBucketWrite) { - stage.pipeline.grantBucketReadWrite(project.role); + info.pipeline.grantBucketReadWrite(project); } else { - stage.pipeline.grantBucketRead(project.role); + info.pipeline.grantBucketRead(project); } } function handleAdditionalInputOutputArtifacts(props: CommonCodeBuildActionProps, action: codepipeline.Action, + inputArtifacts: codepipeline.Artifact[], addInputArtifact: (_: codepipeline.Artifact) => void, addOutputArtifact: (_: string) => void) { if ((props.additionalInputArtifacts || []).length > 0) { // we have to set the primary source in the configuration - action.configuration.PrimarySource = action._inputArtifacts[0].artifactName; + action.configuration.PrimarySource = inputArtifacts[0].artifactName; // add the additional artifacts for (const additionalInputArtifact of props.additionalInputArtifacts || []) { addInputArtifact(additionalInputArtifact); diff --git a/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts similarity index 53% rename from packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts index b661e90cf63bd..3a44d307c5d4c 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts @@ -1,26 +1,23 @@ -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); +import codecommit = require('@aws-cdk/aws-codecommit'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); -import cdk = require('@aws-cdk/cdk'); -import { IRepository } from './repository'; /** - * Common properties for creating {@link PipelineSourceAction} - - * either directly, through its constructor, - * or through {@link IRepository#toCodePipelineSourceAction}. + * Construction properties of the {@link CodeCommitSourceAction CodeCommit source CodePipeline Action}. */ -export interface CommonPipelineSourceActionProps extends codepipeline.CommonActionProps { +export interface CodeCommitSourceActionProps extends codepipeline.CommonActionProps { /** * The name of the source's output artifact. - * CfnOutput artifacts are used by CodePipeline as inputs into other actions. + * Output artifacts are used by CodePipeline as inputs into other actions. * * @default a name will be auto-generated */ - outputArtifactName?: string; + readonly outputArtifactName?: string; /** * @default 'master' */ - branch?: string; + readonly branch?: string; /** * Whether AWS CodePipeline should poll for source changes. @@ -28,26 +25,21 @@ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActi * * @default false */ - pollForSourceChanges?: boolean; -} + readonly pollForSourceChanges?: boolean; -/** - * Construction properties of the {@link PipelineSourceAction CodeCommit source CodePipeline Action}. - */ -export interface PipelineSourceActionProps extends CommonPipelineSourceActionProps { /** * The CodeCommit repository. */ - repository: IRepository; + readonly repository: codecommit.IRepository; } /** * CodePipeline Source that is provided by an AWS CodeCommit repository. */ -export class PipelineSourceAction extends codepipeline.SourceAction { - private readonly props: PipelineSourceActionProps; +export class CodeCommitSourceAction extends codepipeline.SourceAction { + private readonly props: CodeCommitSourceActionProps; - constructor(props: PipelineSourceActionProps) { + constructor(props: CodeCommitSourceActionProps) { super({ ...props, provider: 'CodeCommit', @@ -62,10 +54,10 @@ export class PipelineSourceAction extends codepipeline.SourceAction { this.props = props; } - protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { + protected bind(info: codepipeline.ActionBind): void { if (!this.props.pollForSourceChanges) { - this.props.repository.onCommit(stage.pipeline.node.uniqueId + 'EventRule', - stage.pipeline, this.props.branch || 'master'); + this.props.repository.onCommit(info.pipeline.node.uniqueId + 'EventRule', + info.pipeline, this.props.branch || 'master'); } // https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control-permissions-reference.html#aa-acp @@ -77,7 +69,7 @@ export class PipelineSourceAction extends codepipeline.SourceAction { 'codecommit:CancelUploadArchive', ]; - stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + info.role.addToPolicy(new iam.PolicyStatement() .addResource(this.props.repository.repositoryArn) .addActions(...actions)); } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts similarity index 51% rename from packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts index d2e23fa05b34a..f576c40ed359e 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codedeploy/server-deploy-action.ts @@ -1,34 +1,26 @@ -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); +import codedeploy = require('@aws-cdk/aws-codedeploy'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); -import cdk = require('@aws-cdk/cdk'); -import { IServerDeploymentGroup } from './server'; /** - * Common properties for creating a {@link PipelineDeployAction}, - * either directly, through its constructor, - * or through {@link IServerDeploymentGroup#toCodePipelineDeployAction}. + * Construction properties of the {@link CodeDeployServerDeployAction CodeDeploy server deploy CodePipeline Action}. */ -export interface CommonPipelineDeployActionProps extends codepipeline.CommonActionProps { +export interface CodeDeployServerDeployActionProps extends codepipeline.CommonActionProps { /** * The source to use as input for deployment. */ - inputArtifact: codepipeline.Artifact; -} + readonly inputArtifact: codepipeline.Artifact; -/** - * Construction properties of the {@link PipelineDeployAction CodeDeploy deploy CodePipeline Action}. - */ -export interface PipelineDeployActionProps extends CommonPipelineDeployActionProps { /** - * The CodeDeploy Deployment Group to deploy to. + * The CodeDeploy server Deployment Group to deploy to. */ - deploymentGroup: IServerDeploymentGroup; + readonly deploymentGroup: codedeploy.IServerDeploymentGroup; } -export class PipelineDeployAction extends codepipeline.DeployAction { - private readonly deploymentGroup: IServerDeploymentGroup; +export class CodeDeployServerDeployAction extends codepipeline.DeployAction { + private readonly deploymentGroup: codedeploy.IServerDeploymentGroup; - constructor(props: PipelineDeployActionProps) { + constructor(props: CodeDeployServerDeployActionProps) { super({ ...props, artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 }, @@ -43,33 +35,33 @@ export class PipelineDeployAction extends codepipeline.DeployAction { this.deploymentGroup = props.deploymentGroup; } - protected bind(stage: codepipeline.IStage, scope: cdk.Construct): void { + protected bind(info: codepipeline.ActionBind): void { // permissions, based on: // https://docs.aws.amazon.com/codedeploy/latest/userguide/auth-and-access-control-permissions-reference.html - stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + info.role.addToPolicy(new iam.PolicyStatement() .addResource(this.deploymentGroup.application.applicationArn) .addActions( 'codedeploy:GetApplicationRevision', 'codedeploy:RegisterApplicationRevision', )); - stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + info.role.addToPolicy(new iam.PolicyStatement() .addResource(this.deploymentGroup.deploymentGroupArn) .addActions( 'codedeploy:CreateDeployment', 'codedeploy:GetDeployment', )); - stage.pipeline.role.addToPolicy(new iam.PolicyStatement() - .addResource(this.deploymentGroup.deploymentConfig.deploymentConfigArn(scope)) + info.role.addToPolicy(new iam.PolicyStatement() + .addResource(this.deploymentGroup.deploymentConfig.deploymentConfigArn(info.scope)) .addActions( 'codedeploy:GetDeploymentConfig', )); // grant the ASG Role permissions to read from the Pipeline Bucket for (const asg of this.deploymentGroup.autoScalingGroups || []) { - stage.pipeline.grantBucketRead(asg.role); + info.pipeline.grantBucketRead(asg.role); } } } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts similarity index 93% rename from packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts index d1920ac6e7195..ed5881287509d 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/custom-action-registration.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/custom-action-registration.ts @@ -1,6 +1,5 @@ -import cpapi = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import cdk = require('@aws-cdk/cdk'); -import { CfnCustomActionType } from './codepipeline.generated'; /** * The creation attributes used for defining a configuration property @@ -71,12 +70,12 @@ export interface CustomActionRegistrationProps { /** * The category of the Action. */ - category: cpapi.ActionCategory; + category: codepipeline.ActionCategory; /** * The artifact bounds of the Action. */ - artifactBounds: cpapi.ActionArtifactBounds; + artifactBounds: codepipeline.ActionArtifactBounds; /** * The provider of the Action. @@ -119,7 +118,7 @@ export class CustomActionRegistration extends cdk.Construct { constructor(parent: cdk.Construct, id: string, props: CustomActionRegistrationProps) { super(parent, id); - new CfnCustomActionType(this, 'Resource', { + new codepipeline.CfnCustomActionType(this, 'Resource', { category: props.category, inputArtifactDetails: { minimumCount: props.artifactBounds.minInputs, 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 new file mode 100644 index 0000000000000..be2f4f964e144 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecr/source-action.ts @@ -0,0 +1,60 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import ecr = require('@aws-cdk/aws-ecr'); +import iam = require('@aws-cdk/aws-iam'); + +/** + * Construction properties of {@link EcrSourceAction}. + */ +export interface EcrSourceActionProps extends codepipeline.CommonActionProps { + /** + * The image tag that will be checked for changes. + * + * @default 'latest' + */ + readonly imageTag?: string; + + /** + * The name of the source's output artifact. + * CfnOutput artifacts are used by CodePipeline as inputs into other actions. + * + * @default a name will be auto-generated + */ + readonly outputArtifactName?: string; + + /** + * The repository that will be watched for changes. + */ + readonly repository: ecr.IRepository; +} + +/** + * The ECR Repository source CodePipeline Action. + */ +export class EcrSourceAction extends codepipeline.SourceAction { + private readonly props: EcrSourceActionProps; + + constructor(props: EcrSourceActionProps) { + super({ + ...props, + provider: 'ECR', + configuration: { + RepositoryName: props.repository.repositoryName, + ImageTag: props.imageTag, + }, + outputArtifactName: props.outputArtifactName || `Artifact_${props.actionName}_${props.repository.node.uniqueId}`, + }); + + this.props = props; + } + + protected bind(info: codepipeline.ActionBind): void { + info.role.addToPolicy(new iam.PolicyStatement() + .addActions( + 'ecr:DescribeImages', + ) + .addResource(this.props.repository.repositoryArn)); + + this.props.repository.onImagePushed(info.pipeline.node.uniqueId + 'SourceEventRule', + info.pipeline, this.props.imageTag); + } +} diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts new file mode 100644 index 0000000000000..7f90a63847a22 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts @@ -0,0 +1,104 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import ecs = require('@aws-cdk/aws-ecs'); +import iam = require('@aws-cdk/aws-iam'); + +/** + * Construction properties of {@link EcsDeployAction}. + */ +export interface EcsDeployActionProps extends codepipeline.CommonActionProps { + /** + * The input artifact that contains the JSON image definitions file to use for deployments. + * The JSON file is a list of objects, + * each with 2 keys: `name` is the name of the container in the Task Definition, + * and `imageUri` is the Docker image URI you want to update your service with. + * If you use this property, it's assumed the file is called 'imagedefinitions.json'. + * If your build uses a different file, leave this property empty, + * and use the `imageFile` property instead. + * + * @default - one of this property, or `imageFile`, is required + * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create.html#pipelines-create-image-definitions + */ + readonly inputArtifact?: codepipeline.Artifact; + + /** + * The name of the JSON image definitions file to use for deployments. + * The JSON file is a list of objects, + * each with 2 keys: `name` is the name of the container in the Task Definition, + * and `imageUri` is the Docker image URI you want to update your service with. + * Use this property if you want to use a different name for this file than the default 'imagedefinitions.json'. + * If you use this property, you don't need to specify the `inputArtifact` property. + * + * @default - one of this property, or `inputArtifact`, is required + * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/pipelines-create.html#pipelines-create-image-definitions + */ + readonly imageFile?: codepipeline.ArtifactPath; + + /** + * The ECS Service to deploy. + */ + readonly service: ecs.BaseService; +} + +/** + * CodePipeline Action to deploy an ECS Service. + */ +export class EcsDeployAction extends codepipeline.DeployAction { + constructor(props: EcsDeployActionProps) { + super({ + ...props, + inputArtifact: determineInputArtifact(props), + provider: 'ECS', + artifactBounds: { + minInputs: 1, + maxInputs: 1, + minOutputs: 0, + maxOutputs: 0, + }, + configuration: { + ClusterName: props.service.clusterName, + ServiceName: props.service.serviceName, + FileName: props.imageFile && props.imageFile.fileName, + }, + }); + } + + protected bind(info: codepipeline.ActionBind): void { + // permissions based on CodePipeline documentation: + // https://docs.aws.amazon.com/codepipeline/latest/userguide/how-to-custom-role.html#how-to-update-role-new-services + info.role.addToPolicy(new iam.PolicyStatement() + .addActions( + 'ecs:DescribeServices', + 'ecs:DescribeTaskDefinition', + 'ecs:DescribeTasks', + 'ecs:ListTasks', + 'ecs:RegisterTaskDefinition', + 'ecs:UpdateService', + ) + .addAllResources()); + + info.role.addToPolicy(new iam.PolicyStatement() + .addActions( + 'iam:PassRole', + ) + .addAllResources() + .addCondition('StringEqualsIfExists', { + 'iam:PassedToService': [ + 'ec2.amazonaws.com', + 'ecs-tasks.amazonaws.com', + ], + })); + } +} + +function determineInputArtifact(props: EcsDeployActionProps): codepipeline.Artifact { + if (props.imageFile && props.inputArtifact) { + throw new Error("Exactly one of 'inputArtifact' or 'imageFile' can be provided in the ECS deploy Action"); + } + if (props.imageFile) { + return props.imageFile.artifact; + } + if (props.inputArtifact) { + return props.inputArtifact; + } + throw new Error("Specifying one of 'inputArtifact' or 'imageFile' is required for the ECS deploy Action"); +} diff --git a/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts similarity index 66% rename from packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts index 30ca355929aeb..a0f2543908fa9 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/github/source-action.ts @@ -1,44 +1,42 @@ -import actions = require('@aws-cdk/aws-codepipeline-api'); -import cdk = require('@aws-cdk/cdk'); -import { CfnWebhook } from './codepipeline.generated'; +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import { SecretValue } from '@aws-cdk/cdk'; /** * Construction properties of the {@link GitHubSourceAction GitHub source action}. */ -export interface GitHubSourceActionProps extends actions.CommonActionProps { +export interface GitHubSourceActionProps extends codepipeline.CommonActionProps { /** * The name of the source's output artifact. CfnOutput artifacts are used by CodePipeline as * inputs into other actions. */ - outputArtifactName: string; + readonly outputArtifactName: string; /** * The GitHub account/user that owns the repo. */ - owner: string; + readonly owner: string; /** * The name of the repo, without the username. */ - repo: string; + readonly repo: string; /** * The branch to use. * * @default "master" */ - branch?: string; + readonly branch?: string; /** * A GitHub OAuth token to use for authentication. * - * It is recommended to use a `SecretParameter` to obtain the token from the SSM - * Parameter Store: + * It is recommended to use a Secrets Manager `SecretString` to obtain the token: * - * const oauth = new cdk.SecretParameter(this, 'GitHubOAuthToken', { ssmParameter: 'my-github-token' }); + * const oauth = new secretsmanager.SecretString(this, 'GitHubOAuthToken', { secretId: 'my-github-token' }); * new GitHubSource(this, 'GitHubAction', { oauthToken: oauth.value, ... }); */ - oauthToken: cdk.Secret; + readonly oauthToken: SecretValue; /** * Whether AWS CodePipeline should poll for source changes. @@ -46,13 +44,13 @@ export interface GitHubSourceActionProps extends actions.CommonActionProps { * * @default false */ - pollForSourceChanges?: boolean; + readonly pollForSourceChanges?: boolean; } /** * Source that is provided by a GitHub repository. */ -export class GitHubSourceAction extends actions.SourceAction { +export class GitHubSourceAction extends codepipeline.SourceAction { private readonly props: GitHubSourceActionProps; constructor(props: GitHubSourceActionProps) { @@ -64,7 +62,7 @@ export class GitHubSourceAction extends actions.SourceAction { Owner: props.owner, Repo: props.repo, Branch: props.branch || "master", - OAuthToken: props.oauthToken, + OAuthToken: props.oauthToken.toString(), PollForSourceChanges: props.pollForSourceChanges || false, }, outputArtifactName: props.outputArtifactName @@ -73,9 +71,9 @@ export class GitHubSourceAction extends actions.SourceAction { this.props = props; } - protected bind(stage: actions.IStage, scope: cdk.Construct): void { + protected bind(info: codepipeline.ActionBind): void { if (!this.props.pollForSourceChanges) { - new CfnWebhook(scope, 'WebhookResource', { + new codepipeline.CfnWebhook(info.scope, 'WebhookResource', { authentication: 'GITHUB_HMAC', authenticationConfiguration: { secretToken: this.props.oauthToken.toString(), @@ -87,7 +85,7 @@ export class GitHubSourceAction extends actions.SourceAction { }, ], targetAction: this.actionName, - targetPipeline: stage.pipeline.pipelineName, + targetPipeline: info.pipeline.pipelineName, targetPipelineVersion: 1, registerWithThirdParty: true, }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts new file mode 100644 index 0000000000000..6554cd00a64e4 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/index.ts @@ -0,0 +1,14 @@ +export * from './alexa-ask/deploy-action'; +export * from './cloudformation/pipeline-actions'; +export * from './codebuild/pipeline-actions'; +export * from './codecommit/source-action'; +export * from './codedeploy/server-deploy-action'; +export * from './ecr/source-action'; +export * from './ecs/deploy-action'; +export * from './github/source-action'; +export * from './jenkins/jenkins-actions'; +export * from './jenkins/jenkins-provider'; +export * from './lambda/invoke-action'; +export * from './manual-approval-action'; +export * from './s3/deploy-action'; +export * from './s3/source-action'; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-actions.ts similarity index 64% rename from packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-actions.ts index 0956e8fe147cf..70a58fe5e2118 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-actions.ts @@ -1,47 +1,39 @@ -import cpapi = require('@aws-cdk/aws-codepipeline-api'); -import cdk = require('@aws-cdk/cdk'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import { IJenkinsProvider, jenkinsArtifactsBounds } from "./jenkins-provider"; /** * Common construction properties of all Jenkins Pipeline Actions. */ -export interface BasicJenkinsActionProps extends cpapi.CommonActionProps { +export interface CommonJenkinsActionProps extends codepipeline.CommonActionProps { /** * The name of the project (sometimes also called job, or task) * on your Jenkins installation that will be invoked by this Action. * * @example 'MyJob' */ - projectName: string; + readonly projectName: string; /** * The source to use as input for this build. */ - inputArtifact: cpapi.Artifact; -} + readonly inputArtifact: codepipeline.Artifact; -/** - * Common properties for creating {@link JenkinsBuildAction} - - * either directly, through its constructor, - * or through {@link IJenkinsProvider#toCodePipelineBuildAction}. - */ -export interface BasicJenkinsBuildActionProps extends BasicJenkinsActionProps { /** - * The name of the build's output artifact. - * - * @default an auto-generated name will be used + * The Jenkins Provider for this Action. */ - outputArtifactName?: string; + readonly jenkinsProvider: IJenkinsProvider; } /** * Construction properties of {@link JenkinsBuildAction}. */ -export interface JenkinsBuildActionProps extends BasicJenkinsBuildActionProps { +export interface JenkinsBuildActionProps extends CommonJenkinsActionProps { /** - * The Jenkins Provider for this Action. + * The name of the build's output artifact. + * + * @default an auto-generated name will be used */ - jenkinsProvider: IJenkinsProvider; + readonly outputArtifactName?: string; } /** @@ -49,7 +41,7 @@ export interface JenkinsBuildActionProps extends BasicJenkinsBuildActionProps { * * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-four-stage-pipeline.html */ -export class JenkinsBuildAction extends cpapi.BuildAction { +export class JenkinsBuildAction extends codepipeline.BuildAction { private readonly jenkinsProvider: IJenkinsProvider; constructor(props: JenkinsBuildActionProps) { @@ -68,17 +60,15 @@ export class JenkinsBuildAction extends cpapi.BuildAction { this.jenkinsProvider = props.jenkinsProvider; } - protected bind(_stage: cpapi.IStage, _scope: cdk.Construct): void { + protected bind(_info: codepipeline.ActionBind): void { this.jenkinsProvider._registerBuildProvider(); } } /** - * Common properties for creating {@link JenkinsTestAction} - - * either directly, through its constructor, - * or through {@link IJenkinsProvider#toCodePipelineTestAction}. + * Construction properties of {@link JenkinsTestAction}. */ -export interface BasicJenkinsTestActionProps extends BasicJenkinsActionProps { +export interface JenkinsTestActionProps extends CommonJenkinsActionProps { /** * The optional name of the primary output artifact. * If you provide a value here, @@ -87,17 +77,7 @@ export interface BasicJenkinsTestActionProps extends BasicJenkinsActionProps { * * @default the Action will not have an output artifact */ - outputArtifactName?: string; -} - -/** - * Construction properties of {@link JenkinsTestAction}. - */ -export interface JenkinsTestActionProps extends BasicJenkinsTestActionProps { - /** - * The Jenkins Provider for this Action. - */ - jenkinsProvider: IJenkinsProvider; + readonly outputArtifactName?: string; } /** @@ -105,7 +85,7 @@ export interface JenkinsTestActionProps extends BasicJenkinsTestActionProps { * * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-four-stage-pipeline.html */ -export class JenkinsTestAction extends cpapi.TestAction { +export class JenkinsTestAction extends codepipeline.TestAction { private readonly jenkinsProvider: IJenkinsProvider; constructor(props: JenkinsTestActionProps) { @@ -123,7 +103,7 @@ export class JenkinsTestAction extends cpapi.TestAction { this.jenkinsProvider = props.jenkinsProvider; } - protected bind(_stage: cpapi.IStage, _scope: cdk.Construct): void { + protected bind(_info: codepipeline.ActionBind): void { this.jenkinsProvider._registerTestProvider(); } } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts similarity index 78% rename from packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts index bd2ce57736f7d..58287ce3346e5 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/jenkins/jenkins-provider.ts @@ -1,12 +1,6 @@ -import cpapi = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import cdk = require('@aws-cdk/cdk'); -import { CustomActionRegistration } from "./custom-action-registration"; -import { - BasicJenkinsBuildActionProps, - BasicJenkinsTestActionProps, - JenkinsBuildAction, - JenkinsTestAction -} from "./jenkins-actions"; +import { CustomActionRegistration } from "../custom-action-registration"; /** * A Jenkins provider. @@ -22,29 +16,13 @@ export interface IJenkinsProvider extends cdk.IConstruct { readonly serverUrl: string; readonly version: string; - /** - * Convenience method for creating a new {@link JenkinsBuildAction}. - * - * @param props construction properties of the new Action - * @returns the newly created {@link JenkinsBuildAction} - */ - toCodePipelineBuildAction(props: BasicJenkinsBuildActionProps): - JenkinsBuildAction; - - /** - * Convenience method for creating a new {@link JenkinsTestAction}. - * - * @param props construction properties of the new Action - * @returns the newly created {@link JenkinsTestAction} - */ - toCodePipelineTestAction(props: BasicJenkinsTestActionProps): - JenkinsTestAction; - /** * Registers a Jenkins Provider for the build category. * This method will be automatically called when creating * a {@link JenkinsBuildAction}, * so you should never need to call it explicitly. + * + * @internal */ _registerBuildProvider(): void; @@ -53,6 +31,8 @@ export interface IJenkinsProvider extends cdk.IConstruct { * This method will be automatically called when creating * a {@link JenkinsTestAction}, * so you should never need to call it explicitly. + * + * @internal */ _registerTestProvider(): void; } @@ -66,21 +46,21 @@ export interface JenkinsProviderImportProps { * * @example 'MyJenkinsProvider' */ - providerName: string; + readonly providerName: string; /** * The base URL of your Jenkins server. * * @example 'http://myjenkins.com:8080' */ - serverUrl: string; + readonly serverUrl: string; /** * The version of your provider. * * @default '1' */ - version?: string; + readonly version?: string; } export interface JenkinsProviderProps { @@ -89,33 +69,33 @@ export interface JenkinsProviderProps { * * @example 'MyJenkinsProvider' */ - providerName: string; + readonly providerName: string; /** * The base URL of your Jenkins server. * * @example 'http://myjenkins.com:8080' */ - serverUrl: string; + readonly serverUrl: string; /** * The version of your provider. * * @default '1' */ - version?: string; + readonly version?: string; /** * Whether to immediately register a Jenkins Provider for the build category. * The Provider will always be registered if you create a {@link JenkinsBuildAction}. */ - forBuild?: boolean; + readonly forBuild?: boolean; /** * Whether to immediately register a Jenkins Provider for the test category. * The Provider will always be registered if you create a {@link JenkinsTestAction}. */ - forTest?: boolean; + readonly forTest?: boolean; } export abstract class BaseJenkinsProvider extends cdk.Construct implements IJenkinsProvider { @@ -143,21 +123,14 @@ export abstract class BaseJenkinsProvider extends cdk.Construct implements IJenk }; } - public toCodePipelineBuildAction(props: BasicJenkinsBuildActionProps): JenkinsBuildAction { - return new JenkinsBuildAction({ - ...props, - jenkinsProvider: this, - }); - } - - public toCodePipelineTestAction(props: BasicJenkinsTestActionProps): JenkinsTestAction { - return new JenkinsTestAction({ - ...props, - jenkinsProvider: this, - }); - } - + /** + * @internal + */ public abstract _registerBuildProvider(): void; + + /** + * @internal + */ public abstract _registerTestProvider(): void; } @@ -199,23 +172,29 @@ export class JenkinsProvider extends BaseJenkinsProvider { } } + /** + * @internal + */ public _registerBuildProvider(): void { if (this.buildIncluded) { return; } this.buildIncluded = true; - this.registerJenkinsCustomAction('JenkinsBuildProviderResource', cpapi.ActionCategory.Build); + this.registerJenkinsCustomAction('JenkinsBuildProviderResource', codepipeline.ActionCategory.Build); } + /** + * @internal + */ public _registerTestProvider(): void { if (this.testIncluded) { return; } this.testIncluded = true; - this.registerJenkinsCustomAction('JenkinsTestProviderResource', cpapi.ActionCategory.Test); + this.registerJenkinsCustomAction('JenkinsTestProviderResource', codepipeline.ActionCategory.Test); } - private registerJenkinsCustomAction(id: string, category: cpapi.ActionCategory) { + private registerJenkinsCustomAction(id: string, category: codepipeline.ActionCategory) { new CustomActionRegistration(this, id, { category, artifactBounds: jenkinsArtifactsBounds, @@ -259,7 +238,7 @@ function appendToUrl(baseUrl: string, path: string): string { return baseUrl.endsWith('/') ? baseUrl + path : `${baseUrl}/${path}`; } -export const jenkinsArtifactsBounds: cpapi.ActionArtifactBounds = { +export const jenkinsArtifactsBounds: codepipeline.ActionArtifactBounds = { minInputs: 0, maxInputs: 5, minOutputs: 0, diff --git a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts similarity index 72% rename from packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts index e7dcb4c447a40..660f261a73dad 100644 --- a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts @@ -1,14 +1,11 @@ -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); -import cdk = require('@aws-cdk/cdk'); -import { IFunction } from './function-base'; +import lambda = require('@aws-cdk/aws-lambda'); /** - * Common properties for creating a {@link PipelineInvokeAction} - - * either directly, through its constructor, - * or through {@link IFunction#toCodePipelineInvokeAction}. + * Construction properties of the {@link LambdaInvokeAction Lambda invoke CodePipeline Action}. */ -export interface CommonPipelineInvokeActionProps extends codepipeline.CommonActionProps { +export interface LambdaInvokeActionProps extends codepipeline.CommonActionProps { // because of @see links // tslint:disable:max-line-length @@ -21,7 +18,7 @@ export interface CommonPipelineInvokeActionProps extends codepipeline.CommonActi * @default the Action will not have any inputs * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-json-event-example */ - inputArtifacts?: codepipeline.Artifact[]; + readonly inputArtifacts?: codepipeline.Artifact[]; // tslint:enable:max-line-length @@ -34,7 +31,7 @@ export interface CommonPipelineInvokeActionProps extends codepipeline.CommonActi * * @default the Action will not have any outputs */ - outputArtifactNames?: string[]; + readonly outputArtifactNames?: string[]; /** * String to be used in the event data parameter passed to the Lambda @@ -44,7 +41,7 @@ export interface CommonPipelineInvokeActionProps extends codepipeline.CommonActi * * https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-json-event-example */ - userParameters?: any; + readonly userParameters?: any; /** * Adds the "codepipeline:PutJobSuccessResult" and @@ -56,22 +53,16 @@ export interface CommonPipelineInvokeActionProps extends codepipeline.CommonActi * (the pipeline references) the Lambda and the Lambda needs permissions on * the pipeline. * - * @see - * https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-create-function + * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html#actions-invoke-lambda-function-create-function * * @default true */ - addPutJobResultPolicy?: boolean; -} + readonly addPutJobResultPolicy?: boolean; -/** - * Construction properties of the {@link PipelineInvokeAction Lambda invoke CodePipeline Action}. - */ -export interface PipelineInvokeActionProps extends CommonPipelineInvokeActionProps { /** * The lambda function to invoke. */ - lambda: IFunction; + readonly lambda: lambda.IFunction; } /** @@ -79,10 +70,10 @@ export interface PipelineInvokeActionProps extends CommonPipelineInvokeActionPro * * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html */ -export class PipelineInvokeAction extends codepipeline.Action { - private readonly props: PipelineInvokeActionProps; +export class LambdaInvokeAction extends codepipeline.Action { + private readonly props: LambdaInvokeActionProps; - constructor(props: PipelineInvokeActionProps) { + constructor(props: LambdaInvokeActionProps) { super({ ...props, category: codepipeline.ActionCategory.Invoke, @@ -108,11 +99,11 @@ export class PipelineInvokeAction extends codepipeline.Action { } public outputArtifacts(): codepipeline.Artifact[] { - return this._outputArtifacts; + return this.actionOutputArtifacts; } public outputArtifact(artifactName: string): codepipeline.Artifact { - const result = this._outputArtifacts.find(a => (a.artifactName === artifactName)); + const result = this.actionOutputArtifacts.find(a => (a.artifactName === artifactName)); if (result === undefined) { throw new Error(`Could not find the output Artifact with name '${artifactName}'`); } else { @@ -120,14 +111,14 @@ export class PipelineInvokeAction extends codepipeline.Action { } } - protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { + protected bind(info: codepipeline.ActionBind): void { // allow pipeline to list functions - stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + info.role.addToPolicy(new iam.PolicyStatement() .addAction('lambda:ListFunctions') .addAllResources()); // allow pipeline to invoke this lambda functionn - stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + info.role.addToPolicy(new iam.PolicyStatement() .addAction('lambda:InvokeFunction') .addResource(this.props.lambda.functionArn)); diff --git a/packages/@aws-cdk/aws-codepipeline/lib/manual-approval-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/manual-approval-action.ts similarity index 76% rename from packages/@aws-cdk/aws-codepipeline/lib/manual-approval-action.ts rename to packages/@aws-cdk/aws-codepipeline-actions/lib/manual-approval-action.ts index a15a591acac13..6acb9487f6610 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/manual-approval-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/manual-approval-action.ts @@ -1,33 +1,33 @@ -import actions = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import sns = require('@aws-cdk/aws-sns'); import cdk = require('@aws-cdk/cdk'); /** * Construction properties of the {@link ManualApprovalAction}. */ -export interface ManualApprovalActionProps extends actions.CommonActionProps { +export interface ManualApprovalActionProps extends codepipeline.CommonActionProps { /** * Optional SNS topic to send notifications to when an approval is pending. */ - notificationTopic?: sns.ITopic; + readonly notificationTopic?: sns.ITopic; /** * A list of email addresses to subscribe to notifications when this Action is pending approval. * If this has been provided, but not `notificationTopic`, * a new Topic will be created. */ - notifyEmails?: string[]; + readonly notifyEmails?: string[]; /** * Any additional information that you want to include in the notification email message. */ - additionalInformation?: string; + readonly additionalInformation?: string; } /** * Manual approval action. */ -export class ManualApprovalAction extends actions.Action { +export class ManualApprovalAction extends codepipeline.Action { /** * The SNS Topic passed when constructing the Action. * If no Topic was passed, but `notifyEmails` were provided, @@ -39,7 +39,7 @@ export class ManualApprovalAction extends actions.Action { constructor(props: ManualApprovalActionProps) { super({ ...props, - category: actions.ActionCategory.Approval, + category: codepipeline.ActionCategory.Approval, provider: 'Manual', artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 0, maxOutputs: 0 }, configuration: new cdk.Token(() => this.actionConfiguration()), @@ -52,15 +52,15 @@ export class ManualApprovalAction extends actions.Action { return this._notificationTopic; } - protected bind(stage: actions.IStage, scope: cdk.Construct): void { + protected bind(info: codepipeline.ActionBind): void { if (this.props.notificationTopic) { this._notificationTopic = this.props.notificationTopic; } else if ((this.props.notifyEmails || []).length > 0) { - this._notificationTopic = new sns.Topic(scope, 'TopicResource'); + this._notificationTopic = new sns.Topic(info.scope, 'TopicResource'); } if (this._notificationTopic) { - this._notificationTopic.grantPublish(stage.pipeline.role); + this._notificationTopic.grantPublish(info.role); for (const notifyEmail of this.props.notifyEmails || []) { this._notificationTopic.subscribeEmail(`Subscription-${notifyEmail}`, notifyEmail); } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts new file mode 100644 index 0000000000000..af0ef9eef5202 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/deploy-action.ts @@ -0,0 +1,61 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import s3 = require('@aws-cdk/aws-s3'); + +/** + * Construction properties of the {@link S3DeployAction S3 deploy Action}. + */ +export interface S3DeployActionProps extends codepipeline.CommonActionProps { + /** + * Should the deploy action extract the artifact before deploying to Amazon S3. + * + * @default true + */ + readonly extract?: boolean; + + /** + * The key of the target object. This is required if extract is false. + */ + readonly objectKey?: string; + + /** + * The inputArtifact to deploy to Amazon S3. + */ + readonly inputArtifact: codepipeline.Artifact; + + /** + * The Amazon S3 bucket that is the deploy target. + */ + readonly bucket: s3.IBucket; +} + +/** + * Deploys the sourceArtifact to Amazon S3. + */ +export class S3DeployAction extends codepipeline.DeployAction { + private readonly bucket: s3.IBucket; + + constructor(props: S3DeployActionProps) { + super({ + ...props, + provider: 'S3', + artifactBounds: { + minInputs: 1, + maxInputs: 1, + minOutputs: 0, + maxOutputs: 0, + }, + configuration: { + BucketName: props.bucket.bucketName, + Extract: (props.extract === false) ? 'false' : 'true', + ObjectKey: props.objectKey, + }, + }); + + this.bucket = props.bucket; + } + + protected bind(info: codepipeline.ActionBind): void { + // pipeline needs permissions to write to the S3 bucket + this.bucket.grantWrite(info.role); + } +} 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 new file mode 100644 index 0000000000000..5e6f255776888 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts @@ -0,0 +1,70 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import s3 = require('@aws-cdk/aws-s3'); + +/** + * Construction properties of the {@link S3SourceAction S3 source Action}. + */ +export interface S3SourceActionProps extends codepipeline.CommonActionProps { + /** + * The name of the source's output artifact. Output artifacts are used by CodePipeline as + * inputs into other actions. + * + * @default a name will be auto-generated + */ + readonly outputArtifactName?: string; + + /** + * The key within the S3 bucket that stores the source code. + * + * @example 'path/to/file.zip' + */ + readonly bucketKey: string; + + /** + * Whether AWS CodePipeline should poll for source changes. + * If this is `false`, the Pipeline will use CloudWatch Events to detect source changes instead. + * Note that if this is `false`, you need to make sure to include the source Bucket in a CloudTrail Trail, + * as otherwise the CloudWatch Events will not be emitted. + * + * @default true + * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/log-s3-data-events.html + */ + readonly pollForSourceChanges?: boolean; + + /** + * The Amazon S3 bucket that stores the source code + */ + readonly bucket: s3.IBucket; +} + +/** + * Source that is provided by a specific Amazon S3 object. + */ +export class S3SourceAction extends codepipeline.SourceAction { + private readonly props: S3SourceActionProps; + + constructor(props: S3SourceActionProps) { + super({ + ...props, + provider: 'S3', + outputArtifactName: props.outputArtifactName || `Artifact_${props.actionName}_${props.bucket.node.uniqueId}`, + configuration: { + S3Bucket: props.bucket.bucketName, + S3ObjectKey: props.bucketKey, + PollForSourceChanges: props.pollForSourceChanges, + }, + }); + + this.props = props; + } + + protected bind(info: codepipeline.ActionBind): void { + if (this.props.pollForSourceChanges === false) { + this.props.bucket.onPutObject(info.pipeline.node.uniqueId + 'SourceEventRule', + info.pipeline, this.props.bucketKey); + } + + // pipeline needs permissions to read from the S3 bucket + this.props.bucket.grantRead(info.role); + } +} diff --git a/packages/@aws-cdk/aws-cloudformation/package-lock.json b/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json similarity index 89% rename from packages/@aws-cdk/aws-cloudformation/package-lock.json rename to packages/@aws-cdk/aws-codepipeline-actions/package-lock.json index 8c97d92d18e51..7c59ec4be6731 100644 --- a/packages/@aws-cdk/aws-cloudformation/package-lock.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package-lock.json @@ -1,6 +1,6 @@ { - "name": "@aws-cdk/aws-cloudformation", - "version": "0.25.3", + "name": "@aws-cdk/aws-codepipeline-actions", + "version": "0.27.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json new file mode 100644 index 0000000000000..c0140f19a2f54 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -0,0 +1,108 @@ +{ + "name": "@aws-cdk/aws-codepipeline-actions", + "version": "0.28.0", + "description": "Concrete Actions for AWS Code Pipeline", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.codepipeline.actions", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "codepipeline-actions" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.Codepipeline.Actions", + "packageId": "Amazon.CDK.AWS.Codepipeline.Actions", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk" + }, + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-codepipeline-actions", + "module": "aws_cdk.aws_codepipeline_actions" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codepipeline-actions" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint" + }, + "nyc": { + "statements": 70 + }, + "keywords": [ + "aws", + "aws-clib", + "aws-cloudlib", + "cdk", + "cloudlib", + "codepipeline", + "pipeline" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-cloudtrail": "^0.28.0", + "@types/lodash": "^4.14.118", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "lodash": "^4.17.11", + "pkglint": "^0.28.0" + }, + "dependencies": { + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-codebuild": "^0.28.0", + "@aws-cdk/aws-codecommit": "^0.28.0", + "@aws-cdk/aws-codedeploy": "^0.28.0", + "@aws-cdk/aws-codepipeline": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-ecr": "^0.28.0", + "@aws-cdk/aws-ecs": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" + }, + "homepage": "https://github.com/awslabs/aws-cdk", + "peerDependencies": { + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-codebuild": "^0.28.0", + "@aws-cdk/aws-codecommit": "^0.28.0", + "@aws-cdk/aws-codedeploy": "^0.28.0", + "@aws-cdk/aws-codepipeline": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-ecr": "^0.28.0", + "@aws-cdk/aws-ecs": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" + }, + "engines": { + "node": ">= 8.10.0" + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts similarity index 86% rename from packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts index cb65b3292595b..abb3021358a60 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts @@ -1,13 +1,12 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; -import { PipelineCreateReplaceChangeSetAction, PipelineCreateUpdateStackAction, PipelineExecuteChangeSetAction } from '@aws-cdk/aws-cloudformation'; -import { CodePipelineBuildArtifacts, CodePipelineSource, PipelineBuildAction, Project } from '@aws-cdk/aws-codebuild'; -import { PipelineSourceAction, Repository } from '@aws-cdk/aws-codecommit'; -import cpapi = require('@aws-cdk/aws-codepipeline-api'); -import { PolicyStatement, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { CodePipelineBuildArtifacts, CodePipelineSource, Project } from '@aws-cdk/aws-codebuild'; +import { Repository } from '@aws-cdk/aws-codecommit'; +import codepipeline = require('@aws-cdk/aws-codepipeline'); import { Role } from '@aws-cdk/aws-iam'; +import { PolicyStatement, ServicePrincipal } from '@aws-cdk/aws-iam'; import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; -import { Pipeline } from '../lib'; +import cpactions = require('../../lib'); // tslint:disable:object-literal-key-quotes @@ -15,7 +14,7 @@ export = { 'CreateChangeSetAction can be used to make a change set from a CodePipeline'(test: Test) { const stack = new cdk.Stack(); - const pipeline = new Pipeline(stack, 'MagicPipeline'); + const pipeline = new codepipeline.Pipeline(stack, 'MagicPipeline'); const changeSetExecRole = new Role(stack, 'ChangeSetRole', { assumedBy: new ServicePrincipal('cloudformation.amazonaws.com'), @@ -24,7 +23,7 @@ export = { /** Source! */ const repo = new Repository(stack, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); - const source = new PipelineSourceAction({ + const source = new cpactions.CodeCommitSourceAction({ actionName: 'source', outputArtifactName: 'SourceArtifact', repository: repo, @@ -43,7 +42,7 @@ export = { artifacts: buildArtifacts, }); - const buildAction = new PipelineBuildAction({ + const buildAction = new cpactions.CodeBuildBuildAction({ actionName: 'build', project, inputArtifact: source.outputArtifact, @@ -64,16 +63,16 @@ export = { pipeline.addStage({ name: 'prod', actions: [ - new PipelineCreateReplaceChangeSetAction({ + new cpactions.CloudFormationCreateReplaceChangeSetAction({ actionName: 'BuildChangeSetProd', stackName, changeSetName, deploymentRole: changeSetExecRole, - templatePath: new cpapi.ArtifactPath(buildAction.outputArtifact, 'template.yaml'), - templateConfiguration: new cpapi.ArtifactPath(buildAction.outputArtifact, 'templateConfig.json'), + templatePath: new codepipeline.ArtifactPath(buildAction.outputArtifact, 'template.yaml'), + templateConfiguration: new codepipeline.ArtifactPath(buildAction.outputArtifact, 'templateConfig.json'), adminPermissions: false, }), - new PipelineExecuteChangeSetAction({ + new cpactions.CloudFormationExecuteChangeSetAction({ actionName: 'ExecuteChangeSetProd', stackName, changeSetName, @@ -209,7 +208,7 @@ export = { const stack = new TestFixture(); // WHEN - stack.deployStage.addAction(new PipelineCreateUpdateStackAction({ + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ actionName: 'CreateUpdate', stackName: 'MyStack', templatePath: stack.source.outputArtifact.atPath('template.yaml'), @@ -264,7 +263,7 @@ export = { const stack = new TestFixture(); // WHEN - stack.deployStage.addAction(new PipelineCreateUpdateStackAction({ + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ actionName: 'CreateUpdate', stackName: 'MyStack', templatePath: stack.source.outputArtifact.atPath('template.yaml'), @@ -296,7 +295,7 @@ export = { const stack = new TestFixture(); // WHEN - stack.deployStage.addAction(new PipelineCreateUpdateStackAction({ + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ actionName: 'CreateUpdate', stackName: 'MyStack', templatePath: stack.source.outputArtifact.atPath('template.yaml'), @@ -330,7 +329,7 @@ export = { const stack = new TestFixture(); // WHEN - stack.deployStage.addAction(new PipelineCreateUpdateStackAction({ + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ actionName: 'CreateUpdate', stackName: 'MyStack', templatePath: stack.source.outputArtifact.atPath('template.yaml'), @@ -375,14 +374,14 @@ export = { assumedBy: new ServicePrincipal('magicservice') }); - stack.deployStage.addAction(new PipelineExecuteChangeSetAction({ + stack.deployStage.addAction(new cpactions.CloudFormationExecuteChangeSetAction({ actionName: 'ImportedRoleAction', role: importedRole, changeSetName: 'magicSet', stackName: 'magicStack', })); - stack.deployStage.addAction(new PipelineExecuteChangeSetAction({ + stack.deployStage.addAction(new cpactions.CloudFormationExecuteChangeSetAction({ actionName: 'FreshRoleAction', role: freshRole, changeSetName: 'magicSet', @@ -423,20 +422,20 @@ export = { * A test stack with a half-prepared pipeline ready to add CloudFormation actions to */ class TestFixture extends cdk.Stack { - public readonly pipeline: Pipeline; - public readonly sourceStage: cpapi.IStage; - public readonly deployStage: cpapi.IStage; + public readonly pipeline: codepipeline.Pipeline; + public readonly sourceStage: codepipeline.IStage; + public readonly deployStage: codepipeline.IStage; public readonly repo: Repository; - public readonly source: PipelineSourceAction; + public readonly source: cpactions.CodeCommitSourceAction; constructor() { super(); - this.pipeline = new Pipeline(this, 'Pipeline'); + this.pipeline = new codepipeline.Pipeline(this, 'Pipeline'); this.sourceStage = this.pipeline.addStage({ name: 'Source' }); this.deployStage = this.pipeline.addStage({ name: 'Deploy' }); this.repo = new Repository(this, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); - this.source = new PipelineSourceAction({ + this.source = new cpactions.CodeCommitSourceAction({ actionName: 'Source', outputArtifactName: 'SourceArtifact', repository: this.repo, diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts similarity index 87% rename from packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts index 9f0512875f118..1ce4c7d4f8fa0 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.pipeline-actions.ts @@ -1,18 +1,18 @@ -import cpapi = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); import _ = require('lodash'); import nodeunit = require('nodeunit'); -import cloudformation = require('../lib'); +import cpactions = require('../../lib'); export = nodeunit.testCase({ 'CreateReplaceChangeSet': { 'works'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const artifact = new cpapi.Artifact('TestArtifact'); - const action = new cloudformation.PipelineCreateReplaceChangeSetAction({ + const artifact = new codepipeline.Artifact('TestArtifact'); + const action = new cpactions.CloudFormationCreateReplaceChangeSetAction({ actionName: 'Action', changeSetName: 'MyChangeSet', stackName: 'MyStack', @@ -33,7 +33,8 @@ export = nodeunit.testCase({ _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:CreateChangeSet', stackArn, changeSetCondition); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DeleteChangeSet', stackArn, changeSetCondition); - test.deepEqual(action._inputArtifacts, [artifact], + // TODO: revert "as any" once we move all actions into a single package. + test.deepEqual((action as any).actionInputArtifacts, [artifact], 'The inputArtifact was correctly registered'); _assertActionMatches(test, stage.actions, 'AWS', 'CloudFormation', 'Deploy', { @@ -48,18 +49,18 @@ export = nodeunit.testCase({ 'uses a single permission statement if the same ChangeSet name is used'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const artifact = new cpapi.Artifact('TestArtifact'); + const artifact = new codepipeline.Artifact('TestArtifact'); new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }), actions: [ - new cloudformation.PipelineCreateReplaceChangeSetAction({ + new cpactions.CloudFormationCreateReplaceChangeSetAction({ actionName: 'ActionA', changeSetName: 'MyChangeSet', stackName: 'StackA', adminPermissions: false, templatePath: artifact.atPath('path/to/file') }), - new cloudformation.PipelineCreateReplaceChangeSetAction({ + new cpactions.CloudFormationCreateReplaceChangeSetAction({ actionName: 'ActionB', changeSetName: 'MyChangeSet', stackName: 'StackB', @@ -110,7 +111,7 @@ export = nodeunit.testCase({ const stage = new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }), actions: [ - new cloudformation.PipelineExecuteChangeSetAction({ + new cpactions.CloudFormationExecuteChangeSetAction({ actionName: 'Action', changeSetName: 'MyChangeSet', stackName: 'MyStack', @@ -137,12 +138,12 @@ export = nodeunit.testCase({ new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }), actions: [ - new cloudformation.PipelineExecuteChangeSetAction({ + new cpactions.CloudFormationExecuteChangeSetAction({ actionName: 'ActionA', changeSetName: 'MyChangeSet', stackName: 'StackA', }), - new cloudformation.PipelineExecuteChangeSetAction({ + new cpactions.CloudFormationExecuteChangeSetAction({ actionName: 'ActionB', changeSetName: 'MyChangeSet', stackName: 'StackB', @@ -174,9 +175,9 @@ export = nodeunit.testCase({ 'the CreateUpdateStack Action sets the DescribeStack*, Create/Update/DeleteStack & PassRole permissions'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const action = new cloudformation.PipelineCreateUpdateStackAction({ + const action = new cpactions.CloudFormationCreateUpdateStackAction({ actionName: 'Action', - templatePath: new cpapi.Artifact('TestArtifact').atPath('some/file'), + templatePath: new codepipeline.Artifact('TestArtifact').atPath('some/file'), stackName: 'MyStack', adminPermissions: false, replaceOnFailure: true, @@ -200,7 +201,7 @@ export = nodeunit.testCase({ 'the DeleteStack Action sets the DescribeStack*, DeleteStack & PassRole permissions'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const action = new cloudformation.PipelineDeleteStackAction({ + const action = new cpactions.CloudFormationDeleteStackAction({ actionName: 'Action', adminPermissions: false, stackName: 'MyStack', @@ -228,7 +229,7 @@ interface PolicyStatementJson { } function _assertActionMatches(test: nodeunit.Test, - actions: cpapi.Action[], + actions: codepipeline.Action[], owner: string, provider: string, category: string, @@ -243,7 +244,7 @@ function _assertActionMatches(test: nodeunit.Test, `Expected to find an action with owner ${owner}, provider ${provider}, category ${category}${configurationStr}, but found ${actionsStr}`); } -function _hasAction(actions: cpapi.Action[], owner: string, provider: string, category: string, configuration?: { [key: string]: any}) { +function _hasAction(actions: codepipeline.Action[], owner: string, provider: string, category: string, configuration?: { [key: string]: any}) { for (const action of actions) { if (action.owner !== owner) { continue; } if (action.provider !== provider) { continue; } @@ -300,7 +301,7 @@ function _stackArn(stackName: string, scope: cdk.IConstruct): string { }); } -class PipelineDouble extends cdk.Construct implements cpapi.IPipeline { +class PipelineDouble extends cdk.Construct implements codepipeline.IPipeline { public readonly pipelineName: string; public readonly pipelineArn: string; public readonly role: iam.Role; @@ -316,37 +317,42 @@ class PipelineDouble extends cdk.Construct implements cpapi.IPipeline { throw new Error('asEventRuleTarget() is unsupported in PipelineDouble'); } - public grantBucketRead(_identity?: iam.IPrincipal): void { + public grantBucketRead(_identity?: iam.IGrantable): iam.Grant { throw new Error('grantBucketRead() is unsupported in PipelineDouble'); } - public grantBucketReadWrite(_identity?: iam.IPrincipal): void { + public grantBucketReadWrite(_identity?: iam.IGrantable): iam.Grant { throw new Error('grantBucketReadWrite() is unsupported in PipelineDouble'); } } -class StageDouble implements cpapi.IStage { +class StageDouble implements codepipeline.IStage { public readonly stageName: string; - public readonly pipeline: cpapi.IPipeline; - public readonly actions: cpapi.Action[]; + public readonly pipeline: codepipeline.IPipeline; + public readonly actions: codepipeline.Action[]; public get node(): cdk.ConstructNode { throw new Error('StageDouble is not a real construct'); } - constructor({ name, pipeline, actions }: { name?: string, pipeline: PipelineDouble, actions: cpapi.Action[] }) { + constructor({ name, pipeline, actions }: { name?: string, pipeline: PipelineDouble, actions: codepipeline.Action[] }) { this.stageName = name || 'TestStage'; this.pipeline = pipeline; const stageParent = new cdk.Construct(pipeline, this.stageName); for (const action of actions) { const actionParent = new cdk.Construct(stageParent, action.actionName); - (action as any)._attachActionToPipeline(this, actionParent); + (action as any)._actionAttachedToPipeline({ + pipeline, + stage: this, + scope: actionParent, + role: pipeline.role, + }); } this.actions = actions; } - public addAction(_action: cpapi.Action): void { + public addAction(_action: codepipeline.Action): void { throw new Error('addAction() is not supported on StageDouble'); } @@ -363,9 +369,10 @@ class RoleDouble extends iam.Role { super(scope, id, props); } - public addToPolicy(statement: iam.PolicyStatement) { + public addToPolicy(statement: iam.PolicyStatement): boolean { super.addToPolicy(statement); this.statements.push(statement); + return true; } } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/test.ecs-deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/test.ecs-deploy-action.ts new file mode 100644 index 0000000000000..f022ba18d139b --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/test.ecs-deploy-action.ts @@ -0,0 +1,85 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import ec2 = require('@aws-cdk/aws-ec2'); +import ecs = require('@aws-cdk/aws-ecs'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import cpactions = require('../../lib'); + +export = { + 'ECS deploy Action': { + 'throws an exception if neither inputArtifact nor imageFile were provided'(test: Test) { + const service = anyEcsService(); + + test.throws(() => { + new cpactions.EcsDeployAction({ + actionName: 'ECS', + service, + }); + }, /one of 'inputArtifact' or 'imageFile' is required/); + + test.done(); + }, + + 'can be created just by specifying the inputArtifact'(test: Test) { + const service = anyEcsService(); + const artifact = new codepipeline.Artifact('Artifact'); + + const action = new cpactions.EcsDeployAction({ + actionName: 'ECS', + service, + inputArtifact: artifact, + }); + + test.equal(action.configuration.FileName, undefined); + + test.done(); + }, + + 'can be created just by specifying the imageFile'(test: Test) { + const service = anyEcsService(); + const artifact = new codepipeline.Artifact('Artifact'); + + const action = new cpactions.EcsDeployAction({ + actionName: 'ECS', + service, + imageFile: artifact.atPath('imageFile.json'), + }); + + test.equal(action.configuration.FileName, 'imageFile.json'); + + test.done(); + }, + + 'throws an exception if both inputArtifact and imageFile were provided'(test: Test) { + const service = anyEcsService(); + const artifact = new codepipeline.Artifact('Artifact'); + + test.throws(() => { + new cpactions.EcsDeployAction({ + actionName: 'ECS', + service, + inputArtifact: artifact, + imageFile: artifact.atPath('file.json'), + }); + }, /one of 'inputArtifact' or 'imageFile' can be provided/); + + test.done(); + }, + }, +}; + +function anyEcsService(): ecs.FargateService { + const stack = new cdk.Stack(); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDefinition'); + taskDefinition.addContainer('MainContainer', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { + vpc, + }); + return new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + }); +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.ts similarity index 79% rename from packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.ts index 0b408ecea9fcd..14f6a8efe5061 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.cfn-template-from-repo.lit.ts @@ -1,7 +1,7 @@ -import cfn = require('@aws-cdk/aws-cloudformation'); import codecommit = require('@aws-cdk/aws-codecommit'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-cloudformation'); @@ -11,7 +11,7 @@ const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-cloudformation'); const repo = new codecommit.Repository(stack, 'TemplateRepo', { repositoryName: 'template-repo' }); -const source = new codecommit.PipelineSourceAction({ +const source = new cpactions.CodeCommitSourceAction({ actionName: 'Source', repository: repo, outputArtifactName: 'SourceArtifact', @@ -29,7 +29,7 @@ const changeSetName = 'StagedChangeSet'; const prodStage = { name: 'Deploy', actions: [ - new cfn.PipelineCreateReplaceChangeSetAction({ + new cpactions.CloudFormationCreateReplaceChangeSetAction({ actionName: 'PrepareChanges', stackName, changeSetName, @@ -37,11 +37,11 @@ const prodStage = { templatePath: source.outputArtifact.atPath('template.yaml'), runOrder: 1, }), - new codepipeline.ManualApprovalAction({ + new cpactions.ManualApprovalAction({ actionName: 'ApproveChanges', runOrder: 2, }), - new cfn.PipelineExecuteChangeSetAction({ + new cpactions.CloudFormationExecuteChangeSetAction({ actionName: 'ExecuteChanges', stackName, changeSetName, 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 new file mode 100644 index 0000000000000..f7976a8bf1f94 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.expected.json @@ -0,0 +1,934 @@ +{ + "Resources": { + "PipelineArtifactsBucket22248F97": { + "Type": "AWS::S3::Bucket", + "DeletionPolicy": "Retain" + }, + "PipelineRoleD68726F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "codepipeline.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicyC7A05455": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "codecommit:GetBranch", + "codecommit:GetCommit", + "codecommit:UploadArchive", + "codecommit:GetUploadArchiveStatus", + "codecommit:CancelUploadArchive" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "CdkCodeRepo7D2EC742", + "Arn" + ] + } + }, + { + "Action": [ + "codecommit:GetBranch", + "codecommit:GetCommit", + "codecommit:UploadArchive", + "codecommit:GetUploadArchiveStatus", + "codecommit:CancelUploadArchive" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "LambdaCodeRepoE08DD409", + "Arn" + ] + } + }, + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "CdkBuildProject9382C38D", + "Arn" + ] + } + }, + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "LambdaBuildProject7E2DAB11", + "Arn" + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineDeployLambdaCFNDeployRole89CA1043", + "Arn" + ] + } + }, + { + "Action": [ + "cloudformation:CreateStack", + "cloudformation:DescribeStack*", + "cloudformation:GetStackPolicy", + "cloudformation:GetTemplate*", + "cloudformation:SetStackPolicy", + "cloudformation:UpdateStack", + "cloudformation:ValidateTemplate" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":cloudformation:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":stack/LambdaStackDeployedName/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicyC7A05455", + "Roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "PipelineC660917D": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "CodeCommit", + "Version": "1" + }, + "Configuration": { + "RepositoryName": { + "Fn::GetAtt": [ + "CdkCodeRepo7D2EC742", + "Name" + ] + }, + "BranchName": "master", + "PollForSourceChanges": false + }, + "InputArtifacts": [], + "Name": "CdkCode_Source", + "OutputArtifacts": [ + { + "Name": "Artifact_CdkCode_Source_PipelineStackCdkCodeRepo766C119A" + } + ], + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "CodeCommit", + "Version": "1" + }, + "Configuration": { + "RepositoryName": { + "Fn::GetAtt": [ + "LambdaCodeRepoE08DD409", + "Name" + ] + }, + "BranchName": "master", + "PollForSourceChanges": false + }, + "InputArtifacts": [], + "Name": "LambdaCode_Source", + "OutputArtifacts": [ + { + "Name": "Artifact_LambdaCode_Source_PipelineStackLambdaCodeRepo0F311735" + } + ], + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "CdkBuildProject9382C38D" + } + }, + "InputArtifacts": [ + { + "Name": "Artifact_CdkCode_Source_PipelineStackCdkCodeRepo766C119A" + } + ], + "Name": "CDK_Build", + "OutputArtifacts": [ + { + "Name": "Artifact_CDK_Build_PipelineStackCdkBuildProject3F998A60" + } + ], + "RunOrder": 1 + }, + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "LambdaBuildProject7E2DAB11" + } + }, + "InputArtifacts": [ + { + "Name": "Artifact_LambdaCode_Source_PipelineStackLambdaCodeRepo0F311735" + } + ], + "Name": "Lambda_Build", + "OutputArtifacts": [ + { + "Name": "Artifact_Lambda_Build_PipelineStackLambdaBuildProjectC90BA2C4" + } + ], + "RunOrder": 1 + } + ], + "Name": "Build" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "CloudFormation", + "Version": "1" + }, + "Configuration": { + "StackName": "LambdaStackDeployedName", + "ActionMode": "CREATE_UPDATE", + "TemplatePath": "Artifact_CDK_Build_PipelineStackCdkBuildProject3F998A60::LambdaStack.template.yaml", + "Capabilities": "CAPABILITY_NAMED_IAM", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineDeployLambdaCFNDeployRole89CA1043", + "Arn" + ] + }, + "ParameterOverrides": "{\"LambdaLambdaSourceBucketNameParameter159473FC\":{\"Fn::GetArtifactAtt\":[\"Artifact_Lambda_Build_PipelineStackLambdaBuildProjectC90BA2C4\",\"BucketName\"]},\"LambdaLambdaSourceObjectKeyParameter06573F1D\":{\"Fn::GetArtifactAtt\":[\"Artifact_Lambda_Build_PipelineStackLambdaBuildProjectC90BA2C4\",\"ObjectKey\"]}}" + }, + "InputArtifacts": [ + { + "Name": "Artifact_Lambda_Build_PipelineStackLambdaBuildProjectC90BA2C4" + }, + { + "Name": "Artifact_CDK_Build_PipelineStackCdkBuildProject3F998A60" + } + ], + "Name": "Lambda_CFN_Deploy", + "OutputArtifacts": [], + "RunOrder": 1 + } + ], + "Name": "Deploy" + } + ], + "ArtifactStore": { + "Location": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "Type": "S3" + } + }, + "DependsOn": [ + "PipelineRoleDefaultPolicyC7A05455", + "PipelineRoleD68726F7" + ] + }, + "PipelineEventsRole46BEEA7C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "events.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineEventsRoleDefaultPolicyFF4FCCE0": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "codepipeline:StartPipelineExecution", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codepipeline:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "PipelineC660917D" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineEventsRoleDefaultPolicyFF4FCCE0", + "Roles": [ + { + "Ref": "PipelineEventsRole46BEEA7C" + } + ] + } + }, + "PipelineDeployLambdaCFNDeployRole89CA1043": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "cloudformation.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineDeployLambdaCFNDeployRoleDefaultPolicyE83FD793": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineDeployLambdaCFNDeployRoleDefaultPolicyE83FD793", + "Roles": [ + { + "Ref": "PipelineDeployLambdaCFNDeployRole89CA1043" + } + ] + } + }, + "CdkCodeRepo7D2EC742": { + "Type": "AWS::CodeCommit::Repository", + "Properties": { + "RepositoryName": "CdkCodeRepo", + "Triggers": [] + } + }, + "CdkCodeRepoPipelineStackPipeline9DB740AFEventRule97707F9A": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "source": [ + "aws.codecommit" + ], + "resources": [ + { + "Fn::GetAtt": [ + "CdkCodeRepo7D2EC742", + "Arn" + ] + } + ], + "detail-type": [ + "CodeCommit Repository State Change" + ], + "detail": { + "event": [ + "referenceCreated", + "referenceUpdated" + ], + "referenceName": [ + "master" + ] + } + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codepipeline:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "PipelineC660917D" + } + ] + ] + }, + "Id": "Pipeline", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineEventsRole46BEEA7C", + "Arn" + ] + } + } + ] + } + }, + "LambdaCodeRepoE08DD409": { + "Type": "AWS::CodeCommit::Repository", + "Properties": { + "RepositoryName": "LambdaCodeRepo", + "Triggers": [] + } + }, + "LambdaCodeRepoPipelineStackPipeline9DB740AFEventRule2C34743D": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "source": [ + "aws.codecommit" + ], + "resources": [ + { + "Fn::GetAtt": [ + "LambdaCodeRepoE08DD409", + "Arn" + ] + } + ], + "detail-type": [ + "CodeCommit Repository State Change" + ], + "detail": { + "event": [ + "referenceCreated", + "referenceUpdated" + ], + "referenceName": [ + "master" + ] + } + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codepipeline:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "PipelineC660917D" + } + ] + ] + }, + "Id": "Pipeline", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineEventsRole46BEEA7C", + "Arn" + ] + } + } + ] + } + }, + "CdkBuildProjectRoleE0B6FEB0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "codebuild.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "CdkBuildProjectRoleDefaultPolicy3C7ECB00": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "CdkBuildProject9382C38D" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "CdkBuildProject9382C38D" + }, + ":*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CdkBuildProjectRoleDefaultPolicy3C7ECB00", + "Roles": [ + { + "Ref": "CdkBuildProjectRoleE0B6FEB0" + } + ] + } + }, + "CdkBuildProject9382C38D": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/nodejs:10.1.0", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "CdkBuildProjectRoleE0B6FEB0", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"install\": {\n \"commands\": \"npm install\"\n },\n \"build\": {\n \"commands\": [\n \"npm run build\",\n \"npm run cdk synth LambdaStack -- -o .\"\n ]\n }\n },\n \"artifacts\": {\n \"files\": \"LambdaStack.template.yaml\"\n }\n}", + "Type": "NO_SOURCE" + } + } + }, + "LambdaBuildProjectRoleD0C4F982": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "codebuild.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "LambdaBuildProjectRoleDefaultPolicyA3A66624": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "LambdaBuildProject7E2DAB11" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "LambdaBuildProject7E2DAB11" + }, + ":*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LambdaBuildProjectRoleDefaultPolicyA3A66624", + "Roles": [ + { + "Ref": "LambdaBuildProjectRoleD0C4F982" + } + ] + } + }, + "LambdaBuildProject7E2DAB11": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "NO_ARTIFACTS" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/nodejs:10.1.0", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "LambdaBuildProjectRoleD0C4F982", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"install\": {\n \"commands\": \"npm install\"\n },\n \"build\": {\n \"commands\": \"npm run build\"\n }\n },\n \"artifacts\": {\n \"files\": [\n \"index.js\",\n \"node_modules/**/*\"\n ]\n }\n}", + "Type": "NO_SOURCE" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts new file mode 100644 index 0000000000000..0418bf57fe41a --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-deployed-through-codepipeline.lit.ts @@ -0,0 +1,126 @@ +import codebuild = require('@aws-cdk/aws-codebuild'); +import codecommit = require('@aws-cdk/aws-codecommit'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import lambda = require('@aws-cdk/aws-lambda'); +import cdk = require('@aws-cdk/cdk'); +import codepipeline_actions = require('../lib'); + +const app = new cdk.App(); + +/// !show +const lambdaStack = new cdk.Stack(app, 'LambdaStack', { + // remove the Stack from `cdk synth` and `cdk deploy` + // unless you explicitly filter for it + autoDeploy: false, +}); +const lambdaCode = lambda.Code.cfnParameters(); +new lambda.Function(lambdaStack, 'Lambda', { + code: lambdaCode, + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810, +}); +// other resources that your Lambda needs, added to the lambdaStack... + +const pipelineStack = new cdk.Stack(app, 'PipelineStack'); +const pipeline = new codepipeline.Pipeline(pipelineStack, 'Pipeline'); + +// add the source code repository containing this code to your Pipeline, +// and the source code of the Lambda Function, if they're separate +const cdkSourceAction = new codepipeline_actions.CodeCommitSourceAction({ + repository: new codecommit.Repository(pipelineStack, 'CdkCodeRepo', { repositoryName: 'CdkCodeRepo'}), + actionName: 'CdkCode_Source', +}); +const lambdaSourceAction = new codepipeline_actions.CodeCommitSourceAction({ + repository: new codecommit.Repository(pipelineStack, 'LambdaCodeRepo', { repositoryName: 'LambdaCodeRepo'}), + actionName: 'LambdaCode_Source', +}); +pipeline.addStage({ + name: 'Source', + actions: [cdkSourceAction, lambdaSourceAction], +}); + +// synthesize the Lambda CDK template, using CodeBuild +// the below values are just examples, assuming your CDK code is in TypeScript/JavaScript - +// adjust the build environment and/or commands accordingly +const cdkBuildProject = new codebuild.Project(pipelineStack, 'CdkBuildProject', { + environment: { + buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_10_1_0, + }, + buildSpec: { + version: '0.2', + phases: { + install: { + commands: 'npm install', + }, + build: { + commands: [ + 'npm run build', + 'npm run cdk synth LambdaStack -- -o .', + ], + }, + }, + artifacts: { + files: 'LambdaStack.template.yaml', + }, + }, +}); +const cdkBuildAction = new codepipeline_actions.CodeBuildBuildAction({ + actionName: 'CDK_Build', + project: cdkBuildProject, + inputArtifact: cdkSourceAction.outputArtifact, +}); + +// build your Lambda code, using CodeBuild +// again, this example assumes your Lambda is written in TypeScript/JavaScript - +// make sure to adjust the build environment and/or commands if they don't match your specific situation +const lambdaBuildProject = new codebuild.Project(pipelineStack, 'LambdaBuildProject', { + environment: { + buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_10_1_0, + }, + buildSpec: { + version: '0.2', + phases: { + install: { + commands: 'npm install', + }, + build: { + commands: 'npm run build', + }, + }, + artifacts: { + files: [ + 'index.js', + 'node_modules/**/*', + ], + }, + }, +}); +const lambdaBuildAction = new codepipeline_actions.CodeBuildBuildAction({ + actionName: 'Lambda_Build', + project: lambdaBuildProject, + inputArtifact: lambdaSourceAction.outputArtifact, +}); + +pipeline.addStage({ + name: 'Build', + actions: [cdkBuildAction, lambdaBuildAction], +}); + +// finally, deploy your Lambda Stack +pipeline.addStage({ + name: 'Deploy', + actions: [ + new codepipeline_actions.CloudFormationCreateUpdateStackAction({ + actionName: 'Lambda_CFN_Deploy', + templatePath: cdkBuildAction.outputArtifact.atPath('LambdaStack.template.yaml'), + stackName: 'LambdaStackDeployedName', + adminPermissions: true, + parameterOverrides: { + ...lambdaCode.assign(lambdaBuildAction.outputArtifact.s3Coordinates), + }, + additionalInputArtifacts: [ + lambdaBuildAction.outputArtifact, + ], + }), + ], +}); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.ts similarity index 82% rename from packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.ts index 8c88017d0238c..35c5c99e1fc42 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.lambda-pipeline.ts @@ -1,8 +1,9 @@ import cloudtrail = require('@aws-cdk/aws-cloudtrail'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import lambda = require('@aws-cdk/aws-lambda'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -18,7 +19,7 @@ const bucket = new s3.Bucket(stack, 'PipelineBucket', { const key = 'key'; const trail = new cloudtrail.CloudTrail(stack, 'CloudTrail'); trail.addS3EventSelector([bucket.arnForObjects(key)], { readWriteType: cloudtrail.ReadWriteType.WriteOnly, includeManagementEvents: false }); -sourceStage.addAction(new s3.PipelineSourceAction({ +sourceStage.addAction(new cpactions.S3SourceAction({ actionName: 'Source', outputArtifactName: 'SourceArtifact', bucket, @@ -36,6 +37,9 @@ const lambdaFun = new lambda.Function(stack, 'LambdaFun', { runtime: lambda.Runtime.NodeJS610, }); const lambdaStage = pipeline.addStage({ name: 'Lambda' }); -lambdaStage.addAction(lambdaFun.toCodePipelineInvokeAction({ actionName: 'Lambda' })); +lambdaStage.addAction(new cpactions.LambdaInvokeAction({ + actionName: 'Lambda' , + lambda: lambdaFun, +})); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-alexa-deploy.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-alexa-deploy.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-alexa-deploy.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.ts similarity index 54% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-alexa-deploy.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.ts index 654d4d2325f97..236337acce16f 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-alexa-deploy.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-alexa-deploy.ts @@ -1,17 +1,17 @@ -import alexa = require('@aws-cdk/alexa-ask'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import s3 = require('@aws-cdk/aws-s3'); -import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import { App, RemovalPolicy, SecretValue, Stack } from '@aws-cdk/cdk'; +import cpactions = require('../lib'); -const app = new cdk.App(); +const app = new App(); -const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-alexa-deploy'); +const stack = new Stack(app, 'aws-cdk-codepipeline-alexa-deploy'); const bucket = new s3.Bucket(stack, 'PipelineBucket', { versioned: true, - removalPolicy: cdk.RemovalPolicy.Destroy, + removalPolicy: RemovalPolicy.Destroy, }); -const sourceAction = new s3.PipelineSourceAction({ +const sourceAction = new cpactions.S3SourceAction({ actionName: 'Source', outputArtifactName: 'SourceArtifact', bucket, @@ -25,13 +25,13 @@ const sourceStage = { const deployStage = { name: 'Deploy', actions: [ - new alexa.AlexaSkillDeployAction({ + new cpactions.AlexaSkillDeployAction({ actionName: 'DeploySkill', runOrder: 1, inputArtifact: sourceAction.outputArtifact, - clientId: new cdk.Secret('clientId'), - clientSecret: new cdk.Secret('clientSecret'), - refreshToken: new cdk.Secret('refreshToken'), + clientId: 'clientId', + clientSecret: SecretValue.plainText('clientSecret'), + refreshToken: SecretValue.plainText('refreshToken'), skillId: 'amzn1.ask.skill.12345678-1234-1234-1234-123456789012', }), ], diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-cross-region.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-cross-region.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-cross-region.ts similarity index 80% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-cross-region.ts index f1820a8a596e5..96a0dd69dcfd3 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-cross-region.ts @@ -1,7 +1,7 @@ -import cloudformation = require('@aws-cdk/aws-cloudformation'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -17,9 +17,10 @@ const bucket = new s3.Bucket(stack, 'MyBucket', { removalPolicy: cdk.RemovalPolicy.Destroy, }); -const sourceAction = bucket.toCodePipelineSourceAction({ +const sourceAction = new cpactions.S3SourceAction({ actionName: 'S3', bucketKey: 'some/path', + bucket, }); new codepipeline.Pipeline(stack, 'MyPipeline', { @@ -32,7 +33,7 @@ new codepipeline.Pipeline(stack, 'MyPipeline', { { name: 'CFN', actions: [ - new cloudformation.PipelineCreateUpdateStackAction({ + new cpactions.CloudFormationCreateUpdateStackAction({ actionName: 'CFN_Deploy', stackName: 'aws-cdk-codepipeline-cross-region-deploy-stack', templatePath: sourceAction.outputArtifact.atPath('template.yml'), diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-wtih-action-role.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-wtih-action-role.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-wtih-action-role.ts similarity index 85% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-wtih-action-role.ts index 8f33499041846..9a7a67f9d3a7d 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn-wtih-action-role.ts @@ -1,8 +1,8 @@ -import cloudformation = require('@aws-cdk/aws-cloudformation'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -13,9 +13,10 @@ const bucket = new s3.Bucket(stack, 'MyBucket', { removalPolicy: cdk.RemovalPolicy.Destroy, }); -const sourceAction = bucket.toCodePipelineSourceAction({ +const sourceAction = new cpactions.S3SourceAction({ actionName: 'S3', bucketKey: 'some/path', + bucket, }); const sourceStage = { name: 'Source', @@ -32,7 +33,7 @@ role.addToPolicy(new iam.PolicyStatement() const cfnStage = { name: 'CFN', actions: [ - new cloudformation.PipelineCreateUpdateStackAction({ + new cpactions.CloudFormationCreateUpdateStackAction({ actionName: 'CFN_Deploy', stackName: 'aws-cdk-codepipeline-cross-region-deploy-stack', templatePath: sourceAction.outputArtifact.atPath('template.yml'), diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.ts similarity index 82% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.ts index 44589acaff0bf..c3e05cbdb005a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-cfn.ts @@ -1,10 +1,9 @@ -import cfn = require('@aws-cdk/aws-cloudformation'); -import cpapi = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import { Role } from '@aws-cdk/aws-iam'; import { ServicePrincipal } from '@aws-cdk/aws-iam'; import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -17,7 +16,7 @@ const bucket = new s3.Bucket(stack, 'PipelineBucket', { removalPolicy: cdk.RemovalPolicy.Destroy, }); -const source = new s3.PipelineSourceAction({ +const source = new cpactions.S3SourceAction({ actionName: 'Source', outputArtifactName: 'SourceArtifact', bucket, @@ -35,13 +34,13 @@ const role = new Role(stack, 'CfnChangeSetRole', { }); // fake Artifact, just for testing -const additionalArtifact = new cpapi.Artifact('AdditionalArtifact'); +const additionalArtifact = new codepipeline.Artifact('AdditionalArtifact'); pipeline.addStage(sourceStage); pipeline.addStage({ name: 'CFN', actions: [ - new cfn.PipelineCreateReplaceChangeSetAction({ + new cpactions.CloudFormationCreateReplaceChangeSetAction({ actionName: 'DeployCFN', changeSetName, stackName, diff --git a/packages/@aws-cdk/aws-codepipeline/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 similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.ts similarity index 84% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.ts index 611d573635e88..10fad9f249cef 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-build-multiple-inputs-outputs.ts @@ -1,8 +1,9 @@ import codebuild = require('@aws-cdk/aws-codebuild'); import codecommit = require('@aws-cdk/aws-codecommit'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -20,10 +21,14 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, }); -const sourceAction1 = repository.toCodePipelineSourceAction({ actionName: 'Source1' }); -const sourceAction2 = bucket.toCodePipelineSourceAction({ +const sourceAction1 = new cpactions.CodeCommitSourceAction({ + actionName: 'Source1', + repository, +}); +const sourceAction2 = new cpactions.S3SourceAction({ actionName: 'Source2', bucketKey: 'some/path', + bucket, }); pipeline.addStage({ name: 'Source', @@ -34,8 +39,9 @@ pipeline.addStage({ }); const project = new codebuild.PipelineProject(stack, 'MyBuildProject'); -const buildAction = project.toCodePipelineBuildAction({ +const buildAction = new cpactions.CodeBuildBuildAction({ actionName: 'Build1', + project, inputArtifact: sourceAction1.outputArtifact, additionalInputArtifacts: [ sourceAction2.outputArtifact, @@ -44,8 +50,9 @@ const buildAction = project.toCodePipelineBuildAction({ 'CustomOutput1', ], }); -const testAction = project.toCodePipelineTestAction({ +const testAction = new cpactions.CodeBuildTestAction({ actionName: 'Build2', + project, inputArtifact: sourceAction2.outputArtifact, additionalInputArtifacts: [ sourceAction1.outputArtifact, diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.ts similarity index 78% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.ts index 617c624d735e6..40a60e95714ee 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit-build.ts @@ -1,7 +1,8 @@ import codebuild = require('@aws-cdk/aws-codebuild'); import codecommit = require('@aws-cdk/aws-codecommit'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -10,7 +11,7 @@ const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-codecommit-codebuild'); const repository = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'my-repo', }); -const sourceAction = new codecommit.PipelineSourceAction({ +const sourceAction = new cpactions.CodeCommitSourceAction({ actionName: 'source', outputArtifactName: 'SourceArtifact', repository, @@ -20,12 +21,12 @@ const sourceAction = new codecommit.PipelineSourceAction({ const project = new codebuild.Project(stack, 'MyBuildProject', { source: new codebuild.CodePipelineSource(), }); -const buildAction = new codebuild.PipelineBuildAction({ +const buildAction = new cpactions.CodeBuildBuildAction({ actionName: 'build', project, inputArtifact: sourceAction.outputArtifact, }); -const testAction = new codebuild.PipelineTestAction({ +const testAction = new cpactions.CodeBuildTestAction({ actionName: 'test', project, inputArtifact: sourceAction.outputArtifact, diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.ts similarity index 70% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.ts index 0ee53d78b74b2..77dfca10e20c9 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-commit.ts @@ -1,6 +1,7 @@ import codecommit = require('@aws-cdk/aws-codecommit'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -13,8 +14,9 @@ new codepipeline.Pipeline(stack, 'Pipeline', { { name: 'source', actions: [ - repo.toCodePipelineSourceAction({ + new cpactions.CodeCommitSourceAction({ actionName: 'source', + repository: repo, outputArtifactName: 'SourceArtifact', }), ], @@ -22,7 +24,7 @@ new codepipeline.Pipeline(stack, 'Pipeline', { { name: 'build', actions: [ - new codepipeline.ManualApprovalAction({ actionName: 'manual' }), + new cpactions.ManualApprovalAction({ actionName: 'manual' }), ], }, ], diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-deploy.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-deploy.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-deploy.ts similarity index 83% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-deploy.ts index 990cc5f4168fa..bd1c190939061 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-code-deploy.ts @@ -1,7 +1,8 @@ import codedeploy = require('@aws-cdk/aws-codedeploy'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -31,16 +32,18 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { }); const sourceStage = pipeline.addStage({ name: 'Source' }); -const sourceAction = bucket.toCodePipelineSourceAction({ +const sourceAction = new cpactions.S3SourceAction({ actionName: 'S3Source', bucketKey: 'application.zip', outputArtifactName: 'SourceOutput', + bucket, }); sourceStage.addAction(sourceAction); const deployStage = pipeline.addStage({ name: 'Deploy' }); -deployStage.addAction(deploymentGroup.toCodePipelineDeployAction({ +deployStage.addAction(new cpactions.CodeDeployServerDeployAction({ actionName: 'CodeDeploy', + deploymentGroup, inputArtifact: sourceAction.outputArtifact, })); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecr-source.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecr-source.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecr-source.ts similarity index 67% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecr-source.ts index 4a2278608482a..baac957018dcf 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecr-source.ts @@ -1,7 +1,8 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); import ecr = require('@aws-cdk/aws-ecr'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -16,9 +17,12 @@ const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline', { const repository = new ecr.Repository(stack, 'MyEcrRepo'); const sourceStage = pipeline.addStage({ name: 'Source' }); -sourceStage.addAction(repository.toCodePipelineSourceAction({ actionName: 'ECR_Source' })); +sourceStage.addAction(new cpactions.EcrSourceAction({ + actionName: 'ECR_Source', + repository, +})); const approveStage = pipeline.addStage({ name: 'Approve' }); -approveStage.addAction(new codepipeline.ManualApprovalAction({ actionName: 'ManualApproval' })); +approveStage.addAction(new cpactions.ManualApprovalAction({ actionName: 'ManualApproval' })); app.run(); 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 new file mode 100644 index 0000000000000..231618e99c41e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.expected.json @@ -0,0 +1,826 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codepipeline-ecs-deploy/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/17", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codepipeline-ecs-deploy/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codepipeline-ecs-deploy/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" + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codepipeline-ecs-deploy/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/17", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codepipeline-ecs-deploy/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codepipeline-ecs-deploy/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" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-codepipeline-ecs-deploy/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "EcsCluster97242B84": { + "Type": "AWS::ECS::Cluster" + }, + "EcrRepoBB83A592": { + "Type": "AWS::ECR::Repository" + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ecs-tasks.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "Links": [], + "LinuxParameters": { + "Capabilities": { + "Add": [], + "Drop": [] + }, + "Devices": [], + "Tmpfs": [] + }, + "MountPoints": [], + "Name": "Container", + "PortMappings": [], + "Ulimits": [], + "VolumesFrom": [] + } + ], + "Cpu": "256", + "Family": "awscdkcodepipelineecsdeployTaskDefCF95BCAC", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "Volumes": [] + } + }, + "FargateServiceAC2B3B85": { + "Type": "AWS::ECS::Service", + "Properties": { + "TaskDefinition": { + "Ref": "TaskDef54694570" + }, + "Cluster": { + "Ref": "EcsCluster97242B84" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "DesiredCount": 1, + "LaunchType": "FARGATE", + "LoadBalancers": [], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + ] + } + }, + "ServiceRegistries": [] + } + }, + "FargateServiceSecurityGroup0A0E79CB": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-codepipeline-ecs-deploy/FargateService/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "Properties": { + "VersioningConfiguration": { + "Status": "Enabled" + } + } + }, + "EcsProjectRoleE2F0E9D2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "codebuild.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "EcsProjectRoleDefaultPolicy1A8C91E0": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "EcsProject54EFDCA6" + } + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "EcsProject54EFDCA6" + }, + ":*" + ] + ] + } + ] + }, + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EcrRepoBB83A592", + "Arn" + ] + } + }, + { + "Action": "ecr:GetAuthorizationToken", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecr:PutImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EcrRepoBB83A592", + "Arn" + ] + } + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "EcsProjectRoleDefaultPolicy1A8C91E0", + "Roles": [ + { + "Ref": "EcsProjectRoleE2F0E9D2" + } + ] + } + }, + "EcsProject54EFDCA6": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "EnvironmentVariables": [ + { + "Name": "REPOSITORY_URI", + "Type": "PLAINTEXT", + "Value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 4, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "EcrRepoBB83A592", + "Arn" + ] + } + ] + } + ] + }, + ".dkr.ecr.", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "EcrRepoBB83A592", + "Arn" + ] + } + ] + } + ] + }, + ".amazonaws.com/", + { + "Ref": "EcrRepoBB83A592" + } + ] + ] + } + } + ], + "Image": "aws/codebuild/docker:17.09.0", + "PrivilegedMode": true, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "EcsProjectRoleE2F0E9D2", + "Arn" + ] + }, + "Source": { + "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"pre_build\": {\n \"commands\": \"$(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)\"\n },\n \"build\": {\n \"commands\": \"docker build -t $REPOSITORY_URI:latest .\"\n },\n \"post_build\": {\n \"commands\": [\n \"docker push $REPOSITORY_URI:latest\",\n \"printf '[{ \\\"name\\\": \\\"Container\\\", \\\"imageUri\\\": \\\"%s\\\" }]' $REPOSITORY_URI:latest > imagedefinitions.json\"\n ]\n }\n },\n \"artifacts\": {\n \"files\": \"imagedefinitions.json\"\n }\n}", + "Type": "CODEPIPELINE" + } + } + }, + "MyPipelineRoleC0D47CA4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "codepipeline.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "MyPipelineRoleDefaultPolicy34F09EFA": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:GetObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "EcsProject54EFDCA6", + "Arn" + ] + } + }, + { + "Action": [ + "ecs:DescribeServices", + "ecs:DescribeTaskDefinition", + "ecs:DescribeTasks", + "ecs:ListTasks", + "ecs:RegisterTaskDefinition", + "ecs:UpdateService" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringEqualsIfExists": { + "iam:PassedToService": [ + "ec2.amazonaws.com", + "ecs-tasks.amazonaws.com" + ] + } + }, + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "MyPipelineRoleDefaultPolicy34F09EFA", + "Roles": [ + { + "Ref": "MyPipelineRoleC0D47CA4" + } + ] + } + }, + "MyPipelineAED38ECF": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "MyPipelineRoleC0D47CA4", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Ref": "MyBucketF68F3FF0" + }, + "S3ObjectKey": "path/to/Dockerfile" + }, + "InputArtifacts": [], + "Name": "Source", + "OutputArtifacts": [ + { + "Name": "SourceArtifact" + } + ], + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "EcsProject54EFDCA6" + } + }, + "InputArtifacts": [ + { + "Name": "SourceArtifact" + } + ], + "Name": "CodeBuild", + "OutputArtifacts": [ + { + "Name": "Artifact_CodeBuild_awscdkcodepipelineecsdeployEcsProject77DC1B55" + } + ], + "RunOrder": 1 + } + ], + "Name": "Build" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Deploy", + "Owner": "AWS", + "Provider": "ECS", + "Version": "1" + }, + "Configuration": { + "ClusterName": { + "Ref": "EcsCluster97242B84" + }, + "ServiceName": { + "Fn::GetAtt": [ + "FargateServiceAC2B3B85", + "Name" + ] + } + }, + "InputArtifacts": [ + { + "Name": "Artifact_CodeBuild_awscdkcodepipelineecsdeployEcsProject77DC1B55" + } + ], + "Name": "DeployAction", + "OutputArtifacts": [], + "RunOrder": 1 + } + ], + "Name": "Deploy" + } + ], + "ArtifactStore": { + "Location": { + "Ref": "MyBucketF68F3FF0" + }, + "Type": "S3" + } + }, + "DependsOn": [ + "MyPipelineRoleDefaultPolicy34F09EFA", + "MyPipelineRoleC0D47CA4" + ] + } + } +} diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.ts new file mode 100644 index 0000000000000..72539617325de --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-ecs-deploy.ts @@ -0,0 +1,107 @@ +import codebuild = require('@aws-cdk/aws-codebuild'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import ec2 = require('@aws-cdk/aws-ec2'); +import ecr = require('@aws-cdk/aws-ecr'); +import ecs = require('@aws-cdk/aws-ecs'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/cdk'); +import cpactions = require('../lib'); + +// tslint:disable:object-literal-key-quotes + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-ecs-deploy'); + +const vpc = new ec2.VpcNetwork(stack, 'VPC', { + maxAZs: 1, +}); +const cluster = new ecs.Cluster(stack, "EcsCluster", { + vpc, +}); +const repository = new ecr.Repository(stack, 'EcrRepo'); +const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); +const containerName = 'Container'; +taskDefinition.addContainer(containerName, { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), +}); +const service = new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, +}); + +const bucket = new s3.Bucket(stack, 'MyBucket', { + versioned: true, + removalPolicy: cdk.RemovalPolicy.Destroy, +}); +const sourceAction = new cpactions.S3SourceAction({ + actionName: 'Source', + outputArtifactName: 'SourceArtifact', + bucket, + bucketKey: 'path/to/Dockerfile', +}); + +const project = new codebuild.PipelineProject(stack, 'EcsProject', { + environment: { + buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_DOCKER_17_09_0, + privileged: true, + }, + buildSpec: { + version: '0.2', + phases: { + pre_build: { + commands: '$(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)', + }, + build: { + commands: 'docker build -t $REPOSITORY_URI:latest .', + }, + post_build: { + commands: [ + 'docker push $REPOSITORY_URI:latest', + `printf '[{ "name": "${containerName}", "imageUri": "%s" }]' $REPOSITORY_URI:latest > imagedefinitions.json`, + ], + }, + }, + artifacts: { + files: 'imagedefinitions.json', + }, + }, + environmentVariables: { + 'REPOSITORY_URI': { + value: repository.repositoryUri, + }, + }, +}); +// needed for `docker push` +repository.grantPullPush(project); +const buildAction = new cpactions.CodeBuildBuildAction({ + actionName: 'CodeBuild', + project, + inputArtifact: sourceAction.outputArtifact, +}); + +new codepipeline.Pipeline(stack, 'MyPipeline', { + artifactBucket: bucket, + stages: [ + { + name: 'Source', + actions: [sourceAction], + }, + { + name: 'Build', + actions: [buildAction], + }, + { + name: 'Deploy', + actions: [ + new cpactions.EcsDeployAction({ + actionName: 'DeployAction', + inputArtifact: buildAction.outputArtifact, + service, + }), + ], + }, + ], +}); + +app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.ts similarity index 87% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.ts index 50a3207e69265..6a1d34e49f24f 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-events.ts @@ -2,9 +2,10 @@ import codebuild = require('@aws-cdk/aws-codebuild'); import codecommit = require('@aws-cdk/aws-codecommit'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import sns = require('@aws-cdk/aws-sns'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -17,7 +18,7 @@ const repository = new codecommit.Repository(stack, 'CodeCommitRepo', { }); const project = new codebuild.PipelineProject(stack, 'BuildProject'); -const sourceAction = new codecommit.PipelineSourceAction({ +const sourceAction = new cpactions.CodeCommitSourceAction({ actionName: 'CodeCommitSource', outputArtifactName: 'Source', repository, @@ -31,7 +32,7 @@ const sourceStage = pipeline.addStage({ pipeline.addStage({ name: 'Build', actions: [ - new codebuild.PipelineBuildAction({ + new cpactions.CodeBuildBuildAction({ actionName: 'CodeBuildAction', inputArtifact: sourceAction.outputArtifact, project, diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-jenkins.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-jenkins.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-jenkins.ts similarity index 70% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-jenkins.ts index 9dd6fbadbb058..fcbe83aa2f331 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-jenkins.ts @@ -1,6 +1,7 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -14,16 +15,17 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, }); -const sourceAction = bucket.toCodePipelineSourceAction({ +const sourceAction = new cpactions.S3SourceAction({ actionName: 'S3', bucketKey: 'some/path', + bucket, }); pipeline.addStage({ name: 'Source', actions: [sourceAction], }); -const jenkinsProvider = new codepipeline.JenkinsProvider(stack, 'JenkinsProvider', { +const jenkinsProvider = new cpactions.JenkinsProvider(stack, 'JenkinsProvider', { providerName: 'JenkinsProvider', serverUrl: 'http://myjenkins.com:8080', version: '2', @@ -32,18 +34,21 @@ const jenkinsProvider = new codepipeline.JenkinsProvider(stack, 'JenkinsProvider pipeline.addStage({ name: 'Build', actions: [ - jenkinsProvider.toCodePipelineBuildAction({ + new cpactions.JenkinsBuildAction({ actionName: 'JenkinsBuild', + jenkinsProvider, projectName: 'JenkinsProject1', inputArtifact: sourceAction.outputArtifact, }), - jenkinsProvider.toCodePipelineTestAction({ + new cpactions.JenkinsTestAction({ actionName: 'JenkinsTest', + jenkinsProvider, projectName: 'JenkinsProject2', inputArtifact: sourceAction.outputArtifact, }), - jenkinsProvider.toCodePipelineTestAction({ + new cpactions.JenkinsTestAction({ actionName: 'JenkinsTest2', + jenkinsProvider, projectName: 'JenkinsProject3', inputArtifact: sourceAction.outputArtifact, }), diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-manual-approval.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-manual-approval.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-manual-approval.ts similarity index 78% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-manual-approval.ts index e7e6628bf1578..46d4052807a5f 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-manual-approval.ts @@ -1,6 +1,7 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -14,7 +15,7 @@ new codepipeline.Pipeline(stack, 'Pipeline', { { name: 'Source', actions: [ - new s3.PipelineSourceAction({ + new cpactions.S3SourceAction({ actionName: 'S3', bucket, bucketKey: 'file.zip', @@ -24,7 +25,7 @@ new codepipeline.Pipeline(stack, 'Pipeline', { { name: 'Approve', actions: [ - new codepipeline.ManualApprovalAction({ + new cpactions.ManualApprovalAction({ actionName: 'ManualApproval', notifyEmails: ['adamruka85@gmail.com'], }), diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-s3-deploy.expected.json b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.expected.json similarity index 100% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-s3-deploy.expected.json rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.expected.json diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-s3-deploy.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts similarity index 76% rename from packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-s3-deploy.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts index a13b1b47d0630..fb272be5422db 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-s3-deploy.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/integ.pipeline-s3-deploy.ts @@ -1,6 +1,7 @@ +import codepipeline = require('@aws-cdk/aws-codepipeline'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import codepipeline = require('../lib'); +import cpactions = require('../lib'); const app = new cdk.App(); @@ -10,7 +11,7 @@ const bucket = new s3.Bucket(stack, 'PipelineBucket', { versioned: true, removalPolicy: cdk.RemovalPolicy.Destroy, }); -const sourceAction = new s3.PipelineSourceAction({ +const sourceAction = new cpactions.S3SourceAction({ actionName: 'Source', outputArtifactName: 'SourceArtifact', bucket, @@ -28,10 +29,11 @@ new codepipeline.Pipeline(stack, 'Pipeline', { { name: 'Deploy', actions: [ - deployBucket.toCodePipelineDeployAction({ + new cpactions.S3DeployAction({ actionName: 'DeployAction', inputArtifact: sourceAction.outputArtifact, - }), + bucket: deployBucket, + }) ], }, ], diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.action.ts similarity index 74% rename from packages/@aws-cdk/aws-codepipeline/test/test.action.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/test.action.ts index b12452eea55c3..46ce54e094f52 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.action.ts @@ -1,10 +1,10 @@ import { expect, haveResourceLike } from '@aws-cdk/assert'; import codebuild = require('@aws-cdk/aws-codebuild'); import codecommit = require('@aws-cdk/aws-codecommit'); -import actions = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; -import codepipeline = require('../lib'); +import cpactions = require('../lib'); // tslint:disable:object-literal-key-quotes @@ -35,27 +35,27 @@ export = { 'action type validation': { 'must be source and is source'(test: Test) { - const result = actions.validateSourceAction(true, actions.ActionCategory.Source, 'test action', 'test stage'); + const result = codepipeline.validateSourceAction(true, codepipeline.ActionCategory.Source, 'test action', 'test stage'); test.deepEqual(result.length, 0); test.done(); }, 'must be source and is not source'(test: Test) { - const result = actions.validateSourceAction(true, actions.ActionCategory.Deploy, 'test action', 'test stage'); + const result = codepipeline.validateSourceAction(true, codepipeline.ActionCategory.Deploy, 'test action', 'test stage'); test.deepEqual(result.length, 1); test.ok(result[0].match(/may only contain Source actions/), 'the validation should have failed'); test.done(); }, 'cannot be source and is source'(test: Test) { - const result = actions.validateSourceAction(false, actions.ActionCategory.Source, 'test action', 'test stage'); + const result = codepipeline.validateSourceAction(false, codepipeline.ActionCategory.Source, 'test action', 'test stage'); test.deepEqual(result.length, 1); test.ok(result[0].match(/may only occur in first stage/), 'the validation should have failed'); test.done(); }, 'cannot be source and is not source'(test: Test) { - const result = actions.validateSourceAction(false, actions.ActionCategory.Deploy, 'test action', 'test stage'); + const result = codepipeline.validateSourceAction(false, codepipeline.ActionCategory.Deploy, 'test action', 'test stage'); test.deepEqual(result.length, 0); test.done(); }, @@ -68,7 +68,10 @@ export = { const repo = new codecommit.Repository(stack, 'Repo', { repositoryName: 'Repo', }); - const sourceAction = repo.toCodePipelineSourceAction({ actionName: 'CodeCommit' }); + const sourceAction = new cpactions.CodeCommitSourceAction({ + actionName: 'CodeCommit', + repository: repo, + }); pipeline.addStage({ name: 'Source', actions: [sourceAction], @@ -78,8 +81,9 @@ export = { pipeline.addStage({ name: 'Build', actions: [ - project.toCodePipelineBuildAction({ + new cpactions.CodeBuildBuildAction({ actionName: 'CodeBuild', + project, inputArtifact: sourceAction.outputArtifact, }), ], @@ -152,31 +156,33 @@ export = { 'input Artifacts': { 'can be added multiple times to an Action safely'(test: Test) { - const artifact = new actions.Artifact('SomeArtifact'); + const artifact = new codepipeline.Artifact('SomeArtifact'); const stack = new cdk.Stack(); const project = new codebuild.PipelineProject(stack, 'Project'); - const action = project.toCodePipelineBuildAction({ + const action = new cpactions.CodeBuildBuildAction({ actionName: 'CodeBuild', + project, inputArtifact: artifact, additionalInputArtifacts: [artifact], }); - test.equal(action._inputArtifacts.length, 1); + test.equal((action as any).actionInputArtifacts.length, 1); test.done(); }, 'cannot have duplicate names'(test: Test) { - const artifact1 = new actions.Artifact('SomeArtifact'); - const artifact2 = new actions.Artifact('SomeArtifact'); + const artifact1 = new codepipeline.Artifact('SomeArtifact'); + const artifact2 = new codepipeline.Artifact('SomeArtifact'); const stack = new cdk.Stack(); const project = new codebuild.PipelineProject(stack, 'Project'); test.throws(() => - project.toCodePipelineBuildAction({ + new cpactions.CodeBuildBuildAction({ actionName: 'CodeBuild', + project, inputArtifact: artifact1, additionalInputArtifacts: [artifact2], }) @@ -188,12 +194,13 @@ export = { 'output Artifact names': { 'accept the same name multiple times safely'(test: Test) { - const artifact = new actions.Artifact('SomeArtifact'); + const artifact = new codepipeline.Artifact('SomeArtifact'); const stack = new cdk.Stack(); const project = new codebuild.PipelineProject(stack, 'Project'); - const action = project.toCodePipelineBuildAction({ + const action = new cpactions.CodeBuildBuildAction({ actionName: 'CodeBuild', + project, inputArtifact: artifact, outputArtifactName: 'Artifact1', additionalOutputArtifactNames: [ @@ -202,7 +209,7 @@ export = { ], }); - test.equal(action._outputArtifacts.length, 1); + test.equal((action as any).actionOutputArtifacts.length, 1); test.done(); }, @@ -210,24 +217,24 @@ export = { }; function boundsValidationResult(numberOfArtifacts: number, min: number, max: number): string[] { - const artifacts: actions.Artifact[] = []; + const artifacts: codepipeline.Artifact[] = []; for (let i = 0; i < numberOfArtifacts; i++) { - artifacts.push(new actions.Artifact(`TestArtifact${i}`)); + artifacts.push(new codepipeline.Artifact(`TestArtifact${i}`)); } - return actions.validateArtifactBounds('output', artifacts, min, max, 'testCategory', 'testProvider'); + return codepipeline.validateArtifactBounds('output', artifacts, min, max, 'testCategory', 'testProvider'); } -class FakeAction extends actions.Action { +class FakeAction extends codepipeline.Action { constructor(actionName: string) { super({ actionName, - category: actions.ActionCategory.Source, + category: codepipeline.ActionCategory.Source, provider: 'SomeService', - artifactBounds: actions.defaultBounds(), + artifactBounds: codepipeline.defaultBounds(), }); } - protected bind(_stage: actions.IStage, _scope: cdk.Construct): void { + protected bind(_info: codepipeline.ActionBind): void { // do nothing } } diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts similarity index 86% rename from packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts rename to packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts index e2a69af8e5f06..610b9ab454820 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts @@ -1,27 +1,26 @@ -import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; -import cloudformation = require('@aws-cdk/aws-cloudformation'); +import { expect, haveResource, haveResourceLike, SynthUtils } from '@aws-cdk/assert'; import codebuild = require('@aws-cdk/aws-codebuild'); import codecommit = require('@aws-cdk/aws-codecommit'); -import cpapi = require('@aws-cdk/aws-codepipeline-api'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); import lambda = require('@aws-cdk/aws-lambda'); import s3 = require('@aws-cdk/aws-s3'); import sns = require('@aws-cdk/aws-sns'); -import cdk = require('@aws-cdk/cdk'); +import { App, CfnParameter, SecretValue, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import codepipeline = require('../lib'); +import cpactions = require('../lib'); // tslint:disable:object-literal-key-quotes export = { 'basic pipeline'(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const repository = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'my-repo', }); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - const source = new codecommit.PipelineSourceAction({ + const source = new cpactions.CodeCommitSourceAction({ actionName: 'source', outputArtifactName: 'SourceArtifact', repository, @@ -37,7 +36,7 @@ export = { pipeline.addStage({ name: 'build', actions: [ - new codebuild.PipelineBuildAction({ + new cpactions.CodeBuildBuildAction({ actionName: 'build', inputArtifact: source.outputArtifact, project, @@ -45,13 +44,13 @@ export = { ], }); - test.notDeepEqual(stack._toCloudFormation(), {}); + test.notDeepEqual(SynthUtils.toCloudFormation(stack), {}); test.deepEqual([], pipeline.node.validateTree()); test.done(); }, 'Tokens can be used as physical names of the Pipeline'(test: Test) { - const stack = new cdk.Stack(undefined, 'StackName'); + const stack = new Stack(undefined, 'StackName'); new codepipeline.Pipeline(stack, 'Pipeline', { pipelineName: stack.stackName, @@ -67,21 +66,21 @@ export = { }, 'github action uses ThirdParty owner'(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); - const secret = new cdk.SecretParameter(stack, 'GitHubToken', { ssmParameter: 'my-token' }); + const secret = new CfnParameter(stack, 'GitHubToken', { type: 'String', default: 'my-token' }); const p = new codepipeline.Pipeline(stack, 'P'); p.addStage({ name: 'Source', actions: [ - new codepipeline.GitHubSourceAction({ + new cpactions.GitHubSourceAction({ actionName: 'GH', runOrder: 8, outputArtifactName: 'A', branch: 'branch', - oauthToken: secret.value, + oauthToken: SecretValue.plainText(secret.stringValue), owner: 'foo', repo: 'bar' }), @@ -91,7 +90,7 @@ export = { p.addStage({ name: 'Two', actions: [ - new codepipeline.ManualApprovalAction({ actionName: 'Boo' }), + new cpactions.ManualApprovalAction({ actionName: 'Boo' }), ], }); @@ -123,7 +122,7 @@ export = { "Repo": "bar", "Branch": "branch", "OAuthToken": { - "Ref": "GitHubTokenParameterBB166B9D" + "Ref": "GitHubToken" }, "PollForSourceChanges": false }, @@ -164,7 +163,7 @@ export = { }, 'onStateChange'(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const topic = new sns.Topic(stack, 'Topic'); @@ -173,7 +172,7 @@ export = { pipeline.addStage({ name: 'S1', actions: [ - new s3.PipelineSourceAction({ + new cpactions.S3SourceAction({ actionName: 'A1', outputArtifactName: 'Artifact', bucket: new s3.Bucket(stack, 'Bucket'), @@ -185,7 +184,7 @@ export = { pipeline.addStage({ name: 'S2', actions: [ - new codepipeline.ManualApprovalAction({ actionName: 'A2' }), + new cpactions.ManualApprovalAction({ actionName: 'A2' }), ], }); @@ -257,9 +256,9 @@ export = { 'manual approval Action': { 'allows passing an SNS Topic when constructing it'(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const topic = new sns.Topic(stack, 'Topic'); - const manualApprovalAction = new codepipeline.ManualApprovalAction({ + const manualApprovalAction = new cpactions.ManualApprovalAction({ actionName: 'Approve', notificationTopic: topic, }); @@ -274,7 +273,7 @@ export = { 'PipelineProject': { 'with a custom Project Name': { 'sets the source and artifacts to CodePipeline'(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); new codebuild.PipelineProject(stack, 'MyProject', { projectName: 'MyProject', @@ -308,7 +307,7 @@ export = { }, 'Lambda PipelineInvokeAction can be used to invoke Lambda functions from a CodePipeline'(test: Test) { - const stack = new cdk.Stack(); + const stack = new Stack(); const lambdaFun = new lambda.Function(stack, 'Function', { code: new lambda.InlineCode('bla'), @@ -319,15 +318,17 @@ export = { const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); const bucket = new s3.Bucket(stack, 'Bucket'); - const source1 = bucket.toCodePipelineSourceAction({ + const source1 = new cpactions.S3SourceAction({ actionName: 'SourceAction1', bucketKey: 'some/key', outputArtifactName: 'sourceArtifact1', + bucket, }); - const source2 = bucket.toCodePipelineSourceAction({ + const source2 = new cpactions.S3SourceAction({ actionName: 'SourceAction2', bucketKey: 'another/key', outputArtifactName: 'sourceArtifact2', + bucket, }); pipeline.addStage({ name: 'Source', @@ -337,7 +338,7 @@ export = { ], }); - const lambdaAction = new lambda.PipelineInvokeAction({ + const lambdaAction = new cpactions.LambdaInvokeAction({ actionName: 'InvokeAction', lambda: lambdaFun, userParameters: 'foo-bar/42', @@ -436,8 +437,8 @@ export = { 'CodeCommit Action': { 'does not poll for changes by default'(test: Test) { - const stack = new cdk.Stack(); - const sourceAction = new codecommit.PipelineSourceAction({ + const stack = new Stack(); + const sourceAction = new cpactions.CodeCommitSourceAction({ actionName: 'stage', outputArtifactName: 'SomeArtifact', repository: repositoryForTesting(stack), @@ -449,8 +450,8 @@ export = { }, 'does not poll for source changes when explicitly set to false'(test: Test) { - const stack = new cdk.Stack(); - const sourceAction = new codecommit.PipelineSourceAction({ + const stack = new Stack(); + const sourceAction = new cpactions.CodeCommitSourceAction({ actionName: 'stage', outputArtifactName: 'SomeArtifact', repository: repositoryForTesting(stack), @@ -468,9 +469,9 @@ export = { const pipelineRegion = 'us-west-2'; const pipelineAccount = '123'; - const app = new cdk.App(); + const app = new App(); - const stack = new cdk.Stack(app, 'TestStack', { + const stack = new Stack(app, 'TestStack', { env: { region: pipelineRegion, account: pipelineAccount, @@ -483,9 +484,10 @@ export = { }, }); - const sourceAction = bucket.toCodePipelineSourceAction({ + const sourceAction = new cpactions.S3SourceAction({ actionName: 'BucketSource', bucketKey: '/some/key', + bucket, }); pipeline.addStage({ name: 'Stage1', @@ -495,7 +497,7 @@ export = { pipeline.addStage({ name: 'Stage2', actions: [ - new cloudformation.PipelineCreateReplaceChangeSetAction({ + new cpactions.CloudFormationCreateReplaceChangeSetAction({ actionName: 'Action1', changeSetName: 'ChangeSet', templatePath: sourceAction.outputArtifact.atPath('template.yaml'), @@ -503,14 +505,14 @@ export = { region: pipelineRegion, adminPermissions: false, }), - new cloudformation.PipelineCreateUpdateStackAction({ + new cpactions.CloudFormationCreateUpdateStackAction({ actionName: 'Action2', templatePath: sourceAction.outputArtifact.atPath('template.yaml'), stackName: 'OtherStack', region: 'us-east-1', adminPermissions: false, }), - new cloudformation.PipelineExecuteChangeSetAction({ + new cpactions.CloudFormationExecuteChangeSetAction({ actionName: 'Action3', changeSetName: 'ChangeSet', stackName: 'SomeStack', @@ -580,12 +582,12 @@ export = { }, }; -function stageForTesting(stack: cdk.Stack): cpapi.IStage { +function stageForTesting(stack: Stack): codepipeline.IStage { const pipeline = new codepipeline.Pipeline(stack, 'pipeline'); return pipeline.addStage({ name: 'stage' }); } -function repositoryForTesting(stack: cdk.Stack): codecommit.Repository { +function repositoryForTesting(stack: Stack): codecommit.Repository { return new codecommit.Repository(stack, 'Repository', { repositoryName: 'Repository' }); diff --git a/packages/@aws-cdk/aws-codepipeline-api/README.md b/packages/@aws-cdk/aws-codepipeline-api/README.md index bd7b88fd43aa9..16176e56a75f3 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/README.md +++ b/packages/@aws-cdk/aws-codepipeline-api/README.md @@ -1,10 +1,3 @@ ## AWS CodePipeline Actions API -This package contains the abstract API of Pipeline Actions. -It's used by the `aws-codepipeline` module, -and the AWS service modules that integrate with AWS CodePipeline. - -You should never need to depend on it directly - -instead, depend on `aws-codepipeline`, -and the module you need the concrete Actions from -(like `aws-codecommit`, `aws-codebuild`, etc.). +This package is no longer used, and will be removed in a future version of the CDK. diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/index.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/index.ts index 96229c6f95a9f..e69de29bb2d1d 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/index.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/index.ts @@ -1,7 +0,0 @@ -export * from './artifact'; -export * from './action'; -export * from './build-action'; -export * from './deploy-action'; -export * from './source-action'; -export * from './test-action'; -export * from './validation'; diff --git a/packages/@aws-cdk/aws-codepipeline-api/package.json b/packages/@aws-cdk/aws-codepipeline-api/package.json index 730b2bf7c843b..2f40f64cb9483 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/package.json +++ b/packages/@aws-cdk/aws-codepipeline-api/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codepipeline-api", - "version": "0.26.0", + "version": "0.28.0", "description": "Actions API for AWS Code Pipeline", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-codepipeline-api", + "module": "aws_cdk.aws_codepipeline_api" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codepipeline-api" }, "scripts": { "build": "cdk-build", @@ -53,29 +58,13 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "pkglint": "^0.26.0" - }, - "dependencies": { - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", - "peerDependencies": { - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" - }, "engines": { "node": ">= 8.10.0" - }, - "awslint": { - "exclude": [ - "construct-ctor:@aws-cdk/aws-codepipeline-api.Artifact..params[0]", - "construct-ctor:@aws-cdk/aws-codepipeline-api.Artifact..params[1]" - ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codepipeline/README.md b/packages/@aws-cdk/aws-codepipeline/README.md index f34f41cdb6815..b6571a4205c1e 100644 --- a/packages/@aws-cdk/aws-codepipeline/README.md +++ b/packages/@aws-cdk/aws-codepipeline/README.md @@ -49,7 +49,7 @@ const sourceStage = pipeline.addStage({ You can insert the new Stage at an arbitrary point in the Pipeline: ```ts -pipeline.addStage({ +const someStage = pipeline.addStage({ name: 'SomeStage', placement: { // note: you can only specify one of the below properties @@ -63,98 +63,14 @@ pipeline.addStage({ ### Actions -To add an Action to a Stage: +Actions live in a separate package, `@aws-cdk/aws-codepipeline-actions`. -```ts -const sourceAction = new codepipeline.GitHubSourceAction({ - actionName: 'GitHub_Source', - owner: 'awslabs', - repo: 'aws-cdk', - branch: 'develop', // default: 'master' - oauthToken: ..., - outputArtifactName: 'SourceOutput', // this will be the name of the output artifact in the Pipeline -}); -sourceStage.addAction(sourceAction); -``` - -#### Manual approval Action - -This package contains an Action that stops the Pipeline until someone manually clicks the approve button: - -```typescript -const manualApprovalAction = new codepipeline.ManualApprovalAction({ - actionName: 'Approve', - notificationTopic: new sns.Topic(this, 'Topic'), // optional - notifyEmails: [ - 'some_email@example.com', - ], // optional - additionalInformation: 'additional info', // optional -}); -approveStage.addAction(manualApprovalAction); -// `manualApprovalAction.notificationTopic` can be used to access the Topic -// after the Action has been added to a Pipeline -``` - -If the `notificationTopic` has not been provided, -but `notifyEmails` were, -a new SNS Topic will be created -(and accessible through the `notificationTopic` property of the Action). - -#### Jenkins Actions - -In order to use Jenkins Actions in the Pipeline, -you first need to create a `JenkinsProvider`: +To add an Action to a Stage, you can provide it when creating the Stage, +in the `actions` property, +or you can use the `IStage.addAction()` method to mutate an existing Stage: ```ts -const jenkinsProvider = new codepipeline.JenkinsProvider(this, 'JenkinsProvider', { - providerName: 'MyJenkinsProvider', - serverUrl: 'http://my-jenkins.com:8080', - version: '2', // optional, default: '1' -}); -``` - -If you've registered a Jenkins provider in a different CDK app, -or outside the CDK (in the CodePipeline AWS Console, for example), -you can import it: - -```ts -const jenkinsProvider = codepipeline.JenkinsProvider.import(this, 'JenkinsProvider', { - providerName: 'MyJenkinsProvider', - serverUrl: 'http://my-jenkins.com:8080', - version: '2', // optional, default: '1' -}); -``` - -Note that a Jenkins provider -(identified by the provider name-category(build/test)-version tuple) -must always be registered in the given account, in the given AWS region, -before it can be used in CodePipeline. - -With a `JenkinsProvider`, -we can create a Jenkins Action: - -```ts -const buildAction = new codepipeline.JenkinsBuildAction({ - actionName: 'JenkinsBuild', - jenkinsProvider: jenkinsProvider, - projectName: 'MyProject', -}); -// there's also a JenkinsTestAction that works identically -``` - -You can also add the Action to the Pipeline directly: - -```ts -// equivalent to the code above: -const buildAction = jenkinsProvider.toCodePipelineBuildAction({ - actionName: 'JenkinsBuild', - projectName: 'MyProject', -}); - -const testAction = jenkinsProvider.toCodePipelineTestAction({ - actionName: 'JenkinsTest', - projectName: 'MyProject', -}); +sourceStage.addAction(someAction); ``` ### Cross-region CodePipelines @@ -174,7 +90,7 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { }); // later in the code... -new cloudformation.PipelineCreateUpdateStackAction({ +new codepipeline_actions.CloudFormationCreateUpdateStackAction({ actionName: 'CFN_US_West_1', // ... region: 'us-west-1', diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline/lib/action.ts similarity index 83% rename from packages/@aws-cdk/aws-codepipeline-api/lib/action.ts rename to packages/@aws-cdk/aws-codepipeline/lib/action.ts index d98bbc52dd2c6..476049a5fe810 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/action.ts @@ -36,6 +36,32 @@ export function defaultBounds(): ActionArtifactBounds { }; } +/** + * The interface used in the {@link Action#bind()} callback. + */ +export interface ActionBind { + /** + * The pipeline this action has been added to. + */ + readonly pipeline: IPipeline; + + /** + * The stage this action has been added to. + */ + readonly stage: IStage; + + /** + * The scope construct for this action. + * Can be used by the action implementation to create any resources it needs to work correctly. + */ + readonly scope: cdk.Construct; + + /** + * The IAM Role to add the necessary permissions to. + */ + readonly role: iam.IRole; +} + /** * The abstract view of an AWS CodePipeline as required and used by Actions. * It extends {@link events.IEventRuleTarget}, @@ -52,24 +78,19 @@ export interface IPipeline extends cdk.IConstruct, events.IEventRuleTarget { */ readonly pipelineArn: string; - /** - * The service Role of the Pipeline. - */ - readonly role: iam.Role; - /** * Grants read permissions to the Pipeline's S3 Bucket to the given Identity. * * @param identity the IAM Identity to grant the permissions to */ - grantBucketRead(identity?: iam.IPrincipal): void; + grantBucketRead(identity: iam.IGrantable): iam.Grant; /** * Grants read & write permissions to the Pipeline's S3 Bucket to the given Identity. * * @param identity the IAM Identity to grant the permissions to */ - grantBucketReadWrite(identity?: iam.IPrincipal): void; + grantBucketReadWrite(identity: iam.IGrantable): iam.Grant; } /** @@ -81,11 +102,6 @@ export interface IStage { */ readonly stageName: string; - /** - * The Pipeline this Stage belongs to. - */ - readonly pipeline: IPipeline; - addAction(action: Action): void; onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule; @@ -99,7 +115,7 @@ export interface CommonActionProps { * The physical, human-readable name of the Action. * Not that Action names must be unique within a single Stage. */ - actionName: string; + readonly actionName: string; /** * The runOrder property for this Action. @@ -108,22 +124,22 @@ export interface CommonActionProps { * @default 1 * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html */ - runOrder?: number; + readonly runOrder?: number; } /** * Construction properties of the low-level {@link Action Action class}. */ export interface ActionProps extends CommonActionProps { - category: ActionCategory; - provider: string; + readonly category: ActionCategory; + readonly provider: string; /** * The region this Action resides in. * * @default the Action resides in the same region as the Pipeline */ - region?: string; + readonly region?: string; /** * The service role that is assumed during execution of action. @@ -132,12 +148,12 @@ export interface ActionProps extends CommonActionProps { * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html */ - role?: iam.IRole; + readonly role?: iam.IRole; - artifactBounds: ActionArtifactBounds; - configuration?: any; - version?: string; - owner?: string; + readonly artifactBounds: ActionArtifactBounds; + readonly configuration?: any; + readonly version?: string; + readonly owner?: string; } /** @@ -202,6 +218,7 @@ export abstract class Action { private readonly _actionOutputArtifacts = new Array(); private readonly artifactBounds: ActionArtifactBounds; + private _pipeline?: IPipeline; private _stage?: IStage; private _scope?: cdk.Construct; @@ -226,7 +243,7 @@ export abstract class Action { rule.addEventPattern({ detailType: [ 'CodePipeline Stage Execution State Change' ], source: [ 'aws.codepipeline' ], - resources: [ this.stage.pipeline.pipelineArn ], + resources: [ this.pipeline.pipelineArn ], detail: { stage: [ this.stage.stageName ], action: [ this.actionName ], @@ -235,11 +252,11 @@ export abstract class Action { return rule; } - public get _inputArtifacts(): Artifact[] { + protected get actionInputArtifacts(): Artifact[] { return this._actionInputArtifacts.slice(); } - public get _outputArtifacts(): Artifact[] { + protected get actionOutputArtifacts(): Artifact[] { return this._actionOutputArtifacts.slice(); } @@ -254,7 +271,7 @@ export abstract class Action { protected addOutputArtifact(name: string): Artifact { // adding the same name multiple times doesn't do anything - // addOutputArtifact is idempotent - const ret = this._outputArtifacts.find(output => output.artifactName === name); + const ret = this.actionOutputArtifacts.find(output => output.artifactName === name); if (ret) { return ret; } @@ -297,24 +314,32 @@ export abstract class Action { * The method called when an Action is attached to a Pipeline. * This method is guaranteed to be called only once for each Action instance. * - * @param stage the stage this action has been added to - * (includes a reference to the pipeline as well) - * @param scope the scope construct for this action, - * can be used by the action implementation to create any resources it needs to work correctly + * @info an instance of the {@link ActionBind} class, + * that contains the necessary information for the Action + * to configure itself, like a reference to the Pipeline, Stage, Role, etc. */ - protected abstract bind(stage: IStage, scope: cdk.Construct): void; + protected abstract bind(info: ActionBind): void; - // ignore unused private method (it's actually used in Stage) + // ignore unused private method (it's actually used in Pipeline) // @ts-ignore - private _attachActionToPipeline(stage: IStage, scope: cdk.Construct): void { + private _actionAttachedToPipeline(info: ActionBind): void { if (this._stage) { throw new Error(`Action '${this.actionName}' has been added to a pipeline twice`); } - this._stage = stage; - this._scope = scope; + this._pipeline = info.pipeline; + this._stage = info.stage; + this._scope = info.scope; + + this.bind(info); + } - this.bind(stage, scope); + private get pipeline(): IPipeline { + if (this._pipeline) { + return this._pipeline; + } else { + throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange'); + } } private get stage(): IStage { diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts b/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts similarity index 86% rename from packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts rename to packages/@aws-cdk/aws-codepipeline/lib/artifact.ts index 0e4f03a1e0c6e..ea99ed0ac6262 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/artifact.ts @@ -1,3 +1,4 @@ +import s3 = require("@aws-cdk/aws-s3"); import { Token } from "@aws-cdk/cdk"; /** @@ -48,6 +49,17 @@ export class Artifact { return artifactGetParam(this, jsonFile, keyName); } + /** + * Returns the coordinates of the .zip file in S3 that this Artifact represents. + * Used by Lambda's `CfnParametersCode` when being deployed in a CodePipeline. + */ + public get s3Coordinates(): s3.Coordinates { + return { + bucketName: this.bucketName, + objectKey: this.objectKey, + }; + } + public toString() { return this.artifactName; } diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/build-action.ts similarity index 86% rename from packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts rename to packages/@aws-cdk/aws-codepipeline/lib/build-action.ts index 1fea727ef1b3c..375d91673535f 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/build-action.ts @@ -8,36 +8,36 @@ export interface BuildActionProps extends CommonActionProps { /** * The source to use as input for this build. */ - inputArtifact: Artifact; + readonly inputArtifact: Artifact; /** * The service provider that the action calls. For example, a valid provider for Source actions is CodeBuild. */ - provider: string; + readonly provider: string; /** * The upper and lower bounds on the number of input and output artifacts for this Action. */ - artifactBounds: ActionArtifactBounds; + readonly artifactBounds: ActionArtifactBounds; /** * The build Action owner (could be 'AWS', 'ThirdParty' or 'Custom'). * * @default 'AWS' */ - owner?: string; + readonly owner?: string; /** * The build Action version. * * @default '1' */ - version?: string; + readonly version?: string; /** * The name of the build's output artifact. */ - outputArtifactName: string; + readonly outputArtifactName: string; /** * The action's configuration. These are key-value pairs that specify input values for an action. @@ -45,7 +45,7 @@ export interface BuildActionProps extends CommonActionProps { * * http://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#action-requirements */ - configuration?: any; + readonly configuration?: any; } /** diff --git a/packages/@aws-cdk/aws-codepipeline/lib/cross-region-scaffold-stack.ts b/packages/@aws-cdk/aws-codepipeline/lib/cross-region-scaffold-stack.ts index 43d37c1f388a6..7b7ffdf084d81 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/cross-region-scaffold-stack.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/cross-region-scaffold-stack.ts @@ -9,14 +9,14 @@ export interface CrossRegionScaffoldStackProps { /** * The AWS region this Stack resides in. */ - region: string; + readonly region: string; /** * The AWS account ID this Stack belongs to. * * @example '012345678901' */ - account: string; + readonly account: string; } /** diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/deploy-action.ts similarity index 71% rename from packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts rename to packages/@aws-cdk/aws-codepipeline/lib/deploy-action.ts index 4a4d0155453a4..a199d66a38a98 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/deploy-action.ts @@ -2,15 +2,15 @@ import { Action, ActionArtifactBounds, ActionCategory, CommonActionProps } from import { Artifact } from './artifact'; export interface DeployActionProps extends CommonActionProps { - provider: string; + readonly provider: string; - owner?: string; + readonly owner?: string; - artifactBounds: ActionArtifactBounds; + readonly artifactBounds: ActionArtifactBounds; - inputArtifact: Artifact; + readonly inputArtifact: Artifact; - configuration?: any; + readonly configuration?: any; } export abstract class DeployAction extends Action { diff --git a/packages/@aws-cdk/aws-codepipeline/lib/index.ts b/packages/@aws-cdk/aws-codepipeline/lib/index.ts index 2a1bec4de1230..d2ff046e9bdba 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/index.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/index.ts @@ -1,9 +1,12 @@ +export * from './action'; +export * from './artifact'; +export * from './build-action'; export * from './cross-region-scaffold-stack'; -export * from './github-source-action'; -export * from './jenkins-actions'; -export * from './jenkins-provider'; -export * from './manual-approval-action'; +export * from './deploy-action'; export * from './pipeline'; +export * from './source-action'; +export * from './test-action'; +export * from './validation'; // AWS::CodePipeline CloudFormation Resources: export * from './codepipeline.generated'; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index add15cb903ab4..f17e421fc34e3 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -1,11 +1,12 @@ -import cpapi = require('@aws-cdk/aws-codepipeline-api'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); +import { Action, IPipeline, IStage } from "./action"; import { CfnPipeline } from './codepipeline.generated'; import { CrossRegionScaffoldStack } from './cross-region-scaffold-stack'; import { Stage } from './stage'; +import { validateName, validateSourceAction } from "./validation"; /** * Allows you to control where to place a new Stage when it's added to the Pipeline. @@ -21,13 +22,13 @@ export interface StagePlacement { * Inserts the new Stage as a parent of the given Stage * (changing its current parent Stage, if it had one). */ - readonly rightBefore?: cpapi.IStage; + readonly rightBefore?: IStage; /** * Inserts the new Stage as a child of the given Stage * (changing its current child Stage, if it had one). */ - readonly justAfter?: cpapi.IStage; + readonly justAfter?: IStage; /** * Inserts the new Stage at the given index in the Pipeline, @@ -47,17 +48,17 @@ export interface StageProps { /** * The physical, human-readable name to assign to this Pipeline Stage. */ - name: string; + readonly name: string; /** * The list of Actions to create this Stage with. * You can always add more Actions later by calling {@link IStage#addAction}. */ - actions?: cpapi.Action[]; + readonly actions?: Action[]; } export interface StageAddToPipelineProps extends StageProps { - placement?: StagePlacement; + readonly placement?: StagePlacement; } export interface PipelineProps { @@ -65,18 +66,18 @@ export interface PipelineProps { * The S3 bucket used by this Pipeline to store artifacts. * If not specified, a new S3 bucket will be created. */ - artifactBucket?: s3.IBucket; + readonly artifactBucket?: s3.IBucket; /** * Indicates whether to rerun the AWS CodePipeline pipeline after you update it. */ - restartExecutionOnUpdate?: boolean; + readonly restartExecutionOnUpdate?: boolean; /** * Name of the pipeline. If you don't specify a name, AWS CloudFormation generates an ID * and uses that for the pipeline name. */ - pipelineName?: string; + readonly pipelineName?: string; /** * A map of region to S3 bucket name used for cross-region CodePipeline. @@ -86,14 +87,14 @@ export interface PipelineProps { * Note that you will have to `cdk deploy` that Stack before you can deploy your Pipeline-containing Stack. * You can query the generated Stacks using the {@link Pipeline#crossRegionScaffoldStacks} property. */ - crossRegionReplicationBuckets?: { [region: string]: string }; + readonly crossRegionReplicationBuckets?: { [region: string]: string }; /** * The list of Stages, in order, * to create this Pipeline with. * You can always add more Stages later by calling {@link Pipeline#addStage}. */ - stages?: StageProps[]; + readonly stages?: StageProps[]; } /** @@ -107,7 +108,7 @@ export interface PipelineProps { * const sourceStage = pipeline.addStage({ name: 'Source' }); * * // add a source action to the stage - * sourceStage.addAction(new codecommit.PipelineSourceAction({ + * sourceStage.addAction(new codepipeline_actions.CodeCommitSourceAction({ * actionName: 'Source', * outputArtifactName: 'SourceArtifact', * repository: repo, @@ -115,7 +116,7 @@ export interface PipelineProps { * * // ... add more stages */ -export class Pipeline extends cdk.Construct implements cpapi.IPipeline { +export class Pipeline extends cdk.Construct implements IPipeline { /** * The IAM role AWS CodePipeline will use to perform actions or assume roles for actions with * a more specific IAM role. @@ -153,7 +154,7 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { super(scope, id); props = props || {}; - cpapi.validateName('Pipeline', props.pipelineName); + validateName('Pipeline', props.pipelineName); // If a bucket has been provided, use it - otherwise, create a bucket. let propsBucket = props.artifactBucket; @@ -204,7 +205,7 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { * @param props the creation properties of the new Stage * @returns the newly created Stage */ - public addStage(props: StageAddToPipelineProps): cpapi.IStage { + public addStage(props: StageAddToPipelineProps): IStage { // check for duplicate Stages and names if (this.stages.find(s => s.stageName === props.name)) { throw new Error(`Stage with duplicate name '${props.name}' added to the Pipeline`); @@ -292,12 +293,12 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { return this.stages.length; } - public grantBucketRead(identity?: iam.IPrincipal): void { - this.artifactBucket.grantRead(identity); + public grantBucketRead(identity: iam.IGrantable): iam.Grant { + return this.artifactBucket.grantRead(identity); } - public grantBucketReadWrite(identity?: iam.IPrincipal): void { - this.artifactBucket.grantReadWrite(identity); + public grantBucketReadWrite(identity: iam.IGrantable): iam.Grant { + return this.artifactBucket.grantReadWrite(identity); } /** @@ -329,43 +330,51 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { // ignore unused private method (it's actually used in Stage) // @ts-ignore - private _attachActionToRegion(stage: Stage, action: cpapi.Action): void { - // handle cross-region Actions here - if (!action.region) { - return; + private _attachActionToPipeline(stage: Stage, action: Action, actionScope: cdk.Construct): void { + if (action.region) { + // handle cross-region Actions here + this.ensureReplicationBucketExistsFor(action.region); } + (action as any)._actionAttachedToPipeline({ + pipeline: this, + stage, + scope: actionScope, + role: this.role, + }); + } + private ensureReplicationBucketExistsFor(region: string) { // get the region the Pipeline itself is in const pipelineRegion = this.node.stack.requireRegion( - "You need to specify an explicit region when using CodePipeline's cross-region support"); + "You need to specify an explicit region when using CodePipeline's cross-region support"); // if we already have an ArtifactStore generated for this region, or it's the Pipeline's region, nothing to do - if (this.artifactStores[action.region] || action.region === pipelineRegion) { + if (this.artifactStores[region] || region === pipelineRegion) { return; } - let replicationBucketName = this.crossRegionReplicationBuckets[action.region]; + let replicationBucketName = this.crossRegionReplicationBuckets[region]; if (!replicationBucketName) { const pipelineAccount = this.node.stack.requireAccountId( - "You need to specify an explicit account when using CodePipeline's cross-region support"); + "You need to specify an explicit account when using CodePipeline's cross-region support"); const app = this.node.stack.parentApp(); if (!app) { throw new Error(`Pipeline stack which uses cross region actions must be part of an application`); } const crossRegionScaffoldStack = new CrossRegionScaffoldStack(app, { - region: action.region, + region, account: pipelineAccount, }); - this._crossRegionScaffoldStacks[action.region] = crossRegionScaffoldStack; + this._crossRegionScaffoldStacks[region] = crossRegionScaffoldStack; replicationBucketName = crossRegionScaffoldStack.replicationBucketName; } - const replicationBucket = s3.Bucket.import(this, 'CrossRegionCodePipelineReplicationBucket-' + action.region, { + const replicationBucket = s3.Bucket.import(this, 'CrossRegionCodePipelineReplicationBucket-' + region, { bucketName: replicationBucketName, }); replicationBucket.grantReadWrite(this.role); - this.artifactStores[action.region] = { + this.artifactStores[region] = { Location: replicationBucket.bucketName, Type: 'S3', }; @@ -412,7 +421,7 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { return this.stageCount; } - private findStageIndex(targetStage: cpapi.IStage) { + private findStageIndex(targetStage: IStage) { return this.stages.findIndex(stage => stage === targetStage); } @@ -422,7 +431,7 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { for (const stage of this.stages) { const onlySourceActionsPermitted = firstStage; for (const action of stage.actions) { - errors.push(...cpapi.validateSourceAction(onlySourceActionsPermitted, action.category, action.actionName, stage.stageName)); + errors.push(...validateSourceAction(onlySourceActionsPermitted, action.category, action.actionName, stage.stageName)); } firstStage = false; } diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/source-action.ts similarity index 91% rename from packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts rename to packages/@aws-cdk/aws-codepipeline/lib/source-action.ts index 100cde6cf333f..9edc9b1af981b 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/source-action.ts @@ -10,26 +10,26 @@ export interface SourceActionProps extends CommonActionProps { * * @default "AWS" */ - owner?: string; + readonly owner?: string; /** * The source Action version. * * @default "1" */ - version?: string; + readonly version?: string; /** * The name of the source's output artifact. * CfnOutput artifacts are used by CodePipeline as inputs into other actions. */ - outputArtifactName: string; + readonly outputArtifactName: string; /** * The service provider that the action calls. * For example, a valid provider for Source actions is "S3". */ - provider: string; + readonly provider: string; /** * The action's configuration. These are key-value pairs that specify input values for an action. @@ -37,7 +37,7 @@ export interface SourceActionProps extends CommonActionProps { * * http://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#action-requirements */ - configuration?: any; + readonly configuration?: any; } /** diff --git a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts index 25070078db5d0..b8d8f725be02d 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts @@ -1,31 +1,32 @@ -import cpapi = require('@aws-cdk/aws-codepipeline-api'); import events = require('@aws-cdk/aws-events'); import cdk = require('@aws-cdk/cdk'); +import { Action, IPipeline, IStage } from "./action"; import { CfnPipeline } from './codepipeline.generated'; import { Pipeline, StageProps } from './pipeline'; +import { validateName } from "./validation"; /** * A Stage in a Pipeline. * * Stages are added to a Pipeline by calling {@link Pipeline#addStage}, - * which returns an instance of {@link cpapi.IStage}. + * which returns an instance of {@link codepipeline.IStage}. * * This class is private to the CodePipeline module. */ -export class Stage implements cpapi.IStage { +export class Stage implements IStage { /** * The Pipeline this Stage is a part of. */ - public readonly pipeline: cpapi.IPipeline; + public readonly pipeline: IPipeline; public readonly stageName: string; private readonly scope: cdk.Construct; - private readonly _actions = new Array(); + private readonly _actions = new Array(); /** * Create a new Stage. */ constructor(props: StageProps, pipeline: Pipeline) { - cpapi.validateName('Stage', props.name); + validateName('Stage', props.name); this.stageName = props.name; this.pipeline = pipeline; @@ -39,7 +40,7 @@ export class Stage implements cpapi.IStage { /** * Get a duplicate of this stage's list of actions. */ - public get actions(): cpapi.Action[] { + public get actions(): Action[] { return this._actions.slice(); } @@ -50,7 +51,7 @@ export class Stage implements cpapi.IStage { }; } - public addAction(action: cpapi.Action): void { + public addAction(action: Action): void { // check for duplicate Actions and names if (this._actions.find(a => a.actionName === action.actionName)) { throw new Error(`Stage ${this.stageName} already contains an action with name '${action.actionName}'`); @@ -78,19 +79,17 @@ export class Stage implements cpapi.IStage { return this.validateHasActions(); } - private attachActionToPipeline(action: cpapi.Action) { - const actionParent = new cdk.Construct(this.scope, action.actionName); - (action as any)._attachActionToPipeline(this, actionParent); - - // also notify the Pipeline of the new Action - // (useful for cross-region Actions, for example) - (this.pipeline as any)._attachActionToRegion(this, action); + private attachActionToPipeline(action: Action) { + // notify the Pipeline of the new Action + const actionScope = new cdk.Construct(this.scope, action.actionName); + (this.pipeline as any)._attachActionToPipeline(this, action, actionScope); } - private renderAction(action: cpapi.Action): CfnPipeline.ActionDeclarationProperty { + private renderAction(action: Action): CfnPipeline.ActionDeclarationProperty { return { name: action.actionName, - inputArtifacts: action._inputArtifacts.map(a => ({ name: a.artifactName })), + // TODO: remove "as any" + inputArtifacts: (action as any).actionInputArtifacts.map((a: any) => ({ name: a.artifactName })), actionTypeId: { category: action.category.toString(), version: action.version, @@ -98,7 +97,8 @@ export class Stage implements cpapi.IStage { provider: action.provider, }, configuration: action.configuration, - outputArtifacts: action._outputArtifacts.map(a => ({ name: a.artifactName })), + // TODO: remove "as any" + outputArtifacts: (action as any).actionOutputArtifacts.map((a: any) => ({ name: a.artifactName })), runOrder: action.runOrder, roleArn: action.role ? action.role.roleArn : undefined }; diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/test-action.ts similarity index 89% rename from packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts rename to packages/@aws-cdk/aws-codepipeline/lib/test-action.ts index 944580ed6785a..061c8ae623a3d 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/test-action.ts @@ -8,7 +8,7 @@ export interface TestActionProps extends CommonActionProps { /** * The source to use as input for this test. */ - inputArtifact: Artifact; + readonly inputArtifact: Artifact; /** * The optional name of the output artifact. @@ -18,33 +18,33 @@ export interface TestActionProps extends CommonActionProps { * * @default the Action will not have an output artifact */ - outputArtifactName?: string; + readonly outputArtifactName?: string; /** * The service provider that the action calls. * * @example 'CodeBuild' */ - provider: string; + readonly provider: string; /** * The upper and lower bounds on the number of input and output artifacts for this Action. */ - artifactBounds: ActionArtifactBounds; + readonly artifactBounds: ActionArtifactBounds; /** * The test Action owner (could be 'AWS', 'ThirdParty' or 'Custom'). * * @default 'AWS' */ - owner?: string; + readonly owner?: string; /** * The test Action version. * * @default '1' */ - version?: string; + readonly version?: string; /** * The action's configuration. These are key-value pairs that specify input values for an action. @@ -52,7 +52,7 @@ export interface TestActionProps extends CommonActionProps { * * http://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#action-requirements */ - configuration?: any; + readonly configuration?: any; } /** diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/validation.ts b/packages/@aws-cdk/aws-codepipeline/lib/validation.ts similarity index 100% rename from packages/@aws-cdk/aws-codepipeline-api/lib/validation.ts rename to packages/@aws-cdk/aws-codepipeline/lib/validation.ts diff --git a/packages/@aws-cdk/aws-codepipeline/package.json b/packages/@aws-cdk/aws-codepipeline/package.json index 108944cac4099..359d657a6ad93 100644 --- a/packages/@aws-cdk/aws-codepipeline/package.json +++ b/packages/@aws-cdk/aws-codepipeline/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-codepipeline", - "version": "0.26.0", + "version": "0.28.0", "description": "Better interface to AWS Code Pipeline", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-codepipeline", + "module": "aws_cdk.aws_codepipeline" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-codepipeline" }, "scripts": { "build": "cdk-build", @@ -42,8 +47,8 @@ "cloudformation": "AWS::CodePipeline" }, "nyc": { - "statements": 66, - "lines": 66 + "statements": 50, + "lines": 50 }, "keywords": [ "aws", @@ -61,36 +66,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/alexa-ask": "^0.26.0", - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-cloudtrail": "^0.26.0", - "@aws-cdk/aws-codebuild": "^0.26.0", - "@aws-cdk/aws-codecommit": "^0.26.0", - "@aws-cdk/aws-codedeploy": "^0.26.0", - "@aws-cdk/aws-ecr": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -98,7 +91,9 @@ "awslint": { "exclude": [ "construct-ctor:@aws-cdk/aws-codepipeline.CrossRegionScaffoldStack..params[0]", - "construct-ctor:@aws-cdk/aws-codepipeline.CrossRegionScaffoldStack..params[1]" + "construct-ctor:@aws-cdk/aws-codepipeline.CrossRegionScaffoldStack..params[1]", + "export:@aws-cdk/aws-codepipeline.IPipeline", + "import-props-interface:@aws-cdk/aws-codepipeline.PipelineImportProps" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts index 92b400299a67d..efc26db32809b 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts @@ -1,8 +1,9 @@ -import actions = require('@aws-cdk/aws-codepipeline-api'); -import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; +import { ActionBind, IStage } from "../lib/action"; import { Pipeline } from '../lib/pipeline'; +import { SourceAction, SourceActionProps } from "../lib/source-action"; +import { validateName } from "../lib/validation"; interface NameValidationTestCase { name: string; @@ -21,7 +22,7 @@ export = { cases.forEach(testCase => { const name = testCase.name; - const validationBlock = () => { actions.validateName('test thing', name); }; + const validationBlock = () => { validateName('test thing', name); }; if (testCase.shouldPassValidation) { test.doesNotThrow(validationBlock, Error, `${name} failed validation but ${testCase.explanation}`); } else { @@ -56,16 +57,14 @@ export = { const stack = new cdk.Stack(); const pipeline = new Pipeline(stack, 'Pipeline'); - const bucket = new s3.Bucket(stack, 'PipelineBucket'); pipeline.addStage({ name: 'FirstStage', actions: [ - new s3.PipelineSourceAction({ - actionName: 'FirstAction', - outputArtifactName: 'FirstArtifact', - bucket, - bucketKey: 'key', - }) + new FakeSourceAction({ + actionName: 'FakeSource', + provider: 'Fake', + outputArtifactName: 'SourceOutput', + }), ], }); @@ -76,7 +75,17 @@ export = { } }; -function stageForTesting(): actions.IStage { +class FakeSourceAction extends SourceAction { + constructor(props: SourceActionProps) { + super(props); + } + + protected bind(_info: ActionBind): void { + // do nothing + } +} + +function stageForTesting(): IStage { const stack = new cdk.Stack(); const pipeline = new Pipeline(stack, 'Pipeline'); return pipeline.addStage({ name: 'stage' }); diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts index b01375ef17405..5b911ae6a2d25 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool-client.ts @@ -27,24 +27,24 @@ export interface UserPoolClientProps { * Name of the application client * @default cloudformation generated name */ - clientName?: string; + readonly clientName?: string; /** * The UserPool resource this client will have access to */ - userPool: IUserPool; + readonly userPool: IUserPool; /** * Whether to generate a client secret * @default false */ - generateSecret?: boolean; + readonly generateSecret?: boolean; /** * List of enabled authentication flows * @default no enabled flows */ - enabledAuthFlows?: AuthFlow[] + readonly enabledAuthFlows?: AuthFlow[] } /** diff --git a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts index fff3b0945374c..f64190ef44f8a 100644 --- a/packages/@aws-cdk/aws-cognito/lib/user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/lib/user-pool.ts @@ -150,49 +150,49 @@ export interface UserPoolTriggers { * Creates an authentication challenge. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html */ - createAuthChallenge?: lambda.IFunction; + readonly createAuthChallenge?: lambda.IFunction; /** * A custom Message AWS Lambda trigger. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-custom-message.html */ - customMessage?: lambda.IFunction; + readonly customMessage?: lambda.IFunction; /** * Defines the authentication challenge. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html */ - defineAuthChallenge?: lambda.IFunction; + readonly defineAuthChallenge?: lambda.IFunction; /** * A post-authentication AWS Lambda trigger. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-authentication.html */ - postAuthentication?: lambda.IFunction; + readonly postAuthentication?: lambda.IFunction; /** * A post-confirmation AWS Lambda trigger. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html */ - postConfirmation?: lambda.IFunction; + readonly postConfirmation?: lambda.IFunction; /** * A pre-authentication AWS Lambda trigger. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-authentication.html */ - preAuthentication?: lambda.IFunction; + readonly preAuthentication?: lambda.IFunction; /** * A pre-registration AWS Lambda trigger. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html */ - preSignUp?: lambda.IFunction; + readonly preSignUp?: lambda.IFunction; /** * Verifies the authentication challenge response. * @see https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-verify-auth-challenge-response.html */ - verifyAuthChallengeResponse?: lambda.IFunction; + readonly verifyAuthChallengeResponse?: lambda.IFunction; /** * Index signature @@ -205,55 +205,55 @@ export interface UserPoolProps { * Name of the user pool * @default unique ID */ - poolName?: string; + readonly poolName?: string; /** * Method used for user registration & sign in. * Allows either username with aliases OR sign in with email, phone, or both. * @default SignInType.USERNAME */ - signInType?: SignInType; + readonly signInType?: SignInType; /** * Attributes to allow as username alias. * Only valid if signInType is USERNAME * @default no alias */ - usernameAliasAttributes?: UserPoolAttribute[]; + readonly usernameAliasAttributes?: UserPoolAttribute[]; /** * Attributes which Cognito will automatically send a verification message to. * Must be either EMAIL, PHONE, or both. * @default no auto verification */ - autoVerifiedAttributes?: UserPoolAttribute[]; + readonly autoVerifiedAttributes?: UserPoolAttribute[]; /** * Lambda functions to use for supported Cognito triggers. */ - lambdaTriggers?: UserPoolTriggers; + readonly lambdaTriggers?: UserPoolTriggers; } export interface UserPoolImportProps { /** * The ID of an existing user pool */ - userPoolId: string; + readonly userPoolId: string; /** * The ARN of the imported user pool */ - userPoolArn: string; + readonly userPoolArn: string; /** * The provider name of the imported user pool */ - userPoolProviderName: string; + readonly userPoolProviderName: string; /** * The URL of the imported user pool */ - userPoolProviderUrl: string; + readonly userPoolProviderUrl: string; } export interface IUserPool extends cdk.IConstruct { @@ -395,7 +395,7 @@ export class UserPool extends cdk.Construct implements IUserPool { */ public onCreateAuthChallenge(fn: lambda.IFunction): void { this.addLambdaPermission(fn, 'CreateAuthChallenge'); - this.triggers.createAuthChallenge = fn.functionArn; + this.triggers = { ...this.triggers, createAuthChallenge: fn.functionArn }; } /** @@ -406,7 +406,7 @@ export class UserPool extends cdk.Construct implements IUserPool { */ public onCustomMessage(fn: lambda.IFunction): void { this.addLambdaPermission(fn, 'CustomMessage'); - this.triggers.customMessage = fn.functionArn; + this.triggers = { ...this.triggers, customMessage: fn.functionArn }; } /** @@ -417,7 +417,7 @@ export class UserPool extends cdk.Construct implements IUserPool { */ public onDefineAuthChallenge(fn: lambda.IFunction): void { this.addLambdaPermission(fn, 'DefineAuthChallenge'); - this.triggers.defineAuthChallenge = fn.functionArn; + this.triggers = { ...this.triggers, defineAuthChallenge: fn.functionArn }; } /** @@ -428,7 +428,7 @@ export class UserPool extends cdk.Construct implements IUserPool { */ public onPostAuthentication(fn: lambda.IFunction): void { this.addLambdaPermission(fn, 'PostAuthentication'); - this.triggers.postAuthentication = fn.functionArn; + this.triggers = { ...this.triggers, postAuthentication: fn.functionArn }; } /** @@ -439,7 +439,7 @@ export class UserPool extends cdk.Construct implements IUserPool { */ public onPostConfirmation(fn: lambda.IFunction): void { this.addLambdaPermission(fn, 'PostConfirmation'); - this.triggers.postConfirmation = fn.functionArn; + this.triggers = { ...this.triggers, postConfirmation: fn.functionArn }; } /** @@ -450,7 +450,7 @@ export class UserPool extends cdk.Construct implements IUserPool { */ public onPreAuthentication(fn: lambda.IFunction): void { this.addLambdaPermission(fn, 'PreAuthentication'); - this.triggers.preAuthentication = fn.functionArn; + this.triggers = { ...this.triggers, preAuthentication: fn.functionArn }; } /** @@ -461,7 +461,7 @@ export class UserPool extends cdk.Construct implements IUserPool { */ public onPreSignUp(fn: lambda.IFunction): void { this.addLambdaPermission(fn, 'PreSignUp'); - this.triggers.preSignUp = fn.functionArn; + this.triggers = { ...this.triggers, preSignUp: fn.functionArn }; } /** @@ -472,7 +472,7 @@ export class UserPool extends cdk.Construct implements IUserPool { */ public onVerifyAuthChallengeResponse(fn: lambda.IFunction): void { this.addLambdaPermission(fn, 'VerifyAuthChallengeResponse'); - this.triggers.verifyAuthChallengeResponse = fn.functionArn; + this.triggers = { ...this.triggers, verifyAuthChallengeResponse: fn.functionArn }; } public export(): UserPoolImportProps { diff --git a/packages/@aws-cdk/aws-cognito/package.json b/packages/@aws-cdk/aws-cognito/package.json index 9527347909cd7..41521a638050a 100644 --- a/packages/@aws-cdk/aws-cognito/package.json +++ b/packages/@aws-cdk/aws-cognito/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-cognito", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Cognito", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-cognito", + "module": "aws_cdk.aws_cognito" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-cognito" }, "scripts": { "build": "cdk-build", @@ -54,22 +59,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts b/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts index bfae0eb1513e1..11c92b4669180 100644 --- a/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts +++ b/packages/@aws-cdk/aws-cognito/test/test.user-pool.ts @@ -105,7 +105,7 @@ export = { // THEN expect(stack).to(haveResourceLike('AWS::Lambda::Permission', { - FunctionName: fn.node.resolve(fn.functionName), + FunctionName: fn.node.resolve(fn.functionArn), Principal: 'cognito-idp.amazonaws.com' })); diff --git a/packages/@aws-cdk/aws-config/package.json b/packages/@aws-cdk/aws-config/package.json index 4919f6f556f69..c17f153c9c788 100644 --- a/packages/@aws-cdk/aws-config/package.json +++ b/packages/@aws-cdk/aws-config/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-config", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Config", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-config", + "module": "aws_cdk.aws_config" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-config" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-datapipeline/package.json b/packages/@aws-cdk/aws-datapipeline/package.json index 2588577c286f5..016f267fdb37c 100644 --- a/packages/@aws-cdk/aws-datapipeline/package.json +++ b/packages/@aws-cdk/aws-datapipeline/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-datapipeline", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::DataPipeline", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-datapipeline", + "module": "aws_cdk.aws_datapipeline" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-datapipeline" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-dax/package.json b/packages/@aws-cdk/aws-dax/package.json index ee090a7a8a1fe..b26b6bf5f9bad 100644 --- a/packages/@aws-cdk/aws-dax/package.json +++ b/packages/@aws-cdk/aws-dax/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-dax", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::DAX", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-dax", + "module": "aws_cdk.aws_dax" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-dax" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-directoryservice/package.json b/packages/@aws-cdk/aws-directoryservice/package.json index be276a8a98cb3..08090cd3bfb02 100644 --- a/packages/@aws-cdk/aws-directoryservice/package.json +++ b/packages/@aws-cdk/aws-directoryservice/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-directoryservice", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::DirectoryService", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-directoryservice", + "module": "aws_cdk.aws_directoryservice" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-directoryservice" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-dlm/package.json b/packages/@aws-cdk/aws-dlm/package.json index 76ebe5e08d971..eea4905c128aa 100644 --- a/packages/@aws-cdk/aws-dlm/package.json +++ b/packages/@aws-cdk/aws-dlm/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-dlm", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::DLM", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "dlm" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-dlm", + "module": "aws_cdk.aws_dlm" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-dlm" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -55,18 +60,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-dms/package.json b/packages/@aws-cdk/aws-dms/package.json index 68cbe65cdbfe8..4f7d93ea784ba 100644 --- a/packages/@aws-cdk/aws-dms/package.json +++ b/packages/@aws-cdk/aws-dms/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-dms", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::DMS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-dms", + "module": "aws_cdk.aws_dms" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-dms" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-docdb/package.json b/packages/@aws-cdk/aws-docdb/package.json index 5921071fc8440..ded5a66b2148c 100644 --- a/packages/@aws-cdk/aws-docdb/package.json +++ b/packages/@aws-cdk/aws-docdb/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-docdb", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::DocDB", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "docdb" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-docdb", + "module": "aws_cdk.aws_docdb" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-docdb" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -56,18 +61,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-dynamodb-global/.gitignore b/packages/@aws-cdk/aws-dynamodb-global/.gitignore new file mode 100644 index 0000000000000..c76cbf2d9c4c6 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/.gitignore @@ -0,0 +1,19 @@ +*.js +*.js.map +*.d.ts +node_modules +*.generated.ts +dist +tsconfig.json +tslint.json + +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE + +*.snk +!test/test.lambda.handler.js diff --git a/packages/@aws-cdk/aws-dynamodb-global/.npmignore b/packages/@aws-cdk/aws-dynamodb-global/.npmignore new file mode 100644 index 0000000000000..4d2809ffd24a7 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/.npmignore @@ -0,0 +1,17 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk +lambda-packages/aws-dynamodb-global-lambda/node_modules diff --git a/packages/@aws-cdk/aws-dynamodb-global/LICENSE b/packages/@aws-cdk/aws-dynamodb-global/LICENSE new file mode 100644 index 0000000000000..46c185646b439 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/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-2019 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-dynamodb-global/NOTICE b/packages/@aws-cdk/aws-dynamodb-global/NOTICE new file mode 100644 index 0000000000000..8585168af8b7d --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-dynamodb-global/README.md b/packages/@aws-cdk/aws-dynamodb-global/README.md new file mode 100644 index 0000000000000..3190adaf2ecf1 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/README.md @@ -0,0 +1,29 @@ +## @aws-cdk/aws-dynamodb-global + +Global Tables builds upon DynamoDB’s global footprint to provide you with a fully managed, multi-region, and multi-master database that provides fast, local, read and write performance for massively scaled, global applications. Global Tables replicates your Amazon DynamoDB tables automatically across your choice of AWS regions. + +Here is a minimal deployable Global DynamoDB tables definition: + +```typescript +import { AttributeType } from '@aws-cdk/aws-dynamodb'; +import { GlobalTable } from '@aws-cdk/aws-dynamodb-global'; +import { App } from '@aws-cdk/cdk'; + +const app = new App(); +new GlobalTable(app, 'globdynamodb', { + partitionKey: { name: 'hashKey', type: AttributeType.String }, + tableName: 'GlobalTable', + regions: [ "us-east-1", "us-east-2", "us-west-2" ] +}); +app.run(); +``` + +## Implementation Notes +AWS Global DynamoDB Tables is an odd case currently. The way this package works - + +* Creates a DynamoDB table in a separate stack in each `DynamoDBGlobalStackProps.region` specified +* Deploys a CFN Custom Resource to your stack's specified region that calls a lambda that runs the aws cli which calls `createGlobalTable()` + +### Notes + +GlobalTable() will set `dynamoProps.streamSpecification` to be `NEW_AND_OLD_IMAGES` since this is a required attribute for AWS Global DynamoDB tables to work. The package will throw an error if any other `streamSpecification` is set in `DynamoDBGlobalStackProps`. diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/.no-packagejson-validator b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/.no-packagejson-validator new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/.gitignore b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/.gitignore new file mode 100644 index 0000000000000..b50a1c9030545 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/.gitignore @@ -0,0 +1,7 @@ +node_modules/ + +dist +.LAST_PACKAGE +.LAST_BUILD +*.snk +!*.js \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/LICENSE b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/LICENSE new file mode 100644 index 0000000000000..46c185646b439 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/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-2019 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-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/NOTICE b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/NOTICE new file mode 100644 index 0000000000000..8585168af8b7d --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/README.md b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/README.md new file mode 100644 index 0000000000000..9ad633918d8c8 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/README.md @@ -0,0 +1,2 @@ +## CloudFormation Custom Resource for linking DynamoDB Global Tables +This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/jest.config.js b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/jest.config.js new file mode 100644 index 0000000000000..a9acbf03c2b7a --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/jest.config.js @@ -0,0 +1,19 @@ +module.exports = { + "roots": [ + "/lib", + "/test" + ], + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testRegex": "(/test/.*|(\\.|/)(test|spec))\\.(ts|js)x?$", + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "testEnvironment": "node" +} diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/lib/index.js b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/lib/index.js new file mode 100644 index 0000000000000..27732a5f5e9ac --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/lib/index.js @@ -0,0 +1,164 @@ +const AWS = require('aws-sdk'); + +// These are used for test purposes only +let defaultResponseURL; + +/** + * Upload a CloudFormation response for a Custom Resource + * + * @param {object} event the Lambda event payload received by the handler function + * @param {object} context the Lambda context received by the handler function + * @param {string} responseStatus the response status, either 'SUCCESS' or 'FAILED' + * @param {string} physicalResourceId CloudFormation physical resource ID + * @param {object} [responseData] arbitrary response data object + * @param {string} [reason] reason for failure, if any, to convey to the user + * @returns {Promise} Promise that is resolved on success, or rejected on connection error or HTTP error response + */ +let report = function (event, context, responseStatus, physicalResourceId, responseData, reason) { + return new Promise((resolve, reject) => { + const https = require('https'); + const { URL } = require('url'); + + var responseBody = JSON.stringify({ + Status: responseStatus, + Reason: reason, + PhysicalResourceId: physicalResourceId || context.logStreamName, + StackId: event.StackId, + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + Data: responseData + }); + + const parsedUrl = new URL(event.ResponseURL || defaultResponseURL); + const options = { + hostname: parsedUrl.hostname, + port: 443, + path: parsedUrl.pathname + parsedUrl.search, + method: 'PUT', + headers: { + 'Content-Type': '', + 'Content-Length': responseBody.length + } + }; + + https.request(options) + .on('error', reject) + .on('response', res => { + res.resume(); + if (res.statusCode >= 400) { + reject(new Error(`Server returned error ${res.statusCode}: ${res.statusMessage}`)); + } else { + resolve(); + } + }) + .end(responseBody, 'utf8'); + }); +}; +/** + * Lambda to handle linking together DynamoDB tables into a global table. + */ +exports.handler = async function (event, context) { + console.log("REQUEST RECEIVED:\n" + JSON.stringify(event)); + var responseData = {}; + var physicalResourceId; + + try { + switch (event.RequestType) { + case 'Create': + console.log("CREATE!"); + var dynamodb = new AWS.DynamoDB(); + var regions = []; + for (var i = 0; i < event.ResourceProperties.Regions.length; i++) { + regions.push({ + "RegionName": event.ResourceProperties.Regions[i] + }); + } + var params = { + GlobalTableName: event["ResourceProperties"]["TableName"], + ReplicationGroup: regions + }; + console.log(params); + await dynamodb.createGlobalTable(params).promise(); + await report(event, context, 'SUCCESS', physicalResourceId, responseData); + break; + case 'Update': + console.log("UPDATE!"); + var dynamodb = new AWS.DynamoDB(); + var params = { + GlobalTableName: event["ResourceProperties"]["TableName"] + }; + // Get the current regions deployed for this table + let data = await dynamodb.describeGlobalTable(params).promise(); + // Flatten the data + var currentDynamoRegions = []; + for (let region of data["GlobalTableDescription"]["ReplicationGroup"]) { + currentDynamoRegions.push(region["RegionName"]); + } + + // Build the params for regions to build + var addRegions = []; + for (var i = 0; i < event.ResourceProperties.Regions.length; i++) { + if(currentDynamoRegions.indexOf(event.ResourceProperties.Regions[i]) < 0 ){ + addRegions.push({ + Create: { + RegionName: String(event.ResourceProperties.Regions[i]) + } + }); + } + } + // Call AWS sdk to update DynamoDB to add the regions + console.log("Add Regions: "+ JSON.stringify(addRegions)); + if(addRegions.length > 0){ + var params = { + GlobalTableName: event["ResourceProperties"]["TableName"], + ReplicaUpdates: addRegions + }; + console.log(params); + await dynamodb.updateGlobalTable(params).promise(); + } + + // Need to add/remove regions in separate steps + // Build the params for removing regions + var removeRegions = []; + for (var i = 0; i < currentDynamoRegions.length; i++) { + if(event.ResourceProperties.Regions.indexOf(currentDynamoRegions[i]) < 0 ){ + removeRegions.push({ + Delete: { + RegionName: String(currentDynamoRegions[i]) + } + }); + } + } + // Call AWS sdk to update DynamoDB to remove the regions + console.log("Remove Regions: "+ JSON.stringify(removeRegions)); + if(removeRegions.length > 0){ + var params = { + GlobalTableName: event["ResourceProperties"]["TableName"], + ReplicaUpdates: removeRegions + }; + console.log(params); + await dynamodb.updateGlobalTable(params).promise(); + } + await report(event, context, 'SUCCESS', physicalResourceId, responseData); + break; + case 'Delete': + console.log("DELETE!"); + await report(event, context, 'SUCCESS', physicalResourceId, responseData); + break; + default: + throw new Error(`Unsupported request type ${event.RequestType}`); + } + + console.log(`Done!`); + } catch (err) { + console.log(`Caught error ${err}.`); + await report(event, context, 'FAILED', physicalResourceId, null, err.message); + } +}; + +/** + * @private + */ +exports.withDefaultResponseURL = function (url) { + defaultResponseURL = url; +}; diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/package-lock.json b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/package-lock.json new file mode 100644 index 0000000000000..22d07be2586a4 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/package-lock.json @@ -0,0 +1,5542 @@ +{ + "name": "dns_validated_certificate_handler", + "version": "0.27.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.3.4.tgz", + "integrity": "sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.3.4", + "@babel/helpers": "^7.2.0", + "@babel/parser": "^7.3.4", + "@babel/template": "^7.2.2", + "@babel/traverse": "^7.3.4", + "@babel/types": "^7.3.4", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.4.tgz", + "integrity": "sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==", + "dev": true, + "requires": { + "@babel/types": "^7.3.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-split-export-declaration": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", + "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helpers": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", + "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", + "dev": true, + "requires": { + "@babel/template": "^7.1.2", + "@babel/traverse": "^7.1.5", + "@babel/types": "^7.3.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", + "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==", + "dev": true + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/template": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", + "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.2.2", + "@babel/types": "^7.2.2" + } + }, + "@babel/traverse": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", + "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.3.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.0.0", + "@babel/parser": "^7.3.4", + "@babel/types": "^7.3.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", + "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "@cnakazawa/watch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", + "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@jest/console": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.3.0.tgz", + "integrity": "sha512-NaCty/OOei6rSDcbPdMiCbYCI0KGFGPgGO6B09lwWt5QTxnkuhKYET9El5u5z1GAcSxkQmSMtM63e24YabCWqA==", + "dev": true, + "requires": { + "@jest/source-map": "^24.3.0", + "@types/node": "*", + "chalk": "^2.0.1", + "slash": "^2.0.0" + } + }, + "@jest/core": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.4.0.tgz", + "integrity": "sha512-S48krBwigVm3DwLSEtMiiWnWz+G3uGii192LIZYbWULYSOCwQeG7hWb6a3yWBLYuZnATh3W6QMxs7whS0/hQMQ==", + "dev": true, + "requires": { + "@jest/console": "^24.3.0", + "@jest/reporters": "^24.4.0", + "@jest/test-result": "^24.3.0", + "@jest/transform": "^24.4.0", + "@jest/types": "^24.3.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-changed-files": "^24.3.0", + "jest-config": "^24.4.0", + "jest-haste-map": "^24.4.0", + "jest-message-util": "^24.3.0", + "jest-regex-util": "^24.3.0", + "jest-resolve-dependencies": "^24.4.0", + "jest-runner": "^24.4.0", + "jest-runtime": "^24.4.0", + "jest-snapshot": "^24.4.0", + "jest-util": "^24.3.0", + "jest-validate": "^24.4.0", + "jest-watcher": "^24.3.0", + "micromatch": "^3.1.10", + "p-each-series": "^1.0.0", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "rimraf": "^2.5.4", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz", + "integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "@jest/environment": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.4.0.tgz", + "integrity": "sha512-YuPsWWwTS4wkMsvCNXvBZPZQGOVtsVyle9OzHIAdWvV+B9qjs0vA85Il1+FSG0b765VqznPvpfIe1wKoIFOleQ==", + "dev": true, + "requires": { + "@jest/fake-timers": "^24.3.0", + "@jest/transform": "^24.4.0", + "@jest/types": "^24.3.0", + "@types/node": "*", + "jest-mock": "^24.3.0" + } + }, + "@jest/fake-timers": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.3.0.tgz", + "integrity": "sha512-rHwVI17dGMHxHzfAhnZ04+wFznjFfZ246QugeBnbiYr7/bDosPD2P1qeNjWnJUUcfl0HpS6kkr+OB/mqSJxQFg==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0", + "@types/node": "*", + "jest-message-util": "^24.3.0", + "jest-mock": "^24.3.0" + } + }, + "@jest/reporters": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.4.0.tgz", + "integrity": "sha512-teO0to16UaYJTLWXCWCa1kBPx/PY4dw2/8I2LPIzk5mNN5km8jyx5jz8D1Yy0nqascVtbpG4+VnSt7E16cnrcw==", + "dev": true, + "requires": { + "@jest/environment": "^24.4.0", + "@jest/test-result": "^24.3.0", + "@jest/transform": "^24.4.0", + "@jest/types": "^24.3.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-api": "^2.1.1", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-source-maps": "^3.0.1", + "jest-haste-map": "^24.4.0", + "jest-resolve": "^24.4.0", + "jest-runtime": "^24.4.0", + "jest-util": "^24.3.0", + "jest-worker": "^24.4.0", + "node-notifier": "^5.2.1", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + } + }, + "@jest/source-map": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz", + "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.3.0.tgz", + "integrity": "sha512-j7UZ49T8C4CVipEY99nLttnczVTtLyVzFfN20OiBVn7awOs0U3endXSTq7ouPrLR5y4YjI5GDcbcvDUjgeamzg==", + "dev": true, + "requires": { + "@jest/console": "^24.3.0", + "@jest/types": "^24.3.0", + "@types/istanbul-lib-coverage": "^1.1.0" + } + }, + "@jest/transform": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.4.0.tgz", + "integrity": "sha512-Y928pU6bqWqMlGugRiaWOresox/CIrRuBVdPnYiSoIcRtwNKZujCOkzIzRalcTTxm77wuLjNihcq8OWfdm+Dxg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.3.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.4.0", + "jest-regex-util": "^24.3.0", + "jest-util": "^24.3.0", + "micromatch": "^3.1.10", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + } + }, + "@jest/types": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.3.0.tgz", + "integrity": "sha512-VoO1F5tU2n/93QN/zaZ7Q8SeV/Rj+9JJOgbvKbBwy4lenvmdj1iDaQEPXGTKrO6OSvDeb2drTFipZJYxgo6kIQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^1.1.0", + "@types/yargs": "^12.0.9" + } + }, + "@sinonjs/commons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.3.0.tgz", + "integrity": "sha512-j4ZwhaHmwsCb4DlDOIWnI5YyKDNMoNThsmwEpfHx6a1EpsGZ9qYLxP++LMlmBRjtGptGHFsGItJ768snllFWpA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.1.0.tgz", + "integrity": "sha512-ZAR2bPHOl4Xg6eklUGpsdiIJ4+J1SNag1DHHrG/73Uz/nVwXqjgUtRPLoS+aVyieN9cSbc0E4LsU984tWcDyNg==", + "dev": true, + "requires": { + "@sinonjs/samsam": "^2 || ^3" + } + }, + "@sinonjs/samsam": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.2.0.tgz", + "integrity": "sha512-j5F1rScewLtx6pbTK0UAjA3jJj4RYiSKOix53YWv+Jzy/AZ69qHxUpU8fwVLjyKbEEud9QrLpv6Ggs7WqTimYw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@types/babel__core": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.0.tgz", + "integrity": "sha512-wJTeJRt7BToFx3USrCDs2BhEi4ijBInTQjOIukj6a/5tEkwpFMVZ+1ppgmE+Q/FQyc5P/VWUbx7I9NELrKruHA==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz", + "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.6.tgz", + "integrity": "sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz", + "integrity": "sha512-ohkhb9LehJy+PA40rDtGAji61NCgdtKLAlFoYp4cnuuQEswwdK3vz9SOIkkyc3wrk8dzjphQApNs56yyXLStaQ==", + "dev": true + }, + "@types/node": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.1.tgz", + "integrity": "sha512-2azXFP9n4aA2QNLkKm/F9pzKxgYj1SMawZ5Eh9iC21RH3XNcFsivLVU2NhpMgQm7YobSByvIol4c42ZFusXFHQ==", + "dev": true + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "@types/yargs": { + "version": "12.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.9.tgz", + "integrity": "sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==", + "dev": true + }, + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + }, + "acorn-globals": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", + "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + } + }, + "acorn-jsx": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "app-root-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.1.0.tgz", + "integrity": "sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo=", + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sdk": { + "version": "2.419.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.419.0.tgz", + "integrity": "sha512-v071NTHwsgYmrnJBAboOjhcTVqB99GAQuqVaaeLBBt33/AebawxoJUkuJrezIni+inqnKOi9F6pi87g48iR1+w==", + "dev": true, + "requires": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.8", + "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-mock": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/aws-sdk-mock/-/aws-sdk-mock-4.3.1.tgz", + "integrity": "sha512-uOaf7/Tq9kSoRc2/EQfAn24AAwU6UwvR8xSFSg0vTRxK0xHHEZ5UB/KF6ibF2gj0I4977lM35237E5sbzhRxKA==", + "dev": true, + "requires": { + "aws-sdk": "^2.369.0", + "sinon": "^7.1.1", + "traverse": "^0.6.6" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-jest": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.4.0.tgz", + "integrity": "sha512-wh23nKbWZf9SeO0GNOQc2QDqaMXOmbaI2Hvbcl6FGqg9zqHwr9Jy0e0ZqsXiJ2Cv8YKqD+eOE2wAGVhq4nzWDQ==", + "dev": true, + "requires": { + "@jest/transform": "^24.4.0", + "@jest/types": "^24.3.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.3.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz", + "integrity": "sha512-RNNVv2lsHAXJQsEJ5jonQwrJVWK8AcZpG1oxhnjCUaAjL7xahYLANhPUZbzEQHjKy1NMYUwn+0NPKQc8iSY4xQ==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.0.0", + "test-exclude": "^5.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.3.0.tgz", + "integrity": "sha512-nWh4N1mVH55Tzhx2isvUN5ebM5CDUvIpXPZYMRazQughie/EqGnbR+czzoQlhUmJG9pPJmYDRhvocotb2THl1w==", + "dev": true, + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.3.0.tgz", + "integrity": "sha512-VGTV2QYBa/Kn3WCOKdfS31j9qomaXSgJqi65B6o05/1GsJyj9LVhSljM9ro4S+IBGj/ENhNBuH9bpqzztKAQSw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.3.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsites": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.0.0.tgz", + "integrity": "sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==", + "dev": true + }, + "camelcase": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.2.0.tgz", + "integrity": "sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ==", + "dev": true + }, + "capture-exit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", + "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", + "dev": true, + "requires": { + "rsvp": "^3.3.3" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + }, + "compare-versions": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", + "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", + "dev": true + }, + "cssstyle": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.1.tgz", + "integrity": "sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diff-sequences": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz", + "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "dotenv": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", + "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", + "dev": true + }, + "dotenv-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dotenv-json/-/dotenv-json-1.0.0.tgz", + "integrity": "sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, + "eslint": { + "version": "5.15.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.15.1.tgz", + "integrity": "sha512-NTcm6vQ+PTgN3UBsALw5BMhgO6i5EpIjQF/Xb5tIh3sk9QhrFafujUOczGz4J24JBlzWclSB9Vmx8d+9Z6bFCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.2", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + } + }, + "eslint-config-standard": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz", + "integrity": "sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-module-utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.3.0.tgz", + "integrity": "sha512-lmDJgeOOjk8hObTysjqH7wyMi+nsHwwvfBykwfhjR1LNdd7C2uFJBvx4OpWYpXOw4df1yE1cDEVd1yLHitk34w==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + } + } + }, + "eslint-plugin-es": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz", + "integrity": "sha512-XfFmgFdIUDgvaRAlaXUkxrRg5JSADoRC8IkKLc/cISeR3yHVMefFHQZpcyXXEUUPHfy5DwviBcrfqlyqEwlQVw==", + "dev": true, + "requires": { + "eslint-utils": "^1.3.0", + "regexpp": "^2.0.1" + } + }, + "eslint-plugin-import": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.16.0.tgz", + "integrity": "sha512-z6oqWlf1x5GkHIFgrSvtmudnqM6Q60KM4KvpWi5ubonMjycLjndvd5+8VAZIsTlHC03djdgJuyKG6XO577px6A==", + "dev": true, + "requires": { + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.3.0", + "has": "^1.0.3", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "read-pkg-up": "^2.0.0", + "resolve": "^1.9.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + } + } + }, + "eslint-plugin-node": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz", + "integrity": "sha512-ZjOjbjEi6jd82rIpFSgagv4CHWzG9xsQAVp1ZPlhRnnYxcTgENUVBvhYmkQ7GvT1QFijUSo69RaiOJKhMu6i8w==", + "dev": true, + "requires": { + "eslint-plugin-es": "^1.3.1", + "eslint-utils": "^1.3.1", + "ignore": "^5.0.2", + "minimatch": "^3.0.4", + "resolve": "^1.8.1", + "semver": "^5.5.0" + }, + "dependencies": { + "ignore": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.0.5.tgz", + "integrity": "sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA==", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz", + "integrity": "sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg==", + "dev": true + }, + "eslint-plugin-standard": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz", + "integrity": "sha512-OwxJkR6TQiYMmt1EsNRMe5qG3GsbjlcOhbGUBY4LtavF9DsLaTcoR+j2Tdjqi23oUwKNUqX7qcn5fPStafMdlA==", + "dev": true + }, + "eslint-scope": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.2.tgz", + "integrity": "sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "exec-sh": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", + "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "expect": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.4.0.tgz", + "integrity": "sha512-p3QGkNhxN4WXih12lOx4vuhJpl/ZFD1AWu9lWh8IXNZD10ySSOzDN4Io8zuEOWvzylFkDpU9oQ/KRTZ/Bs9/ag==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.3.0", + "jest-matcher-utils": "^24.4.0", + "jest-message-util": "^24.3.0", + "jest-regex-util": "^24.3.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "dev": true, + "requires": { + "bser": "^2.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "handlebars": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", + "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.0.0.tgz", + "integrity": "sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.2.tgz", + "integrity": "sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.11", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz", + "integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-generator-fn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.0.0.tgz", + "integrity": "sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.1.tgz", + "integrity": "sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==", + "dev": true, + "requires": { + "async": "^2.6.1", + "compare-versions": "^3.2.1", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.3", + "istanbul-lib-hook": "^2.0.3", + "istanbul-lib-instrument": "^3.1.0", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.2", + "istanbul-reports": "^2.1.1", + "js-yaml": "^3.12.0", + "make-dir": "^1.3.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz", + "integrity": "sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz", + "integrity": "sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz", + "integrity": "sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.1.1.tgz", + "integrity": "sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==", + "dev": true, + "requires": { + "handlebars": "^4.1.0" + } + }, + "jest": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.4.0.tgz", + "integrity": "sha512-gAGfjvu8hHN0N6/aDyCBpncWWBcpY6wq69Msq/I6Xd763q/ZYBEMh0SKUomrViFoJ/dyistA6b4aJh8e+5QMyw==", + "dev": true, + "requires": { + "import-local": "^2.0.0", + "jest-cli": "^24.4.0" + }, + "dependencies": { + "jest-cli": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.4.0.tgz", + "integrity": "sha512-QQOgpRpXoDqpxhEux/AGyI9XJzVOJ5ppz4Kb9MlA5PvzsyYD3DRk/uiyJgmvBhCCXvcA1CKEl/g/LH0kbKg10Q==", + "dev": true, + "requires": { + "@jest/core": "^24.4.0", + "@jest/test-result": "^24.3.0", + "@jest/types": "^24.3.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "jest-config": "^24.4.0", + "jest-util": "^24.3.0", + "jest-validate": "^24.4.0", + "prompts": "^2.0.1", + "realpath-native": "^1.1.0", + "yargs": "^12.0.2" + } + } + } + }, + "jest-changed-files": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.3.0.tgz", + "integrity": "sha512-fTq0YAUR6644fgsqLC7Zi2gXA/bAplMRvfXQdutmkwgrCKK6upkj+sgXqsUfUZRm15CVr3YSojr/GRNn71IMvg==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0", + "execa": "^1.0.0", + "throat": "^4.0.0" + } + }, + "jest-config": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.4.0.tgz", + "integrity": "sha512-H2R6qkfUPck+OlIWsjeShecbqYiEDUvzZfsfgQkx6LVakAORy7wZFptONVF+Qz7iO9Bl6x35cBA2A1o1W+ctDg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.3.0", + "babel-jest": "^24.4.0", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.4.0", + "jest-environment-node": "^24.4.0", + "jest-get-type": "^24.3.0", + "jest-jasmine2": "^24.4.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.4.0", + "jest-util": "^24.3.0", + "jest-validate": "^24.4.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.4.0", + "realpath-native": "^1.1.0" + } + }, + "jest-diff": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.4.0.tgz", + "integrity": "sha512-2GdKN8GOledWkMGXcRCSr3KVTrjZU6vxbfZzwzRlM7gSG8HNIx+eoFXauQNQ5j7q73fZCoPnyS5/uOcXQ3wkWg==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.3.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.4.0" + } + }, + "jest-docblock": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz", + "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==", + "dev": true, + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.4.0.tgz", + "integrity": "sha512-W98N4Ep6BBdCanynA9jdJDUaPvZ9OAnIHNA8mK6kbH7JYdnNQKGvp5ivl/PjCTqiI2wnHKYRI06EjsfOqT8ZFQ==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.3.0", + "jest-util": "^24.3.0", + "pretty-format": "^24.4.0" + } + }, + "jest-environment-jsdom": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.4.0.tgz", + "integrity": "sha512-7irZXPZLQF79r97uH9dG9mm76H+27CMSH8TEcF70x6pY4xFJipjjluiXRw1C2lh0o6FrbSQKpkSXncdOw+hY0A==", + "dev": true, + "requires": { + "@jest/environment": "^24.4.0", + "@jest/fake-timers": "^24.3.0", + "@jest/types": "^24.3.0", + "jest-mock": "^24.3.0", + "jest-util": "^24.3.0", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.4.0.tgz", + "integrity": "sha512-ed1TjncsHO+Ird4BDrWwqsMQQM+bg9AFHj0AcCumgzfc+Us6ywWUQUg+5UbKLKnu1EWp5mK7mmbLxLqdz2kc9w==", + "dev": true, + "requires": { + "@jest/environment": "^24.4.0", + "@jest/fake-timers": "^24.3.0", + "@jest/types": "^24.3.0", + "jest-mock": "^24.3.0", + "jest-util": "^24.3.0" + } + }, + "jest-get-type": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", + "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", + "dev": true + }, + "jest-haste-map": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.4.0.tgz", + "integrity": "sha512-X20xhhPBjbz4UVTN9BMBjlFUM/gmi1TmYWWxZUgLg4fZXMIve4RUdA/nS/QgC76ouGgvwb9z52KwZ85bmNx55A==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.4.0", + "jest-util": "^24.3.0", + "jest-worker": "^24.4.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3" + } + }, + "jest-jasmine2": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.4.0.tgz", + "integrity": "sha512-J9A0SKWuUNDmXKU+a3Yj69NmUXK7R3btHHu1ZMpjHKlMoHggVjdzsolpNHELCENBOTXvcLXqEH0Xm+pYRoNfMw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.4.0", + "@jest/test-result": "^24.3.0", + "@jest/types": "^24.3.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.4.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.4.0", + "jest-matcher-utils": "^24.4.0", + "jest-message-util": "^24.3.0", + "jest-runtime": "^24.4.0", + "jest-snapshot": "^24.4.0", + "jest-util": "^24.3.0", + "pretty-format": "^24.4.0", + "throat": "^4.0.0" + } + }, + "jest-leak-detector": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.4.0.tgz", + "integrity": "sha512-PAo0y19ZkWZWYmdoPAQKpYTDt7IGwrTFhIwGmHO1xkRjzAWW8zcCoiMLrFwNSi9rir2ZH7el8gXZ0d2mmU7O9Q==", + "dev": true, + "requires": { + "pretty-format": "^24.4.0" + } + }, + "jest-matcher-utils": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.4.0.tgz", + "integrity": "sha512-JDWrJ1G+GfxtEQlX7DlCV/0sk0uYbnra0jVl3DiDbS0FUX0HeGA1CxRW/U87LB3XNHQydhBKbXgf+pDCiUCn4w==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.4.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.4.0" + } + }, + "jest-message-util": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.3.0.tgz", + "integrity": "sha512-lXM0YgKYGqN5/eH1NGw4Ix+Pk2I9Y77beyRas7xM24n+XTTK3TbT0VkT3L/qiyS7WkW0YwyxoXnnAaGw4hsEDA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.3.0", + "@jest/types": "^24.3.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.3.0.tgz", + "integrity": "sha512-AhAo0qjbVWWGvcbW5nChFjR0ObQImvGtU6DodprNziDOt+pP0CBdht/sYcNIOXeim8083QUi9bC8QdKB8PTK4Q==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "dev": true + }, + "jest-regex-util": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", + "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", + "dev": true + }, + "jest-resolve": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.4.0.tgz", + "integrity": "sha512-XvMIuDH6wQi76YJfNG40iolXP2l+fA+LLORGgNSZ5VgowCeyV/XVygTN4L3No3GP1cthUdl/ULzWBd2CfYmTkw==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0", + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "jest-pnp-resolver": "^1.2.1", + "realpath-native": "^1.1.0" + } + }, + "jest-resolve-dependencies": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.4.0.tgz", + "integrity": "sha512-3ssDSve3iSsIKm5daivq1mrCaBVFAa+TMG4qardNPoi7IJfupDUETIBCXYF9GRtIfNuD/dJOSag4u6oMHRxTGg==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0", + "jest-regex-util": "^24.3.0", + "jest-snapshot": "^24.4.0" + } + }, + "jest-runner": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.4.0.tgz", + "integrity": "sha512-eCuEMDbJknyKEUBWBDebW3GQ6Ty8wwB3YqDjFb4p3UQozA2HarPq0n9N83viq18vvZ/BDrQvW6RLdZaiLipM4Q==", + "dev": true, + "requires": { + "@jest/console": "^24.3.0", + "@jest/environment": "^24.4.0", + "@jest/test-result": "^24.3.0", + "@jest/types": "^24.3.0", + "chalk": "^2.4.2", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-config": "^24.4.0", + "jest-docblock": "^24.3.0", + "jest-haste-map": "^24.4.0", + "jest-jasmine2": "^24.4.0", + "jest-leak-detector": "^24.4.0", + "jest-message-util": "^24.3.0", + "jest-resolve": "^24.4.0", + "jest-runtime": "^24.4.0", + "jest-util": "^24.3.0", + "jest-worker": "^24.4.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + } + }, + "jest-runtime": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.4.0.tgz", + "integrity": "sha512-wmopIA6EqgfSvYmqFvfZViJy5LCyIATUSRRt16HQDNN4ypWUQAaFwZ9fpbPo7e2UnKHTe2CK0dCRB1o/a6JUfQ==", + "dev": true, + "requires": { + "@jest/console": "^24.3.0", + "@jest/environment": "^24.4.0", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.4.0", + "@jest/types": "^24.3.0", + "@types/yargs": "^12.0.2", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.4.0", + "jest-haste-map": "^24.4.0", + "jest-message-util": "^24.3.0", + "jest-mock": "^24.3.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.4.0", + "jest-snapshot": "^24.4.0", + "jest-util": "^24.3.0", + "jest-validate": "^24.4.0", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "yargs": "^12.0.2" + } + }, + "jest-serializer": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz", + "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==", + "dev": true + }, + "jest-snapshot": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.4.0.tgz", + "integrity": "sha512-h+xO+ZQC+XEcf5wsy6+yducTKw6ku+oS5E2eJZI4YI65AT/lvbMjKgulgQWUOxga4HP0qHnz9uwa67/Zo7jVrw==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^24.3.0", + "chalk": "^2.0.1", + "expect": "^24.4.0", + "jest-diff": "^24.4.0", + "jest-matcher-utils": "^24.4.0", + "jest-message-util": "^24.3.0", + "jest-resolve": "^24.4.0", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^24.4.0", + "semver": "^5.5.0" + } + }, + "jest-util": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.3.0.tgz", + "integrity": "sha512-eKIAC+MTKWZthUUVOwZ3Tc5a0cKMnxalQHr6qZ4kPzKn6k09sKvsmjCygqZ1SxVVfUKoa8Sfn6XDv9uTJ1iXTg==", + "dev": true, + "requires": { + "@jest/console": "^24.3.0", + "@jest/fake-timers": "^24.3.0", + "@jest/source-map": "^24.3.0", + "@jest/test-result": "^24.3.0", + "@jest/types": "^24.3.0", + "@types/node": "*", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + } + }, + "jest-validate": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.4.0.tgz", + "integrity": "sha512-XESrpRYneLmiN9ayFm9RhBV5dwmhRZ+LbebScuuQ5GsY6ILpX9UeUMUdQ5Iz++YxFsmn5Lyi/Wkw6EV4v7nNTg==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0", + "camelcase": "^5.0.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.3.0", + "leven": "^2.1.0", + "pretty-format": "^24.4.0" + } + }, + "jest-watcher": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.3.0.tgz", + "integrity": "sha512-EpJS/aUG8D3DMuy9XNA4fnkKWy3DQdoWhY92ZUdlETIeEn1xya4Np/96MBSh4II5YvxwKe6JKwbu3Bnzfwa7vA==", + "dev": true, + "requires": { + "@jest/test-result": "^24.3.0", + "@jest/types": "^24.3.0", + "@types/node": "*", + "@types/yargs": "^12.0.9", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.3.0", + "string-length": "^2.0.0" + } + }, + "jest-worker": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.4.0.tgz", + "integrity": "sha512-BH9X/klG9vxwoO99ZBUbZFfV8qO0XNZ5SIiCyYK2zOuJBl6YJVAeNIQjcoOVNu4HGEHeYEKsUWws8kSlSbZ9YQ==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", + "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "kleur": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.2.tgz", + "integrity": "sha512-3h7B2WRT5LNXOtQiAaWonilegHcPSf9nLVXlSTci8lu1dZUuui61+EsPEZqSVxY7rXYmB2DVKMQILxaO5WL61Q==", + "dev": true + }, + "lambda-leak": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lambda-leak/-/lambda-leak-2.0.0.tgz", + "integrity": "sha1-dxmF02KEh/boha+uK1RRDc+yzX4=", + "dev": true + }, + "lambda-tester": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lambda-tester/-/lambda-tester-3.5.0.tgz", + "integrity": "sha512-QTz0JsIxjOKEyxryDtQD1+fT9KRYifdId53fq+hDwplAe71DB7UuZwNWqjJy1GQZfW57UrQVOWEDuFm4S808GQ==", + "dev": true, + "requires": { + "app-root-path": "^2.1.0", + "dotenv": "^5.0.0", + "dotenv-json": "^1.0.0", + "lambda-leak": "^2.0.0", + "semver": "^5.5.0", + "uuid": "^3.3.2", + "vandium-utils": "^1.1.1" + } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lolex": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", + "integrity": "sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "mem": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz", + "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nise": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", + "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + } + } + }, + "nock": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", + "integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==", + "dev": true, + "requires": { + "chai": "^4.1.2", + "debug": "^4.1.0", + "deep-equal": "^1.0.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.5", + "mkdirp": "^0.5.0", + "propagate": "^1.0.0", + "qs": "^6.5.1", + "semver": "^5.5.0" + } + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwsapi": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.1.tgz", + "integrity": "sha512-T5GaA1J/d34AC8mkrFD2O0DR17kwJ702ZOtJOsS8RpbsQZVOC2/xYFb1i/cw+xdM54JIlMuojjDOYct8GIWtwg==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", + "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "requires": { + "p-reduce": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, + "p-try": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", + "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "dev": true + }, + "parent-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.0.tgz", + "integrity": "sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "pretty-format": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.4.0.tgz", + "integrity": "sha512-SEXFzT01NwO4vaymwhz1/CM+wKCLOT92uqrzxIjmdRQMt7JAEuZ2eInCMvDS+4ZidEB+Rdq+fMs/Vwse8VAh1A==", + "dev": true, + "requires": { + "@jest/types": "^24.3.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prompts": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.3.tgz", + "integrity": "sha512-H8oWEoRZpybm6NV4to9/1limhttEo13xK62pNvn2JzY0MA03p7s0OjtmhXyon3uJmxiJJVSuUwEJFFssI3eBiQ==", + "dev": true, + "requires": { + "kleur": "^3.0.2", + "sisteransi": "^1.0.0" + } + }, + "propagate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", + "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", + "dev": true + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "react-is": { + "version": "16.8.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.4.tgz", + "integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "dev": true, + "requires": { + "util.promisify": "^1.0.0" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "dev": true, + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rsvp": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", + "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", + "dev": true + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", + "integrity": "sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sane": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.0.3.tgz", + "integrity": "sha512-hSLkC+cPHiBQs7LSyXkotC3UUtyn8C4FMn50TNaacRyvBlI+3ebcxMpqckmTdtXVtel87YS7GXN3UIOj7NiGVQ==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^1.2.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + } + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sinon": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.2.5.tgz", + "integrity": "sha512-1c2KK6g5NQr9XNYCEcUbeFtBpKZD1FXEw0VX7gNhWUBtkchguT2lNdS7XmS7y64OpQWfSNeeV/f8py3NNcQ63Q==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.3.0", + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/samsam": "^3.2.0", + "diff": "^3.5.0", + "lolex": "^3.1.0", + "nise": "^1.4.10", + "supports-color": "^5.5.0" + } + }, + "sisteransi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", + "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", + "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, + "table": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/table/-/table-5.2.3.tgz", + "integrity": "sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==", + "dev": true, + "requires": { + "ajv": "^6.9.1", + "lodash": "^4.17.11", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz", + "integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "test-exclude": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.1.0.tgz", + "integrity": "sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vandium-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vandium-utils/-/vandium-utils-1.2.0.tgz", + "integrity": "sha1-RHNd5LdkGgXeWevpRfF05YLbT1k=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/package.json b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/package.json new file mode 100644 index 0000000000000..2c4dd62266e72 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/package.json @@ -0,0 +1,40 @@ +{ + "name": "aws-dynamodb-global-lambda", + "private": true, + "version": "0.28.0", + "description": "This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project.", + "main": "lib/handler.js", + "directories": { + "test": "test" + }, + "scripts": { + "build": "echo No build", + "test": "jest", + "lint": "eslint lib" + }, + "keywords": [ + "aws", + "cdk", + "dynamodb", + "global" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "aws-sdk": "^2.419.0", + "aws-sdk-mock": "^4.3.1", + "eslint": "^5.15.1", + "eslint-config-standard": "^12.0.0", + "eslint-plugin-import": "^2.16.0", + "eslint-plugin-node": "^8.0.1", + "eslint-plugin-promise": "^4.0.1", + "eslint-plugin-standard": "^4.0.0", + "jest": "^24.4.0", + "lambda-tester": "^3.5.0", + "nock": "^10.0.6" + } +} diff --git a/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/test/test.lambda.handler.js b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/test/test.lambda.handler.js new file mode 100644 index 0000000000000..d165376c4cf2a --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lambda-packages/aws-dynamodb-global-lambda/test/test.lambda.handler.js @@ -0,0 +1,137 @@ +'use strict'; + +const handler = require('../lib'); +const AWS = require('aws-sdk-mock'); +const LambdaTester = require('lambda-tester').noVersionCheck(); +const sinon = require('sinon'); +const nock = require('nock'); + +describe('Global DynamoDB Handler', () => { + const origLog = console.log; + const testRequestId = 'f4ef1b10-c39a-44e3-99c0-fbf7e53c3943'; + const testTableName = 'testTable'; + const ResponseURL = 'https://cloudwatch-response-mock.example.com/'; + + beforeEach(() => { + handler.withDefaultResponseURL(ResponseURL); + console.log = function () {}; + }); + afterEach(() => { + AWS.restore(); + console.log = origLog; + }); + + test('Fails if the event payload is empty', () => { + const request = nock(ResponseURL).put('/', body => { + return body.Status === 'FAILED' && body.Reason === 'Unsupported request type undefined'; + }).reply(200); + return LambdaTester(handler.handler) + .event({}) + .expectResolve(() => { + expect(request.isDone()).toBe(true); + }); + }); + + test('Fails if the request type is bogus', () => { + const bogusType = 'bogus'; + const request = nock(ResponseURL).put('/', body => { + return body.Status === 'FAILED' && body.Reason === 'Unsupported request type ' + bogusType; + }).reply(200); + return LambdaTester(handler.handler) + .event({ + RequestType: bogusType + }) + .expectResolve(() => { + expect(request.isDone()).toBe(true); + }); + }); + + test('Call createGlobalTable if RequestType is Create', () => { + const createTableFake = sinon.fake.resolves({ + GlobalTableDescription: { + GlobalTableName: testTableName, + ReplicationGroup: [{ RegionName: "us-west-2" }, { RegionName: "us-east-1" }] + } + }); + + AWS.mock('DynamoDB', 'createGlobalTable', createTableFake); + + const request = nock(ResponseURL).put('/', body => { + return body.Status === 'SUCCESS'; + }).reply(200); + + return LambdaTester(handler.handler) + .event({ + RequestType: 'Create', + RequestId: testRequestId, + ResourceProperties: { + TableName: testTableName, + Regions: [ 'us-west-2', 'us-east-1'] + } + }) + .expectResolve(() => { + sinon.assert.calledWith(createTableFake, sinon.match({ + GlobalTableName: testTableName, + ReplicationGroup: [{ RegionName: "us-west-2" }, { RegionName: "us-east-1" }] + })); + expect(request.isDone()).toBe(true); + }); + }); + + test('Call describeGlobalTable and updateGlobalTable if RequestType is Update', () => { + const describeTablesFake = sinon.fake.resolves({ + GlobalTableDescription: { + GlobalTableName: testTableName, + ReplicationGroup: [{ RegionName: 'us-west-2' }, { RegionName: 'us-east-2' }] + } + }); + + const updateTablesFake = sinon.fake.resolves({ + GlobalTableDescription: { + GlobalTableName: testTableName + } + }); + + AWS.mock('DynamoDB', 'describeGlobalTable', describeTablesFake); + AWS.mock('DynamoDB', 'updateGlobalTable', updateTablesFake); + + const request = nock(ResponseURL).put('/', body => { + return body.Status === 'SUCCESS'; + }).reply(200); + + return LambdaTester(handler.handler) + .event({ + RequestType: 'Update', + RequestId: testRequestId, + ResourceProperties: { + TableName: testTableName, + Regions: [ 'us-west-2', 'us-east-1'] + } + }) + .expectResolve(() => { + sinon.assert.calledWith(updateTablesFake, sinon.match({ + GlobalTableName: testTableName + })); + sinon.assert.calledWith(describeTablesFake, sinon.match({ + GlobalTableName: testTableName + })); + expect(request.isDone()).toBe(true); + }); + }); + + test('Do nothing if RequestType is Delete', () => { + const request = nock(ResponseURL).put('/', body => { + return body.Status === 'SUCCESS'; + }).reply(200); + + return LambdaTester(handler.handler) + .event({ + RequestType: 'Delete', + RequestId: testRequestId, + ResourceProperties: {} + }) + .expectResolve(() => { + expect(request.isDone()).toBe(true); + }); + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts b/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts new file mode 100644 index 0000000000000..d8a86f1bf2093 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lib/aws-dynamodb-global.ts @@ -0,0 +1,72 @@ +import dynamodb = require("@aws-cdk/aws-dynamodb"); +import cdk = require("@aws-cdk/cdk"); +import { LambdaGlobalDynamoDBMaker } from "./lambda-global-dynamodb"; + +/** + * Properties for the mutliple DynamoDB tables to mash together into a + * global table + */ +export interface GlobalTableProps extends cdk.StackProps, dynamodb.TableOptions { + /** + * Name of the DynamoDB table to use across all regional tables. + * This is required for global tables. + */ + readonly tableName: string; + + /** + * Array of environments to create DynamoDB tables in. + * The tables will all be created in the same account. + */ + readonly regions: string[]; +} + +/** + * This class works by deploying an AWS DynamoDB table into each region specified in GlobalTableProps.regions[], + * then triggering a CloudFormation Custom Resource Lambda to link them all together to create linked AWS Global DynamoDB tables. + */ +export class GlobalTable extends cdk.Construct { + /** + * Creates the cloudformation custom resource that launches a lambda to tie it all together + */ + private lambdaGlobalDynamodbMaker: LambdaGlobalDynamoDBMaker; + + /** + * Creates dynamoDB tables across regions that will be able to be globbed together into a global table + */ + private readonly _regionalTables = new Array(); + + constructor(scope: cdk.Construct, id: string, props: GlobalTableProps) { + super(scope, id); + this._regionalTables = []; + if (props.streamSpecification != null && props.streamSpecification !== dynamodb.StreamViewType.NewAndOldImages) { + throw(new TypeError("dynamoProps.streamSpecification MUST be set to dynamodb.StreamViewType.NewAndOldImages")); + } + // Need to set this streamSpecification, otherwise global tables don't work + // And no way to set a default value in an interface + const stackProps = { + ...props, + streamSpecification: dynamodb.StreamViewType.NewAndOldImages + }; + /** + * Here we loop through the configured regions. + * In each region we'll deploy a separate stack with a DynamoDB Table with identical properties in the individual stacks + */ + for (const reg of props.regions) { + const regionalStack = new cdk.Stack(this, id + "-" + reg, { env: { region: reg } }); + const regionalTable = new dynamodb.Table(regionalStack, id + '-GlobalTable-' + reg, stackProps); + this._regionalTables.push(regionalTable); + } + + this.lambdaGlobalDynamodbMaker = new LambdaGlobalDynamoDBMaker(scope, id + "-CustomResource", props); + for (const table of this._regionalTables) { + this.lambdaGlobalDynamodbMaker.customResource.node.addDependency(table); + } + } + + /** + * Obtain tables deployed in other each region + */ + public get regionalTables() { + return this._regionalTables.map(x => x); + } +} diff --git a/packages/@aws-cdk/aws-dynamodb-global/lib/index.ts b/packages/@aws-cdk/aws-dynamodb-global/lib/index.ts new file mode 100644 index 0000000000000..1f2df3d00d7a3 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lib/index.ts @@ -0,0 +1 @@ +export * from "./aws-dynamodb-global"; diff --git a/packages/@aws-cdk/aws-dynamodb-global/lib/lambda-global-dynamodb.ts b/packages/@aws-cdk/aws-dynamodb-global/lib/lambda-global-dynamodb.ts new file mode 100644 index 0000000000000..76c178c3cb0fa --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/lib/lambda-global-dynamodb.ts @@ -0,0 +1,63 @@ +import cfn = require("@aws-cdk/aws-cloudformation"); +import iam = require("@aws-cdk/aws-iam"); +import lambda = require("@aws-cdk/aws-lambda"); +import cdk = require("@aws-cdk/cdk"); +import path = require("path"); +import { GlobalTableProps } from "./aws-dynamodb-global"; + +/** + * A stack that will make a Lambda that will launch a lambda to glue + * together all the DynamoDB tables into a global table + */ +export class LambdaGlobalDynamoDBMaker extends cdk.Stack { + /** + * Permits an IAM Principal to create a global dynamodb table. + * @param principal The principal (no-op if undefined) + */ + public static grantCreateGlobalTableLambda(principal?: iam.IPrincipal): void { + if (principal) { + principal.addToPolicy(new iam.PolicyStatement() + .allow() + .addAllResources() + .addAction("iam:CreateServiceLinkedRole") + .addAction("application-autoscaling:DeleteScalingPolicy") + .addAction("application-autoscaling:DeregisterScalableTarget") + .addAction("dynamodb:CreateGlobalTable") + .addAction("dynamodb:DescribeLimits") + .addAction("dynamodb:DeleteTable") + .addAction("dynamodb:DescribeGlobalTable") + .addAction("dynamodb:UpdateGlobalTable")); + } + } + + /** + * The singleton Lambda function that will connect all the DynamoDB tables together into a global table + */ + public lambdaFunction: lambda.SingletonFunction; + + /** + * The CloudFormation CustomResource that will manage the lambda + */ + public customResource: cfn.CustomResource; + + constructor(scope: cdk.Construct, id: string, props: GlobalTableProps) { + super(scope, id, props); + this.lambdaFunction = new lambda.SingletonFunction(this, "SingletonLambda", { + code: lambda.Code.asset(path.resolve(__dirname, "../", "lambda-packages", "aws-dynamodb-global-lambda", "lib")), + description: "Lambda to make DynamoDB a global table", + handler: "index.handler", + runtime: lambda.Runtime.NodeJS810, + timeout: 300, + uuid: "D38B65A6-6B54-4FB6-9BAD-9CD40A6DAC12", + }); + LambdaGlobalDynamoDBMaker.grantCreateGlobalTableLambda(this.lambdaFunction.role); + this.customResource = new cfn.CustomResource(this, "CfnCustomResource", { + lambdaProvider: this.lambdaFunction, + properties: { + regions: props.regions, + resourceType: "Custom::MakeGlobalDynamoDB", + tableName: props.tableName, + }, + }); + } +} diff --git a/packages/@aws-cdk/aws-dynamodb-global/package.json b/packages/@aws-cdk/aws-dynamodb-global/package.json new file mode 100644 index 0000000000000..fde296581fd3f --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/package.json @@ -0,0 +1,87 @@ +{ + "name": "@aws-cdk/aws-dynamodb-global", + "version": "0.28.0", + "description": "Build a global dynamodb table", + "license": "Apache-2.0", + "homepage": "https://github.com/awslabs/aws-cdk", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-dynamodb-global" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.dynamodb.global", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "dynamodb-global" + } + }, + "python": { + "distName": "aws-cdk.aws-dynamodb-global", + "module": "aws_cdk.aws_dynamodb_global" + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.DynamoDB.Global", + "packageId": "Amazon.CDK.AWS.DynamoDB.Global", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk" + }, + "sphinx": {} + } + }, + "cdk-build": { + "cloudformation": "AWS::DynamoDB" + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "dynamodb", + "global" + ], + "dependencies": { + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-dynamodb": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" + }, + "devDependencies": { + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" + }, + "peerDependencies": { + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/aws-dynamodb": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" + }, + "engines": { + "node": ">= 8.10.0" + }, + "main": "lib/index.js", + "types": "lib/index.d.ts" +} diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json new file mode 100644 index 0000000000000..faae370b4dbbf --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.expected.json @@ -0,0 +1,249 @@ +[ + { + "Resources": { + "globdynamodbintegGlobalTableuseast162596384": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "hashKey", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "hashKey", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + }, + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "TableName": "integrationtest" + } + } + } + }, + { + "Resources": { + "globdynamodbintegGlobalTableuseast2EF897C2E": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "hashKey", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "hashKey", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + }, + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "TableName": "integrationtest" + } + } + } + }, + { + "Resources": { + "globdynamodbintegGlobalTableuswest27374C2EA": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "KeySchema": [ + { + "AttributeName": "hashKey", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "hashKey", + "AttributeType": "S" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + }, + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "TableName": "integrationtest" + } + } + } + }, + { + "Resources": { + "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12ServiceRoleD9686810": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12ServiceRoleDefaultPolicy6E7EDDB6": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "iam:CreateServiceLinkedRole", + "application-autoscaling:DeleteScalingPolicy", + "application-autoscaling:DeregisterScalableTarget", + "dynamodb:CreateGlobalTable", + "dynamodb:DescribeLimits", + "dynamodb:DeleteTable", + "dynamodb:DescribeGlobalTable", + "dynamodb:UpdateGlobalTable" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12ServiceRoleDefaultPolicy6E7EDDB6", + "Roles": [ + { + "Ref": "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12ServiceRoleD9686810" + } + ] + } + }, + "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC1233FDC96A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12CodeS3BucketF66FB543" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12CodeS3VersionKey59DB89A0" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12CodeS3VersionKey59DB89A0" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12ServiceRoleD9686810", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Description": "Lambda to make DynamoDB a global table", + "Timeout": 300 + }, + "DependsOn": [ + "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12ServiceRoleDefaultPolicy6E7EDDB6", + "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12ServiceRoleD9686810" + ] + }, + "CfnCustomResource": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC1233FDC96A", + "Arn" + ] + }, + "Regions": [ + "us-east-1", + "us-east-2", + "us-west-2" + ], + "ResourceType": "Custom::MakeGlobalDynamoDB", + "TableName": "integrationtest" + } + } + }, + "Parameters": { + "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12CodeS3BucketF66FB543": { + "Type": "String", + "Description": "S3 bucket for asset \"globdynamodbinteg-CustomResource/SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12/Code\"" + }, + "SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12CodeS3VersionKey59DB89A0": { + "Type": "String", + "Description": "S3 key for asset version \"globdynamodbinteg-CustomResource/SingletonLambdaD38B65A66B544FB69BAD9CD40A6DAC12/Code\"" + } + } + } +] \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts new file mode 100644 index 0000000000000..f557f6e6c1077 --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/test/integ.dynamodb.global.ts @@ -0,0 +1,11 @@ +import { AttributeType } from '@aws-cdk/aws-dynamodb'; +import { App } from '@aws-cdk/cdk'; +import { GlobalTable } from '../lib'; + +const app = new App(); +new GlobalTable(app, 'globdynamodbinteg', { + partitionKey: { name: 'hashKey', type: AttributeType.String }, + tableName: 'integrationtest', + regions: [ "us-east-1", "us-east-2", "us-west-2" ] +}); +app.run(); diff --git a/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts new file mode 100644 index 0000000000000..11b6bec25a75f --- /dev/null +++ b/packages/@aws-cdk/aws-dynamodb-global/test/test.dynamodb.global.ts @@ -0,0 +1,103 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import { Attribute, AttributeType, StreamViewType } from '@aws-cdk/aws-dynamodb'; +import { Table } from '@aws-cdk/aws-dynamodb'; +import { Stack } from '@aws-cdk/cdk'; +import * as assert from 'assert'; +import { Test } from 'nodeunit'; +import { + GlobalTable, + GlobalTableProps +} from '../lib'; + +// tslint:disable:object-literal-key-quotes + +// CDK parameters +const CONSTRUCT_NAME = 'aws-cdk-dynamodb-global'; + +// DynamoDB table parameters +const TABLE_NAME = 'GlobalTable'; +const TABLE_PARTITION_KEY: Attribute = { name: 'hashKey', type: AttributeType.String }; + +const STACK_PROPS: GlobalTableProps = { + partitionKey: TABLE_PARTITION_KEY, + tableName: TABLE_NAME, + regions: [ 'us-east-1', 'us-east-2', 'us-west-2' ] +}; + +export = { + 'Default Global DynamoDB stack': { + 'global dynamo'(test: Test) { + const stack = new Stack(); + new GlobalTable(stack, CONSTRUCT_NAME, STACK_PROPS); + const topStack = stack.node.findChild(CONSTRUCT_NAME) as Stack; + for ( const reg of STACK_PROPS.regions ) { + const tableStack = topStack.node.findChild(CONSTRUCT_NAME + '-' + reg) as Stack; + expect(tableStack).to(haveResource('AWS::DynamoDB::Table', { + "KeySchema": [ + { + "AttributeName": "hashKey", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "hashKey", + "AttributeType": "S" + } + ], + "StreamSpecification": { + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "TableName": "GlobalTable" + })); + } + const customResourceStack = stack.node.findChild(CONSTRUCT_NAME + "-CustomResource") as Stack; + expect(customResourceStack).to(haveResource('AWS::Lambda::Function', { + Description: "Lambda to make DynamoDB a global table", + Handler: "index.handler", + Timeout: 300 + })); + expect(customResourceStack).to(haveResource('AWS::CloudFormation::CustomResource', { + Regions: STACK_PROPS.regions, + ResourceType: "Custom::MakeGlobalDynamoDB", + TableName: STACK_PROPS.tableName + })); + test.done(); + }, + }, + 'Enforce StreamSpecification': { + 'global dynamo should only allow NEW_AND_OLD_IMAGES'(test: Test) { + const stack = new Stack(); + try { + new GlobalTable(stack, CONSTRUCT_NAME, { + tableName: TABLE_NAME, + streamSpecification: StreamViewType.KeysOnly, + partitionKey: TABLE_PARTITION_KEY, + regions: [ 'us-east-1', 'us-east-2', 'us-west-2' ] + }); + // We are expecting the above line to throw a TypeError since + // the streamSpecification is wrong. Force a failure on this + // line if we get there. + expect(stack).to(haveResource('Fail::this::test::IfWeGetThisFar', {})); + } catch ( TypeError ) { + expect(stack); + } + test.done(); + }, + }, + 'Check getting tables': { + 'global dynamo should only allow NEW_AND_OLD_IMAGES'(test: Test) { + const stack = new Stack(); + const regTables = new GlobalTable(stack, CONSTRUCT_NAME, { + tableName: TABLE_NAME, + partitionKey: TABLE_PARTITION_KEY, + regions: [ 'us-east-1', 'us-east-2', 'us-west-2' ] + }); + assert(regTables.regionalTables.length === 3); + for (const table of regTables.regionalTables) { + assert(table instanceof Table); + } + test.done(); + }, + } +}; diff --git a/packages/@aws-cdk/aws-dynamodb/README.md b/packages/@aws-cdk/aws-dynamodb/README.md index 6b0bcb15cb7da..c956073f5cb7d 100644 --- a/packages/@aws-cdk/aws-dynamodb/README.md +++ b/packages/@aws-cdk/aws-dynamodb/README.md @@ -47,3 +47,6 @@ Auto-scaling is only relevant for tables with the billing mode, PROVISIONED. Further reading: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AutoScaling.html https://aws.amazon.com/blogs/database/how-to-use-aws-cloudformation-to-configure-auto-scaling-for-amazon-dynamodb-tables-and-indexes/ + +### Amazon DynamoDB Global Tables +Please see the `@aws-cdk/aws-dynamodb-global` package [here](../aws-dynamodb-global/). \ No newline at end of file diff --git a/packages/@aws-cdk/aws-dynamodb/lib/scalable-attribute-api.ts b/packages/@aws-cdk/aws-dynamodb/lib/scalable-attribute-api.ts index ef96b3c417580..c1fc9923edc47 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/scalable-attribute-api.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/scalable-attribute-api.ts @@ -22,12 +22,12 @@ export interface EnableScalingProps { /** * Minimum capacity to scale to */ - minCapacity: number; + readonly minCapacity: number; /** * Maximum capacity to scale to */ - maxCapacity: number; + readonly maxCapacity: number; } /** @@ -37,5 +37,5 @@ export interface UtilizationScalingProps extends appscaling.BaseTargetTrackingPr /** * Target utilization percentage for the attribute */ - targetUtilizationPercent: number; + readonly targetUtilizationPercent: number; } diff --git a/packages/@aws-cdk/aws-dynamodb/lib/table.ts b/packages/@aws-cdk/aws-dynamodb/lib/table.ts index 2d6b54030099a..92ea1122278c1 100644 --- a/packages/@aws-cdk/aws-dynamodb/lib/table.ts +++ b/packages/@aws-cdk/aws-dynamodb/lib/table.ts @@ -35,26 +35,26 @@ export interface Attribute { /** * The name of an attribute. */ - name: string; + readonly name: string; /** * The data type of an attribute. */ - type: AttributeType; + readonly type: AttributeType; } -export interface TableProps { +export interface TableOptions { /** * Partition key attribute definition. */ - partitionKey: Attribute; + readonly partitionKey: Attribute; /** * Table sort key attribute definition. * * @default no sort key */ - sortKey?: Attribute; + readonly sortKey?: Attribute; /** * The read capacity for the table. Careful if you add Global Secondary Indexes, as @@ -64,7 +64,7 @@ export interface TableProps { * * @default 5 */ - readCapacity?: number; + readonly readCapacity?: number; /** * The write capacity for the table. Careful if you add Global Secondary Indexes, as * those will share the table's provisioned throughput. @@ -73,76 +73,78 @@ export interface TableProps { * * @default 5 */ - writeCapacity?: number; + readonly writeCapacity?: number; /** * Specify how you are charged for read and write throughput and how you manage capacity. * @default Provisioned */ - billingMode?: BillingMode; - - /** - * Enforces a particular physical table name. - * @default - */ - tableName?: string; + readonly billingMode?: BillingMode; /** * Whether point-in-time recovery is enabled. * @default undefined, point-in-time recovery is disabled */ - pitrEnabled?: boolean; + readonly pitrEnabled?: boolean; /** * Whether server-side encryption with an AWS managed customer master key is enabled. * @default undefined, server-side encryption is enabled with an AWS owned customer master key */ - sseEnabled?: boolean; + readonly sseEnabled?: boolean; /** - * When an item in the table is modified, StreamViewType determines what information - * is written to the stream for this table. Valid values for StreamViewType are: - * @default undefined, streams are disabled + * The name of TTL attribute. + * @default undefined, TTL is disabled */ - streamSpecification?: StreamViewType; + readonly ttlAttributeName?: string; +} +export interface TableProps extends TableOptions { /** - * The name of TTL attribute. - * @default undefined, TTL is disabled + * Enforces a particular physical table name. + * @default */ - ttlAttributeName?: string; + readonly tableName?: string; + + /** + * When an item in the table is modified, StreamViewType determines what information + * is written to the stream for this table. Valid values for StreamViewType are: + * @default undefined, streams are disabled + */ + readonly streamSpecification?: StreamViewType; } export interface SecondaryIndexProps { /** * The name of the secondary index. */ - indexName: string; + readonly indexName: string; /** * The set of attributes that are projected into the secondary index. * @default ALL */ - projectionType?: ProjectionType; + readonly projectionType?: ProjectionType; /** * The non-key attributes that are projected into the secondary index. * @default undefined */ - nonKeyAttributes?: string[]; + readonly nonKeyAttributes?: string[]; } export interface GlobalSecondaryIndexProps extends SecondaryIndexProps { /** * The attribute of a partition key for the global secondary index. */ - partitionKey: Attribute; + readonly partitionKey: Attribute; /** * The attribute of a sort key for the global secondary index. * @default undefined */ - sortKey?: Attribute; + readonly sortKey?: Attribute; /** * The read capacity for the global secondary index. @@ -151,7 +153,7 @@ export interface GlobalSecondaryIndexProps extends SecondaryIndexProps { * * @default 5 */ - readCapacity?: number; + readonly readCapacity?: number; /** * The write capacity for the global secondary index. @@ -160,14 +162,14 @@ export interface GlobalSecondaryIndexProps extends SecondaryIndexProps { * * @default 5 */ - writeCapacity?: number; + readonly writeCapacity?: number; } export interface LocalSecondaryIndexProps extends SecondaryIndexProps { /** * The attribute of a sort key for the local secondary index. */ - sortKey: Attribute; + readonly sortKey: Attribute; } /** @@ -176,15 +178,15 @@ export interface LocalSecondaryIndexProps extends SecondaryIndexProps { export class Table extends Construct { /** * Permits an IAM Principal to list all DynamoDB Streams. - * @param principal The principal (no-op if undefined) + * @param grantee The principal (no-op if undefined) */ - public static grantListStreams(principal?: iam.IPrincipal): void { - if (principal) { - principal.addToPolicy(new iam.PolicyStatement() - .addAction('dynamodb:ListStreams') - .addResource("*")); - } - } + public static grantListStreams(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['dynamodb:ListStreams'], + resourceArns: ['*'], + }); + } public readonly tableArn: string; public readonly tableName: string; @@ -404,31 +406,34 @@ export class Table extends Construct { /** * Adds an IAM policy statement associated with this table to an IAM * principal's policy. - * @param principal The principal (no-op if undefined) + * @param grantee The principal (no-op if undefined) * @param actions The set of actions to allow (i.e. "dynamodb:PutItem", "dynamodb:GetItem", ...) */ - public grant(principal?: iam.IPrincipal, ...actions: string[]) { - if (!principal) { - return; - } - principal.addToPolicy(new iam.PolicyStatement() - .addResources(this.tableArn, new cdk.Token(() => this.hasIndex ? `${this.tableArn}/index/*` : cdk.Aws.noValue).toString()) - .addActions(...actions)); + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [ + this.tableArn, + new cdk.Token(() => this.hasIndex ? `${this.tableArn}/index/*` : cdk.Aws.noValue).toString() + ], + scope: this, + }); } /** * Adds an IAM policy statement associated with this table's stream to an * IAM principal's policy. - * @param principal The principal (no-op if undefined) + * @param grantee The principal (no-op if undefined) * @param actions The set of actions to allow (i.e. "dynamodb:DescribeStream", "dynamodb:GetRecords", ...) */ - public grantStream(principal?: iam.IPrincipal, ...actions: string[]) { - if (!principal) { - return; - } - principal.addToPolicy(new iam.PolicyStatement() - .addResource(this.tableStreamArn) - .addActions(...actions)); + public grantStream(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [this.tableStreamArn], + scope: this, + }); } /** @@ -436,18 +441,18 @@ export class Table extends Construct { * BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan. * @param principal The principal to grant access to */ - public grantReadData(principal?: iam.IPrincipal) { - this.grant(principal, ...READ_DATA_ACTIONS); + public grantReadData(grantee: iam.IGrantable) { + return this.grant(grantee, ...READ_DATA_ACTIONS); } /** * Permis an IAM principal all stream data read operations for this * table's stream: * DescribeStream, GetRecords, GetShardIterator, ListStreams. - * @param principal The principal to grant access to + * @param grantee The principal to grant access to */ - public grantStreamRead(principal?: iam.IPrincipal) { - this.grantStream(principal, ...READ_STREAM_DATA_ACTIONS); + public grantStreamRead(grantee: iam.IGrantable) { + return this.grantStream(grantee, ...READ_STREAM_DATA_ACTIONS); } /** @@ -455,26 +460,26 @@ export class Table extends Construct { * BatchWriteItem, PutItem, UpdateItem, DeleteItem. * @param principal The principal to grant access to */ - public grantWriteData(principal?: iam.IPrincipal) { - this.grant(principal, ...WRITE_DATA_ACTIONS); + public grantWriteData(grantee: iam.IGrantable) { + return this.grant(grantee, ...WRITE_DATA_ACTIONS); } /** * Permits an IAM principal to all data read/write operations to this table. * BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan, * BatchWriteItem, PutItem, UpdateItem, DeleteItem - * @param principal The principal to grant access to + * @param grantee The principal to grant access to */ - public grantReadWriteData(principal?: iam.IPrincipal) { - this.grant(principal, ...READ_DATA_ACTIONS, ...WRITE_DATA_ACTIONS); + public grantReadWriteData(grantee: iam.IGrantable) { + return this.grant(grantee, ...READ_DATA_ACTIONS, ...WRITE_DATA_ACTIONS); } /** * Permits all DynamoDB operations ("dynamodb:*") to an IAM principal. - * @param principal The principal to grant access to + * @param grantee The principal to grant access to */ - public grantFullAccess(principal?: iam.IPrincipal) { - this.grant(principal, 'dynamodb:*'); + public grantFullAccess(grantee: iam.IGrantable) { + return this.grant(grantee, 'dynamodb:*'); } /** diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 9898eb83b515a..54f2d4510486b 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-dynamodb", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS DynamoDB", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-dynamodb", + "module": "aws_cdk.aws_dynamodb" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-dynamodb" }, "scripts": { "build": "cdk-build", @@ -54,24 +59,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-applicationautoscaling": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-applicationautoscaling": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-applicationautoscaling": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-applicationautoscaling": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-dynamodb/test/integ.autoscaling.lit.expected.json b/packages/@aws-cdk/aws-dynamodb/test/integ.autoscaling.lit.expected.json index 9590004579403..ee252d0339d35 100644 --- a/packages/@aws-cdk/aws-dynamodb/test/integ.autoscaling.lit.expected.json +++ b/packages/@aws-cdk/aws-dynamodb/test/integ.autoscaling.lit.expected.json @@ -9,16 +9,16 @@ "KeyType": "HASH" } ], - "ProvisionedThroughput": { - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5 - }, "AttributeDefinitions": [ { "AttributeName": "hashKey", "AttributeType": "S" } - ] + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } } }, "TableReadScalingTargetF96E9F76": { @@ -94,4 +94,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/README.md b/packages/@aws-cdk/aws-ec2/README.md index 26a04b5a6d5df..bea0e304a8917 100644 --- a/packages/@aws-cdk/aws-ec2/README.md +++ b/packages/@aws-cdk/aws-ec2/README.md @@ -21,7 +21,7 @@ instances for your project. Our default `VpcNetwork` class creates a private and public subnet for every availability zone. Classes that use the VPC will generally launch instances -into all private subnets, and provide a parameter called `vpcPlacement` to +into all private subnets, and provide a parameter called `vpcSubnets` to allow you to override the placement. [Read more about subnets](https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html). @@ -172,6 +172,52 @@ The `VpcNetwork` above will have the exact same subnet definitions as listed above. However, this time the VPC will have only 1 NAT Gateway and all Application subnets will route to the NAT Gateway. +#### Reserving subnet IP space +There are situations where the IP space for a subnet or number of subnets + will need to be reserved. This is useful in situations where subnets +would need to be added after the vpc is originally deployed, without causing +IP renumbering for existing subnets. The IP space for a subnet may be reserved +by setting the `reserved` subnetConfiguration property to true, as shown below: + +```ts +import ec2 = require('@aws-cdk/aws-ec2'); +const vpc = new ec2.VpcNetwork(this, 'TheVPC', { + cidr: '10.0.0.0/16', + natGateways: 1, + subnetConfiguration: [ + { + cidrMask: 26, + name: 'Public', + subnetType: SubnetType.Public, + }, + { + cidrMask: 26, + name: 'Application1', + subnetType: SubnetType.Private, + }, + { + cidrMask: 26, + name: 'Application2', + subnetType: SubnetType.Private, + reserved: true, + }, + { + cidrMask: 27, + name: 'Database', + subnetType: SubnetType.Isolated, + } + ], +}); +``` + +In the example above, the subnet for Application2 is not actually provisioned +but its IP space is still reserved. If in the future this subnet needs to be +provisioned, then the `reserved: true` property should be removed. Most +importantly, this action would not cause the Database subnet to get renumbered, +but rather the IP space that was previously reserved will be used for the +subnet provisioned for Application2. The `reserved` property also takes into +consideration the number of availability zones when reserving IP space. + #### Sharing VPCs between stacks If you are creating multiple `Stack`s inside the same CDK application, you @@ -353,3 +399,10 @@ const vpnConnection = vpc.addVpnConnection('Dynamic', { }); const state = vpnConnection.metricTunnelState(); ``` + +### VPC endpoints +A VPC endpoint enables you to privately connect your VPC to supported AWS services and VPC endpoint services powered by PrivateLink without requiring an internet gateway, NAT device, VPN connection, or AWS Direct Connect connection. Instances in your VPC do not require public IP addresses to communicate with resources in the service. Traffic between your VPC and the other service does not leave the Amazon network. + +Endpoints are virtual devices. They are horizontally scaled, redundant, and highly available VPC components that allow communication between instances in your VPC and services without imposing availability risks or bandwidth constraints on your network traffic. + +[example of setting up VPC endpoints](test/integ.vpc-endpoint.lit.ts) diff --git a/packages/@aws-cdk/aws-ec2/lib/connections.ts b/packages/@aws-cdk/aws-ec2/lib/connections.ts index 08bec6b065159..72854790af010 100644 --- a/packages/@aws-cdk/aws-ec2/lib/connections.ts +++ b/packages/@aws-cdk/aws-ec2/lib/connections.ts @@ -33,21 +33,21 @@ export interface ConnectionsProps { * * @default Derived from securityGroup if set. */ - securityGroupRule?: ISecurityGroupRule; + readonly securityGroupRule?: ISecurityGroupRule; /** * What securityGroup(s) this object is managing connections for * * @default No security groups */ - securityGroups?: ISecurityGroup[]; + readonly securityGroups?: ISecurityGroup[]; /** * Default port range for initiating connections to and from this object * * @default No default port range */ - defaultPortRange?: IPortRange; + readonly defaultPortRange?: IPortRange; } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/index.ts b/packages/@aws-cdk/aws-ec2/lib/index.ts index fdfe81aa9b97a..32946b75d470c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/index.ts +++ b/packages/@aws-cdk/aws-ec2/lib/index.ts @@ -7,6 +7,7 @@ export * from './vpc'; export * from './vpc-ref'; export * from './vpc-network-provider'; export * from './vpn'; +export * from './vpc-endpoint'; // AWS::EC2 CloudFormation Resources: export * from './ec2.generated'; diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index 43a3c44d8d6fc..f235f2a7ebb9a 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -203,7 +203,7 @@ export enum InstanceClass { * What size of instance to use */ export enum InstanceSize { - None = 'nano', + Nano = 'nano', Micro = 'micro', Small = 'small', Medium = 'medium', diff --git a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts index 9f318f791428e..ab916b06d4915 100644 --- a/packages/@aws-cdk/aws-ec2/lib/machine-image.ts +++ b/packages/@aws-cdk/aws-ec2/lib/machine-image.ts @@ -50,28 +50,28 @@ export interface AmazonLinuxImageProps { * * @default AmazonLinux */ - generation?: AmazonLinuxGeneration; + readonly generation?: AmazonLinuxGeneration; /** * What edition of Amazon Linux to use * * @default Standard */ - edition?: AmazonLinuxEdition; + readonly edition?: AmazonLinuxEdition; /** * Virtualization type * * @default HVM */ - virtualization?: AmazonLinuxVirt; + readonly virtualization?: AmazonLinuxVirt; /** * What storage backed image to use * * @default GeneralPurpose */ - storage?: AmazonLinuxStorage; + readonly storage?: AmazonLinuxStorage; } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index e15ba76415d3d..7c14cd11ee630 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -44,7 +44,7 @@ export interface SecurityGroupImportProps { /** * ID of security group */ - securityGroupId: string; + readonly securityGroupId: string; } /** @@ -207,19 +207,19 @@ export interface SecurityGroupProps { * @default If you don't specify a GroupName, AWS CloudFormation generates a * unique physical ID and uses that ID for the group name. */ - groupName?: string; + readonly groupName?: string; /** * A description of the security group. * * @default The default name will be the construct's CDK path. */ - description?: string; + readonly description?: string; /** * The VPC in which to create the security group. */ - vpc: IVpcNetwork; + readonly vpc: IVpcNetwork; /** * Whether to allow all outbound traffic by default. @@ -230,7 +230,7 @@ export interface SecurityGroupProps { * * @default true */ - allowAllOutbound?: boolean; + readonly allowAllOutbound?: boolean; } /** @@ -460,7 +460,7 @@ export interface ConnectionRule { * * @default tcp */ - protocol?: string; + readonly protocol?: string; /** * Start of port range for the TCP and UDP protocols, or an ICMP type number. @@ -468,7 +468,7 @@ export interface ConnectionRule { * If you specify icmp for the IpProtocol property, you can specify * -1 as a wildcard (i.e., any ICMP type number). */ - fromPort: number; + readonly fromPort: number; /** * End of port range for the TCP and UDP protocols, or an ICMP code. @@ -478,7 +478,7 @@ export interface ConnectionRule { * * @default If toPort is not specified, it will be the same as fromPort. */ - toPort?: number; + readonly toPort?: number; /** * Description of this connection. It is applied to both the ingress rule @@ -486,7 +486,7 @@ export interface ConnectionRule { * * @default No description */ - description?: string; + readonly description?: string; } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts new file mode 100644 index 0000000000000..416339ee1a4b9 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -0,0 +1,483 @@ +import iam = require('@aws-cdk/aws-iam'); +import cdk = require('@aws-cdk/cdk'); +import { Connections, IConnectable } from './connections'; +import { CfnVPCEndpoint } from './ec2.generated'; +import { SecurityGroup } from './security-group'; +import { TcpPort, TcpPortFromAttribute } from './security-group-rule'; +import { IVpcNetwork, SubnetSelection, SubnetType } from './vpc-ref'; + +/** + * A VPC endpoint. + */ +export interface IVpcEndpoint extends cdk.IConstruct { + /** + * The VPC endpoint identifier. + */ + readonly vpcEndpointId: string; +} + +export abstract class VpcEndpoint extends cdk.Construct implements IVpcEndpoint { + public abstract readonly vpcEndpointId: string; + + protected policyDocument?: iam.PolicyDocument; + + /** + * Adds a statement to the policy document of the VPC endpoint. The statement + * must have a Principal. + * + * Not all interface VPC endpoints support policy. For more information + * see https://docs.aws.amazon.com/vpc/latest/userguide/vpce-interface.html + * + * @param statement the IAM statement to add + */ + public addToPolicy(statement: iam.PolicyStatement) { + if (!statement.hasPrincipal) { + throw new Error('Statement must have a `Principal`.'); + } + + if (!this.policyDocument) { + this.policyDocument = new iam.PolicyDocument(); + } + + this.policyDocument.addStatement(statement); + } +} + +/** + * A gateway VPC endpoint. + */ +export interface IGatewayVpcEndpoint extends IVpcEndpoint { + /** + * Exports this VPC endpoint from the stack. + */ + export(): GatewayVpcEndpointImportProps; +} + +/** + * The type of VPC endpoint. + */ +export enum VpcEndpointType { + /** + * Interface + * + * An interface endpoint is an elastic network interface with a private IP + * address that serves as an entry point for traffic destined to a supported + * service. + */ + Interface = 'Interface', + + /** + * Gateway + * + * A gateway endpoint is a gateway that is a target for a specified route in + * your route table, used for traffic destined to a supported AWS service. + */ + Gateway = 'Gateway' +} + +/** + * A service for a gateway VPC endpoint. + */ +export interface IGatewayVpcEndpointService { + /** + * The name of the service. + */ + readonly name: string; +} + +/** + * An AWS service for a gateway VPC endpoint. + */ +export class GatewayVpcEndpointAwsService implements IGatewayVpcEndpointService { + public static readonly DynamoDb = new GatewayVpcEndpointAwsService('dynamodb'); + public static readonly S3 = new GatewayVpcEndpointAwsService('s3'); + + /** + * The name of the service. + */ + public readonly name: string; + + constructor(name: string, prefix?: string) { + this.name = `${prefix || 'com.amazonaws'}.${cdk.Aws.region}.${name}`; + } +} + +/** + * Options to add a gateway endpoint to a VPC. + */ +export interface GatewayVpcEndpointOptions { + /** + * The service to use for this gateway VPC endpoint. + */ + readonly service: IGatewayVpcEndpointService; + + /** + * Where to add endpoint routing. + * + * @default private subnets + */ + readonly subnets?: SubnetSelection[] +} + +/** + * Construction properties for a GatewayVpcEndpoint. + */ +export interface GatewayVpcEndpointProps extends GatewayVpcEndpointOptions { + /** + * The VPC network in which the gateway endpoint will be used. + */ + readonly vpc: IVpcNetwork +} + +/** + * A gateway VPC endpoint. + */ +export class GatewayVpcEndpoint extends VpcEndpoint implements IGatewayVpcEndpoint { + /** + * Imports an existing gateway VPC endpoint. + */ + public static import(scope: cdk.Construct, id: string, props: GatewayVpcEndpointImportProps): IGatewayVpcEndpoint { + return new ImportedGatewayVpcEndpoint(scope, id, props); + } + + /** + * The gateway VPC endpoint identifier. + */ + public readonly vpcEndpointId: string; + + /** + * The date and time the gateway VPC endpoint was created. + */ + public readonly vpcEndpointCreationTimestamp: string; + + constructor(scope: cdk.Construct, id: string, props: GatewayVpcEndpointProps) { + super(scope, id); + + const subnets = props.subnets || [{ subnetType: SubnetType.Private }]; + const routeTableIds = [...new Set(Array().concat(...subnets.map(s => props.vpc.selectSubnets(s).routeTableIds)))]; + + if (routeTableIds.length === 0) { + throw new Error(`Can't add a gateway endpoint to VPC; route table IDs are not available`); + } + + const endpoint = new CfnVPCEndpoint(this, 'Resource', { + policyDocument: new cdk.Token(() => this.policyDocument), + routeTableIds, + serviceName: props.service.name, + vpcEndpointType: VpcEndpointType.Gateway, + vpcId: props.vpc.vpcId + }); + + this.vpcEndpointId = endpoint.vpcEndpointId; + this.vpcEndpointCreationTimestamp = endpoint.vpcEndpointCreationTimestamp; + } + + /** + * Exports this gateway VPC endpoint from the stack. + */ + public export(): GatewayVpcEndpointImportProps { + return { + vpcEndpointId: new cdk.CfnOutput(this, 'VpcEndpointId', { value: this.vpcEndpointId }).makeImportValue().toString() + }; + } +} + +/** + * Construction properties for an ImportedGatewayVpcEndpoint. + */ +export interface GatewayVpcEndpointImportProps { + /** + * The gateway VPC endpoint identifier. + */ + readonly vpcEndpointId: string; +} + +/** + * An imported gateway VPC endpoint. + */ +class ImportedGatewayVpcEndpoint extends cdk.Construct implements IGatewayVpcEndpoint { + /** + * The gateway VPC endpoint identifier. + */ + public readonly vpcEndpointId: string; + + constructor(scope: cdk.Construct, id: string, private readonly props: GatewayVpcEndpointImportProps) { + super(scope, id); + + this.vpcEndpointId = props.vpcEndpointId; + } + + /** + * Exports this gateway VPC endpoint from the stack. + */ + public export(): GatewayVpcEndpointImportProps { + return this.props; + } +} + +/** + * A service for an interface VPC endpoint. + */ +export interface IInterfaceVpcEndpointService { + /** + * The name of the service. + */ + readonly name: string; + + /** + * The port of the service. + */ + readonly port: number; +} + +/** + * An AWS service for an interface VPC endpoint. + */ +export class InterfaceVpcEndpointAwsService implements IInterfaceVpcEndpointService { + public static readonly SageMakerNotebook = new InterfaceVpcEndpointAwsService('sagemaker', 'aws.sagemaker'); + public static readonly CloudFormation = new InterfaceVpcEndpointAwsService('cloudformation'); + public static readonly CloudTrail = new InterfaceVpcEndpointAwsService('cloudtrail'); + public static readonly CodeBuild = new InterfaceVpcEndpointAwsService('codebuild'); + public static readonly CodeBuildFips = new InterfaceVpcEndpointAwsService('codebuil-fips'); + public static readonly CodeCommit = new InterfaceVpcEndpointAwsService('codecommit'); + public static readonly CodeCommitFips = new InterfaceVpcEndpointAwsService('codecommit-fips'); + public static readonly CodePipeline = new InterfaceVpcEndpointAwsService('codepipeline'); + public static readonly Config = new InterfaceVpcEndpointAwsService('config'); + public static readonly Ec2 = new InterfaceVpcEndpointAwsService('ec2'); + public static readonly Ec2Messages = new InterfaceVpcEndpointAwsService('ec2messages'); + public static readonly Ecr = new InterfaceVpcEndpointAwsService('ecr.api'); + public static readonly EcrDocker = new InterfaceVpcEndpointAwsService('ecr.dkr'); + public static readonly Ecs = new InterfaceVpcEndpointAwsService('ecs'); + public static readonly EcsAgent = new InterfaceVpcEndpointAwsService('ecs-agent'); + public static readonly EcsTelemetry = new InterfaceVpcEndpointAwsService('ecs-telemetry'); + public static readonly ElasticInferenceRuntime = new InterfaceVpcEndpointAwsService('elastic-inference.runtime'); + public static readonly ElasticLoadBalancing = new InterfaceVpcEndpointAwsService('elasticloadbalancing'); + public static readonly CloudWatchEvents = new InterfaceVpcEndpointAwsService('events'); + public static readonly ApiGateway = new InterfaceVpcEndpointAwsService('execute-api'); + public static readonly CodeCommitGit = new InterfaceVpcEndpointAwsService('git-codecommit'); + public static readonly CodeCommitGitFips = new InterfaceVpcEndpointAwsService('git-codecommit-fips'); + public static readonly KinesisStreams = new InterfaceVpcEndpointAwsService('kinesis-streams'); + public static readonly Kms = new InterfaceVpcEndpointAwsService('kms'); + public static readonly CloudWatchLogs = new InterfaceVpcEndpointAwsService('logs'); + public static readonly CloudWatch = new InterfaceVpcEndpointAwsService('monitoring'); + public static readonly SageMakerApi = new InterfaceVpcEndpointAwsService('sagemaker.api'); + public static readonly SageMakerRuntime = new InterfaceVpcEndpointAwsService('sagemaker.runtime'); + public static readonly SageMakerRuntimeFips = new InterfaceVpcEndpointAwsService('sagemaker.runtime-fips'); + public static readonly SecretsManager = new InterfaceVpcEndpointAwsService('secretsmanager'); + public static readonly ServiceCatalog = new InterfaceVpcEndpointAwsService('servicecatalog'); + public static readonly Sns = new InterfaceVpcEndpointAwsService('sns'); + public static readonly Sqs = new InterfaceVpcEndpointAwsService('sqs'); + public static readonly Ssm = new InterfaceVpcEndpointAwsService('ssm'); + public static readonly SsmMessages = new InterfaceVpcEndpointAwsService('ssmmessages'); + public static readonly Sts = new InterfaceVpcEndpointAwsService('sts'); + public static readonly Transfer = new InterfaceVpcEndpointAwsService('transfer.server'); + + /** + * The name of the service. + */ + public readonly name: string; + + /** + * The port of the service. + */ + public readonly port: number; + + constructor(name: string, prefix?: string, port?: number) { + this.name = `${prefix || 'com.amazonaws'}.${cdk.Aws.region}.${name}`; + this.port = port || 443; + } +} + +/** + * Options to add an interface endpoint to a VPC. + */ +export interface InterfaceVpcEndpointOptions { + /** + * The service to use for this interface VPC endpoint. + */ + readonly service: IInterfaceVpcEndpointService; + + /** + * Whether to associate a private hosted zone with the specified VPC. This + * allows you to make requests to the service using its default DNS hostname. + * + * @default true + */ + readonly privateDnsEnabled?: boolean; + + /** + * The subnets in which to create an endpoint network interface. At most one + * per availability zone. + * + * @default private subnets + */ + readonly subnets?: SubnetSelection; +} + +/** + * Construction properties for an InterfaceVpcEndpoint. + */ +export interface InterfaceVpcEndpointProps extends InterfaceVpcEndpointOptions { + /** + * The VPC network in which the interface endpoint will be used. + */ + readonly vpc: IVpcNetwork +} + +/** + * An interface VPC endpoint. + */ +export interface IInterfaceVpcEndpoint extends IVpcEndpoint, IConnectable { + /** + * Exports this interface VPC endpoint from the stack. + */ + export(): InterfaceVpcEndpointImportProps; +} + +/** + * A interface VPC endpoint. + */ +export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEndpoint { + /** + * Imports an existing interface VPC endpoint. + */ + public static import(scope: cdk.Construct, id: string, props: InterfaceVpcEndpointImportProps): IInterfaceVpcEndpoint { + return new ImportedInterfaceVpcEndpoint(scope, id, props); + } + + /** + * The interface VPC endpoint identifier. + */ + public readonly vpcEndpointId: string; + + /** + * The date and time the interface VPC endpoint was created. + */ + public readonly vpcEndpointCreationTimestamp: string; + + /** + * The DNS entries for the interface VPC endpoint. + */ + public readonly dnsEntries: string[]; + + /** + * One or more network interfaces for the interface VPC endpoint. + */ + public readonly networkInterfaceIds: string[]; + + /** + * The identifier of the security group associated with this interface VPC + * endpoint. + */ + public readonly securityGroupId: string; + + /** + * Access to network connections. + */ + public readonly connections: Connections; + + private readonly port: number; + + constructor(scope: cdk.Construct, id: string, props: InterfaceVpcEndpointProps) { + super(scope, id); + + this.port = props.service.port; + const securityGroup = new SecurityGroup(this, 'SecurityGroup', { + vpc: props.vpc + }); + this.securityGroupId = securityGroup.securityGroupId; + this.connections = new Connections({ + defaultPortRange: new TcpPort(props.service.port), + securityGroups: [securityGroup] + }); + + const subnets = props.vpc.selectSubnets({ ...props.subnets, onePerAz: true }); + const subnetIds = subnets.subnetIds; + + const endpoint = new CfnVPCEndpoint(this, 'Resource', { + privateDnsEnabled: props.privateDnsEnabled || true, + policyDocument: new cdk.Token(() => this.policyDocument), + securityGroupIds: [this.securityGroupId], + serviceName: props.service.name, + vpcEndpointType: VpcEndpointType.Interface, + subnetIds, + vpcId: props.vpc.vpcId + }); + + this.vpcEndpointId = endpoint.vpcEndpointId; + this.vpcEndpointCreationTimestamp = endpoint.vpcEndpointCreationTimestamp; + this.dnsEntries = endpoint.vpcEndpointDnsEntries; + this.networkInterfaceIds = endpoint.vpcEndpointNetworkInterfaceIds; + } + + /** + * Exports this interface VPC endpoint from the stack. + */ + public export(): InterfaceVpcEndpointImportProps { + return { + vpcEndpointId: new cdk.CfnOutput(this, 'VpcEndpointId', { value: this.vpcEndpointId }).makeImportValue().toString(), + securityGroupId: new cdk.CfnOutput(this, 'SecurityGroupId', { value: this.securityGroupId }).makeImportValue().toString(), + port: new cdk.CfnOutput(this, 'port', { value: this.port }).makeImportValue().toString() + }; + } +} + +/** + * Construction properties for an ImportedInterfaceVpcEndpoint. + */ +export interface InterfaceVpcEndpointImportProps { + /** + * The interface VPC endpoint identifier. + */ + readonly vpcEndpointId: string; + + /** + * The identifier of the security group associated with the interface VPC endpoint. + */ + readonly securityGroupId: string; + + /** + * The port of the service of the interface VPC endpoint. + */ + readonly port: string; +} + +/** + * An imported VPC interface endpoint. + */ +class ImportedInterfaceVpcEndpoint extends cdk.Construct implements IInterfaceVpcEndpoint { + /** + * The interface VPC endpoint identifier. + */ + public readonly vpcEndpointId: string; + + /** + * The identifier of the security group associated with the interface VPC endpoint. + */ + public readonly securityGroupId: string; + + /** + * Access to network connections. + */ + public readonly connections: Connections; + + constructor(scope: cdk.Construct, id: string, private readonly props: InterfaceVpcEndpointImportProps) { + super(scope, id); + + this.vpcEndpointId = props.vpcEndpointId; + + this.securityGroupId = props.securityGroupId; + + this.connections = new Connections({ + defaultPortRange: new TcpPortFromAttribute(props.port), + securityGroups: [SecurityGroup.import(this, 'SecurityGroup', props)], + }); + } + + /** + * Exports this interface VPC endpoint from the stack. + */ + public export(): InterfaceVpcEndpointImportProps { + return this.props; + } +} diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts index 29e8b42977f71..2207287fad670 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts @@ -16,7 +16,7 @@ export interface VpcNetworkProviderProps { * * @default Don't filter on vpcId */ - vpcId?: string; + readonly vpcId?: string; /** * The name of the VPC @@ -25,7 +25,7 @@ export interface VpcNetworkProviderProps { * * @default Don't filter on vpcName */ - vpcName?: string; + readonly vpcName?: string; /** * Tags on the VPC @@ -34,14 +34,14 @@ export interface VpcNetworkProviderProps { * * @default Don't filter on tags */ - tags?: {[key: string]: string}; + readonly tags?: {[key: string]: string}; /** * Whether to match the default VPC * * @default Don't care whether we return the default VPC */ - isDefault?: boolean; + readonly isDefault?: boolean; } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index 8ac6acad7fd63..87dadb2ee7870 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -1,5 +1,6 @@ -import { Construct, IConstruct, IDependable } from "@aws-cdk/cdk"; -import { subnetName } from './util'; +import { Construct, IConstruct, IDependable } from '@aws-cdk/cdk'; +import { DEFAULT_SUBNET_NAME, subnetName } from './util'; +import { InterfaceVpcEndpoint, InterfaceVpcEndpointOptions } from './vpc-endpoint'; import { VpnConnection, VpnConnectionOptions } from './vpn'; export interface IVpcSubnet extends IConstruct { @@ -18,6 +19,11 @@ export interface IVpcSubnet extends IConstruct { */ readonly internetConnectivityEstablished: IDependable; + /** + * Route table ID + */ + readonly routeTableId?: string; + /** * Exports this subnet to another stack. */ @@ -61,24 +67,38 @@ export interface IVpcNetwork extends IConstruct { readonly vpnGatewayId?: string; /** - * Return the subnets appropriate for the placement strategy + * Return IDs of the subnets appropriate for the given selection strategy + * + * Requires that at least one subnet is matched, throws a descriptive + * error message otherwise. + * + * @deprecated Use selectSubnets() instead. */ - subnets(placement?: VpcPlacementStrategy): IVpcSubnet[]; + selectSubnetIds(selection?: SubnetSelection): string[]; /** - * Return whether the given subnet is one of this VPC's public subnets. + * Return information on the subnets appropriate for the given selection strategy * - * The subnet must literally be one of the subnet object obtained from - * this VPC. A subnet that merely represents the same subnet will - * never return true. + * Requires that at least one subnet is matched, throws a descriptive + * error message otherwise. */ - isPublicSubnet(subnet: IVpcSubnet): boolean; + selectSubnets(selection?: SubnetSelection): SelectedSubnets; + + /** + * Return whether all of the given subnets are from the VPC's public subnets. + */ + isPublicSubnets(subnetIds: string[]): boolean; /** * Adds a new VPN connection to this VPC */ addVpnConnection(id: string, options: VpnConnectionOptions): VpnConnection; + /** + * Adds a new interface endpoint to this VPC + */ + addInterfaceEndpoint(id: string, options: InterfaceVpcEndpointOptions): InterfaceVpcEndpoint + /** * Exports this VPC so it can be consumed by another stack. */ @@ -125,33 +145,65 @@ export enum SubnetType { } /** - * Customize how instances are placed inside a VPC + * Customize subnets that are selected for placement of ENIs * * Constructs that allow customization of VPC placement use parameters of this * type to provide placement settings. * * By default, the instances are placed in the private subnets. */ -export interface VpcPlacementStrategy { +export interface SubnetSelection { /** * Place the instances in the subnets of the given type * - * At most one of `subnetsToUse` and `subnetName` can be supplied. + * At most one of `subnetType` and `subnetName` can be supplied. * * @default SubnetType.Private */ - subnetsToUse?: SubnetType; + readonly subnetType?: SubnetType; /** * Place the instances in the subnets with the given name * * (This is the name supplied in subnetConfiguration). * - * At most one of `subnetsToUse` and `subnetName` can be supplied. + * At most one of `subnetType` and `subnetName` can be supplied. * * @default name */ - subnetName?: string; + readonly subnetName?: string; + + /** + * If true, return at most one subnet per AZ + * + * @defautl false + */ + readonly onePerAz?: boolean; +} + +/** + * Result of selecting a subset of subnets from a VPC + */ +export interface SelectedSubnets { + /** + * The subnet IDs + */ + readonly subnetIds: string[]; + + /** + * The respective AZs of each subnet + */ + readonly availabilityZones: string[]; + + /** + * Route table IDs of each respective subnet + */ + readonly routeTableIds: string[]; + + /** + * Dependency representing internet connectivity for these subnets + */ + readonly internetConnectedDependency: IDependable; } /** @@ -199,32 +251,22 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { */ public readonly natDependencies = new Array(); + public selectSubnetIds(selection?: SubnetSelection): string[] { + return this.selectSubnets(selection).subnetIds; + } + /** - * Return the subnets appropriate for the placement strategy + * Returns IDs of selected subnets */ - public subnets(placement: VpcPlacementStrategy = {}): IVpcSubnet[] { - if (placement.subnetsToUse !== undefined && placement.subnetName !== undefined) { - throw new Error('At most one of subnetsToUse and subnetName can be supplied'); - } - - // Select by name - if (placement.subnetName !== undefined) { - const allSubnets = this.privateSubnets.concat(this.publicSubnets).concat(this.isolatedSubnets); - const selectedSubnets = allSubnets.filter(s => subnetName(s) === placement.subnetName); - if (selectedSubnets.length === 0) { - throw new Error(`No subnets with name: ${placement.subnetName}`); - } - return selectedSubnets; - } - - // Select by type - if (placement.subnetsToUse === undefined) { return this.privateSubnets; } + public selectSubnets(selection: SubnetSelection = {}): SelectedSubnets { + const subnets = this.selectSubnetObjects(selection); return { - [SubnetType.Isolated]: this.isolatedSubnets, - [SubnetType.Private]: this.privateSubnets, - [SubnetType.Public]: this.publicSubnets, - }[placement.subnetsToUse]; + subnetIds: subnets.map(s => s.subnetId), + availabilityZones: subnets.map(s => s.availabilityZone), + routeTableIds: subnets.map(s => s.routeTableId).filter(notUndefined), // Possibly don't have this information + internetConnectedDependency: tap(new CompositeDependable(), d => subnets.forEach(s => d.add(s.internetConnectivityEstablished))), + }; } /** @@ -237,20 +279,27 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { }); } + /** + * Adds a new interface endpoint to this VPC + */ + public addInterfaceEndpoint(id: string, options: InterfaceVpcEndpointOptions): InterfaceVpcEndpoint { + return new InterfaceVpcEndpoint(this, id, { + vpc: this, + ...options + }); + } + /** * Export this VPC from the stack */ public abstract export(): VpcNetworkImportProps; /** - * Return whether the given subnet is one of this VPC's public subnets. - * - * The subnet must literally be one of the subnet object obtained from - * this VPC. A subnet that merely represents the same subnet will - * never return true. + * Return whether all of the given subnets are from the VPC's public subnets. */ - public isPublicSubnet(subnet: IVpcSubnet) { - return this.publicSubnets.indexOf(subnet) > -1; + public isPublicSubnets(subnetIds: string[]): boolean { + const pubIds = new Set(this.publicSubnets.map(n => n.subnetId)); + return subnetIds.every(pubIds.has.bind(pubIds)); } /** @@ -260,6 +309,35 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { return this.node.stack.region; } + /** + * Return the subnets appropriate for the placement strategy + */ + protected selectSubnetObjects(selection: SubnetSelection = {}): IVpcSubnet[] { + selection = reifySelectionDefaults(selection); + let subnets: IVpcSubnet[] = []; + + if (selection.subnetName !== undefined) { // Select by name + const allSubnets = [...this.publicSubnets, ...this.privateSubnets, ...this.isolatedSubnets]; + subnets = allSubnets.filter(s => subnetName(s) === selection.subnetName); + } else { // Select by type + subnets = { + [SubnetType.Isolated]: this.isolatedSubnets, + [SubnetType.Private]: this.privateSubnets, + [SubnetType.Public]: this.publicSubnets, + }[selection.subnetType || SubnetType.Private]; + + if (selection.onePerAz && subnets.length > 0) { + // Restrict to at most one subnet group + subnets = subnets.filter(s => subnetName(s) === subnetName(subnets[0])); + } + } + + if (subnets.length === 0) { + throw new Error(`There are no ${describeSelection(selection)} in this VPC. Use a different VPC subnet selection.`); + } + + return subnets; + } } /** @@ -269,69 +347,134 @@ export interface VpcNetworkImportProps { /** * VPC's identifier */ - vpcId: string; + readonly vpcId: string; /** * List of availability zones for the subnets in this VPC. */ - availabilityZones: string[]; + readonly availabilityZones: string[]; /** * List of public subnet IDs * * Must be undefined or match the availability zones in length and order. */ - publicSubnetIds?: string[]; + readonly publicSubnetIds?: string[]; /** * List of names for the public subnets * * Must be undefined or have a name for every public subnet group. */ - publicSubnetNames?: string[]; + readonly publicSubnetNames?: string[]; /** * List of private subnet IDs * * Must be undefined or match the availability zones in length and order. */ - privateSubnetIds?: string[]; + readonly privateSubnetIds?: string[]; /** * List of names for the private subnets * * Must be undefined or have a name for every private subnet group. */ - privateSubnetNames?: string[]; + readonly privateSubnetNames?: string[]; /** * List of isolated subnet IDs * * Must be undefined or match the availability zones in length and order. */ - isolatedSubnetIds?: string[]; + readonly isolatedSubnetIds?: string[]; /** * List of names for the isolated subnets * * Must be undefined or have a name for every isolated subnet group. */ - isolatedSubnetNames?: string[]; + readonly isolatedSubnetNames?: string[]; /** * VPN gateway's identifier */ - vpnGatewayId?: string; + readonly vpnGatewayId?: string; } export interface VpcSubnetImportProps { /** * The Availability Zone the subnet is located in */ - availabilityZone: string; + readonly availabilityZone: string; /** * The subnetId for this particular subnet */ - subnetId: string; + readonly subnetId: string; +} + +/** + * If the placement strategy is completely "default", reify the defaults so + * consuming code doesn't have to reimplement the same analysis every time. + * + * Returns "private subnets" by default. + */ +function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { + if (placement.subnetType !== undefined && placement.subnetName !== undefined) { + throw new Error('Only one of subnetType and subnetName can be supplied'); + } + + if (placement.subnetType === undefined && placement.subnetName === undefined) { + return { subnetType: SubnetType.Private, onePerAz: placement.onePerAz }; + } + + return placement; +} + +/** + * Describe the given placement strategy + */ +function describeSelection(placement: SubnetSelection): string { + if (placement.subnetType !== undefined) { + return `'${DEFAULT_SUBNET_NAME[placement.subnetType]}' subnets`; + } + if (placement.subnetName !== undefined) { + return `subnets named '${placement.subnetName}'`; + } + return JSON.stringify(placement); +} + +class CompositeDependable implements IDependable { + private readonly dependables = new Array(); + + /** + * Add a construct to the dependency roots + */ + public add(dep: IDependable) { + this.dependables.push(dep); + } + + /** + * Retrieve the current set of dependency roots + */ + public get dependencyRoots(): IConstruct[] { + const ret = []; + for (const dep of this.dependables) { + ret.push(...dep.dependencyRoots); + } + return ret; + } +} + +/** + * Invoke a function on a value (for its side effect) and return the value + */ +function tap(x: T, fn: (x: T) => void): T { + fn(x); + return x; +} + +function notUndefined(x: T | undefined): x is T { + return x !== undefined; } diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 3d74cb89479c1..e102b8a915067 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -4,8 +4,9 @@ import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnVPNGateway, Cfn import { CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCGatewayAttachment } from './ec2.generated'; import { NetworkBuilder } from './network-util'; import { DEFAULT_SUBNET_NAME, ExportSubnetGroup, ImportSubnetGroup, subnetId } from './util'; +import { GatewayVpcEndpoint, GatewayVpcEndpointAwsService, GatewayVpcEndpointOptions } from './vpc-endpoint'; import { VpcNetworkProvider, VpcNetworkProviderProps } from './vpc-network-provider'; -import { IVpcNetwork, IVpcSubnet, SubnetType, VpcNetworkBase, VpcNetworkImportProps, VpcPlacementStrategy, VpcSubnetImportProps } from './vpc-ref'; +import { IVpcNetwork, IVpcSubnet, SubnetSelection, SubnetType, VpcNetworkBase, VpcNetworkImportProps, VpcSubnetImportProps } from './vpc-ref'; import { VpnConnectionOptions, VpnConnectionType } from './vpn'; /** @@ -22,14 +23,14 @@ export interface VpcNetworkProps { * The CIDR range to use for the VPC (e.g. '10.0.0.0/16'). Should be a minimum of /28 and maximum size of /16. * The range will be split evenly into two subnets per Availability Zone (one public, one private). */ - cidr?: string; + readonly cidr?: string; /** * Indicates whether the instances launched in the VPC get public DNS hostnames. * If this attribute is true, instances in the VPC get public DNS hostnames, * but only if the enableDnsSupport attribute is also set to true. */ - enableDnsHostnames?: boolean; + readonly enableDnsHostnames?: boolean; /** * Indicates whether the DNS resolution is supported for the VPC. If this attribute @@ -38,7 +39,7 @@ export interface VpcNetworkProps { * provided DNS server at the 169.254.169.253 IP address, or the reserved IP address * at the base of the VPC IPv4 network range plus two will succeed. */ - enableDnsSupport?: boolean; + readonly enableDnsSupport?: boolean; /** * The default tenancy of instances launched into the VPC. @@ -47,7 +48,7 @@ export interface VpcNetworkProps { * to a single AWS customer, unless specifically specified at instance launch time. * Please note, not all instance types are usable with Dedicated tenancy. */ - defaultInstanceTenancy?: DefaultInstanceTenancy; + readonly defaultInstanceTenancy?: DefaultInstanceTenancy; /** * Define the maximum number of AZs to use in this region @@ -61,7 +62,7 @@ export interface VpcNetworkProps { * * @default 3 */ - maxAZs?: number; + readonly maxAZs?: number; /** * The number of NAT Gateways to create. @@ -70,7 +71,7 @@ export interface VpcNetworkProps { * one of the Public subnets will have a gateway and all Private subnets will route to this NAT Gateway. * @default maxAZs */ - natGateways?: number; + readonly natGateways?: number; /** * Configures the subnets which will have NAT Gateways @@ -80,7 +81,7 @@ export interface VpcNetworkProps { * * @default All public subnets */ - natGatewayPlacement?: VpcPlacementStrategy; + readonly natGatewaySubnets?: SubnetSelection; /** * Configure the subnets to build for each AZ @@ -115,35 +116,40 @@ export interface VpcNetworkProps { * @default the VPC CIDR will be evenly divided between 1 public and 1 * private subnet per AZ */ - subnetConfiguration?: SubnetConfiguration[]; + readonly subnetConfiguration?: SubnetConfiguration[]; /** * Indicates whether a VPN gateway should be created and attached to this VPC. * * @default true when vpnGatewayAsn or vpnConnections is specified. */ - vpnGateway?: boolean; + readonly vpnGateway?: boolean; /** * The private Autonomous System Number (ASN) for the VPN gateway. * * @default Amazon default ASN */ - vpnGatewayAsn?: number; + readonly vpnGatewayAsn?: number; /** * VPN connections to this VPC. * * @default no connections */ - vpnConnections?: { [id: string]: VpnConnectionOptions } + readonly vpnConnections?: { [id: string]: VpnConnectionOptions } /** * Where to propagate VPN routes. * * @default on the route tables associated with private subnets */ - vpnRoutePropagation?: SubnetType[] + readonly vpnRoutePropagation?: SubnetSelection[] + + /** + * Gateway endpoints to add to this VPC. + */ + readonly gatewayEndpoints?: { [id: string]: GatewayVpcEndpointOptions } } /** @@ -170,7 +176,7 @@ export interface SubnetConfiguration { * * Valid values are 16 - 28 */ - cidrMask?: number; + readonly cidrMask?: number; /** * The type of Subnet to configure. @@ -178,15 +184,27 @@ export interface SubnetConfiguration { * The Subnet type will control the ability to route and connect to the * Internet. */ - subnetType: SubnetType; + readonly subnetType: SubnetType; /** * The common Logical Name for the `VpcSubnet` * - * Thi name will be suffixed with an integer correlating to a specific + * This name will be suffixed with an integer correlating to a specific * availability zone. */ - name: string; + readonly name: string; + + /** + * Controls if subnet IP space needs to be reserved. + * + * When true, the IP space for the subnet is reserved but no actual + * resources are provisioned. This space is only dependent on the + * number of availibility zones and on `cidrMask` - all other subnet + * properties are ignored. + * + * @default false + */ + readonly reserved?: boolean; } /** @@ -365,7 +383,7 @@ export class VpcNetwork extends VpcNetworkBase { }); // if gateways are needed create them - this.createNatGateways(props.natGateways, props.natGatewayPlacement); + this.createNatGateways(props.natGateways, props.natGatewaySubnets); (this.privateSubnets as VpcPrivateSubnet[]).forEach((privateSubnet, i) => { let ngwId = this.natGatewayByAZ[privateSubnet.availabilityZone]; @@ -396,19 +414,10 @@ export class VpcNetwork extends VpcNetworkBase { this.vpnGatewayId = vpnGateway.vpnGatewayName; // Propagate routes on route tables associated with the right subnets - const vpnRoutePropagation = props.vpnRoutePropagation || [SubnetType.Private]; - let subnets: IVpcSubnet[] = []; - if (vpnRoutePropagation.includes(SubnetType.Public)) { - subnets = [...subnets, ...this.publicSubnets]; - } - if (vpnRoutePropagation.includes(SubnetType.Private)) { - subnets = [...subnets, ...this.privateSubnets]; - } - if (vpnRoutePropagation.includes(SubnetType.Isolated)) { - subnets = [...subnets, ...this.isolatedSubnets]; - } + const vpnRoutePropagation = props.vpnRoutePropagation || [{ subnetType: SubnetType.Private }]; + const routeTableIds = [...new Set(Array().concat(...vpnRoutePropagation.map(s => this.selectSubnets(s).routeTableIds)))]; const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', { - routeTableIds: (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId), + routeTableIds, vpnGatewayId: this.vpnGatewayId }); @@ -422,6 +431,48 @@ export class VpcNetwork extends VpcNetworkBase { this.addVpnConnection(connectionId, connection); } } + + // Allow creation of gateway endpoints on VPC instantiation as those can be + // immediately functional without further configuration. This is not the case + // for interface endpoints where the security group must be configured. + if (props.gatewayEndpoints) { + const gatewayEndpoints = props.gatewayEndpoints || {}; + for (const [endpointId, endpoint] of Object.entries(gatewayEndpoints)) { + this.addGatewayEndpoint(endpointId, endpoint); + } + } + } + + /** + * Adds a new gateway endpoint to this VPC + */ + public addGatewayEndpoint(id: string, options: GatewayVpcEndpointOptions): GatewayVpcEndpoint { + return new GatewayVpcEndpoint(this, id, { + vpc: this, + ...options + }); + } + + /** + * Adds a new S3 gateway endpoint to this VPC + */ + public addS3Endpoint(id: string, subnets?: SubnetSelection[]): GatewayVpcEndpoint { + return new GatewayVpcEndpoint(this, id, { + service: GatewayVpcEndpointAwsService.S3, + vpc: this, + subnets + }); + } + + /** + * Adds a new DynamoDB gateway endpoint to this VPC + */ + public addDynamoDbEndpoint(id: string, subnets?: SubnetSelection[]): GatewayVpcEndpoint { + return new GatewayVpcEndpoint(this, id, { + service: GatewayVpcEndpointAwsService.DynamoDb, + vpc: this, + subnets + }); } /** @@ -432,9 +483,13 @@ export class VpcNetwork extends VpcNetworkBase { const priv = new ExportSubnetGroup(this, 'PrivateSubnetIDs', this.privateSubnets, SubnetType.Private, this.availabilityZones.length); const iso = new ExportSubnetGroup(this, 'IsolatedSubnetIDs', this.isolatedSubnets, SubnetType.Isolated, this.availabilityZones.length); + const vpnGatewayId = this.vpnGatewayId + ? new cdk.CfnOutput(this, 'VpnGatewayId', { value: this.vpnGatewayId }).makeImportValue().toString() + : undefined; + return { vpcId: new cdk.CfnOutput(this, 'VpcId', { value: this.vpcId }).makeImportValue().toString(), - vpnGatewayId: new cdk.CfnOutput(this, 'VpnGatewayId', { value: this.vpnGatewayId }).makeImportValue().toString(), + vpnGatewayId, availabilityZones: this.availabilityZones, publicSubnetIds: pub.ids, publicSubnetNames: pub.names, @@ -445,7 +500,7 @@ export class VpcNetwork extends VpcNetworkBase { }; } - private createNatGateways(gateways?: number, placement?: VpcPlacementStrategy): void { + private createNatGateways(gateways?: number, placement?: SubnetSelection): void { const useNatGateway = this.subnetConfiguration.filter( subnet => (subnet.subnetType === SubnetType.Private)).length > 0; @@ -454,9 +509,9 @@ export class VpcNetwork extends VpcNetworkBase { let natSubnets: VpcPublicSubnet[]; if (placement) { - const subnets = this.subnets(placement); + const subnets = this.selectSubnetObjects(placement); for (const sub of subnets) { - if (!this.isPublicSubnet(sub)) { + if (this.publicSubnets.indexOf(sub) === -1) { throw new Error(`natGatewayPlacement ${placement} contains non public subnet ${sub}`); } } @@ -501,6 +556,12 @@ export class VpcNetwork extends VpcNetworkBase { private createSubnetResources(subnetConfig: SubnetConfiguration, cidrMask: number) { this.availabilityZones.forEach((zone, index) => { + if (subnetConfig.reserved === true) { + // For reserved subnets, just allocate ip space but do not create any resources + this.networkBuilder.addSubnet(cidrMask); + return; + } + const name = subnetId(subnetConfig.name, index); const subnetProps: VpcSubnetProps = { availabilityZone: zone, @@ -557,24 +618,24 @@ export interface VpcSubnetProps { /** * The availability zone for the subnet */ - availabilityZone: string; + readonly availabilityZone: string; /** * The VPC which this subnet is part of */ - vpcId: string; + readonly vpcId: string; /** * The CIDR notation for this subnet */ - cidrBlock: string; + readonly cidrBlock: string; /** * Controls if a public IP is associated to an instance at launch * * Defaults to true in Subnet.Public, false in Subnet.Private or Subnet.Isolated. */ - mapPublicIpOnLaunch?: boolean; + readonly mapPublicIpOnLaunch?: boolean; } /** @@ -603,7 +664,7 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet { /** * The routeTableId attached to this subnet. */ - public readonly routeTableId: string; + public readonly routeTableId?: string; private readonly internetDependencies = new ConcreteDependable(); @@ -644,7 +705,7 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet { protected addDefaultRouteToNAT(natGatewayId: string) { const route = new CfnRoute(this, `DefaultRoute`, { - routeTableId: this.routeTableId, + routeTableId: this.routeTableId!, destinationCidrBlock: '0.0.0.0/0', natGatewayId }); @@ -659,7 +720,7 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet { gateway: CfnInternetGateway, gatewayAttachment: CfnVPCGatewayAttachment) { const route = new CfnRoute(this, `DefaultRoute`, { - routeTableId: this.routeTableId, + routeTableId: this.routeTableId!, destinationCidrBlock: '0.0.0.0/0', gatewayId: gateway.ref }); @@ -762,6 +823,7 @@ class ImportedVpcSubnet extends cdk.Construct implements IVpcSubnet { public readonly internetConnectivityEstablished: cdk.IDependable = new cdk.ConcreteDependable(); public readonly availabilityZone: string; public readonly subnetId: string; + public readonly routeTableId?: string = undefined; constructor(scope: cdk.Construct, id: string, private readonly props: VpcSubnetImportProps) { super(scope, id); diff --git a/packages/@aws-cdk/aws-ec2/lib/vpn.ts b/packages/@aws-cdk/aws-ec2/lib/vpn.ts index bed2b9cdc5d98..22e31897a7ee7 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpn.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpn.ts @@ -34,7 +34,7 @@ export interface VpnTunnelOption { * * @default an Amazon generated pre-shared key */ - preSharedKey?: string; + readonly preSharedKey?: string; /** * The range of inside IP addresses for the tunnel. Any specified CIDR blocks must be @@ -43,28 +43,28 @@ export interface VpnTunnelOption { * * @default an Amazon generated inside IP CIDR */ - tunnelInsideCidr?: string; + readonly tunnelInsideCidr?: string; } export interface VpnConnectionOptions { /** * The ip address of the customer gateway. */ - ip: string; + readonly ip: string; /** * The ASN of the customer gateway. * * @default 65000 */ - asn?: number; + readonly asn?: number; /** * The static routes to be routed from the VPN gateway to the customer gateway. * * @default Dynamic routing (BGP) */ - staticRoutes?: string[]; + readonly staticRoutes?: string[]; /** * The tunnel options for the VPN connection. At most two elements (one per tunnel). @@ -72,14 +72,14 @@ export interface VpnConnectionOptions { * * @default Amazon generated tunnel options */ - tunnelOptions?: VpnTunnelOption[]; + readonly tunnelOptions?: VpnTunnelOption[]; } export interface VpnConnectionProps extends VpnConnectionOptions { /** * The VPC to connect to. */ - vpc: IVpcNetwork; + readonly vpc: IVpcNetwork; } /** diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index 1943efc853c03..b098e7205dbff 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ec2", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS EC2", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-ec2", + "module": "aws_cdk.aws_ec2" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-ec2" }, "scripts": { "build": "cdk-build", @@ -54,23 +59,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -80,4 +86,4 @@ "resource-attribute:@aws-cdk/aws-ec2.ISecurityGroup.securityGroupVpcId" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts index 981929957583c..9208c3eaf09bd 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts @@ -18,7 +18,7 @@ new ec2.SecurityGroup(stack, 'SecurityGroup', { }); // Try subnet selection -new cdk.CfnOutput(stack, 'PublicSubnets', { value: 'ids:' + vpc.subnets({ subnetsToUse: ec2.SubnetType.Public }).map(s => s.subnetId).join(',') }); -new cdk.CfnOutput(stack, 'PrivateSubnets', { value: 'ids:' + vpc.subnets({ subnetsToUse: ec2.SubnetType.Private }).map(s => s.subnetId).join(',') }); +new cdk.CfnOutput(stack, 'PublicSubnets', { value: 'ids:' + vpc.publicSubnets.map(s => s.subnetId).join(',') }); +new cdk.CfnOutput(stack, 'PrivateSubnets', { value: 'ids:' + vpc.privateSubnets.map(s => s.subnetId).join(',') }); app.run(); diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.expected.json new file mode 100644 index 0000000000000..e0a19805633f5 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.expected.json @@ -0,0 +1,650 @@ +{ + "Resources": { + "MyVpcF9F0CA6F": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc" + } + ] + } + }, + "MyVpcPublicSubnet1SubnetF6608456": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableC46AB2F4": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + } + } + }, + "MyVpcPublicSubnet1DefaultRoute95FDF9EB": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet1EIP096967CB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "MyVpcPublicSubnet1NATGatewayAD3400C1": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet1EIP096967CB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet1SubnetF6608456" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet1" + } + ] + } + }, + "MyVpcPublicSubnet2Subnet492B6BFB": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTable1DF17386": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet2RouteTableAssociation227DE78D": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + } + } + }, + "MyVpcPublicSubnet2DefaultRoute052936F6": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet2RouteTable1DF17386" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet2EIP8CCBA239": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "MyVpcPublicSubnet2NATGateway91BFBEC9": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet2EIP8CCBA239", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet2Subnet492B6BFB" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet2" + } + ] + } + }, + "MyVpcPublicSubnet3Subnet57EEE236": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "MyVpcPublicSubnet3RouteTable15028F08": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet3" + } + ] + } + }, + "MyVpcPublicSubnet3RouteTableAssociation5C27DDA4": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + } + } + }, + "MyVpcPublicSubnet3DefaultRoute3A83AB36": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPublicSubnet3RouteTable15028F08" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + }, + "DependsOn": [ + "MyVpcVPCGW488ACE0D" + ] + }, + "MyVpcPublicSubnet3EIPC5ACADAB": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "MyVpcPublicSubnet3NATGatewayD4B50EBE": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "MyVpcPublicSubnet3EIPC5ACADAB", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "MyVpcPublicSubnet3Subnet57EEE236" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PublicSubnet3" + } + ] + } + }, + "MyVpcPrivateSubnet1Subnet5057CF7E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTable8819E6E2": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet1" + } + ] + } + }, + "MyVpcPrivateSubnet1RouteTableAssociation56D38C7E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + } + } + }, + "MyVpcPrivateSubnet1DefaultRouteA8CDE2FA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet1NATGatewayAD3400C1" + } + } + }, + "MyVpcPrivateSubnet2Subnet0040C983": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableCEDCEECE": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet2" + } + ] + } + }, + "MyVpcPrivateSubnet2RouteTableAssociation86A610DA": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + } + } + }, + "MyVpcPrivateSubnet2DefaultRoute9CE96294": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet2NATGateway91BFBEC9" + } + } + }, + "MyVpcPrivateSubnet3Subnet772D6AD7": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "MyVpcPrivateSubnet3RouteTableB790927C": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc/PrivateSubnet3" + } + ] + } + }, + "MyVpcPrivateSubnet3RouteTableAssociationD951741C": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + }, + "SubnetId": { + "Ref": "MyVpcPrivateSubnet3Subnet772D6AD7" + } + } + }, + "MyVpcPrivateSubnet3DefaultRouteEC11C0C5": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "MyVpcPublicSubnet3NATGatewayD4B50EBE" + } + } + }, + "MyVpcIGW5C4A4F63": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc" + } + ] + } + }, + "MyVpcVPCGW488ACE0D": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "InternetGatewayId": { + "Ref": "MyVpcIGW5C4A4F63" + } + } + }, + "MyVpcS3FADC1889": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + "Ref": "AWS::Region" + }, + ".s3" + ] + ] + }, + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "RouteTableIds": [ + { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + } + ], + "VpcEndpointType": "Gateway" + } + }, + "MyVpcDynamoDbEndpointE6A39B0D": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + "Ref": "AWS::Region" + }, + ".dynamodb" + ] + ] + }, + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:DescribeTable", + "dynamodb:ListTables" + ], + "Effect": "Allow", + "Principal": "*", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "RouteTableIds": [ + { + "Ref": "MyVpcPrivateSubnet1RouteTable8819E6E2" + }, + { + "Ref": "MyVpcPrivateSubnet2RouteTableCEDCEECE" + }, + { + "Ref": "MyVpcPrivateSubnet3RouteTableB790927C" + } + ], + "VpcEndpointType": "Gateway" + } + }, + "MyVpcEcrDockerEndpointSecurityGroup47BB9CC1": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-ec2-vpc-endpoint/MyVpc/EcrDockerEndpoint/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "from 0.0.0.0/0:443", + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-ec2-vpc-endpoint/MyVpc" + } + ], + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + } + } + }, + "MyVpcEcrDockerEndpoint0385050C": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + "Ref": "AWS::Region" + }, + ".ecr.dkr" + ] + ] + }, + "VpcId": { + "Ref": "MyVpcF9F0CA6F" + }, + "PrivateDnsEnabled": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "MyVpcEcrDockerEndpointSecurityGroup47BB9CC1", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "MyVpcPrivateSubnet1Subnet5057CF7E" + }, + { + "Ref": "MyVpcPrivateSubnet2Subnet0040C983" + }, + { + "Ref": "MyVpcPrivateSubnet3Subnet772D6AD7" + } + ], + "VpcEndpointType": "Interface" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts new file mode 100644 index 0000000000000..4e9dca508c896 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.vpc-endpoint.lit.ts @@ -0,0 +1,47 @@ +import iam = require('@aws-cdk/aws-iam'); +import cdk = require('@aws-cdk/cdk'); +import ec2 = require('../lib'); + +const app = new cdk.App(); + +class VpcEndpointStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + /// !show + // Add gateway endpoints when creating the VPC + const vpc = new ec2.VpcNetwork(this, 'MyVpc', { + gatewayEndpoints: { + S3: { + service: ec2.GatewayVpcEndpointAwsService.S3 + } + } + }); + + // Alternatively gateway endpoints can be added on the VPC + const dynamoDbEndpoint = vpc.addGatewayEndpoint('DynamoDbEndpoint', { + service: ec2.GatewayVpcEndpointAwsService.DynamoDb + }); + + // This allows to customize the endpoint policy + dynamoDbEndpoint.addToPolicy( + new iam.PolicyStatement() // Restrict to listing and describing tables + .addAnyPrincipal() + .addActions('dynamodb:DescribeTable', 'dynamodb:ListTables') + .addAllResources() + ); + + // Add an interface endpoint + const ecrDockerEndpoint = vpc.addInterfaceEndpoint('EcrDockerEndpoint', { + service: ec2.InterfaceVpcEndpointAwsService.EcrDocker + }); + + // When working with an interface endpoint, use the connections object to + // allow traffic to flow to the endpoint. + ecrDockerEndpoint.connections.allowDefaultPortFromAnyIpv4(); + /// !hide + } +} + +new VpcEndpointStack(app, 'aws-cdk-ec2-vpc-endpoint'); +app.run(); diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts new file mode 100644 index 0000000000000..6de8f2f83f38f --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc-endpoint.ts @@ -0,0 +1,338 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import { PolicyStatement } from '@aws-cdk/aws-iam'; +import { Stack } from '@aws-cdk/cdk'; +import { Test } from 'nodeunit'; +// tslint:disable-next-line:max-line-length +import { GatewayVpcEndpoint, GatewayVpcEndpointAwsService, InterfaceVpcEndpoint, InterfaceVpcEndpointAwsService, SubnetType, VpcNetwork } from '../lib'; + +export = { + 'gateway endpoint': { + 'add an endpoint to a vpc'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new VpcNetwork(stack, 'VpcNetwork', { + gatewayEndpoints: { + S3: { + service: GatewayVpcEndpointAwsService.S3 + } + } + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.s3' + ] + ] + }, + VpcId: { + Ref: 'VpcNetworkB258E83A' + }, + RouteTableIds: [ + { + Ref: 'VpcNetworkPrivateSubnet1RouteTableCD085FF1' + }, + { + Ref: 'VpcNetworkPrivateSubnet2RouteTableE97B328B' + }, + { + Ref: 'VpcNetworkPrivateSubnet3RouteTableE0C661A2' + } + ], + VpcEndpointType: 'Gateway' + })); + + test.done(); + }, + + 'routing on private and public subnets'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + new VpcNetwork(stack, 'VpcNetwork', { + gatewayEndpoints: { + S3: { + service: GatewayVpcEndpointAwsService.S3, + subnets: [ + { + subnetType: SubnetType.Public + }, + { + subnetType: SubnetType.Private + } + ] + } + } + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.s3' + ] + ] + }, + VpcId: { + Ref: 'VpcNetworkB258E83A' + }, + RouteTableIds: [ + { + Ref: 'VpcNetworkPublicSubnet1RouteTable25CCC53F' + }, + { + Ref: 'VpcNetworkPublicSubnet2RouteTableE5F348DF' + }, + { + Ref: 'VpcNetworkPublicSubnet3RouteTable36E30B07' + }, + { + Ref: 'VpcNetworkPrivateSubnet1RouteTableCD085FF1' + }, + { + Ref: 'VpcNetworkPrivateSubnet2RouteTableE97B328B' + }, + { + Ref: 'VpcNetworkPrivateSubnet3RouteTableE0C661A2' + } + ], + VpcEndpointType: 'Gateway' + })); + + test.done(); + }, + + 'add statements to policy'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + const endpoint = vpc.addGatewayEndpoint('S3', { + service: GatewayVpcEndpointAwsService.S3 + }); + + // WHEN + endpoint.addToPolicy( + new PolicyStatement() + .addAnyPrincipal() + .addActions('s3:GetObject', 's3:ListBucket') + .addAllResources() + ); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + PolicyDocument: { + Statement: [ + { + Action: [ + 's3:GetObject', + 's3:ListBucket' + ], + Effect: 'Allow', + Principal: '*', + Resource: '*' + } + ], + Version: '2012-10-17' + } + })); + + test.done(); + }, + + 'throws when adding a statement without a principal'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + const endpoint = vpc.addGatewayEndpoint('S3', { + service: GatewayVpcEndpointAwsService.S3 + }); + + // THEN + test.throws(() => endpoint.addToPolicy( + new PolicyStatement() + .addActions('s3:GetObject', 's3:ListBucket') + .addAllResources() + ), /`Principal`/); + + test.done(); + }, + + 'import/export'(test: Test) { + // GIVEN + const stack1 = new Stack(); + const stack2 = new Stack(); + const vpc = new VpcNetwork(stack1, 'Vpc1'); + const endpoint = vpc.addGatewayEndpoint('DynamoDB', { + service: GatewayVpcEndpointAwsService.DynamoDb + }); + + // WHEN + GatewayVpcEndpoint.import(stack2, 'ImportedEndpoint', endpoint.export()); + + // THEN: No error + test.done(); + }, + + 'conveniance methods for S3 and DynamoDB'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + + // WHEN + vpc.addS3Endpoint('S3'); + vpc.addDynamoDbEndpoint('DynamoDb'); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.s3' + ] + ] + }, + })); + + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.dynamodb' + ] + ] + }, + })); + + test.done(); + }, + + 'throws with an imported vpc'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = VpcNetwork.import(stack, 'VPC', { + vpcId: 'id', + privateSubnetIds: ['1', '2', '3'], + availabilityZones: ['a', 'b', 'c'] + }); + + // THEN + test.throws(() => new GatewayVpcEndpoint(stack, 'Gateway', { + service: GatewayVpcEndpointAwsService.S3, + vpc + }), /route table/); + + test.done(); + } + }, + + 'interface endpoint': { + 'add an endpoint to a vpc'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork'); + + // WHEN + vpc.addInterfaceEndpoint('EcrDocker', { + service: InterfaceVpcEndpointAwsService.EcrDocker + }); + + // THEN + expect(stack).to(haveResource('AWS::EC2::VPCEndpoint', { + ServiceName: { + 'Fn::Join': [ + '', + [ + 'com.amazonaws.', + { + Ref: 'AWS::Region' + }, + '.ecr.dkr' + ] + ] + }, + VpcId: { + Ref: 'VpcNetworkB258E83A' + }, + PrivateDnsEnabled: true, + SecurityGroupIds: [ + { + 'Fn::GetAtt': [ + 'VpcNetworkEcrDockerSecurityGroup7C91D347', + 'GroupId' + ] + } + ], + SubnetIds: [ + { + Ref: 'VpcNetworkPrivateSubnet1Subnet07BA143B' + }, + { + Ref: 'VpcNetworkPrivateSubnet2Subnet5E4189D6' + }, + { + Ref: 'VpcNetworkPrivateSubnet3Subnet5D16E0FB' + } + ], + VpcEndpointType: 'Interface' + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + GroupDescription: 'VpcNetwork/EcrDocker/SecurityGroup', + VpcId: { + Ref: 'VpcNetworkB258E83A' + } + })); + + test.done(); + }, + + 'import/export'(test: Test) { + // GIVEN + const stack1 = new Stack(); + const stack2 = new Stack(); + const vpc = new VpcNetwork(stack1, 'Vpc1'); + const endpoint = vpc.addInterfaceEndpoint('EC2', { + service: InterfaceVpcEndpointAwsService.Ec2 + }); + + // WHEN + const importedEndpoint = InterfaceVpcEndpoint.import(stack2, 'ImportedEndpoint', endpoint.export()); + importedEndpoint.connections.allowDefaultPortFromAnyIpv4(); + + // THEN + expect(stack2).to(haveResource('AWS::EC2::SecurityGroupIngress', { + GroupId: { + 'Fn::ImportValue': 'Stack:Vpc1EC2SecurityGroupId3B169C3F' + } + })); + + test.done(); + } + } +}; diff --git a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts index 23a3b79056506..4b384638a46f9 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.vpc.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.vpc.ts @@ -115,7 +115,75 @@ export = { test.done(); }, - "with custom subents, the VPC should have the right number of subnets, an IGW, and a NAT Gateway per AZ"(test: Test) { + "with subnets and reserved subnets defined, VPC subnet count should not contain reserved subnets "(test: Test) { + const stack = getTestStack(); + new VpcNetwork(stack, 'TheVPC', { + cidr: '10.0.0.0/16', + subnetConfiguration: [ + { + cidrMask: 24, + subnetType: SubnetType.Private, + name: 'Private', + }, + { + cidrMask: 24, + name: 'reserved', + subnetType: SubnetType.Private, + reserved: true, + }, + { + cidrMask: 28, + name: 'rds', + subnetType: SubnetType.Isolated, + } + ], + maxAZs: 3 + }); + expect(stack).to(countResources("AWS::EC2::Subnet", 6)); + test.done(); + }, + "with reserved subnets, any other subnets should not have cidrBlock from within reserved space"(test: Test) { + const stack = getTestStack(); + new VpcNetwork(stack, 'TheVPC', { + cidr: '10.0.0.0/16', + subnetConfiguration: [ + { + cidrMask: 24, + name: 'ingress', + subnetType: SubnetType.Private, + }, + { + cidrMask: 24, + name: 'reserved', + subnetType: SubnetType.Private, + reserved: true, + }, + { + cidrMask: 24, + name: 'rds', + subnetType: SubnetType.Private, + } + ], + maxAZs: 3 + }); + for (let i = 0; i < 3; i++) { + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i}.0/24` + })); + } + for (let i = 3; i < 6; i++) { + expect(stack).notTo(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i}.0/24` + })); + } + for (let i = 6; i < 9; i++) { + expect(stack).to(haveResource("AWS::EC2::Subnet", { + CidrBlock: `10.0.${i}.0/24` + })); + } + test.done(); + }, + "with custom subnets, the VPC should have the right number of subnets, an IGW, and a NAT Gateway per AZ"(test: Test) { const stack = getTestStack(); const zones = new AvailabilityZoneProvider(stack).availabilityZones.length; new VpcNetwork(stack, 'TheVPC', { @@ -289,7 +357,7 @@ export = { subnetType: SubnetType.Private, }, ], - natGatewayPlacement: { + natGatewaySubnets: { subnetName: 'egress' }, }); @@ -317,7 +385,7 @@ export = { subnetType: SubnetType.Private, }, ], - natGatewayPlacement: { + natGatewaySubnets: { subnetName: 'notthere', }, })); @@ -371,7 +439,11 @@ export = { { subnetType: SubnetType.Isolated, name: 'Isolated' }, ], vpnGateway: true, - vpnRoutePropagation: [SubnetType.Isolated] + vpnRoutePropagation: [ + { + subnetType: SubnetType.Isolated + } + ] }); expect(stack).to(haveResource('AWS::EC2::VPNGatewayRoutePropagation', { @@ -402,8 +474,12 @@ export = { ], vpnGateway: true, vpnRoutePropagation: [ - SubnetType.Private, - SubnetType.Isolated + { + subnetType: SubnetType.Private + }, + { + subnetType: SubnetType.Isolated + } ] }); @@ -526,55 +602,106 @@ export = { }, }, - 'can select public subnets'(test: Test) { - // GIVEN - const stack = getTestStack(); - const vpc = new VpcNetwork(stack, 'VPC'); + 'subnet selection': { + 'selecting default subnets returns the private ones'(test: Test) { + // GIVEN + const stack = getTestStack(); + const vpc = new VpcNetwork(stack, 'VPC'); - // WHEN - const nets = vpc.subnets({ subnetsToUse: SubnetType.Public }); + // WHEN + const { subnetIds } = vpc.selectSubnets(); - // THEN - test.deepEqual(nets, vpc.publicSubnets); + // THEN + test.deepEqual(subnetIds, vpc.privateSubnets.map(s => s.subnetId)); + test.done(); + }, - test.done(); - }, + 'can select public subnets'(test: Test) { + // GIVEN + const stack = getTestStack(); + const vpc = new VpcNetwork(stack, 'VPC'); - 'can select isolated subnets'(test: Test) { - // GIVEN - const stack = getTestStack(); - const vpc = new VpcNetwork(stack, 'VPC', { - subnetConfiguration: [ - { subnetType: SubnetType.Private, name: 'Private' }, - { subnetType: SubnetType.Isolated, name: 'Isolated' }, - ] - }); + // WHEN + const { subnetIds } = vpc.selectSubnets({ subnetType: SubnetType.Public }); - // WHEN - const nets = vpc.subnets({ subnetsToUse: SubnetType.Isolated }); + // THEN + test.deepEqual(subnetIds, vpc.publicSubnets.map(s => s.subnetId)); - // THEN - test.deepEqual(nets, vpc.isolatedSubnets); + test.done(); + }, - test.done(); - }, + 'can select isolated subnets'(test: Test) { + // GIVEN + const stack = getTestStack(); + const vpc = new VpcNetwork(stack, 'VPC', { + subnetConfiguration: [ + { subnetType: SubnetType.Private, name: 'Private' }, + { subnetType: SubnetType.Isolated, name: 'Isolated' }, + ] + }); + + // WHEN + const { subnetIds } = vpc.selectSubnets({ subnetType: SubnetType.Isolated }); + + // THEN + test.deepEqual(subnetIds, vpc.isolatedSubnets.map(s => s.subnetId)); + + test.done(); + }, - 'can select subnets by name'(test: Test) { - // GIVEN - const stack = getTestStack(); - const vpc = new VpcNetwork(stack, 'VPC', { - subnetConfiguration: [ - { subnetType: SubnetType.Private, name: 'DontTalkToMe' }, - { subnetType: SubnetType.Isolated, name: 'DontTalkAtAll' }, - ] - }); - - // WHEN - const nets = vpc.subnets({ subnetName: 'DontTalkToMe' }); - - // THEN - test.deepEqual(nets, vpc.privateSubnets); - test.done(); + 'can select subnets by name'(test: Test) { + // GIVEN + const stack = getTestStack(); + const vpc = new VpcNetwork(stack, 'VPC', { + subnetConfiguration: [ + { subnetType: SubnetType.Private, name: 'DontTalkToMe' }, + { subnetType: SubnetType.Isolated, name: 'DontTalkAtAll' }, + ] + }); + + // WHEN + const { subnetIds } = vpc.selectSubnets({ subnetName: 'DontTalkToMe' }); + + // THEN + test.deepEqual(subnetIds, vpc.privateSubnets.map(s => s.subnetId)); + test.done(); + }, + + 'selecting default subnets in a VPC with only public subnets throws an error'(test: Test) { + // GIVEN + const stack = new Stack(); + const vpc = VpcNetwork.import(stack, 'VPC', { + vpcId: 'vpc-1234', + availabilityZones: ['dummy1a', 'dummy1b', 'dummy1c'], + publicSubnetIds: ['pub-1', 'pub-2', 'pub-3'], + }); + + test.throws(() => { + vpc.selectSubnets(); + }, /There are no 'Private' subnets in this VPC/); + + test.done(); + }, + + 'select subnets with az restriction'(test: Test) { + // GIVEN + const stack = getTestStack(); + const vpc = new VpcNetwork(stack, 'VpcNetwork', { + maxAZs: 1, + subnetConfiguration: [ + {name: 'app', subnetType: SubnetType.Private }, + {name: 'db', subnetType: SubnetType.Private }, + ] + }); + + // WHEN + const { subnetIds } = vpc.selectSubnets({ onePerAz: true }); + + // THEN + test.deepEqual(subnetIds.length, 1); + test.deepEqual(subnetIds[0], vpc.privateSubnets[0].subnetId); + test.done(); + } }, 'export/import': { @@ -634,11 +761,11 @@ export = { }); // WHEN - const nets = importedVpc.subnets({ subnetsToUse: SubnetType.Isolated }); + const { subnetIds } = importedVpc.selectSubnets({ subnetType: SubnetType.Isolated }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(nets, importedVpc.isolatedSubnets); + test.deepEqual(subnetIds, importedVpc.isolatedSubnets.map(s => s.subnetId)); test.done(); }, @@ -657,16 +784,17 @@ export = { }); // WHEN - const nets = importedVpc.subnets({ subnetName: isolatedName }); + const { subnetIds } = importedVpc.selectSubnets({ subnetName: isolatedName }); // THEN test.equal(3, importedVpc.isolatedSubnets.length); - test.deepEqual(nets, importedVpc.isolatedSubnets); + test.deepEqual(subnetIds, importedVpc.isolatedSubnets.map(s => s.subnetId)); } test.done(); }, }, + }; function getTestStack(): Stack { diff --git a/packages/@aws-cdk/aws-ecr/README.md b/packages/@aws-cdk/aws-ecr/README.md index bdcafd84698f5..93a7e841a25bf 100644 --- a/packages/@aws-cdk/aws-ecr/README.md +++ b/packages/@aws-cdk/aws-ecr/README.md @@ -23,31 +23,3 @@ is important here): repository.addLifecycleRule({ tagPrefixList: ['prod'], maxImageCount: 9999 }); repository.addLifecycleRule({ maxImageAgeDays: 30 }); ``` - -### Using with CodePipeline - -This package also contains a source Action that allows you to use an ECR Repository as a source for CodePipeline. -Example: - -```ts -import codepipeline = require('@aws-cdk/aws-codepipeline'); - -const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const sourceAction = new ecr.PipelineSourceAction({ - actionName: 'ECR', - repository: ecrRepository, - imageTag: 'some-tag', // optional, default: 'latest' - outputArtifactName: 'SomeName', // optional -}); -pipeline.addStage({ - actionName: 'Source', - actions: [sourceAction], -}); -``` - -You can also create the action from the Repository directly: - -```ts -// equivalent to the code above: -const sourceAction = ecrRepository.toCodePipelineSourceAction({ actionName: 'ECR' }); -``` diff --git a/packages/@aws-cdk/aws-ecr/lib/index.ts b/packages/@aws-cdk/aws-ecr/lib/index.ts index 39da13ce26a30..21b453e140916 100644 --- a/packages/@aws-cdk/aws-ecr/lib/index.ts +++ b/packages/@aws-cdk/aws-ecr/lib/index.ts @@ -1,7 +1,6 @@ // AWS::ECR CloudFormation Resources: export * from './ecr.generated'; -export * from './pipeline-action'; export * from './repository'; export * from './repository-ref'; export * from './lifecycle'; diff --git a/packages/@aws-cdk/aws-ecr/lib/lifecycle.ts b/packages/@aws-cdk/aws-ecr/lib/lifecycle.ts index ad8af34decbbd..d86bb540c7e7b 100644 --- a/packages/@aws-cdk/aws-ecr/lib/lifecycle.ts +++ b/packages/@aws-cdk/aws-ecr/lib/lifecycle.ts @@ -16,14 +16,14 @@ export interface LifecycleRule { * * @default Automatically assigned */ - rulePriority?: number; + readonly rulePriority?: number; /** * Describes the purpose of the rule * * @default No description */ - description?: string; + readonly description?: string; /** * Select images based on tags @@ -33,28 +33,28 @@ export interface LifecycleRule { * * @default TagStatus.Tagged if tagPrefixList is given, TagStatus.Any otherwise */ - tagStatus?: TagStatus; + readonly tagStatus?: TagStatus; /** * Select images that have ALL the given prefixes in their tag. * * Only if tagStatus == TagStatus.Tagged */ - tagPrefixList?: string[]; + readonly tagPrefixList?: string[]; /** * The maximum number of images to retain * * Specify exactly one of maxImageCount and maxImageAgeDays. */ - maxImageCount?: number; + readonly maxImageCount?: number; /** * The maximum age of images to retain * * Specify exactly one of maxImageCount and maxImageAgeDays. */ - maxImageAgeDays?: number; + readonly maxImageAgeDays?: number; } /** diff --git a/packages/@aws-cdk/aws-ecr/lib/pipeline-action.ts b/packages/@aws-cdk/aws-ecr/lib/pipeline-action.ts deleted file mode 100644 index 8570d006adfd0..0000000000000 --- a/packages/@aws-cdk/aws-ecr/lib/pipeline-action.ts +++ /dev/null @@ -1,68 +0,0 @@ -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); -import iam = require('@aws-cdk/aws-iam'); -import cdk = require('@aws-cdk/cdk'); -import { IRepository } from './repository-ref'; - -/** - * Common properties for the {@link PipelineSourceAction CodePipeline source Action}, - * whether creating it directly, - * or through the {@link IRepository#toCodePipelineSourceAction} method. - */ -export interface CommonPipelineSourceActionProps extends codepipeline.CommonActionProps { - /** - * The image tag that will be checked for changes. - * - * @default 'latest' - */ - imageTag?: string; - - /** - * The name of the source's output artifact. - * CfnOutput artifacts are used by CodePipeline as inputs into other actions. - * - * @default a name will be auto-generated - */ - outputArtifactName?: string; -} - -/** - * Construction properties of {@link PipelineSourceAction}. - */ -export interface PipelineSourceActionProps extends CommonPipelineSourceActionProps { - /** - * The repository that will be watched for changes. - */ - repository: IRepository; -} - -/** - * The ECR Repository source CodePipeline Action. - */ -export class PipelineSourceAction extends codepipeline.SourceAction { - private readonly props: PipelineSourceActionProps; - - constructor(props: PipelineSourceActionProps) { - super({ - ...props, - provider: 'ECR', - configuration: { - RepositoryName: props.repository.repositoryName, - ImageTag: props.imageTag, - }, - outputArtifactName: props.outputArtifactName || `Artifact_${props.actionName}_${props.repository.node.uniqueId}`, - }); - - this.props = props; - } - - protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { - stage.pipeline.role.addToPolicy(new iam.PolicyStatement() - .addActions( - 'ecr:DescribeImages', - ) - .addResource(this.props.repository.repositoryArn)); - - this.props.repository.onImagePushed(stage.pipeline.node.uniqueId + 'SourceEventRule', - stage.pipeline, this.props.imageTag); - } -} diff --git a/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts b/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts index 064ca083e180a..a9a074ada5166 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts @@ -1,7 +1,6 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import { CommonPipelineSourceActionProps, PipelineSourceAction } from './pipeline-action'; /** * Represents an ECR repository. @@ -39,29 +38,20 @@ export interface IRepository extends cdk.IConstruct { */ addToResourcePolicy(statement: iam.PolicyStatement): void; - /** - * Convenience method for creating a new {@link PipelineSourceAction}. - * - * @param props the construction properties of the new Action - * @returns the newly created {@link PipelineSourceAction} - */ - toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): - PipelineSourceAction; - /** * Grant the given principal identity permissions to perform the actions on this repository */ - grant(identity?: iam.IPrincipal, ...actions: string[]): void; + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; /** * Grant the given identity permissions to pull images in this repository. */ - grantPull(identity?: iam.IPrincipal): void; + grantPull(grantee: iam.IGrantable): iam.Grant; /** * Grant the given identity permissions to pull and push images to this repository. */ - grantPullPush(identity?: iam.IPrincipal): void; + grantPullPush(grantee: iam.IGrantable): iam.Grant; /** * Defines an AWS CloudWatch event rule that can trigger a target when an image is pushed to this @@ -88,7 +78,7 @@ export interface RepositoryImportProps { * account/region as the current stack, you can set `repositoryName` instead * and the ARN will be formatted with the current region and account. */ - repositoryArn?: string; + readonly repositoryArn?: string; /** * The full name of the repository to import. @@ -100,7 +90,7 @@ export interface RepositoryImportProps { * If the repository is in the same region/account as the stack, it is sufficient * to only specify the repository name. */ - repositoryName?: string; + readonly repositoryName?: string; } /** @@ -169,13 +159,6 @@ export abstract class RepositoryBase extends cdk.Construct implements IRepositor */ public abstract export(): RepositoryImportProps; - public toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction { - return new PipelineSourceAction({ - ...props, - repository: this, - }); - } - /** * Defines an AWS CloudWatch event rule that can trigger a target when an image is pushed to this * repository. @@ -206,38 +189,41 @@ export abstract class RepositoryBase extends cdk.Construct implements IRepositor /** * Grant the given principal identity permissions to perform the actions on this repository */ - public grant(identity?: iam.IPrincipal, ...actions: string[]) { - if (!identity) { - return; - } - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.repositoryArn) - .addActions(...actions)); + public grant(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipalOrResource({ + grantee, + actions, + resourceArns: [this.repositoryArn], + resource: this, + }); } /** * Grant the given identity permissions to use the images in this repository */ - public grantPull(identity?: iam.IPrincipal) { - this.grant(identity, "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage"); + public grantPull(grantee: iam.IGrantable) { + const ret = this.grant(grantee, "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage"); + + iam.Grant.addToPrincipal({ + grantee, + actions: ["ecr:GetAuthorizationToken"], + resourceArns: ['*'], + scope: this, + }); - if (identity) { - identity.addToPolicy(new iam.PolicyStatement() - .addActions("ecr:GetAuthorizationToken", "logs:CreateLogStream", "logs:PutLogEvents") - .addAllResources()); - } + return ret; } /** * Grant the given identity permissions to pull and push images to this repository. */ - public grantPullPush(identity?: iam.IPrincipal) { - this.grantPull(identity); - this.grant(identity, - "ecr:PutImage", - "ecr:InitiateLayerUpload", - "ecr:UploadLayerPart", - "ecr:CompleteLayerUpload"); + public grantPullPush(grantee: iam.IGrantable) { + this.grantPull(grantee); + return this.grant(grantee, + "ecr:PutImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload"); } } @@ -255,7 +241,7 @@ class ImportedRepository extends RepositoryBase { this.repositoryArn = props.repositoryArn; } else { if (!props.repositoryName) { - throw new Error('If "repositoruyArn" is not specified, you must specify "repositoryName", ' + + throw new Error('If "repositoryArn" is not specified, you must specify "repositoryName", ' + 'which also implies that the repository resides in the same region/account as this stack'); } diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 79b9b083caa6b..26ee72be5d54f 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -10,14 +10,14 @@ export interface RepositoryProps { * * @default Automatically generated name. */ - repositoryName?: string; + readonly repositoryName?: string; /** * Life cycle rules to apply to this registry * * @default No life cycle rules */ - lifecycleRules?: LifecycleRule[]; + readonly lifecycleRules?: LifecycleRule[]; /** * The AWS account ID associated with the registry that contains the repository. @@ -25,7 +25,7 @@ export interface RepositoryProps { * @see https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_PutLifecyclePolicy.html * @default The default registry is assumed. */ - lifecycleRegistryId?: string; + readonly lifecycleRegistryId?: string; /** * Retain the repository on stack deletion @@ -35,7 +35,7 @@ export interface RepositoryProps { * * @default false */ - retain?: boolean; + readonly retain?: boolean; } /** @@ -97,7 +97,7 @@ export class Repository extends RepositoryBase { public addLifecycleRule(rule: LifecycleRule) { // Validate rule here so users get errors at the expected location if (rule.tagStatus === undefined) { - rule.tagStatus = rule.tagPrefixList === undefined ? TagStatus.Any : TagStatus.Tagged; + rule = { ...rule, tagStatus: rule.tagPrefixList === undefined ? TagStatus.Any : TagStatus.Tagged }; } if (rule.tagStatus === TagStatus.Tagged && (rule.tagPrefixList === undefined || rule.tagPrefixList.length === 0)) { diff --git a/packages/@aws-cdk/aws-ecr/package.json b/packages/@aws-cdk/aws-ecr/package.json index 54b2959da772d..f20d7fb06f2a9 100644 --- a/packages/@aws-cdk/aws-ecr/package.json +++ b/packages/@aws-cdk/aws-ecr/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ecr", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::ECR", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-ecr", + "module": "aws_cdk.aws_ecr" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-ecr" }, "scripts": { "build": "cdk-build", @@ -58,24 +63,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -85,4 +88,4 @@ "import:@aws-cdk/aws-ecr.Repository" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index f6811d72eec3f..be6b0df0cccc1 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -3,8 +3,10 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import iam = require('@aws-cdk/aws-iam'); +import cloudmap = require('@aws-cdk/aws-servicediscovery'); import cdk = require('@aws-cdk/cdk'); import { NetworkMode, TaskDefinition } from '../base/task-definition'; +import { ICluster } from '../cluster'; import { CfnService } from '../ecs.generated'; import { ScalableTaskCount } from './scalable-task-count'; @@ -12,19 +14,24 @@ import { ScalableTaskCount } from './scalable-task-count'; * Basic service properties */ export interface BaseServiceProps { + /** + * Cluster where service will be deployed + */ + readonly cluster: ICluster; + /** * Number of desired copies of running tasks * * @default 1 */ - desiredCount?: number; + readonly desiredCount?: number; /** * A name for the service. * * @default CloudFormation-generated name */ - serviceName?: string; + readonly serviceName?: string; /** * The maximum number of tasks, specified as a percentage of the Amazon ECS @@ -33,7 +40,7 @@ export interface BaseServiceProps { * * @default 200 */ - maximumPercent?: number; + readonly maximumPercent?: number; /** * The minimum number of tasks, specified as a percentage of @@ -42,14 +49,34 @@ export interface BaseServiceProps { * * @default 50 */ - minimumHealthyPercent?: number; + readonly minimumHealthyPercent?: number; /** * Time after startup to ignore unhealthy load balancer checks. * * @default ??? FIXME */ - healthCheckGracePeriodSeconds?: number; + readonly healthCheckGracePeriodSeconds?: number; + + /** + * Options for enabling AWS Cloud Map service discovery for the service + */ + readonly serviceDiscoveryOptions?: ServiceDiscoveryOptions; + + /** + * Whether the new long ARN format has been enabled on ECS services. + * NOTE: This assumes customer has opted into the new format for the IAM role used for the service, and is a + * workaround for a current bug in Cloudformation in which the service name is not correctly returned when long ARN is + * enabled. + * + * Old ARN format: arn:aws:ecs:region:aws_account_id:service/service-name + * New ARN format: arn:aws:ecs:region:aws_account_id:service/cluster-name/service-name + * + * See: https://docs.aws.amazon.com/AmazonECS/latest/userguide/ecs-resource-ids.html + * + * @default false + */ + readonly longArnEnabled?: boolean; } /** @@ -83,8 +110,12 @@ export abstract class BaseService extends cdk.Construct */ public readonly taskDefinition: TaskDefinition; + protected cloudmapService?: cloudmap.Service; + protected cluster: ICluster; protected loadBalancers = new Array(); protected networkConfiguration?: CfnService.NetworkConfigurationProperty; + protected serviceRegistries = new Array(); + private readonly resource: CfnService; private scalableTaskCount?: ScalableTaskCount; @@ -109,11 +140,25 @@ export abstract class BaseService extends cdk.Construct healthCheckGracePeriodSeconds: props.healthCheckGracePeriodSeconds, /* role: never specified, supplanted by Service Linked Role */ networkConfiguration: new cdk.Token(() => this.networkConfiguration), + serviceRegistries: new cdk.Token(() => this.serviceRegistries), ...additionalProps }); + this.serviceArn = this.resource.serviceArn; - this.serviceName = this.resource.serviceName; + + // This is a workaround for CFN bug that returns the cluster name instead of the service name when long ARN formats + // are enabled for the principal in a given region. + const longArnEnabled = props.longArnEnabled !== undefined ? props.longArnEnabled : false; + this.serviceName = longArnEnabled + ? cdk.Fn.select(2, cdk.Fn.split('/', this.serviceArn)) + : this.resource.serviceName; + this.clusterName = clusterName; + this.cluster = props.cluster; + + if (props.serviceDiscoveryOptions) { + this.enableServiceDiscovery(props.serviceDiscoveryOptions); + } } /** @@ -154,7 +199,7 @@ export abstract class BaseService extends cdk.Construct return this.scalableTaskCount = new ScalableTaskCount(this, 'TaskCount', { serviceNamespace: appscaling.ServiceNamespace.Ecs, - resourceId: `service/${this.clusterName}/${this.resource.serviceName}`, + resourceId: `service/${this.clusterName}/${this.serviceName}`, dimension: 'ecs:service:DesiredCount', role: this.makeAutoScalingRole(), ...props @@ -177,25 +222,32 @@ export abstract class BaseService extends cdk.Construct * Set up AWSVPC networking for this construct */ // tslint:disable-next-line:max-line-length - protected configureAwsVpcNetworking(vpc: ec2.IVpcNetwork, assignPublicIp?: boolean, vpcPlacement?: ec2.VpcPlacementStrategy, securityGroup?: ec2.ISecurityGroup) { - if (vpcPlacement === undefined) { - vpcPlacement = { subnetsToUse: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; + protected configureAwsVpcNetworking(vpc: ec2.IVpcNetwork, assignPublicIp?: boolean, vpcSubnets?: ec2.SubnetSelection, securityGroup?: ec2.ISecurityGroup) { + if (vpcSubnets === undefined) { + vpcSubnets = { subnetType: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; } if (securityGroup === undefined) { securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); } - const subnets = vpc.subnets(vpcPlacement); this.connections.addSecurityGroup(securityGroup); this.networkConfiguration = { awsvpcConfiguration: { assignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', - subnets: subnets.map(x => x.subnetId), + subnets: vpc.selectSubnets(vpcSubnets).subnetIds, securityGroups: new cdk.Token(() => [securityGroup!.securityGroupId]).toList(), } }; } + private renderServiceRegistry(registry: ServiceRegistry): CfnService.ServiceRegistryProperty { + return { + registryArn: registry.arn, + containerName: registry.containerName, + containerPort: registry.containerPort, + }; + } + /** * Shared logic for attaching to an ELBv2 */ @@ -231,9 +283,138 @@ export abstract class BaseService extends cdk.Construct }) }); } + + /** + * Associate Service Discovery (Cloud Map) service + */ + private addServiceRegistry(registry: ServiceRegistry) { + const sr = this.renderServiceRegistry(registry); + this.serviceRegistries.push(sr); + } + + /** + * Enable CloudMap service discovery for the service + */ + private enableServiceDiscovery(options: ServiceDiscoveryOptions): cloudmap.Service { + const sdNamespace = this.cluster.defaultNamespace; + if (sdNamespace === undefined) { + throw new Error("Cannot enable service discovery if a Cloudmap Namespace has not been created in the cluster."); + } + + // Determine DNS type based on network mode + const networkMode = this.taskDefinition.networkMode; + if (networkMode === NetworkMode.None) { + throw new Error("Cannot use a service discovery if NetworkMode is None. Use Bridge, Host or AwsVpc instead."); + } + + // Bridge or host network mode requires SRV records + let dnsRecordType = options.dnsRecordType; + + if (networkMode === NetworkMode.Bridge || networkMode === NetworkMode.Host) { + if (dnsRecordType === undefined) { + dnsRecordType = cloudmap.DnsRecordType.SRV; + } + if (dnsRecordType !== cloudmap.DnsRecordType.SRV) { + throw new Error("SRV records must be used when network mode is Bridge or Host."); + } + } + + // Default DNS record type for AwsVpc network mode is A Records + if (networkMode === NetworkMode.AwsVpc) { + if (dnsRecordType === undefined) { + dnsRecordType = cloudmap.DnsRecordType.A; + } + } + + // If the task definition that your service task specifies uses the AWSVPC network mode and a type SRV DNS record is + // used, you must specify a containerName and containerPort combination + const containerName = dnsRecordType === cloudmap.DnsRecordType.SRV ? this.taskDefinition.defaultContainer!.node.id : undefined; + const containerPort = dnsRecordType === cloudmap.DnsRecordType.SRV ? this.taskDefinition.defaultContainer!.containerPort : undefined; + + const cloudmapService = new cloudmap.Service(this, 'CloudmapService', { + namespace: sdNamespace, + name: options.name, + dnsRecordType: dnsRecordType!, + customHealthCheck: { failureThreshold: options.failureThreshold || 1 } + }); + + const serviceArn = cloudmapService.serviceArn; + + // add Cloudmap service to the ECS Service's serviceRegistry + this.addServiceRegistry({ + arn: serviceArn, + containerName, + containerPort + }); + + this.cloudmapService = cloudmapService; + + return cloudmapService; + } } /** * The port range to open up for dynamic port mapping */ const EPHEMERAL_PORT_RANGE = new ec2.TcpPortRange(32768, 65535); + +/** + * Options for enabling service discovery on an ECS service + */ +export interface ServiceDiscoveryOptions { + /** + * Name of the cloudmap service to attach to the ECS Service + * + * @default CloudFormation-generated name + */ + readonly name?: string, + + /** + * The DNS type of the record that you want AWS Cloud Map to create. Supported record types include A or SRV. + * + * @default: A + */ + readonly dnsRecordType?: cloudmap.DnsRecordType.A | cloudmap.DnsRecordType.SRV, + + /** + * The amount of time, in seconds, that you want DNS resolvers to cache the settings for this record. + * + * @default 60 + */ + readonly dnsTtlSec?: number; + + /** + * The number of 30-second intervals that you want Cloud Map to wait after receiving an + * UpdateInstanceCustomHealthStatus request before it changes the health status of a service instance. + * NOTE: This is used for HealthCheckCustomConfig + */ + readonly failureThreshold?: number, +} + +/** + * Service Registry for ECS service + */ +export interface ServiceRegistry { + /** + * Arn of the Cloud Map Service that will register a Cloud Map Instance for your ECS Service + */ + readonly arn: string; + + /** + * The container name value, already specified in the task definition, to be used for your service discovery service. + * If the task definition that your service task specifies uses the bridge or host network mode, + * you must specify a containerName and containerPort combination from the task definition. + * If the task definition that your service task specifies uses the awsvpc network mode and a type SRV DNS record is + * used, you must specify either a containerName and containerPort combination or a port value, but not both. + */ + readonly containerName?: string; + + /** + * The container port value, already specified in the task definition, to be used for your service discovery service. + * If the task definition that your service task specifies uses the bridge or host network mode, + * you must specify a containerName and containerPort combination from the task definition. + * If the task definition that your service task specifies uses the awsvpc network mode and a type SRV DNS record is + * used, you must specify either a containerName and containerPort combination or a port value, but not both. + */ + readonly containerPort?: number; +} diff --git a/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts b/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts index 0f0b511053683..6f3b454941cba 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/scalable-task-count.ts @@ -88,7 +88,7 @@ export interface CpuUtilizationScalingProps extends appscaling.BaseTargetTrackin /** * Target average CPU utilization across the task */ - targetUtilizationPercent: number; + readonly targetUtilizationPercent: number; } /** @@ -98,7 +98,7 @@ export interface MemoryUtilizationScalingProps extends appscaling.BaseTargetTrac /** * Target average memory utilization across the task */ - targetUtilizationPercent: number; + readonly targetUtilizationPercent: number; } /** @@ -108,12 +108,12 @@ export interface RequestCountScalingProps extends appscaling.BaseTargetTrackingP /** * ALB requests per target */ - requestsPerTarget: number; + readonly requestsPerTarget: number; /** * ALB Target Group */ - targetGroup: elbv2.ApplicationTargetGroup; + readonly targetGroup: elbv2.ApplicationTargetGroup; } /** @@ -128,10 +128,10 @@ export interface TrackCustomMetricProps extends appscaling.BaseTargetTrackingPro * - metric > targetValue => scale out * - metric < targetValue => scale in */ - metric: cloudwatch.Metric; + readonly metric: cloudwatch.Metric; /** * The target value to achieve for the metric */ - targetValue: number; + readonly targetValue: number; } 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 541799f0a1b8e..fc0d53bda597d 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -13,7 +13,7 @@ export interface CommonTaskDefinitionProps { * * @default Automatically generated name */ - family?: string; + readonly family?: string; /** * The IAM role assumed by the ECS agent. @@ -23,19 +23,19 @@ export interface CommonTaskDefinitionProps { * * @default An execution role will be automatically created if you use ECR images in your task definition */ - executionRole?: iam.IRole; + readonly executionRole?: iam.IRole; /** * The IAM role assumable by your application code running inside the container * * @default A task role is automatically created for you */ - taskRole?: iam.IRole; + readonly taskRole?: iam.IRole; /** * See: https://docs.aws.amazon.com/AmazonECS/latest/developerguide//task_definition_parameters.html#volumes */ - volumes?: Volume[]; + readonly volumes?: Volume[]; } /** @@ -49,7 +49,7 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { * * @default NetworkMode.Bridge for EC2 tasks, AwsVpc for Fargate tasks. */ - networkMode?: NetworkMode; + readonly networkMode?: NetworkMode; /** * An array of placement constraint objects to use for the task. You can @@ -58,12 +58,12 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { * * Not supported in Fargate. */ - placementConstraints?: PlacementConstraint[]; + readonly placementConstraints?: PlacementConstraint[]; /** * What launch types this task definition should be compatible with. */ - compatibility: Compatibility; + readonly compatibility: Compatibility; /** * The number of cpu units used by the task. @@ -74,7 +74,7 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { * 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments * 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments */ - cpu?: string; + readonly cpu?: string; /** * The amount (in MiB) of memory used by the task. @@ -92,7 +92,7 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { * * Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU) */ - memoryMiB?: string; + readonly memoryMiB?: string; } /** @@ -230,7 +230,8 @@ export class TaskDefinition extends cdk.Construct { } /** - * (internal) Links a container to this task definition. + * Links a container to this task definition. + * @internal */ public _linkContainer(container: ContainerDefinition) { this.containers.push(container); @@ -344,17 +345,17 @@ export interface Volume { /** * Path on the host */ - host?: Host; + readonly host?: Host; /** * A name for the volume */ - name: string; + readonly name: string; /** * Specifies this configuration when using Docker volumes */ - dockerVolumeConfiguration?: DockerVolumeConfiguration; + readonly dockerVolumeConfiguration?: DockerVolumeConfiguration; } /** @@ -364,7 +365,7 @@ export interface Host { /** * Source path on the host */ - sourcePath?: string; + readonly sourcePath?: string; } /** @@ -376,27 +377,27 @@ export interface DockerVolumeConfiguration { * * @default false */ - autoprovision?: boolean; + readonly autoprovision?: boolean; /** * The Docker volume driver to use */ - driver: string; + readonly driver: string; /** * A map of Docker driver specific options passed through * * @default No options */ - driverOpts?: string[]; + readonly driverOpts?: string[]; /** * Custom metadata to add to your Docker volume * * @default No labels */ - labels?: string[]; + readonly labels?: string[]; /** * The scope for the Docker volume which determines it's lifecycle */ - scope: Scope; + readonly scope: Scope; } export enum Scope { @@ -418,12 +419,12 @@ export interface PlacementConstraint { /** * The type of constraint */ - type: PlacementConstraintType; + readonly type: PlacementConstraintType; /** * Additional information for the constraint */ - expression?: string; + readonly expression?: string; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 591c0da164f96..4590c2ebb91fd 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -2,6 +2,7 @@ import autoscaling = require('@aws-cdk/aws-autoscaling'); import cloudwatch = require ('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); +import cloudmap = require('@aws-cdk/aws-servicediscovery'); import cdk = require('@aws-cdk/cdk'); import { InstanceDrainHook } from './drain-hook/instance-drain-hook'; import { CfnCluster } from './ecs.generated'; @@ -15,12 +16,12 @@ export interface ClusterProps { * * @default CloudFormation-generated name */ - clusterName?: string; + readonly clusterName?: string; /** * The VPC where your ECS instances will be running or your ENIs will be deployed */ - vpc: ec2.IVpcNetwork; + readonly vpc: ec2.IVpcNetwork; } /** @@ -54,6 +55,11 @@ export class Cluster extends cdk.Construct implements ICluster { */ public readonly clusterName: string; + /** + * The service discovery namespace created in this cluster + */ + private _defaultNamespace?: cloudmap.INamespace; + /** * Whether the cluster has EC2 capacity associated with it */ @@ -69,6 +75,41 @@ export class Cluster extends cdk.Construct implements ICluster { this.clusterName = cluster.clusterName; } + /** + * Add an AWS Cloud Map DNS namespace for this cluster. + * NOTE: HttpNamespaces are not supported, as ECS always requires a DNSConfig when registering an instance to a Cloud + * Map service. + */ + public addDefaultCloudMapNamespace(options: NamespaceOptions): cloudmap.INamespace { + if (this._defaultNamespace !== undefined) { + throw new Error("Can only add default namespace once."); + } + + const namespaceType = options.type === undefined || options.type === NamespaceType.PrivateDns + ? cloudmap.NamespaceType.DnsPrivate + : cloudmap.NamespaceType.DnsPublic; + + const sdNamespace = namespaceType === cloudmap.NamespaceType.DnsPrivate ? + new cloudmap.PrivateDnsNamespace(this, 'DefaultServiceDiscoveryNamespace', { + name: options.name, + vpc: this.vpc + }) : + new cloudmap.PublicDnsNamespace(this, 'DefaultServiceDiscoveryNamespace', { + name: options.name, + }); + + this._defaultNamespace = sdNamespace; + + return sdNamespace; + } + + /** + * Getter for namespace added to cluster + */ + public get defaultNamespace(): cloudmap.INamespace | undefined { + return this._defaultNamespace; + } + /** * Add a default-configured AutoScalingGroup running the ECS-optimized AMI to this Cluster * @@ -149,6 +190,7 @@ export class Cluster extends cdk.Construct implements ICluster { vpc: this.vpc.export(), securityGroups: this.connections.securityGroups.map(sg => sg.export()), hasEc2Capacity: this.hasEc2Capacity, + defaultNamespace: this._defaultNamespace && this._defaultNamespace.export(), }; } @@ -189,7 +231,7 @@ export interface EcsOptimizedAmiProps { * * @default AmazonLinux */ - generation?: ec2.AmazonLinuxGeneration; + readonly generation?: ec2.AmazonLinuxGeneration; } /** @@ -252,6 +294,11 @@ export interface ICluster extends cdk.IConstruct { */ readonly hasEc2Capacity: boolean; + /** + * Getter for Cloudmap namespace created in the cluster + */ + readonly defaultNamespace?: cloudmap.INamespace; + /** * Export the Cluster */ @@ -265,31 +312,38 @@ export interface ClusterImportProps { /** * Name of the cluster */ - clusterName: string; + readonly clusterName: string; /** * ARN of the cluster * * @default Derived from clusterName */ - clusterArn?: string; + readonly clusterArn?: string; /** * VPC that the cluster instances are running in */ - vpc: ec2.VpcNetworkImportProps; + readonly vpc: ec2.VpcNetworkImportProps; /** * Security group of the cluster instances */ - securityGroups: ec2.SecurityGroupImportProps[]; + readonly securityGroups: ec2.SecurityGroupImportProps[]; /** * Whether the given cluster has EC2 capacity * * @default true */ - hasEc2Capacity?: boolean; + readonly hasEc2Capacity?: boolean; + + /** + * Default namespace properties + * + * @default - No default namespace + */ + readonly defaultNamespace?: cloudmap.NamespaceImportProps; } /** @@ -321,11 +375,17 @@ class ImportedCluster extends cdk.Construct implements ICluster { */ public readonly hasEc2Capacity: boolean; + /** + * Cloudmap namespace created in the cluster + */ + private _defaultNamespace?: cloudmap.INamespace; + constructor(scope: cdk.Construct, id: string, private readonly props: ClusterImportProps) { super(scope, id); this.clusterName = props.clusterName; this.vpc = ec2.VpcNetwork.import(this, "vpc", props.vpc); this.hasEc2Capacity = props.hasEc2Capacity !== false; + this._defaultNamespace = props.defaultNamespace && cloudmap.Namespace.import(this, 'Namespace', props.defaultNamespace); this.clusterArn = props.clusterArn !== undefined ? props.clusterArn : this.node.stack.formatArn({ service: 'ecs', @@ -340,6 +400,10 @@ class ImportedCluster extends cdk.Construct implements ICluster { } } + public get defaultNamespace(): cloudmap.INamespace | undefined { + return this._defaultNamespace; + } + public export() { return this.props; } @@ -354,7 +418,7 @@ export interface AddAutoScalingGroupCapacityOptions { * * @default false */ - containersAccessInstanceRole?: boolean; + readonly containersAccessInstanceRole?: boolean; /** * Give tasks this many seconds to complete when instances are being scaled in. @@ -367,7 +431,7 @@ export interface AddAutoScalingGroupCapacityOptions { * * @default 300 */ - taskDrainTimeSeconds?: number; + readonly taskDrainTimeSeconds?: number; } /** @@ -377,5 +441,41 @@ export interface AddCapacityOptions extends AddAutoScalingGroupCapacityOptions, /** * The type of EC2 instance to launch into your Autoscaling Group */ - instanceType: ec2.InstanceType; + readonly instanceType: ec2.InstanceType; +} + +export interface NamespaceOptions { + /** + * The domain name for the namespace, such as foo.com + */ + readonly name: string; + + /** + * The type of CloudMap Namespace to create in your cluster + * + * @default PrivateDns + */ + readonly type?: NamespaceType; + + /** + * The Amazon VPC that you want to associate the namespace with. Required for Private DNS namespaces + * + * @default VPC of the cluster for Private DNS Namespace, otherwise none + */ + readonly vpc?: ec2.IVpcNetwork; +} + +/** + * The type of CloudMap namespace to create + */ +export enum NamespaceType { + /** + * Create a private DNS namespace + */ + PrivateDns = 'PrivateDns', + + /** + * Create a public DNS namespace + */ + PublicDns = 'PublicDns', } diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index 9508737da1843..cda7dd406e156 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -14,7 +14,7 @@ export interface ContainerDefinitionOptions { * repositories (repository-url/image:tag). * TODO: Update these to specify using classes of IContainerImage */ - image: ContainerImage; + readonly image: ContainerImage; /** * The CMD value to pass to the container. @@ -23,47 +23,47 @@ export interface ContainerDefinitionOptions { * * @default CMD value built into container image */ - command?: string[]; + readonly command?: string[]; /** * The minimum number of CPU units to reserve for the container. */ - cpu?: number; + readonly cpu?: number; /** * Indicates whether networking is disabled within the container. * * @default false */ - disableNetworking?: boolean; + readonly disableNetworking?: boolean; /** * A list of DNS search domains that are provided to the container. * * @default No search domains */ - dnsSearchDomains?: string[]; + readonly dnsSearchDomains?: string[]; /** * A list of DNS servers that Amazon ECS provides to the container. * * @default Default DNS servers */ - dnsServers?: string[]; + readonly dnsServers?: string[]; /** * A key-value map of labels for the container. * * @default No labels */ - dockerLabels?: { [key: string]: string }; + readonly dockerLabels?: { [key: string]: string }; /** * A list of custom labels for SELinux and AppArmor multi-level security systems. * * @default No security labels */ - dockerSecurityOptions?: string[]; + readonly dockerSecurityOptions?: string[]; /** * The ENTRYPOINT value to pass to the container. @@ -71,14 +71,14 @@ export interface ContainerDefinitionOptions { * @see https://docs.docker.com/engine/reference/builder/#entrypoint * @default Entry point configured in container */ - entryPoint?: string[]; + readonly entryPoint?: string[]; /** * The environment variables to pass to the container. * * @default No environment variables */ - environment?: { [key: string]: string }; + readonly environment?: { [key: string]: string }; /** * Indicates whether the task stops if this container fails. @@ -91,28 +91,28 @@ export interface ContainerDefinitionOptions { * * @default true */ - essential?: boolean; + readonly essential?: boolean; /** * A list of hostnames and IP address mappings to append to the /etc/hosts file on the container. * * @default No extra hosts */ - extraHosts?: { [name: string]: string }; + readonly extraHosts?: { [name: string]: string }; /** * Container health check. * * @default Health check configuration from container */ - healthCheck?: HealthCheck; + readonly healthCheck?: HealthCheck; /** * The name that Docker uses for the container hostname. * * @default Automatic hostname */ - hostname?: string; + readonly hostname?: string; /** * The hard limit (in MiB) of memory to present to the container. @@ -122,7 +122,7 @@ export interface ContainerDefinitionOptions { * * At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services. */ - memoryLimitMiB?: number; + readonly memoryLimitMiB?: number; /** * The soft limit (in MiB) of memory to reserve for the container. @@ -134,40 +134,40 @@ export interface ContainerDefinitionOptions { * * At least one of memoryLimitMiB and memoryReservationMiB is required for non-Fargate services. */ - memoryReservationMiB?: number; + readonly memoryReservationMiB?: number; /** * Indicates whether the container is given full access to the host container instance. * * @default false */ - privileged?: boolean; + readonly privileged?: boolean; /** * Indicates whether the container's root file system is mounted as read only. * * @default false */ - readonlyRootFilesystem?: boolean; + readonly readonlyRootFilesystem?: boolean; /** * The user name to use inside the container. * * @default root */ - user?: string; + readonly user?: string; /** * The working directory in the container to run commands in. * * @default / */ - workingDirectory?: string; + readonly workingDirectory?: string; /** * Configures a custom log driver for the container. */ - logging?: LogDriver; + readonly logging?: LogDriver; } /** @@ -177,7 +177,7 @@ export interface ContainerDefinitionProps extends ContainerDefinitionOptions { /** * The task this container definition belongs to. */ - taskDefinition: TaskDefinition; + readonly taskDefinition: TaskDefinition; } /** @@ -290,19 +290,24 @@ export class ContainerDefinition extends cdk.Construct { * Add one or more port mappings to this container */ public addPortMappings(...portMappings: PortMapping[]) { - for (const pm of portMappings) { + this.portMappings.push(...portMappings.map(pm => { if (this.taskDefinition.networkMode === NetworkMode.AwsVpc || this.taskDefinition.networkMode === NetworkMode.Host) { if (pm.containerPort !== pm.hostPort && pm.hostPort !== undefined) { throw new Error(`Host port ${pm.hostPort} does not match container port ${pm.containerPort}.`); } } + if (this.taskDefinition.networkMode === NetworkMode.Bridge) { if (pm.hostPort === undefined) { - pm.hostPort = 0; + pm = { + ...pm, + hostPort: 0 + }; } } - } - this.portMappings.push(...portMappings); + + return pm; + })); } /** @@ -403,7 +408,7 @@ export interface HealthCheck { * * If you provide a shell command as a single string, you have to quote command-line arguments. */ - command: string[]; + readonly command: string[]; /** * Time period in seconds between each health check execution. @@ -412,7 +417,7 @@ export interface HealthCheck { * * @default 30 */ - intervalSeconds?: number; + readonly intervalSeconds?: number; /** * Number of times to retry a failed health check before the container is considered unhealthy. @@ -421,7 +426,7 @@ export interface HealthCheck { * * @default 3 */ - retries?: number; + readonly retries?: number; /** * Grace period after startup before failed health checks count. @@ -430,7 +435,7 @@ export interface HealthCheck { * * @default No start period */ - startPeriod?: number; + readonly startPeriod?: number; /** * The time period in seconds to wait for a health check to succeed before it is considered a failure. @@ -439,7 +444,7 @@ export interface HealthCheck { * * @default 5 */ - timeout?: number; + readonly timeout?: number; } function renderKV(env: { [key: string]: string }, keyName: string, valueName: string): any { @@ -491,17 +496,17 @@ export interface Ulimit { /** * What resource to enforce a limit on */ - name: UlimitName, + readonly name: UlimitName, /** * Soft limit of the resource */ - softLimit: number, + readonly softLimit: number, /** * Hard limit of the resource */ - hardLimit: number, + readonly hardLimit: number, } /** @@ -540,7 +545,7 @@ export interface PortMapping { /** * Port inside the container */ - containerPort: number; + readonly containerPort: number; /** * Port on the host @@ -551,14 +556,14 @@ export interface PortMapping { * In Bridge networking mode, leave this out or set it to non-reserved * non-ephemeral port. */ - hostPort?: number; + readonly hostPort?: number; /** * Protocol * * @default Tcp */ - protocol?: Protocol + readonly protocol?: Protocol } /** @@ -585,16 +590,16 @@ function renderPortMapping(pm: PortMapping): CfnTaskDefinition.PortMappingProper } export interface ScratchSpace { - containerPath: string, - readOnly: boolean, - sourcePath: string - name: string, + readonly containerPath: string, + readonly readOnly: boolean, + readonly sourcePath: string + readonly name: string, } export interface MountPoint { - containerPath: string, - readOnly: boolean, - sourceVolume: string, + readonly containerPath: string, + readonly readOnly: boolean, + readonly sourceVolume: string, } function renderMountPoint(mp: MountPoint): CfnTaskDefinition.MountPointProperty { @@ -612,12 +617,12 @@ export interface VolumeFrom { /** * Name of the source container */ - sourceContainer: string, + readonly sourceContainer: string, /** * Whether the volume is read only */ - readOnly: boolean, + readonly readOnly: boolean, } function renderVolumeFrom(vf: VolumeFrom): CfnTaskDefinition.VolumeFromProperty { diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts index 90fdee743248e..060ea4e3f840e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts @@ -12,19 +12,19 @@ export interface Ec2EventRuleTargetProps { /** * Cluster where service will be deployed */ - cluster: ICluster; + readonly cluster: ICluster; /** * Task Definition of the task that should be started */ - taskDefinition: TaskDefinition; + readonly taskDefinition: TaskDefinition; /** * How many tasks should be started when this event is triggered * * @default 1 */ - taskCount?: number; + readonly taskCount?: number; } /** @@ -95,7 +95,7 @@ export class Ec2EventRuleTarget extends cdk.Construct implements events.IEventRu // // It never needs permissions to the Task Role. if (this.taskDefinition.executionRole !== undefined) { - this.taskDefinition.taskRole.grantPassRole(this.eventsRole); + this.taskDefinition.executionRole.grantPassRole(this.eventsRole); } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index 80628ad98ccb6..324ef039cdc49 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -4,7 +4,6 @@ import elb = require('@aws-cdk/aws-elasticloadbalancing'); import cdk = require('@aws-cdk/cdk'); import { BaseService, BaseServiceProps } from '../base/base-service'; import { NetworkMode, TaskDefinition } from '../base/task-definition'; -import { ICluster } from '../cluster'; import { CfnService } from '../ecs.generated'; import { isEc2Compatible } from '../util'; @@ -12,15 +11,10 @@ import { isEc2Compatible } from '../util'; * Properties to define an ECS service */ export interface Ec2ServiceProps extends BaseServiceProps { - /** - * Cluster where service will be deployed - */ - cluster: ICluster; - /** * Task Definition used for running tasks in the service */ - taskDefinition: TaskDefinition; + readonly taskDefinition: TaskDefinition; /** * In what subnets to place the task's ENIs @@ -29,7 +23,7 @@ export interface Ec2ServiceProps extends BaseServiceProps { * * @default Private subnets */ - vpcPlacement?: ec2.VpcPlacementStrategy; + readonly vpcSubnets?: ec2.SubnetSelection; /** * Existing security group to use for the task's ENIs @@ -38,14 +32,14 @@ export interface Ec2ServiceProps extends BaseServiceProps { * * @default A new security group is created */ - securityGroup?: ec2.ISecurityGroup; + readonly securityGroup?: ec2.ISecurityGroup; /** * Whether to start services on distinct instances * * @default true */ - placeOnDistinctInstances?: boolean; + readonly placeOnDistinctInstances?: boolean; /** * Deploy exactly one task on each instance in your cluster. @@ -55,7 +49,7 @@ export interface Ec2ServiceProps extends BaseServiceProps { * * @default false */ - daemon?: boolean; + readonly daemon?: boolean; } /** @@ -70,7 +64,6 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { private readonly constraints: CfnService.PlacementConstraintProperty[]; private readonly strategies: CfnService.PlacementStrategyProperty[]; private readonly daemon: boolean; - private readonly cluster: ICluster; constructor(scope: cdk.Construct, id: string, props: Ec2ServiceProps) { if (props.daemon && props.desiredCount !== undefined) { @@ -95,14 +88,13 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { schedulingStrategy: props.daemon ? 'DAEMON' : 'REPLICA', }, props.cluster.clusterName, props.taskDefinition); - this.cluster = props.cluster; this.clusterName = props.cluster.clusterName; this.constraints = []; this.strategies = []; this.daemon = props.daemon || false; if (props.taskDefinition.networkMode === NetworkMode.AwsVpc) { - this.configureAwsVpcNetworking(props.cluster.vpc, false, props.vpcPlacement, props.securityGroup); + this.configureAwsVpcNetworking(props.cluster.vpc, false, props.vpcSubnets, props.securityGroup); } else { // Either None, Bridge or Host networking. Copy SecurityGroup from ASG. validateNoNetworkingProps(props); @@ -244,8 +236,8 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { * Validate combinations of networking arguments */ function validateNoNetworkingProps(props: Ec2ServiceProps) { - if (props.vpcPlacement !== undefined || props.securityGroup !== undefined) { - throw new Error('vpcPlacement and securityGroup can only be used in AwsVpc networking mode'); + if (props.vpcSubnets !== undefined || props.securityGroup !== undefined) { + throw new Error('vpcSubnets and securityGroup can only be used in AwsVpc networking mode'); } } diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts index a1a130e2a1868..8c1bc5135d4c4 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts @@ -12,7 +12,7 @@ export interface Ec2TaskDefinitionProps extends CommonTaskDefinitionProps { * * @default NetworkMode.Bridge for EC2 tasks, AwsVpc for Fargate tasks. */ - networkMode?: NetworkMode; + readonly networkMode?: NetworkMode; /** * An array of placement constraint objects to use for the task. You can @@ -21,7 +21,7 @@ export interface Ec2TaskDefinitionProps extends CommonTaskDefinitionProps { * * Not supported in Fargate. */ - placementConstraints?: PlacementConstraint[]; + readonly placementConstraints?: PlacementConstraint[]; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index 718102faf0909..f50ad650baadc 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -2,43 +2,37 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { BaseService, BaseServiceProps } from '../base/base-service'; import { TaskDefinition } from '../base/task-definition'; -import { ICluster } from '../cluster'; import { isFargateCompatible } from '../util'; /** * Properties to define a Fargate service */ export interface FargateServiceProps extends BaseServiceProps { - /** - * Cluster where service will be deployed - */ - cluster: ICluster; // should be required? do we assume 'default' exists? - /** * Task Definition used for running tasks in the service */ - taskDefinition: TaskDefinition; + readonly taskDefinition: TaskDefinition; /** * Assign public IP addresses to each task * * @default false */ - assignPublicIp?: boolean; + readonly assignPublicIp?: boolean; /** * In what subnets to place the task's ENIs * * @default Private subnet if assignPublicIp, public subnets otherwise */ - vpcPlacement?: ec2.VpcPlacementStrategy; + readonly vpcSubnets?: ec2.SubnetSelection; /** * Existing security group to use for the tasks * * @default A new security group is created */ - securityGroup?: ec2.ISecurityGroup; + readonly securityGroup?: ec2.ISecurityGroup; /** * Fargate platform version to run this service on @@ -48,7 +42,7 @@ export interface FargateServiceProps extends BaseServiceProps { * * @default Latest */ - platformVersion?: FargatePlatformVersion; + readonly platformVersion?: FargatePlatformVersion; } /** @@ -70,7 +64,7 @@ export class FargateService extends BaseService { platformVersion: props.platformVersion, }, props.cluster.clusterName, props.taskDefinition); - this.configureAwsVpcNetworking(props.cluster.vpc, props.assignPublicIp, props.vpcPlacement, props.securityGroup); + this.configureAwsVpcNetworking(props.cluster.vpc, props.assignPublicIp, props.vpcSubnets, props.securityGroup); if (!props.taskDefinition.defaultContainer) { throw new Error('A TaskDefinition must have at least one essential container'); diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts index f98ca512bb8e2..8ff1efd560d0b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts @@ -16,7 +16,7 @@ export interface FargateTaskDefinitionProps extends CommonTaskDefinitionProps { * * @default 256 */ - cpu?: string; + readonly cpu?: string; /** * The amount (in MiB) of memory used by the task. @@ -36,7 +36,7 @@ export interface FargateTaskDefinitionProps extends CommonTaskDefinitionProps { * * @default 512 */ - memoryMiB?: string; + readonly memoryMiB?: string; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts b/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts index 003fba3222acd..ccaaaa2a4f18d 100644 --- a/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts +++ b/packages/@aws-cdk/aws-ecs/lib/images/asset-image.ts @@ -8,7 +8,7 @@ export interface AssetImageProps { /** * The directory where the Dockerfile is stored */ - directory: string; + readonly directory: string; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/images/repository.ts b/packages/@aws-cdk/aws-ecs/lib/images/repository.ts index 4b21cc503d704..67126e466795a 100644 --- a/packages/@aws-cdk/aws-ecs/lib/images/repository.ts +++ b/packages/@aws-cdk/aws-ecs/lib/images/repository.ts @@ -7,7 +7,7 @@ export interface RepositoryImageProps { /** * Optional secret that houses credentials for the image registry */ - credentials?: secretsmanager.ISecret; + readonly credentials?: secretsmanager.ISecret; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts b/packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts index 04f47572e22e7..a17656298767c 100644 --- a/packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts +++ b/packages/@aws-cdk/aws-ecs/lib/linux-parameters.ts @@ -92,19 +92,19 @@ export interface Device { * * @default Same path as the host */ - containerPath?: string, + readonly containerPath?: string, /** * Path on the host */ - hostPath: string, + readonly hostPath: string, /** * Permissions * * @default Readonly */ - permissions?: DevicePermission[] + readonly permissions?: DevicePermission[] } function renderDevice(device: Device): CfnTaskDefinition.DeviceProperty { @@ -122,17 +122,17 @@ export interface Tmpfs { /** * Path in the container to mount */ - containerPath: string, + readonly containerPath: string, /** * Size of the volume */ - size: number, + readonly size: number, /** * Mount options */ - mountOptions?: TmpfsMountOption[], + readonly mountOptions?: TmpfsMountOption[], } function renderTmpfs(tmpfs: Tmpfs): CfnTaskDefinition.TmpfsProperty { diff --git a/packages/@aws-cdk/aws-ecs/lib/load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs/lib/load-balanced-ecs-service.ts index 4d021fac92f19..31564226042cc 100644 --- a/packages/@aws-cdk/aws-ecs/lib/load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/load-balanced-ecs-service.ts @@ -15,7 +15,7 @@ export interface LoadBalancedEc2ServiceProps extends LoadBalancedServiceBaseProp * * At least one of memoryLimitMiB and memoryReservationMiB is required. */ - memoryLimitMiB?: number; + readonly memoryLimitMiB?: number; /** * The soft limit (in MiB) of memory to reserve for the container. @@ -27,7 +27,7 @@ export interface LoadBalancedEc2ServiceProps extends LoadBalancedServiceBaseProp * * At least one of memoryLimitMiB and memoryReservationMiB is required. */ - memoryReservationMiB?: number; + readonly memoryReservationMiB?: number; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts index 374d546a78f68..9d638dab7cc33 100644 --- a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts +++ b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts @@ -13,7 +13,7 @@ export interface LoadBalancedFargateServiceAppletProps extends cdk.StackProps { /** * The image to start (from DockerHub) */ - image: string; + readonly image: string; /** * The number of cpu units used by the task. @@ -28,7 +28,7 @@ export interface LoadBalancedFargateServiceAppletProps extends cdk.StackProps { * * @default 256 */ - cpu?: string; + readonly cpu?: string; /** * The amount (in MiB) of memory used by the task. @@ -50,58 +50,58 @@ export interface LoadBalancedFargateServiceAppletProps extends cdk.StackProps { * * @default 512 */ - memoryMiB?: string; + readonly memoryMiB?: string; /** * The container port of the application load balancer attached to your Fargate service. Corresponds to container port mapping. * * @default 80 */ - containerPort?: number; + readonly containerPort?: number; /** * Determines whether the Application Load Balancer will be internet-facing * * @default true */ - publicLoadBalancer?: boolean; + readonly publicLoadBalancer?: boolean; /** * Determines whether your Fargate Service will be assigned a public IP address. * * @default false */ - publicTasks?: boolean; + readonly publicTasks?: boolean; /** * Number of desired copies of running tasks * * @default 1 */ - desiredCount?: number; + readonly desiredCount?: number; /* * Domain name for the service, e.g. api.example.com */ - domainName?: string; + readonly domainName?: string; /** * Route53 hosted zone for the domain, e.g. "example.com." */ - domainZone?: string; + readonly domainZone?: string; /** * Certificate Manager certificate to associate with the load balancer. * Setting this option will set the load balancer port to 443. */ - certificate?: string; + readonly certificate?: string; /** * Environment variables to pass to the container * * @default No environment variables */ - environment?: { [key: string]: string }; + readonly environment?: { [key: string]: string }; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service.ts index f1eee7befeaf1..afdf4151af5e1 100644 --- a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service.ts @@ -22,7 +22,7 @@ export interface LoadBalancedFargateServiceProps extends LoadBalancedServiceBase * * @default 256 */ - cpu?: string; + readonly cpu?: string; /** * The amount (in MiB) of memory used by the task. @@ -44,31 +44,31 @@ export interface LoadBalancedFargateServiceProps extends LoadBalancedServiceBase * * @default 512 */ - memoryMiB?: string; + readonly memoryMiB?: string; /** * Determines whether your Fargate Service will be assigned a public IP address. * * @default false */ - publicTasks?: boolean; + readonly publicTasks?: boolean; /* * Domain name for the service, e.g. api.example.com */ - domainName?: string; + readonly domainName?: string; /** * Route53 hosted zone for the domain, e.g. "example.com." */ - domainZone?: IHostedZone; + readonly domainZone?: IHostedZone; /** * Whether to create an AWS log driver * * @default true */ - createLogs?: boolean; + readonly createLogs?: boolean; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs/lib/load-balanced-service-base.ts index 9e9aeed50769c..110f44bf1ab5b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs/lib/load-balanced-service-base.ts @@ -14,52 +14,52 @@ export interface LoadBalancedServiceBaseProps { /** * The cluster where your service will be deployed */ - cluster: ICluster; + readonly cluster: ICluster; /** * The image to start. */ - image: ContainerImage; + readonly image: ContainerImage; /** * The container port of the application load balancer attached to your Fargate service. Corresponds to container port mapping. * * @default 80 */ - containerPort?: number; + readonly containerPort?: number; /** * Determines whether the Application Load Balancer will be internet-facing * * @default true */ - publicLoadBalancer?: boolean; + readonly publicLoadBalancer?: boolean; /** * Number of desired copies of running tasks * * @default 1 */ - desiredCount?: number; + readonly desiredCount?: number; /** * Whether to create an application load balancer or a network load balancer * @default application */ - loadBalancerType?: LoadBalancerType + readonly loadBalancerType?: LoadBalancerType /** * Certificate Manager certificate to associate with the load balancer. * Setting this option will set the load balancer port to 443. */ - certificate?: ICertificate; + readonly certificate?: ICertificate; /** * Environment variables to pass to the container * * @default No environment variables */ - environment?: { [key: string]: string }; + readonly environment?: { [key: string]: string }; } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts b/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts index fc2a83e4c8d53..fd69399683026 100644 --- a/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts +++ b/packages/@aws-cdk/aws-ecs/lib/log-drivers/aws-log-driver.ts @@ -18,14 +18,14 @@ export interface AwsLogDriverProps { * * prefix-name/container-name/ecs-task-id */ - streamPrefix: string; + readonly streamPrefix: string; /** * The log group to log to * * @default A log group is automatically created */ - logGroup?: logs.ILogGroup; + readonly logGroup?: logs.ILogGroup; /** * This option defines a multiline start pattern in Python strftime format. @@ -34,7 +34,7 @@ export interface AwsLogDriverProps { * following lines that don’t match the pattern. Thus the matched line is * the delimiter between log messages. */ - datetimeFormat?: string; + readonly datetimeFormat?: string; /** * This option defines a multiline start pattern using a regular expression. @@ -43,7 +43,7 @@ export interface AwsLogDriverProps { * following lines that don’t match the pattern. Thus the matched line is * the delimiter between log messages. */ - multilinePattern?: string; + readonly multilinePattern?: string; } /** diff --git a/packages/@aws-cdk/aws-ecs/package-lock.json b/packages/@aws-cdk/aws-ecs/package-lock.json index 42b0aab1e99ba..7c814cf29d749 100644 --- a/packages/@aws-cdk/aws-ecs/package-lock.json +++ b/packages/@aws-cdk/aws-ecs/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ecs", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index ab62843aec19c..f613de1ea7960 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ecs", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::ECS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-ecs", + "module": "aws_cdk.aws_ecs" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-ecs" }, "scripts": { "build": "cdk-build", @@ -54,52 +59,54 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", "@types/proxyquire": "^1.3.28", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0", "proxyquire": "^2.1.0" }, "dependencies": { - "@aws-cdk/assets-docker": "^0.26.0", - "@aws-cdk/aws-applicationautoscaling": "^0.26.0", - "@aws-cdk/aws-autoscaling": "^0.26.0", - "@aws-cdk/aws-certificatemanager": "^0.26.0", - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-ecr": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-logs": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/aws-secretsmanager": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/assets-docker": "^0.28.0", + "@aws-cdk/aws-applicationautoscaling": "^0.28.0", + "@aws-cdk/aws-autoscaling": "^0.28.0", + "@aws-cdk/aws-certificatemanager": "^0.28.0", + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-ecr": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/aws-secretsmanager": "^0.28.0", + "@aws-cdk/aws-servicediscovery": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/assets-docker": "^0.26.0", - "@aws-cdk/aws-applicationautoscaling": "^0.26.0", - "@aws-cdk/aws-autoscaling": "^0.26.0", - "@aws-cdk/aws-certificatemanager": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-ecr": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-logs": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/aws-secretsmanager": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/assets-docker": "^0.28.0", + "@aws-cdk/aws-applicationautoscaling": "^0.28.0", + "@aws-cdk/aws-autoscaling": "^0.28.0", + "@aws-cdk/aws-certificatemanager": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-ecr": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/aws-secretsmanager": "^0.28.0", + "@aws-cdk/aws-servicediscovery": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -111,4 +118,4 @@ "construct-ctor:@aws-cdk/aws-ecs.LoadBalancedFargateServiceApplet..params[0]" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json index 8d1363d1343ab..cf64430816ede 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json @@ -490,7 +490,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E" + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E", + "Arn" + ] }, "Principal": "sns.amazonaws.com", "SourceArn": { @@ -823,11 +826,7 @@ } }, { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], + "Action": "ecr:GetAuthorizationToken", "Effect": "Allow", "Resource": "*" }, @@ -909,7 +908,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "TaskDefTaskRole1EDB4A67", + "TaskDefExecutionRoleB4775C97", "Arn" ] } diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.ts index 3b098d1a196f3..3b6844b81c1fb 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.ts @@ -3,6 +3,8 @@ import events = require('@aws-cdk/aws-events'); import cdk = require('@aws-cdk/cdk'); import ecs = require('../../lib'); +import path = require('path'); + const app = new cdk.App(); class EventStack extends cdk.Stack { @@ -20,7 +22,9 @@ class EventStack extends cdk.Stack { // Create a Task Definition for the container to start const taskDefinition = new ecs.Ec2TaskDefinition(this, 'TaskDef'); taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromAsset(this, 'EventImage', { directory: 'eventhandler-image' }), + image: ecs.ContainerImage.fromAsset(this, 'EventImage', { + directory: path.resolve(__dirname, '..', 'eventhandler-image') + }), memoryLimitMiB: 256, logging: new ecs.AwsLogDriver(this, 'TaskLogging', { streamPrefix: 'EventDemo' }) }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json index 4836aa89c2bca..09dc8a742e401 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json @@ -646,7 +646,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E" + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E", + "Arn" + ] }, "Principal": "sns.amazonaws.com", "SourceArn": { @@ -846,7 +849,8 @@ }, "PlacementConstraints": [], "PlacementStrategies": [], - "SchedulingStrategy": "REPLICA" + "SchedulingStrategy": "REPLICA", + "ServiceRegistries": [] }, "DependsOn": [ "LBPublicListenerECSGroupD6A32205", diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json index 28fc6b69b6471..bef9bc1361830 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json @@ -667,7 +667,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E" + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E", + "Arn" + ] }, "Principal": "sns.amazonaws.com", "SourceArn": { @@ -847,7 +850,8 @@ ], "PlacementConstraints": [], "PlacementStrategies": [], - "SchedulingStrategy": "REPLICA" + "SchedulingStrategy": "REPLICA", + "ServiceRegistries": [] }, "DependsOn": [ "LBPublicListenerECSGroupD6A32205", diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json new file mode 100644 index 0000000000000..2e0a8566b610a --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json @@ -0,0 +1,915 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "EcsCluster97242B84": { + "Type": "AWS::ECS::Cluster" + }, + "EcsClusterDefaultAutoScalingGroupInstanceSecurityGroup912E1231": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-ecs/EcsCluster/DefaultAutoScalingGroup/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/EcsCluster/DefaultAutoScalingGroup" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "EcsClusterDefaultAutoScalingGroupInstanceRole3C026863": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "EcsClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy04DC6C80": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecs:CreateCluster", + "ecs:DeregisterContainerInstance", + "ecs:DiscoverPollEndpoint", + "ecs:Poll", + "ecs:RegisterContainerInstance", + "ecs:StartTelemetrySession", + "ecs:Submit*", + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "EcsClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy04DC6C80", + "Roles": [ + { + "Ref": "EcsClusterDefaultAutoScalingGroupInstanceRole3C026863" + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupInstanceProfile2CE606B3": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "EcsClusterDefaultAutoScalingGroupInstanceRole3C026863" + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupLaunchConfigB7E376C1": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": "ami-1234", + "InstanceType": "t2.micro", + "IamInstanceProfile": { + "Ref": "EcsClusterDefaultAutoScalingGroupInstanceProfile2CE606B3" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupInstanceSecurityGroup912E1231", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\necho ECS_CLUSTER=", + { + "Ref": "EcsCluster97242B84" + }, + " >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config" + ] + ] + } + } + }, + "DependsOn": [ + "EcsClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy04DC6C80", + "EcsClusterDefaultAutoScalingGroupInstanceRole3C026863" + ] + }, + "EcsClusterDefaultAutoScalingGroupASGC1A785DB": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "DesiredCapacity": "1", + "LaunchConfigurationName": { + "Ref": "EcsClusterDefaultAutoScalingGroupLaunchConfigB7E376C1" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-ecs-integ-ecs/EcsCluster/DefaultAutoScalingGroup" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + }, + "UpdatePolicy": { + "AutoScalingReplacingUpdate": { + "WillReplace": true + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25": { + "Type": "AWS::SNS::Topic" + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyA45BF396": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "autoscaling:CompleteLifecycleAction", + "ec2:DescribeInstances", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceStatus", + "ec2:DescribeHosts" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecs:ListContainerInstances", + "ecs:SubmitContainerStateChange", + "ecs:SubmitTaskStateChange", + "ecs:DescribeContainerInstances", + "ecs:UpdateContainerInstancesState", + "ecs:ListTasks", + "ecs:DescribeTasks" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyA45BF396", + "Roles": [ + { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n while has_tasks(cluster, instance_arn):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\n\ndef has_tasks(cluster, instance_arn):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n tasks = instance['runningTasksCount'] + instance['pendingTasksCount']\n print('Instance %s has %s tasks' % (instance_arn, tasks))\n\n return tasks > 0\n\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n" + }, + "Handler": "index.lambda_handler", + "Role": { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA", + "Arn" + ] + }, + "Runtime": "python3.6", + "Environment": { + "Variables": { + "CLUSTER": { + "Ref": "EcsCluster97242B84" + } + } + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/EcsCluster/DefaultAutoScalingGroup" + } + ], + "Timeout": 310 + }, + "DependsOn": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyA45BF396", + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" + ] + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25" + }, + "Endpoint": { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E", + "Arn" + ] + } + } + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicE6B1EBA6": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25" + } + } + }, + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleA38EC83B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "autoscaling.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy75002F88": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy75002F88", + "Roles": [ + { + "Ref": "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleA38EC83B" + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookFFA63029": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "EcsClusterDefaultAutoScalingGroupASGC1A785DB" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", + "DefaultResult": "CONTINUE", + "HeartbeatTimeout": 300, + "NotificationTargetARN": { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25" + }, + "RoleARN": { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleA38EC83B", + "Arn" + ] + } + }, + "DependsOn": [ + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy75002F88", + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleA38EC83B" + ] + }, + "EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F": { + "Type": "AWS::ServiceDiscovery::PrivateDnsNamespace", + "Properties": { + "Name": "scorekeep.com", + "Vpc": { + "Ref": "Vpc8378EB38" + } + } + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ecs-tasks.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "Links": [], + "LinuxParameters": { + "Capabilities": { + "Add": [], + "Drop": [] + }, + "Devices": [], + "Tmpfs": [] + }, + "Memory": 256, + "MountPoints": [], + "Name": "frontend", + "PortMappings": [ + { + "ContainerPort": 80, + "HostPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [], + "VolumesFrom": [] + } + ], + "Family": "awsecsintegecsTaskDef8DD0C801", + "NetworkMode": "awsvpc", + "PlacementConstraints": [], + "RequiresCompatibilities": [ + "EC2" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "Volumes": [] + } + }, + "FrontendServiceBC94BA93": { + "Type": "AWS::ECS::Service", + "Properties": { + "TaskDefinition": { + "Ref": "TaskDef54694570" + }, + "Cluster": { + "Ref": "EcsCluster97242B84" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "DesiredCount": 1, + "LaunchType": "EC2", + "LoadBalancers": [], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "FrontendServiceSecurityGroup85470DEC", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + } + }, + "PlacementConstraints": [], + "PlacementStrategies": [], + "SchedulingStrategy": "REPLICA", + "ServiceRegistries": [ + { + "RegistryArn": { + "Fn::GetAtt": [ + "FrontendServiceCloudmapService6FE76C06", + "Arn" + ] + } + } + ] + } + }, + "FrontendServiceSecurityGroup85470DEC": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-ecs/FrontendService/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "FrontendServiceCloudmapService6FE76C06": { + "Type": "AWS::ServiceDiscovery::Service", + "Properties": { + "DnsConfig": { + "DnsRecords": [ + { + "TTL": 60, + "Type": "A" + } + ], + "NamespaceId": { + "Fn::GetAtt": [ + "EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F", + "Id" + ] + }, + "RoutingPolicy": "MULTIVALUE" + }, + "HealthCheckCustomConfig": { + "FailureThreshold": 1 + }, + "Name": "frontend", + "NamespaceId": { + "Fn::GetAtt": [ + "EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F", + "Id" + ] + } + } + } + } +} diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.ts new file mode 100644 index 0000000000000..31a4f592d3c97 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.ts @@ -0,0 +1,47 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import ecs = require('../../lib'); +import { NetworkMode } from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ-ecs'); + +const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 2 }); + +const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + +cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro') +}); + +// Add Private DNS Namespace +const domainName = "scorekeep.com"; +cluster.addDefaultCloudMapNamespace({ + name: domainName, +}); + +// Create frontend service +const frontendTD = new ecs.Ec2TaskDefinition(stack, 'TaskDef', { + networkMode: NetworkMode.AwsVpc +}); + +const frontend = frontendTD.addContainer('frontend', { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + memoryLimitMiB: 256, +}); + +frontend.addPortMappings({ + containerPort: 80, + hostPort: 80, + protocol: ecs.Protocol.Tcp +}); + +new ecs.Ec2Service(stack, "FrontendService", { + cluster, + taskDefinition: frontendTD, + serviceDiscoveryOptions: { + name: "frontend" + } +}); + +app.run(); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json new file mode 100644 index 0000000000000..32b795000af0c --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json @@ -0,0 +1,879 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "EcsCluster97242B84": { + "Type": "AWS::ECS::Cluster" + }, + "EcsClusterDefaultAutoScalingGroupInstanceSecurityGroup912E1231": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-ecs/EcsCluster/DefaultAutoScalingGroup/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/EcsCluster/DefaultAutoScalingGroup" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "EcsClusterDefaultAutoScalingGroupInstanceRole3C026863": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "EcsClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy04DC6C80": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecs:CreateCluster", + "ecs:DeregisterContainerInstance", + "ecs:DiscoverPollEndpoint", + "ecs:Poll", + "ecs:RegisterContainerInstance", + "ecs:StartTelemetrySession", + "ecs:Submit*", + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "EcsClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy04DC6C80", + "Roles": [ + { + "Ref": "EcsClusterDefaultAutoScalingGroupInstanceRole3C026863" + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupInstanceProfile2CE606B3": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "EcsClusterDefaultAutoScalingGroupInstanceRole3C026863" + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupLaunchConfigB7E376C1": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": "ami-1234", + "InstanceType": "t2.micro", + "IamInstanceProfile": { + "Ref": "EcsClusterDefaultAutoScalingGroupInstanceProfile2CE606B3" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupInstanceSecurityGroup912E1231", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\necho ECS_CLUSTER=", + { + "Ref": "EcsCluster97242B84" + }, + " >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config" + ] + ] + } + } + }, + "DependsOn": [ + "EcsClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy04DC6C80", + "EcsClusterDefaultAutoScalingGroupInstanceRole3C026863" + ] + }, + "EcsClusterDefaultAutoScalingGroupASGC1A785DB": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "DesiredCapacity": "1", + "LaunchConfigurationName": { + "Ref": "EcsClusterDefaultAutoScalingGroupLaunchConfigB7E376C1" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-ecs-integ-ecs/EcsCluster/DefaultAutoScalingGroup" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + }, + "UpdatePolicy": { + "AutoScalingReplacingUpdate": { + "WillReplace": true + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25": { + "Type": "AWS::SNS::Topic" + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyA45BF396": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "autoscaling:CompleteLifecycleAction", + "ec2:DescribeInstances", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceStatus", + "ec2:DescribeHosts" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecs:ListContainerInstances", + "ecs:SubmitContainerStateChange", + "ecs:SubmitTaskStateChange", + "ecs:DescribeContainerInstances", + "ecs:UpdateContainerInstancesState", + "ecs:ListTasks", + "ecs:DescribeTasks" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyA45BF396", + "Roles": [ + { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n while has_tasks(cluster, instance_arn):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\n\ndef has_tasks(cluster, instance_arn):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n tasks = instance['runningTasksCount'] + instance['pendingTasksCount']\n print('Instance %s has %s tasks' % (instance_arn, tasks))\n\n return tasks > 0\n\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n" + }, + "Handler": "index.lambda_handler", + "Role": { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA", + "Arn" + ] + }, + "Runtime": "python3.6", + "Environment": { + "Variables": { + "CLUSTER": { + "Ref": "EcsCluster97242B84" + } + } + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-ecs/EcsCluster/DefaultAutoScalingGroup" + } + ], + "Timeout": 310 + }, + "DependsOn": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyA45BF396", + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole94543EDA" + ] + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionDA5F8A10": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25" + }, + "Endpoint": { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E", + "Arn" + ] + } + } + }, + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicE6B1EBA6": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupDrainECSHookFunctionE17A5F5E", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25" + } + } + }, + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleA38EC83B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "autoscaling.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy75002F88": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy75002F88", + "Roles": [ + { + "Ref": "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleA38EC83B" + } + ] + } + }, + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookFFA63029": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "EcsClusterDefaultAutoScalingGroupASGC1A785DB" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", + "DefaultResult": "CONTINUE", + "HeartbeatTimeout": 300, + "NotificationTargetARN": { + "Ref": "EcsClusterDefaultAutoScalingGroupDrainECSHookTopicC705BD25" + }, + "RoleARN": { + "Fn::GetAtt": [ + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleA38EC83B", + "Arn" + ] + } + }, + "DependsOn": [ + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy75002F88", + "EcsClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleA38EC83B" + ] + }, + "EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F": { + "Type": "AWS::ServiceDiscovery::PrivateDnsNamespace", + "Properties": { + "Name": "scorekeep.com", + "Vpc": { + "Ref": "Vpc8378EB38" + } + } + }, + "frontendTDTaskRole638562A0": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ecs-tasks.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "frontendTDB289C8FA": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "Links": [], + "LinuxParameters": { + "Capabilities": { + "Add": [], + "Drop": [] + }, + "Devices": [], + "Tmpfs": [] + }, + "Memory": 256, + "MountPoints": [], + "Name": "frontend", + "PortMappings": [ + { + "ContainerPort": 80, + "HostPort": 80, + "Protocol": "tcp" + } + ], + "Ulimits": [], + "VolumesFrom": [] + } + ], + "Family": "awsecsintegecsfrontendTD16AB905D", + "NetworkMode": "bridge", + "PlacementConstraints": [], + "RequiresCompatibilities": [ + "EC2" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "frontendTDTaskRole638562A0", + "Arn" + ] + }, + "Volumes": [] + } + }, + "FrontendServiceBC94BA93": { + "Type": "AWS::ECS::Service", + "Properties": { + "TaskDefinition": { + "Ref": "frontendTDB289C8FA" + }, + "Cluster": { + "Ref": "EcsCluster97242B84" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "DesiredCount": 1, + "LaunchType": "EC2", + "LoadBalancers": [], + "PlacementConstraints": [], + "PlacementStrategies": [], + "SchedulingStrategy": "REPLICA", + "ServiceRegistries": [ + { + "ContainerName": "frontend", + "ContainerPort": 80, + "RegistryArn": { + "Fn::GetAtt": [ + "FrontendServiceCloudmapService6FE76C06", + "Arn" + ] + } + } + ] + } + }, + "FrontendServiceCloudmapService6FE76C06": { + "Type": "AWS::ServiceDiscovery::Service", + "Properties": { + "DnsConfig": { + "DnsRecords": [ + { + "TTL": 60, + "Type": "SRV" + } + ], + "NamespaceId": { + "Fn::GetAtt": [ + "EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F", + "Id" + ] + }, + "RoutingPolicy": "MULTIVALUE" + }, + "HealthCheckCustomConfig": { + "FailureThreshold": 1 + }, + "Name": "frontend", + "NamespaceId": { + "Fn::GetAtt": [ + "EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F", + "Id" + ] + } + } + } + } +} diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.ts new file mode 100644 index 0000000000000..bb26b16da810d --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.ts @@ -0,0 +1,45 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import ecs = require('../../lib'); + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ-ecs'); + +const vpc = new ec2.VpcNetwork(stack, 'Vpc', { maxAZs: 2 }); + +const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + +cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro') +}); + +// Add Private DNS Namespace +const domainName = "scorekeep.com"; +cluster.addDefaultCloudMapNamespace({ + name: domainName, +}); + +// Create frontend service +// default network mode is bridge +const frontendTD = new ecs.Ec2TaskDefinition(stack, 'frontendTD'); + +const frontend = frontendTD.addContainer('frontend', { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + memoryLimitMiB: 256, +}); + +frontend.addPortMappings({ + containerPort: 80, + hostPort: 80, + protocol: ecs.Protocol.Tcp +}); + +new ecs.Ec2Service(stack, "FrontendService", { + cluster, + taskDefinition: frontendTD, + serviceDiscoveryOptions: { + name: "frontend" + } +}); + +app.run(); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts index 3712676bbcb36..c4d9f677837da 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts @@ -1,10 +1,11 @@ import { expect, haveResource } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); import elb = require('@aws-cdk/aws-elasticloadbalancing'); +import cloudmap = require('@aws-cdk/aws-servicediscovery'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import ecs = require('../../lib'); -import { BinPackResource, BuiltInAttributes, NetworkMode } from '../../lib'; +import { BinPackResource, BuiltInAttributes, ContainerImage, NamespaceType, NetworkMode } from '../../lib'; export = { "When creating an ECS Service": { @@ -147,7 +148,7 @@ export = { }, "with a TaskDefinition with Bridge network mode": { - "it errors if vpcPlacement is specified"(test: Test) { + "it errors if vpcSubnets is specified"(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); @@ -167,8 +168,8 @@ export = { new ecs.Ec2Service(stack, "Ec2Service", { cluster, taskDefinition, - vpcPlacement: { - subnetsToUse: ec2.SubnetType.Public + vpcSubnets: { + subnetType: ec2.SubnetType.Public } }); }); @@ -230,7 +231,7 @@ export = { test.done(); }, - "it allows vpcPlacement"(test: Test) { + "it allows vpcSubnets"(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); @@ -248,8 +249,8 @@ export = { new ecs.Ec2Service(stack, "Ec2Service", { cluster, taskDefinition, - vpcPlacement: { - subnetsToUse: ec2.SubnetType.Public + vpcSubnets: { + subnetType: ec2.SubnetType.Public } }); @@ -522,6 +523,408 @@ export = { ], })); + test.done(); + }, + }, + + 'When enabling service discovery': { + 'throws if namespace has not been added to cluster'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + // default network mode is bridge + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + memoryLimitMiB: 512 + }); + container.addPortMappings({ containerPort: 8000 }); + + // THEN + test.throws(() => { + new ecs.Ec2Service(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp', + } + }); + }, /Cannot enable service discovery if a Cloudmap Namespace has not been created in the cluster./); + + test.done(); + }, + + 'throws if network mode is none'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + networkMode: NetworkMode.None + }); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + memoryLimitMiB: 512 + }); + container.addPortMappings({ containerPort: 8000 }); + + cluster.addDefaultCloudMapNamespace({ name: 'foo.com' }); + + // THEN + test.throws(() => { + new ecs.Ec2Service(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp', + } + }); + }, /Cannot use a service discovery if NetworkMode is None. Use Bridge, Host or AwsVpc instead./); + + test.done(); + }, + + 'creates AWS Cloud Map service for Private DNS namespace with bridge network mode'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + // default network mode is bridge + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + memoryLimitMiB: 512 + }); + container.addPortMappings({ containerPort: 8000 }); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: 'foo.com', + type: NamespaceType.PrivateDns + }); + + new ecs.Ec2Service(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp', + } + }); + + // THEN + expect(stack).to(haveResource("AWS::ECS::Service", { + ServiceRegistries: [ + { + ContainerName: "MainContainer", + ContainerPort: 8000, + RegistryArn: { + "Fn::GetAtt": [ + "ServiceCloudmapService046058A4", + "Arn" + ] + } + } + ] + })); + + expect(stack).to(haveResource('AWS::ServiceDiscovery::Service', { + DnsConfig: { + DnsRecords: [ + { + TTL: 60, + Type: "SRV" + } + ], + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + }, + RoutingPolicy: 'MULTIVALUE' + }, + HealthCheckCustomConfig: { + FailureThreshold: 1 + }, + Name: "myApp", + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + } + })); + + test.done(); + }, + + 'creates AWS Cloud Map service for Private DNS namespace with host network mode'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + networkMode: NetworkMode.Host + }); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + memoryLimitMiB: 512 + }); + container.addPortMappings({ containerPort: 8000 }); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: 'foo.com', + type: NamespaceType.PrivateDns + }); + + new ecs.Ec2Service(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp', + } + }); + + // THEN + expect(stack).to(haveResource("AWS::ECS::Service", { + ServiceRegistries: [ + { + ContainerName: "MainContainer", + ContainerPort: 8000, + RegistryArn: { + "Fn::GetAtt": [ + "ServiceCloudmapService046058A4", + "Arn" + ] + } + } + ] + })); + + expect(stack).to(haveResource('AWS::ServiceDiscovery::Service', { + DnsConfig: { + DnsRecords: [ + { + TTL: 60, + Type: "SRV" + } + ], + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + }, + RoutingPolicy: 'MULTIVALUE' + }, + HealthCheckCustomConfig: { + FailureThreshold: 1 + }, + Name: "myApp", + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + } + })); + + test.done(); + }, + + 'throws if wrong DNS record type specified with bridge network mode'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + // default network mode is bridge + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + memoryLimitMiB: 512 + }); + container.addPortMappings({ containerPort: 8000 }); + + cluster.addDefaultCloudMapNamespace({ + name: 'foo.com', + }); + + // THEN + test.throws(() => { + new ecs.Ec2Service(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp', + dnsRecordType: cloudmap.DnsRecordType.A + } + }); + }, /SRV records must be used when network mode is Bridge or Host./); + + test.done(); + }, + + 'creates AWS Cloud Map service for Private DNS namespace with AwsVpc network mode'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + networkMode: NetworkMode.AwsVpc + }); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + memoryLimitMiB: 512 + }); + container.addPortMappings({ containerPort: 8000 }); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: 'foo.com', + type: NamespaceType.PrivateDns + }); + + new ecs.Ec2Service(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp', + } + }); + + // THEN + expect(stack).to(haveResource("AWS::ECS::Service", { + ServiceRegistries: [ + { + RegistryArn: { + "Fn::GetAtt": [ + "ServiceCloudmapService046058A4", + "Arn" + ] + } + } + ] + })); + + expect(stack).to(haveResource('AWS::ServiceDiscovery::Service', { + DnsConfig: { + DnsRecords: [ + { + TTL: 60, + Type: "A" + } + ], + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + }, + RoutingPolicy: 'MULTIVALUE' + }, + HealthCheckCustomConfig: { + FailureThreshold: 1 + }, + Name: "myApp", + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + } + })); + + test.done(); + }, + + 'creates AWS Cloud Map service for Private DNS namespace with AwsVpc network mode with SRV records'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + networkMode: NetworkMode.AwsVpc + }); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + memoryLimitMiB: 512 + }); + container.addPortMappings({ containerPort: 8000 }); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: 'foo.com', + type: NamespaceType.PrivateDns + }); + + new ecs.Ec2Service(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp', + dnsRecordType: cloudmap.DnsRecordType.SRV + } + }); + + // THEN + expect(stack).to(haveResource("AWS::ECS::Service", { + ServiceRegistries: [ + { + ContainerName: "MainContainer", + ContainerPort: 8000, + RegistryArn: { + "Fn::GetAtt": [ + "ServiceCloudmapService046058A4", + "Arn" + ] + } + } + ] + })); + + expect(stack).to(haveResource('AWS::ServiceDiscovery::Service', { + DnsConfig: { + DnsRecords: [ + { + TTL: 60, + Type: "SRV" + } + ], + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + }, + RoutingPolicy: 'MULTIVALUE' + }, + HealthCheckCustomConfig: { + FailureThreshold: 1 + }, + Name: "myApp", + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + } + })); + test.done(); }, } diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json index 1ec79c6f8beff..95576fee3a795 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.asset-image.expected.json @@ -882,11 +882,7 @@ } }, { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], + "Action": "ecr:GetAuthorizationToken", "Effect": "Allow", "Resource": "*" }, @@ -965,7 +961,8 @@ } ] } - } + }, + "ServiceRegistries": [] }, "DependsOn": [ "FargateServiceLBPublicListenerECSGroupBE57E081", diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.l3.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.l3.expected.json index 0e5ffef65c260..67092cc624428 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.l3.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.l3.expected.json @@ -642,7 +642,8 @@ } ] } - } + }, + "ServiceRegistries": [] }, "DependsOn": [ "L3LBPublicListenerECSGroup648EEA11", @@ -698,4 +699,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json index 714a7bdb5e2a1..21beb1d2c0d63 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.lb-awsvpc-nw.expected.json @@ -461,7 +461,8 @@ } ] } - } + }, + "ServiceRegistries": [] }, "DependsOn": [ "LBPublicListenerFargateGroup5EE2FBAF", @@ -681,4 +682,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts index 705a893c0b763..483d403bf625c 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts @@ -1,10 +1,11 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); import elbv2 = require("@aws-cdk/aws-elasticloadbalancingv2"); +import cloudmap = require('@aws-cdk/aws-servicediscovery'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import ecs = require('../../lib'); -import { ContainerImage } from '../../lib'; +import { ContainerImage, NamespaceType } from '../../lib'; export = { "When creating a Fargate Service": { @@ -215,7 +216,25 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalableTarget', { MaxCapacity: 10, - MinCapacity: 1 + MinCapacity: 1, + ResourceId: { + "Fn::Join": [ + "", + [ + "service/", + { + Ref: "EcsCluster97242B84" + }, + "/", + { + "Fn::GetAtt": [ + "ServiceD69D759B", + "Name" + ] + } + ] + ] + }, })); expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalingPolicy', { @@ -235,8 +254,218 @@ export = { } })); + test.done(); + }, + + 'allows auto scaling by ALB with new service arn format'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + }); + container.addPortMappings({ containerPort: 8000 }); + + const service = new ecs.FargateService(stack, 'Service', { + cluster, + taskDefinition, + longArnEnabled: true + }); + + const lb = new elbv2.ApplicationLoadBalancer(stack, "lb", { vpc }); + const listener = lb.addListener("listener", { port: 80 }); + const targetGroup = listener.addTargets("target", { + port: 80, + targets: [service] + }); + + // WHEN + const capacity = service.autoScaleTaskCount({ maxCapacity: 10, minCapacity: 1 }); + capacity.scaleOnRequestCount("ScaleOnRequests", { + requestsPerTarget: 1000, + targetGroup + }); + + // THEN + expect(stack).to(haveResource('AWS::ApplicationAutoScaling::ScalableTarget', { + MaxCapacity: 10, + MinCapacity: 1, + ResourceId: { + "Fn::Join": [ + "", + [ + "service/", + { + Ref: "EcsCluster97242B84" + }, + "/", + { + "Fn::Select": [ + 2, + { + "Fn::Split": [ + "/", + { + Ref: "ServiceD69D759B" + } + ] + } + ] + } + ] + ] + }, + })); + test.done(); } + }, + + 'When enabling service discovery': { + 'throws if namespace has not been added to cluster'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + memoryLimitMiB: 512 + }); + container.addPortMappings({ containerPort: 8000 }); + + // THEN + test.throws(() => { + new ecs.FargateService(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp', + } + }); + }, /Cannot enable service discovery if a Cloudmap Namespace has not been created in the cluster./); + + test.done(); + }, + + 'creates cloud map service for Private DNS namespace'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + }); + container.addPortMappings({ containerPort: 8000 }); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: 'foo.com', + type: NamespaceType.PrivateDns + }); + + new ecs.FargateService(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp' + } + }); + + // THEN + expect(stack).to(haveResource('AWS::ServiceDiscovery::Service', { + DnsConfig: { + DnsRecords: [ + { + TTL: 60, + Type: "A" + } + ], + NamespaceId: { + "Fn::GetAtt": [ + "EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F", + "Id" + ] + }, + RoutingPolicy: "MULTIVALUE" + }, + HealthCheckCustomConfig: { + FailureThreshold: 1 + }, + Name: "myApp", + NamespaceId: { + 'Fn::GetAtt': [ + "EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F", + "Id" + ] + } + })); + test.done(); + }, + + 'creates AWS Cloud Map service for Private DNS namespace with SRV records'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + const container = taskDefinition.addContainer('MainContainer', { + image: ContainerImage.fromRegistry('hello'), + memoryLimitMiB: 512 + }); + container.addPortMappings({ containerPort: 8000 }); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: 'foo.com', + type: NamespaceType.PrivateDns + }); + + new ecs.FargateService(stack, 'Service', { + cluster, + taskDefinition, + serviceDiscoveryOptions: { + name: 'myApp', + dnsRecordType: cloudmap.DnsRecordType.SRV + } + }); + + // THEN + expect(stack).to(haveResource('AWS::ServiceDiscovery::Service', { + DnsConfig: { + DnsRecords: [ + { + TTL: 60, + Type: "SRV" + } + ], + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + }, + RoutingPolicy: 'MULTIVALUE' + }, + HealthCheckCustomConfig: { + FailureThreshold: 1 + }, + Name: "myApp", + NamespaceId: { + 'Fn::GetAtt': [ + 'EcsClusterDefaultServiceDiscoveryNamespaceB0971B2F', + 'Id' + ] + } + })); + + test.done(); + }, } }; diff --git a/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts b/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts index 48183f6051ba6..3fd7b9aea9563 100644 --- a/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts +++ b/packages/@aws-cdk/aws-ecs/test/test.ecs-cluster.ts @@ -1,6 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); import { InstanceType } from '@aws-cdk/aws-ec2'; +import cloudmap = require('@aws-cdk/aws-servicediscovery'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import ecs = require('../lib'); @@ -218,4 +219,102 @@ export = { test.done(); }, + + "allows adding default service discovery namespace"(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: "foo.com" + }); + + // THEN + expect(stack).to(haveResource("AWS::ServiceDiscovery::PrivateDnsNamespace", { + Name: 'foo.com', + Vpc: { + Ref: 'MyVpcF9F0CA6F' + } + })); + + test.done(); + }, + + "allows adding public service discovery namespace"(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: "foo.com", + type: ecs.NamespaceType.PublicDns + }); + + // THEN + expect(stack).to(haveResource("AWS::ServiceDiscovery::PublicDnsNamespace", { + Name: 'foo.com', + })); + + test.done(); + }, + + "throws if default service discovery namespace added more than once"(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {}); + + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), + }); + + // WHEN + cluster.addDefaultCloudMapNamespace({ + name: "foo.com" + }); + + // THEN + test.throws(() => { + cluster.addDefaultCloudMapNamespace({ + name: "foo.com" + }); + }, /Can only add default namespace once./); + + test.done(); + }, + + 'export/import of a cluster with a namespace'(test: Test) { + // GIVEN + const stack1 = new cdk.Stack(); + const vpc1 = new ec2.VpcNetwork(stack1, 'Vpc'); + const cluster1 = new ecs.Cluster(stack1, 'Cluster', { vpc: vpc1 }); + cluster1.addDefaultCloudMapNamespace({ + name: 'hello.com', + }); + + const stack2 = new cdk.Stack(); + + // WHEN + const cluster2 = ecs.Cluster.import(stack2, 'Cluster', cluster1.export()); + + // THEN + test.equal(cluster2.defaultNamespace!.type, cloudmap.NamespaceType.DnsPrivate); + test.deepEqual(stack2.node.resolve(cluster2.defaultNamespace!.namespaceId), { + 'Fn::ImportValue': 'Stack:ClusterDefaultServiceDiscoveryNamespaceNamespaceId516C01B9', + }); + + test.done(); + } }; diff --git a/packages/@aws-cdk/aws-efs/package.json b/packages/@aws-cdk/aws-efs/package.json index f1f9f4b7152fd..98f8a4f7ca78d 100644 --- a/packages/@aws-cdk/aws-efs/package.json +++ b/packages/@aws-cdk/aws-efs/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-efs", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::EFS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-efs", + "module": "aws_cdk.aws_efs" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-efs" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-eks/lib/ami.ts b/packages/@aws-cdk/aws-eks/lib/ami.ts index e25db075a4de9..c7d4a868b6921 100644 --- a/packages/@aws-cdk/aws-eks/lib/ami.ts +++ b/packages/@aws-cdk/aws-eks/lib/ami.ts @@ -9,14 +9,14 @@ export interface EksOptimizedAmiProps { * * @default Normal */ - nodeType?: NodeType; + readonly nodeType?: NodeType; /** * The Kubernetes version to use * * @default The latest version */ - kubernetesVersion?: string; + readonly kubernetesVersion?: string; } /** diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 46f743b513e55..8dcdd35f767f0 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -14,52 +14,54 @@ export interface ClusterProps { /** * The VPC in which to create the Cluster */ - vpc: ec2.IVpcNetwork; + readonly vpc: ec2.IVpcNetwork; /** * Where to place EKS Control Plane ENIs * * If you want to create public load balancers, this must include public subnets. * + * @example + * * For example, to only select private subnets, supply the following: * - * ``` - * vpcPlacements: [ - * { subnetsToUse: ec2.SubnetType.Private } + * ```ts + * vpcSubnets: [ + * { subnetType: ec2.SubnetType.Private } * ] * ``` * * @default All public and private subnets */ - vpcPlacements?: ec2.VpcPlacementStrategy[]; + readonly vpcSubnets?: ec2.SubnetSelection[]; /** * Role that provides permissions for the Kubernetes control plane to make calls to AWS API operations on your behalf. * * @default A role is automatically created for you */ - role?: iam.IRole; + readonly role?: iam.IRole; /** * Name for the cluster. * * @default Automatically generated name */ - clusterName?: string; + readonly clusterName?: string; /** * Security Group to use for Control Plane ENIs * * @default A security group is automatically created */ - securityGroup?: ec2.ISecurityGroup; + readonly securityGroup?: ec2.ISecurityGroup; /** * The Kubernetes version to run in the cluster * * @default If not supplied, will use Amazon default version */ - version?: string; + readonly version?: string; } /** @@ -160,8 +162,8 @@ export class Cluster extends ClusterBase { }); // Get subnetIds for all selected subnets - const placements = props.vpcPlacements || [{ subnetsToUse: ec2.SubnetType.Public }, { subnetsToUse: ec2.SubnetType.Private }]; - const subnetIds = flatMap(placements, p => this.vpc.subnets(p)).map(s => s.subnetId); + const placements = props.vpcSubnets || [{ subnetType: ec2.SubnetType.Public }, { subnetType: ec2.SubnetType.Private }]; + const subnetIds = [...new Set(Array().concat(...placements.map(s => props.vpc.selectSubnets(s).subnetIds)))]; const resource = new CfnCluster(this, 'Resource', { name: props.clusterName, @@ -264,9 +266,7 @@ export class Cluster extends ClusterBase { * @see https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html */ private tagSubnets() { - const privates = this.vpc.subnets({ subnetsToUse: ec2.SubnetType.Private }); - - for (const subnet of privates) { + for (const subnet of this.vpc.privateSubnets) { if (!isRealSubnetConstruct(subnet)) { // Just give up, all of them will be the same. this.node.addWarning('Could not auto-tag private subnets with "kubernetes.io/role/internal-elb=1", please remember to do this manually'); @@ -289,7 +289,7 @@ export interface AddWorkerNodesOptions extends autoscaling.CommonAutoScalingGrou /** * Instance type of the instances to start */ - instanceType: ec2.InstanceType; + readonly instanceType: ec2.InstanceType; } /** @@ -302,7 +302,7 @@ export interface AddAutoScalingGroupOptions { * Should be at most equal to the maximum number of IP addresses available to * the instance type less one. */ - maxPods: number; + readonly maxPods: number; } /** @@ -332,11 +332,3 @@ class ImportedCluster extends ClusterBase { } } } - -function flatMap(xs: T[], f: (x: T) => U[]): U[] { - const ret = new Array(); - for (const x of xs) { - ret.push(...f(x)); - } - return ret; -} diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index ffd6ccde696eb..e029753104306 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-eks", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::EKS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-eks", + "module": "aws_cdk.aws_eks" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-eks" }, "scripts": { "build": "cdk-build", @@ -54,26 +59,26 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-autoscaling": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts index 0784d1aca03f5..5d188d352cadf 100644 --- a/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts +++ b/packages/@aws-cdk/aws-eks/test/example.ssh-into-nodes.lit.ts @@ -15,7 +15,7 @@ class EksClusterStack extends cdk.Stack { /// !show const asg = cluster.addCapacity('Nodes', { instanceType: new ec2.InstanceType('t2.medium'), - vpcPlacement: { subnetsToUse: ec2.SubnetType.Public }, + vpcSubnets: { subnetType: ec2.SubnetType.Public }, keyName: 'my-key-name', }); diff --git a/packages/@aws-cdk/aws-elasticache/package.json b/packages/@aws-cdk/aws-elasticache/package.json index f33dec78e33ce..8e6bb561ce28e 100644 --- a/packages/@aws-cdk/aws-elasticache/package.json +++ b/packages/@aws-cdk/aws-elasticache/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticache", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::ElastiCache", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-elasticache", + "module": "aws_cdk.aws_elasticache" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-elasticache" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-elasticbeanstalk/package.json b/packages/@aws-cdk/aws-elasticbeanstalk/package.json index f411fae741c3d..b99bc3bca9ee5 100644 --- a/packages/@aws-cdk/aws-elasticbeanstalk/package.json +++ b/packages/@aws-cdk/aws-elasticbeanstalk/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticbeanstalk", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::ElasticBeanstalk", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-elasticbeanstalk", + "module": "aws_cdk.aws_elasticbeanstalk" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-elasticbeanstalk" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index 5ad70f90fd8cb..aff25b0decc7f 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -12,7 +12,7 @@ export interface LoadBalancerProps { /** * VPC network of the fleet instances */ - vpc: IVpcNetwork; + readonly vpc: IVpcNetwork; /** * Whether this is an internet-facing Load Balancer @@ -22,28 +22,28 @@ export interface LoadBalancerProps { * * @default false */ - internetFacing?: boolean; + readonly internetFacing?: boolean; /** * What listeners to set up for the load balancer. * * Can also be added by .addListener() */ - listeners?: LoadBalancerListener[]; + readonly listeners?: LoadBalancerListener[]; /** * What targets to load balance to. * * Can also be added by .addTarget() */ - targets?: ILoadBalancerTarget[]; + readonly targets?: ILoadBalancerTarget[]; /** * Health check settings for the load balancing targets. * * Not required but recommended. */ - healthCheck?: HealthCheck; + readonly healthCheck?: HealthCheck; } /** @@ -53,7 +53,7 @@ export interface HealthCheck { /** * What port number to health check on */ - port: number; + readonly port: number; /** * What protocol to use for health checking @@ -62,7 +62,7 @@ export interface HealthCheck { * * @default Automatic */ - protocol?: LoadBalancingProtocol; + readonly protocol?: LoadBalancingProtocol; /** * What path to use for HTTP or HTTPS health check (must return 200) @@ -72,35 +72,35 @@ export interface HealthCheck { * * @default "/" */ - path?: string; + readonly path?: string; /** * After how many successful checks is an instance considered healthy * * @default 2 */ - healthyThreshold?: number; + readonly healthyThreshold?: number; /** * After how many unsuccessful checks is an instance considered unhealthy * * @default 5 */ - unhealthyThreshold?: number; + readonly unhealthyThreshold?: number; /** * Number of seconds between health checks * * @default 30 */ - interval?: number; + readonly interval?: number; /** * Health check timeout * * @default 5 */ - timeout?: number; + readonly timeout?: number; } /** @@ -120,7 +120,7 @@ export interface LoadBalancerListener { /** * External listening port */ - externalPort: number; + readonly externalPort: number; /** * What public protocol to use for load balancing @@ -129,7 +129,7 @@ export interface LoadBalancerListener { * * May be omitted if the external port is either 80 or 443. */ - externalProtocol?: LoadBalancingProtocol; + readonly externalProtocol?: LoadBalancingProtocol; /** * Instance listening port @@ -138,7 +138,7 @@ export interface LoadBalancerListener { * * @default externalPort */ - internalPort?: number; + readonly internalPort?: number; /** * What public protocol to use for load balancing @@ -151,17 +151,17 @@ export interface LoadBalancerListener { * is 'tcp' or 'ssl', the instance protocol is 'http' if the * front-end protocol is 'https'. */ - internalProtocol?: LoadBalancingProtocol; + readonly internalProtocol?: LoadBalancingProtocol; /** * SSL policy names */ - policyNames?: string[]; + readonly policyNames?: string[]; /** * ID of SSL certificate */ - sslCertificateId?: string; + readonly sslCertificateId?: string; /** * Allow connections to the load balancer from the given set of connection peers @@ -172,7 +172,7 @@ export interface LoadBalancerListener { * * @default Anywhere */ - allowConnectionsFrom?: IConnectable[]; + readonly allowConnectionsFrom?: IConnectable[]; } export enum LoadBalancingProtocol { diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/package.json b/packages/@aws-cdk/aws-elasticloadbalancing/package.json index 9f0402608a3d7..5a34dd2ff5a69 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancing/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticloadbalancing", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS ElasticLoadBalancing", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-elasticloadbalancing", + "module": "aws_cdk.aws_elasticloadbalancing" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-elasticloadbalancing" }, "scripts": { "build": "cdk-build", @@ -54,24 +59,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-codedeploy-api": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-codedeploy-api": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codedeploy-api": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-codedeploy-api": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts index 383b32541e9ce..634070f0b979b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-certificate.ts @@ -9,14 +9,14 @@ export interface ApplicationListenerCertificateProps { /** * The listener to attach the rule to */ - listener: IApplicationListener; + readonly listener: IApplicationListener; /** * ARNs of certificates to attach * * Duplicates are not allowed. */ - certificateArns: string[]; + readonly certificateArns: string[]; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts index d0125d276aeec..55b5e3a14d2cf 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener-rule.ts @@ -14,12 +14,12 @@ export interface BaseApplicationListenerRuleProps { * * Priorities must be unique. */ - priority: number; + readonly priority: number; /** * Target groups to forward requests to */ - targetGroups?: IApplicationTargetGroup[]; + readonly targetGroups?: IApplicationTargetGroup[]; /** * Rule applies if the requested host matches the indicated host @@ -30,7 +30,7 @@ export interface BaseApplicationListenerRuleProps { * * @default No host condition */ - hostHeader?: string; + readonly hostHeader?: string; /** * Rule applies if the requested path matches the given path pattern @@ -41,7 +41,7 @@ export interface BaseApplicationListenerRuleProps { * * @default No path condition */ - pathPattern?: string; + readonly pathPattern?: string; } /** @@ -51,7 +51,7 @@ export interface ApplicationListenerRuleProps extends BaseApplicationListenerRul /** * The listener to attach the rule to */ - listener: IApplicationListener; + readonly listener: IApplicationListener; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts index 6774ce8aa21ea..ab6a663d211ef 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts @@ -18,33 +18,33 @@ export interface BaseApplicationListenerProps { * * @default Determined from port if known */ - protocol?: ApplicationProtocol; + readonly protocol?: ApplicationProtocol; /** * The port on which the listener listens for requests. * * @default Determined from protocol if known */ - port?: number; + readonly port?: number; /** * The certificates to use on this listener */ - certificateArns?: string[]; + readonly certificateArns?: string[]; /** * The security policy that defines which ciphers and protocols are supported. * * @default the current predefined security policy. */ - sslPolicy?: SslPolicy; + readonly sslPolicy?: SslPolicy; /** * Default target groups to load balance to * * @default None */ - defaultTargetGroups?: IApplicationTargetGroup[]; + readonly defaultTargetGroups?: IApplicationTargetGroup[]; /** * Allow anyone to connect to this listener @@ -59,7 +59,7 @@ export interface BaseApplicationListenerProps { * * @default true */ - open?: boolean; + readonly open?: boolean; } /** @@ -69,7 +69,7 @@ export interface ApplicationListenerProps extends BaseApplicationListenerProps { /** * The load balancer to attach this listener to */ - loadBalancer: IApplicationLoadBalancer; + readonly loadBalancer: IApplicationLoadBalancer; } /** @@ -310,17 +310,17 @@ export interface ApplicationListenerImportProps { /** * ARN of the listener */ - listenerArn: string; + readonly listenerArn: string; /** * Security group ID of the load balancer this listener is associated with */ - securityGroupId: string; + readonly securityGroupId: string; /** * The default port on which this listener is listening */ - defaultPort?: string; + readonly defaultPort?: string; } class ImportedApplicationListener extends cdk.Construct implements IApplicationListener { @@ -424,7 +424,7 @@ export interface AddRuleProps { * * @default Target groups are used as defaults */ - priority?: number; + readonly priority?: number; /** * Rule applies if the requested host matches the indicated host @@ -437,7 +437,7 @@ export interface AddRuleProps { * * @default No host condition */ - hostHeader?: string; + readonly hostHeader?: string; /** * Rule applies if the requested path matches the given path pattern @@ -450,7 +450,7 @@ export interface AddRuleProps { * * @default No path condition */ - pathPattern?: string; + readonly pathPattern?: string; } /** @@ -460,7 +460,7 @@ export interface AddApplicationTargetGroupsProps extends AddRuleProps { /** * Target groups to forward requests to */ - targetGroups: IApplicationTargetGroup[]; + readonly targetGroups: IApplicationTargetGroup[]; } /** @@ -472,14 +472,14 @@ export interface AddApplicationTargetsProps extends AddRuleProps { * * @default Determined from port if known */ - protocol?: ApplicationProtocol; + readonly protocol?: ApplicationProtocol; /** * The port on which the listener listens for requests. * * @default Determined from protocol if known */ - port?: number; + readonly port?: number; /** * The time period during which the load balancer sends a newly registered @@ -489,7 +489,7 @@ export interface AddApplicationTargetsProps extends AddRuleProps { * * @default 0 */ - slowStartSec?: number; + readonly slowStartSec?: number; /** * The stickiness cookie expiration period. @@ -501,7 +501,7 @@ export interface AddApplicationTargetsProps extends AddRuleProps { * * @default 86400 (1 day) */ - stickinessCookieDurationSec?: number; + readonly stickinessCookieDurationSec?: number; /** * The targets to add to this target group. @@ -510,7 +510,7 @@ export interface AddApplicationTargetsProps extends AddRuleProps { * target. If you use either `Instance` or `IPAddress` as targets, all * target must be of the same type. */ - targets?: IApplicationLoadBalancerTarget[]; + readonly targets?: IApplicationLoadBalancerTarget[]; /** * The name of the target group. @@ -521,7 +521,7 @@ export interface AddApplicationTargetsProps extends AddRuleProps { * * @default Automatically generated */ - targetGroupName?: string; + readonly targetGroupName?: string; /** * The amount of time for Elastic Load Balancing to wait before deregistering a target. @@ -530,12 +530,12 @@ export interface AddApplicationTargetsProps extends AddRuleProps { * * @default 300 */ - deregistrationDelaySec?: number; + readonly deregistrationDelaySec?: number; /** * Health check configuration * * @default No health check */ - healthCheck?: HealthCheck; + readonly healthCheck?: HealthCheck; } 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 60e2e3f1b465b..62e22c728243d 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 @@ -16,7 +16,7 @@ export interface ApplicationLoadBalancerProps extends BaseLoadBalancerProps { * * @default A security group is created */ - securityGroup?: ec2.ISecurityGroup; + readonly securityGroup?: ec2.ISecurityGroup; /** * The type of IP addresses to use @@ -25,21 +25,21 @@ export interface ApplicationLoadBalancerProps extends BaseLoadBalancerProps { * * @default IpAddressType.Ipv4 */ - ipAddressType?: IpAddressType; + readonly ipAddressType?: IpAddressType; /** * Indicates whether HTTP/2 is enabled. * * @default true */ - http2Enabled?: boolean; + readonly http2Enabled?: boolean; /** * The load balancer idle timeout, in seconds * * @default 60 */ - idleTimeoutSecs?: number; + readonly idleTimeoutSecs?: number; } /** @@ -88,11 +88,11 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic throw new Error(`Cannot enable access logging; don't know ELBv2 account for region ${region}`); } - // FIXME: can't use grantPut() here because that only takes IAM objects, not arbitrary principals - bucket.addToResourcePolicy(new iam.PolicyStatement() - .addPrincipal(new iam.AccountPrincipal(account)) - .addAction('s3:PutObject') - .addResource(bucket.arnForObjects(prefix || '', '*'))); + prefix = prefix || ''; + bucket.grantPut(new iam.AccountPrincipal(account), prefix + '*'); + + // make sure the bucket's policy is created before the ALB (see https://github.com/awslabs/aws-cdk/issues/1633) + this.node.addDependency(bucket); } /** @@ -503,12 +503,12 @@ export interface ApplicationLoadBalancerImportProps { /** * ARN of the load balancer */ - loadBalancerArn: string; + readonly loadBalancerArn: string; /** * ID of the load balancer's security group */ - securityGroupId: string; + readonly securityGroupId: string; } // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index a4b8bbcee1c11..4a3b99ba577ab 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -18,14 +18,14 @@ export interface ApplicationTargetGroupProps extends BaseTargetGroupProps { * * @default Determined from port if known */ - protocol?: ApplicationProtocol; + readonly protocol?: ApplicationProtocol; /** * The port on which the listener listens for requests. * * @default Determined from protocol if known */ - port?: number; + readonly port?: number; /** * The time period during which the load balancer sends a newly registered @@ -35,7 +35,7 @@ export interface ApplicationTargetGroupProps extends BaseTargetGroupProps { * * @default 0 */ - slowStartSec?: number; + readonly slowStartSec?: number; /** * The stickiness cookie expiration period. @@ -47,7 +47,7 @@ export interface ApplicationTargetGroupProps extends BaseTargetGroupProps { * * @default 86400 (1 day) */ - stickinessCookieDurationSec?: number; + readonly stickinessCookieDurationSec?: number; /** * The targets to add to this target group. @@ -56,7 +56,7 @@ export interface ApplicationTargetGroupProps extends BaseTargetGroupProps { * target. If you use either `Instance` or `IPAddress` as targets, all * target must be of the same type. */ - targets?: IApplicationLoadBalancerTarget[]; + readonly targets?: IApplicationLoadBalancerTarget[]; } /** 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 f9e96005ae95a..b31c631d6938c 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts @@ -12,14 +12,14 @@ export interface BaseNetworkListenerProps { /** * The port on which the listener listens for requests. */ - port: number; + readonly port: number; /** * Default target groups to load balance to * * @default None */ - defaultTargetGroups?: INetworkTargetGroup[]; + readonly defaultTargetGroups?: INetworkTargetGroup[]; } /** @@ -29,7 +29,7 @@ export interface NetworkListenerProps extends BaseNetworkListenerProps { /** * The load balancer to attach this listener to */ - loadBalancer: INetworkLoadBalancer; + readonly loadBalancer: INetworkLoadBalancer; } /** @@ -133,7 +133,7 @@ export interface NetworkListenerImportProps { /** * ARN of the listener */ - listenerArn: string; + readonly listenerArn: string; } /** @@ -165,7 +165,7 @@ export interface AddNetworkTargetsProps { * * @default Determined from protocol if known */ - port: number; + readonly port: number; /** * The targets to add to this target group. @@ -174,7 +174,7 @@ export interface AddNetworkTargetsProps { * target. If you use either `Instance` or `IPAddress` as targets, all * target must be of the same type. */ - targets?: INetworkLoadBalancerTarget[]; + readonly targets?: INetworkLoadBalancerTarget[]; /** * The name of the target group. @@ -185,28 +185,28 @@ export interface AddNetworkTargetsProps { * * @default Automatically generated */ - targetGroupName?: string; + readonly targetGroupName?: string; /** * The amount of time for Elastic Load Balancing to wait before deregistering a target. * - * The range is 0–3600 seconds. + * The range is 0-3600 seconds. * * @default 300 */ - deregistrationDelaySec?: number; + readonly deregistrationDelaySec?: number; /** * Indicates whether Proxy Protocol version 2 is enabled. * * @default false */ - proxyProtocolV2?: boolean; + readonly proxyProtocolV2?: boolean; /** * Health check configuration * * @default No health check */ - healthCheck?: HealthCheck; + readonly healthCheck?: HealthCheck; } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts index 96005eec9cf75..dc66da99ff445 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-load-balancer.ts @@ -13,7 +13,7 @@ export interface NetworkLoadBalancerProps extends BaseLoadBalancerProps { * * @default false */ - crossZoneEnabled?: boolean; + readonly crossZoneEnabled?: boolean; } /** @@ -218,7 +218,7 @@ export interface NetworkLoadBalancerImportProps { /** * ARN of the load balancer */ - loadBalancerArn: string; + readonly loadBalancerArn: string; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts index dc20f21d6ff52..441919078550d 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts @@ -12,14 +12,14 @@ export interface NetworkTargetGroupProps extends BaseTargetGroupProps { /** * The port on which the listener listens for requests. */ - port: number; + readonly port: number; /** * Indicates whether Proxy Protocol version 2 is enabled. * * @default false */ - proxyProtocolV2?: boolean; + readonly proxyProtocolV2?: boolean; /** * The targets to add to this target group. @@ -28,7 +28,7 @@ export interface NetworkTargetGroupProps extends BaseTargetGroupProps { * target. If you use either `Instance` or `IPAddress` as targets, all * target must be of the same type. */ - targets?: INetworkLoadBalancerTarget[]; + readonly targets?: INetworkLoadBalancerTarget[]; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts index 74680dcd74787..a5772ff7a8304 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts @@ -32,6 +32,7 @@ export abstract class BaseListener extends cdk.Construct { /** * Add a TargetGroup to the list of default actions of this listener + * @internal */ protected _addDefaultTargetGroup(targetGroup: ITargetGroup) { this.defaultActions.push({ diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts index 4ed43405deacf..5d3e73bb723ee 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-load-balancer.ts @@ -13,33 +13,33 @@ export interface BaseLoadBalancerProps { * * @default Automatically generated name */ - loadBalancerName?: string; + readonly loadBalancerName?: string; /** * The VPC network to place the load balancer in */ - vpc: ec2.IVpcNetwork; + readonly vpc: ec2.IVpcNetwork; /** * Whether the load balancer has an internet-routable address * * @default false */ - internetFacing?: boolean; + readonly internetFacing?: boolean; /** * Where in the VPC to place the load balancer * * @default Public subnets if internetFacing, otherwise private subnets */ - vpcPlacement?: ec2.VpcPlacementStrategy; + readonly vpcSubnets?: ec2.SubnetSelection; /** * Indicates whether deletion protection is enabled. * * @default false */ - deletionProtection?: boolean; + readonly deletionProtection?: boolean; } /** @@ -98,20 +98,22 @@ export abstract class BaseLoadBalancer extends cdk.Construct implements route53. const internetFacing = ifUndefined(baseProps.internetFacing, false); - const subnets = baseProps.vpc.subnets(ifUndefined(baseProps.vpcPlacement, - { subnetsToUse: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private })); + const vpcSubnets = ifUndefined(baseProps.vpcSubnets, + { subnetType: internetFacing ? ec2.SubnetType.Public : ec2.SubnetType.Private }); + + const { subnetIds, internetConnectedDependency } = baseProps.vpc.selectSubnets(vpcSubnets); this.vpc = baseProps.vpc; const resource = new CfnLoadBalancer(this, 'Resource', { name: baseProps.loadBalancerName, - subnets: subnets.map(s => s.subnetId), + subnets: subnetIds, scheme: internetFacing ? 'internet-facing' : 'internal', loadBalancerAttributes: new cdk.Token(() => renderAttributes(this.attributes)), ...additionalProps }); if (internetFacing) { - resource.node.addDependency(...subnets.map(s => s.internetConnectivityEstablished)); + resource.node.addDependency(internetConnectedDependency); } if (baseProps.deletionProtection) { this.setAttribute('deletion_protection.enabled', 'true'); } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts index 8233dc4668331..64f2130f6657b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts @@ -18,12 +18,12 @@ export interface BaseTargetGroupProps { * * @default Automatically generated */ - targetGroupName?: string; + readonly targetGroupName?: string; /** * The virtual private cloud (VPC). */ - vpc: ec2.IVpcNetwork; + readonly vpc: ec2.IVpcNetwork; /** * The amount of time for Elastic Load Balancing to wait before deregistering a target. @@ -32,14 +32,14 @@ export interface BaseTargetGroupProps { * * @default 300 */ - deregistrationDelaySec?: number; + readonly deregistrationDelaySec?: number; /** * Health check configuration * * @default No health check */ - healthCheck?: HealthCheck; + readonly healthCheck?: HealthCheck; /** * The type of targets registered to this TargetGroup, either IP or Instance. @@ -50,7 +50,7 @@ export interface BaseTargetGroupProps { * * @default Determined automatically */ - targetType?: TargetType; + readonly targetType?: TargetType; } /** @@ -62,21 +62,21 @@ export interface HealthCheck { * * @default 30 */ - intervalSecs?: number; + readonly intervalSecs?: number; /** * The ping path destination where Elastic Load Balancing sends health check requests. * * @default / */ - path?: string; + readonly path?: string; /** * The port that the load balancer uses when performing health checks on the targets. * * @default 'traffic-port' */ - port?: string; + readonly port?: string; /** * The protocol the load balancer uses when performing health checks on targets. @@ -86,7 +86,7 @@ export interface HealthCheck { * * @default HTTP for ALBs, TCP for NLBs */ - protocol?: Protocol; + readonly protocol?: Protocol; /** * The amount of time, in seconds, during which no response from a target means a failed health check. @@ -97,7 +97,7 @@ export interface HealthCheck { * * @default 5 for ALBs, 10 or 6 for NLBs */ - timeoutSeconds?: number; + readonly timeoutSeconds?: number; /** * The number of consecutive health checks successes required before considering an unhealthy target healthy. @@ -106,7 +106,7 @@ export interface HealthCheck { * * @default 5 for ALBs, 3 for NLBs */ - healthyThresholdCount?: number; + readonly healthyThresholdCount?: number; /** * The number of consecutive health check failures required before considering a target unhealthy. @@ -116,7 +116,7 @@ export interface HealthCheck { * * @default 2 */ - unhealthyThresholdCount?: number; + readonly unhealthyThresholdCount?: number; /** * HTTP code to use when checking for a successful response from a target. @@ -125,7 +125,7 @@ export interface HealthCheck { * 499, and the default value is 200. You can specify multiple values (for * example, "200,202") or a range of values (for example, "200-299"). */ - healthyHttpCodes?: string; + readonly healthyHttpCodes?: string; } /** @@ -304,17 +304,17 @@ export interface TargetGroupImportProps { /** * ARN of the target group */ - targetGroupArn: string; + readonly targetGroupArn: string; /** * Port target group is listening on */ - defaultPort: string; + readonly defaultPort: string; /** * A Token representing the list of ARNs for the load balancer routing to this target group */ - loadBalancerArns?: string; + readonly loadBalancerArns?: string; } /** @@ -350,14 +350,14 @@ export interface LoadBalancerTargetProps { /** * What kind of target this is */ - targetType: TargetType; + readonly targetType: TargetType; /** * JSON representing the target's direct addition to the TargetGroup list * * May be omitted if the target is going to register itself later. */ - targetJson?: any; + readonly targetJson?: any; } /** diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts index 96f33d4d76645..e2dc46523121b 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/imported.ts @@ -24,7 +24,7 @@ export abstract class ImportedTargetGroupBase extends cdk.Construct implements I super(scope, id); this.targetGroupArn = props.targetGroupArn; - this.loadBalancerArns = props.loadBalancerArns || new cdk.AwsNoValue().toString(); + this.loadBalancerArns = props.loadBalancerArns || cdk.Aws.noValue; } public export() { diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json index d25463697f39d..865750f894c9e 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticloadbalancingv2", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::ElasticLoadBalancingV2", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-elasticloadbalancingv2", + "module": "aws_cdk.aws_elasticloadbalancingv2" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-elasticloadbalancingv2" }, "scripts": { "build": "cdk-build", @@ -54,29 +59,29 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-codedeploy-api": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-codedeploy-api": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-codedeploy-api": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-codedeploy-api": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -88,4 +93,4 @@ "construct-ctor:@aws-cdk/aws-elasticloadbalancingv2.TargetGroupBase." ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts index 7b474d1fb7bc8..af42eff905686 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.load-balancer.ts @@ -122,6 +122,8 @@ export = { lb.logAccessLogs(bucket); // THEN + + // verify that the LB attributes reference the bucket expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { LoadBalancerAttributes: [ { @@ -134,12 +136,14 @@ export = { } ], })); + + // verify the bucket policy allows the ALB to put objects in the bucket expect(stack).to(haveResource('AWS::S3::BucketPolicy', { PolicyDocument: { Version: '2012-10-17', Statement: [ { - Action: "s3:PutObject", + Action: [ "s3:PutObject*", "s3:Abort*" ], Effect: 'Allow', Principal: { AWS: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":iam::127311923021:root" ] ] } }, Resource: { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "AccessLoggingBucketA6D88F29", "Arn" ] }, "/*" ] ] } @@ -148,6 +152,58 @@ export = { } })); + // verify the ALB depends on the bucket *and* the bucket policy + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + DependsOn: [ 'AccessLoggingBucketPolicy700D7CC6', 'AccessLoggingBucketA6D88F29' ] + }, ResourcePart.CompleteDefinition)); + + test.done(); + }, + + 'access logging with prefix'(test: Test) { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { env: { region: 'us-east-1' }}); + const vpc = new ec2.VpcNetwork(stack, 'Stack'); + const bucket = new s3.Bucket(stack, 'AccessLoggingBucket'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc }); + + // WHEN + lb.logAccessLogs(bucket, 'prefix-of-access-logs'); + + // THEN + // verify that the LB attributes reference the bucket + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { + LoadBalancerAttributes: [ + { + Key: "access_logs.s3.enabled", + Value: "true" + }, + { + Key: "access_logs.s3.bucket", + Value: { Ref: "AccessLoggingBucketA6D88F29" } + }, + { + Key: "access_logs.s3.prefix", + Value: "prefix-of-access-logs" + } + ], + })); + + // verify the bucket policy allows the ALB to put objects in the bucket + expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: [ "s3:PutObject*", "s3:Abort*" ], + Effect: 'Allow', + Principal: { AWS: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":iam::127311923021:root" ] ] } }, + Resource: { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "AccessLoggingBucketA6D88F29", "Arn" ] }, "/prefix-of-access-logs*" ] ] } + } + ] + } + })); + test.done(); }, diff --git a/packages/@aws-cdk/aws-elasticsearch/package.json b/packages/@aws-cdk/aws-elasticsearch/package.json index f906fadf9135a..18ddb719e5993 100644 --- a/packages/@aws-cdk/aws-elasticsearch/package.json +++ b/packages/@aws-cdk/aws-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-elasticsearch", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Elasticsearch", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-elasticsearch", + "module": "aws_cdk.aws_elasticsearch" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-elasticsearch" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-emr/package.json b/packages/@aws-cdk/aws-emr/package.json index 329ae6b9dc67e..ff77f8c0d8ffc 100644 --- a/packages/@aws-cdk/aws-emr/package.json +++ b/packages/@aws-cdk/aws-emr/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-emr", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::EMR", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-emr", + "module": "aws_cdk.aws_emr" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-emr" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-events/lib/event-pattern.ts b/packages/@aws-cdk/aws-events/lib/event-pattern.ts index 72b56f923b199..49e2e2ea3f96b 100644 --- a/packages/@aws-cdk/aws-events/lib/event-pattern.ts +++ b/packages/@aws-cdk/aws-events/lib/event-pattern.ts @@ -31,13 +31,13 @@ export interface EventPattern { /** * By default, this is set to 0 (zero) in all events. */ - version?: string[]; + readonly version?: string[]; /** * A unique value is generated for every event. This can be helpful in * tracing events as they move through rules to targets, and are processed. */ - id?: string[]; + readonly id?: string[]; /** * Identifies, in combination with the source field, the fields and values @@ -45,7 +45,7 @@ export interface EventPattern { * * Represents the "detail-type" event field. */ - detailType?: string[]; + readonly detailType?: string[]; /** * Identifies the service that sourced the event. All events sourced from @@ -59,12 +59,12 @@ export interface EventPattern { * * @see http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces */ - source?: string[]; + readonly source?: string[]; /** * The 12-digit number identifying an AWS account. */ - account?: string[]; + readonly account?: string[]; /** * The event timestamp, which can be specified by the service originating @@ -72,12 +72,12 @@ export interface EventPattern { * to report the start time, so this value can be noticeably before the time * the event is actually received. */ - time?: string[]; + readonly time?: string[]; /** * Identifies the AWS region where the event originated. */ - region?: string[]; + readonly region?: string[]; /** * This JSON array contains ARNs that identify resources that are involved @@ -89,11 +89,11 @@ export interface EventPattern { * Auto Scaling groups, but API calls with AWS CloudTrail do not include * resource ARNs. */ - resources?: string[]; + readonly resources?: string[]; /** * A JSON object, whose content is at the discretion of the service * originating the event. */ - detail?: any; + readonly detail?: any; } diff --git a/packages/@aws-cdk/aws-events/lib/input-options.ts b/packages/@aws-cdk/aws-events/lib/input-options.ts index 4bf55aa3fe6a8..f45cedc13a94e 100644 --- a/packages/@aws-cdk/aws-events/lib/input-options.ts +++ b/packages/@aws-cdk/aws-events/lib/input-options.ts @@ -22,7 +22,7 @@ export interface TargetInputTemplate { * } * } */ - textTemplate?: string; + readonly textTemplate?: string; /** * Input template where you can use the values of the keys from @@ -41,12 +41,12 @@ export interface TargetInputTemplate { * } * */ - jsonTemplate?: any; + readonly jsonTemplate?: any; /** * Map of JSON paths to be extracted from the event. These are key-value * pairs, where each value is a JSON path. You must use JSON dot notation, * not bracket notation. */ - pathsMap?: { [key: string]: string }; + readonly pathsMap?: { [key: string]: string }; } diff --git a/packages/@aws-cdk/aws-events/lib/rule-ref.ts b/packages/@aws-cdk/aws-events/lib/rule-ref.ts index fc7b9d8ab481b..1472cce90530b 100644 --- a/packages/@aws-cdk/aws-events/lib/rule-ref.ts +++ b/packages/@aws-cdk/aws-events/lib/rule-ref.ts @@ -5,7 +5,7 @@ export interface EventRuleImportProps { * The value of the event rule Amazon Resource Name (ARN), such as * arn:aws:events:us-east-2:123456789012:rule/example. */ - eventRuleArn: string; + readonly eventRuleArn: string; } export interface IEventRule extends IConstruct { diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 5354dda249ca7..7a8da09647a58 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -10,20 +10,20 @@ export interface EventRuleProps { /** * A description of the rule's purpose. */ - description?: string; + readonly description?: string; /** * A name for the rule. If you don't specify a name, AWS CloudFormation * generates a unique physical ID and uses that ID for the rule name. For * more information, see Name Type. */ - ruleName?: string; + readonly ruleName?: string; /** * Indicates whether the rule is enabled. * @default Rule is enabled */ - enabled?: boolean; + readonly enabled?: boolean; /** * The schedule or rate (frequency) that determines when CloudWatch Events @@ -34,7 +34,7 @@ export interface EventRuleProps { * * You must specify this property, the `eventPattern` property, or both. */ - scheduleExpression?: string; + readonly scheduleExpression?: string; /** * Describes which events CloudWatch Events routes to the specified target. @@ -49,7 +49,7 @@ export interface EventRuleProps { * method `addEventPattern` can be used to add filter values to the event * pattern. */ - eventPattern?: EventPattern; + readonly eventPattern?: EventPattern; /** * Targets to invoke when this rule matches an event. @@ -57,7 +57,7 @@ export interface EventRuleProps { * Input will be the full matched event. If you wish to specify custom * target input, use `addTarget(target[, inputOptions])`. */ - targets?: IEventRuleTarget[]; + readonly targets?: IEventRuleTarget[]; } /** diff --git a/packages/@aws-cdk/aws-events/lib/target.ts b/packages/@aws-cdk/aws-events/lib/target.ts index 0e191ea1401f5..f885562103f5c 100644 --- a/packages/@aws-cdk/aws-events/lib/target.ts +++ b/packages/@aws-cdk/aws-events/lib/target.ts @@ -6,12 +6,12 @@ export interface EventRuleTargetProps { * include alphanumeric characters, periods (.), hyphens (-), and * underscores (_). */ - id: string; + readonly id: string; /** * The Amazon Resource Name (ARN) of the target. */ - arn: string; + readonly arn: string; /** * The Amazon Resource Name (ARN) of the AWS Identity and Access Management @@ -19,26 +19,26 @@ export interface EventRuleTargetProps { * triggers multiple targets, you can use a different IAM role for each * target. */ - roleArn?: string; + readonly roleArn?: string; /** * The Amazon ECS task definition and task count to use, if the event target * is an Amazon ECS task. */ - ecsParameters?: CfnRule.EcsParametersProperty; + readonly ecsParameters?: CfnRule.EcsParametersProperty; /** * Settings that control shard assignment, when the target is a Kinesis * stream. If you don't include this parameter, eventId is used as the * partition key. */ - kinesisParameters?: CfnRule.KinesisParametersProperty; + readonly kinesisParameters?: CfnRule.KinesisParametersProperty; /** * Parameters used when the rule invokes Amazon EC2 Systems Manager Run * Command. */ - runCommandParameters?: CfnRule.RunCommandParametersProperty; + readonly runCommandParameters?: CfnRule.RunCommandParametersProperty; } /** diff --git a/packages/@aws-cdk/aws-events/package.json b/packages/@aws-cdk/aws-events/package.json index b7e8e240934b1..6e5277b59a74c 100644 --- a/packages/@aws-cdk/aws-events/package.json +++ b/packages/@aws-cdk/aws-events/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-events", - "version": "0.26.0", + "version": "0.28.0", "description": "AWS CloudWatch Events Construct Library", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-events", + "module": "aws_cdk.aws_events" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-events" }, "scripts": { "build": "cdk-build", @@ -55,20 +60,20 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-fsx/package.json b/packages/@aws-cdk/aws-fsx/package.json index fb7bdbac3b3d1..c17810ac1f87c 100644 --- a/packages/@aws-cdk/aws-fsx/package.json +++ b/packages/@aws-cdk/aws-fsx/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-fsx", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::FSx", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "fsx" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-fsx", + "module": "aws_cdk.aws_fsx" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-fsx" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -56,18 +61,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-gamelift/package.json b/packages/@aws-cdk/aws-gamelift/package.json index e3e4a1fa7380e..6ca4f8b23493f 100644 --- a/packages/@aws-cdk/aws-gamelift/package.json +++ b/packages/@aws-cdk/aws-gamelift/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-gamelift", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::GameLift", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-gamelift", + "module": "aws_cdk.aws_gamelift" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-gamelift" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-glue/lib/data-format.ts b/packages/@aws-cdk/aws-glue/lib/data-format.ts index a617db32cee1e..db95b25133ff9 100644 --- a/packages/@aws-cdk/aws-glue/lib/data-format.ts +++ b/packages/@aws-cdk/aws-glue/lib/data-format.ts @@ -54,17 +54,17 @@ export interface DataFormat { /** * `InputFormat` for this data format. */ - inputFormat: InputFormat; + readonly inputFormat: InputFormat; /** * `OutputFormat` for this data format. */ - outputFormat: OutputFormat; + readonly outputFormat: OutputFormat; /** * Serialization library for this data format. */ - serializationLibrary: SerializationLibrary; + readonly serializationLibrary: SerializationLibrary; } export namespace DataFormat { diff --git a/packages/@aws-cdk/aws-glue/lib/database.ts b/packages/@aws-cdk/aws-glue/lib/database.ts index 8d44c65548b9b..6a8339dd0fd70 100644 --- a/packages/@aws-cdk/aws-glue/lib/database.ts +++ b/packages/@aws-cdk/aws-glue/lib/database.ts @@ -32,25 +32,25 @@ export interface IDatabase extends cdk.IConstruct { } export interface DatabaseImportProps { - catalogArn: string; - catalogId: string; - databaseArn: string; - databaseName: string; - locationUri: string; + readonly catalogArn: string; + readonly catalogId: string; + readonly databaseArn: string; + readonly databaseName: string; + readonly locationUri: string; } export interface DatabaseProps { /** * The name of the database. */ - databaseName: string; + readonly databaseName: string; /** * The location of the database (for example, an HDFS path). * * @default a bucket is created and the database is stored under s3:/// */ - locationUri?: string; + readonly locationUri?: string; } /** diff --git a/packages/@aws-cdk/aws-glue/lib/schema.ts b/packages/@aws-cdk/aws-glue/lib/schema.ts index a70313fa22721..3b256db5e7bfe 100644 --- a/packages/@aws-cdk/aws-glue/lib/schema.ts +++ b/packages/@aws-cdk/aws-glue/lib/schema.ts @@ -5,19 +5,19 @@ export interface Column { /** * Name of the column. */ - name: string; + readonly name: string; /** * Type of the column. */ - type: Type; + readonly type: Type; /** * Coment describing the column. * * @default none */ - comment?: string; + readonly comment?: string; } /** @@ -27,12 +27,12 @@ export interface Type { /** * Indicates whether this type is a primitive data type. */ - isPrimitive: boolean; + readonly isPrimitive: boolean; /** * Glue InputString for this type. */ - inputString: string; + readonly inputString: string; } /** diff --git a/packages/@aws-cdk/aws-glue/lib/table.ts b/packages/@aws-cdk/aws-glue/lib/table.ts index 553fcd80d9af8..266e4d185d929 100644 --- a/packages/@aws-cdk/aws-glue/lib/table.ts +++ b/packages/@aws-cdk/aws-glue/lib/table.ts @@ -50,65 +50,65 @@ export enum TableEncryption { } export interface TableImportProps { - tableArn: string; - tableName: string; + readonly tableArn: string; + readonly tableName: string; } export interface TableProps { /** * Name of the table. */ - tableName: string; + readonly tableName: string; /** * Description of the table. * * @default generated */ - description?: string; + readonly description?: string; /** * Database in which to store the table. */ - database: IDatabase; + readonly database: IDatabase; /** * S3 bucket in which to store data. * * @default one is created for you */ - bucket?: s3.IBucket; + readonly bucket?: s3.IBucket; /** * S3 prefix under which table objects are stored. * * @default data/ */ - s3Prefix?: string; + readonly s3Prefix?: string; /** * Columns of the table. */ - columns: Column[]; + readonly columns: Column[]; /** * Partition columns of the table. * * @default table is not partitioned */ - partitionKeys?: Column[] + readonly partitionKeys?: Column[] /** * Storage type of the table's data. */ - dataFormat: DataFormat; + readonly dataFormat: DataFormat; /** * Indicates whether the table's data is compressed or not. * * @default false */ - compressed?: boolean; + readonly compressed?: boolean; /** * The kind of encryption to secure the data with. @@ -120,7 +120,7 @@ export interface TableProps { * * @default Unencrypted */ - encryption?: TableEncryption; + readonly encryption?: TableEncryption; /** * External KMS key to use for bucket encryption. @@ -129,14 +129,14 @@ export interface TableProps { * * @default key is managed by KMS. */ - encryptionKey?: kms.IEncryptionKey; + readonly encryptionKey?: kms.IEncryptionKey; /** * Indicates whether the table data is stored in subdirectories. * * @default false */ - storedAsSubDirectories?: boolean; + readonly storedAsSubDirectories?: boolean; } /** @@ -264,56 +264,45 @@ export class Table extends cdk.Construct implements ITable { /** * Grant read permissions to the table and the underlying data stored in S3 to an IAM principal. * - * @param identity the principal + * @param grantee the principal */ - public grantRead(identity: iam.IPrincipal): void { - this.grant(identity, { - permissions: readPermissions, - kmsActions: ['kms:Decrypt'] - }); - this.bucket.grantRead(identity, this.s3Prefix); + public grantRead(grantee: iam.IGrantable): iam.Grant { + const ret = this.grant(grantee, readPermissions); + if (this.encryptionKey && this.encryption === TableEncryption.ClientSideKms) { this.encryptionKey.grantDecrypt(grantee); } + this.bucket.grantRead(grantee, this.s3Prefix); + return ret; } /** * Grant write permissions to the table and the underlying data stored in S3 to an IAM principal. * - * @param identity the principal + * @param grantee the principal */ - public grantWrite(identity: iam.IPrincipal): void { - this.grant(identity, { - permissions: writePermissions, - kmsActions: ['kms:Encrypt', 'kms:GenerateDataKey'] - }); - this.bucket.grantWrite(identity, this.s3Prefix); + public grantWrite(grantee: iam.IGrantable): iam.Grant { + const ret = this.grant(grantee, writePermissions); + if (this.encryptionKey && this.encryption === TableEncryption.ClientSideKms) { this.encryptionKey.grantEncrypt(grantee); } + this.bucket.grantWrite(grantee, this.s3Prefix); + return ret; } /** * Grant read and write permissions to the table and the underlying data stored in S3 to an IAM principal. * - * @param identity the principal + * @param grantee the principal */ - public grantReadWrite(identity: iam.IPrincipal): void { - this.grant(identity, { - permissions: readPermissions.concat(writePermissions), - kmsActions: ['kms:Decrypt', 'kms:Encrypt', 'kms:GenerateDataKey'] - }); - this.bucket.grantReadWrite(identity, this.s3Prefix); + public grantReadWrite(grantee: iam.IGrantable): iam.Grant { + const ret = this.grant(grantee, [...readPermissions, ...writePermissions]); + if (this.encryptionKey && this.encryption === TableEncryption.ClientSideKms) { this.encryptionKey.grantEncryptDecrypt(grantee); } + this.bucket.grantReadWrite(grantee, this.s3Prefix); + return ret; } - private grant(identity: iam.IPrincipal, props: { - permissions: string[]; - // CSE-KMS needs to grant its own KMS policies because the bucket is unaware of the key. - // TODO: we wouldn't need this if kms.EncryptionKey exposed grant methods. - kmsActions?: string[]; - }) { - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.tableArn) - .addActions(...props.permissions)); - if (this.encryption === TableEncryption.ClientSideKms) { - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.encryptionKey!.keyArn) - .addActions(...props.kmsActions!)); - } + private grant(grantee: iam.IGrantable, actions: string[]) { + return iam.Grant.addToPrincipal({ + grantee, + resourceArns: [this.tableArn], + actions, + }); } } diff --git a/packages/@aws-cdk/aws-glue/package.json b/packages/@aws-cdk/aws-glue/package.json index 6d1e7beaeba48..bd1c18f36d236 100644 --- a/packages/@aws-cdk/aws-glue/package.json +++ b/packages/@aws-cdk/aws-glue/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-glue", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Glue", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-glue", + "module": "aws_cdk.aws_glue" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-glue" }, "scripts": { "build": "cdk-build", @@ -54,26 +59,26 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-greengrass/package.json b/packages/@aws-cdk/aws-greengrass/package.json index 7b435a33efaec..23f6a1a299bda 100644 --- a/packages/@aws-cdk/aws-greengrass/package.json +++ b/packages/@aws-cdk/aws-greengrass/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-greengrass", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Greengrass", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "greengrass" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-greengrass", + "module": "aws_cdk.aws_greengrass" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-greengrass" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -56,18 +61,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-guardduty/package.json b/packages/@aws-cdk/aws-guardduty/package.json index a5123aa6df5e5..90503be6e49e3 100644 --- a/packages/@aws-cdk/aws-guardduty/package.json +++ b/packages/@aws-cdk/aws-guardduty/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-guardduty", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::GuardDuty", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-guardduty", + "module": "aws_cdk.aws_guardduty" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-guardduty" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-iam/README.md b/packages/@aws-cdk/aws-iam/README.md index 59b875e56df8b..a1428f1eeacd4 100644 --- a/packages/@aws-cdk/aws-iam/README.md +++ b/packages/@aws-cdk/aws-iam/README.md @@ -22,6 +22,14 @@ an `ExternalId` works like this: [supplying an external ID](test/example.external-id.lit.ts) +### Principals vs Identities + +When we say *Principal*, we mean an entity you grant permissions to. This +entity can be an AWS Service, a Role, or something more abstract such as "all +users in this account" or even "all users in this organization". An +*Identity* is an IAM representing a single IAM entity that can have +a policy attached, one of `Role`, `User`, or `Group`. + ### IAM Principals When defining policy statements as part of an AssumeRole policy or as part of a diff --git a/packages/@aws-cdk/aws-iam/lib/grant.ts b/packages/@aws-cdk/aws-iam/lib/grant.ts new file mode 100644 index 0000000000000..f9eaad56795b7 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/grant.ts @@ -0,0 +1,224 @@ +import cdk = require('@aws-cdk/cdk'); +import { PolicyStatement } from "./policy-document"; +import { IGrantable } from "./principals"; + +/** + * Basic options for a grant operation + */ +export interface CommonGrantOptions { + /** + * The principal to grant to + * + * @default if principal is undefined, no work is done. + */ + readonly grantee: IGrantable; + + /** + * The actions to grant + */ + readonly actions: string[]; + + /** + * The resource ARNs to grant to + */ + readonly resourceArns: string[]; +} + +/** + * Options for a grant operation + */ +export interface GrantWithResourceOptions extends CommonGrantOptions { + /** + * The resource with a resource policy + * + * The statement will be added to the resource policy if it couldn't be + * added to the principal policy. + */ + readonly resource: IResourceWithPolicy; + + /** + * When referring to the resource in a resource policy, use this as ARN. + * + * (Depending on the resource type, this needs to be '*' in a resource policy). + * + * @default Same as regular resource ARNs + */ + readonly resourceSelfArns?: string[]; +} + +/** + * Options for a grant operation that only applies to principals + */ +export interface GrantOnPrincipalOptions extends CommonGrantOptions { + /** + * Construct to report warnings on in case grant could not be registered + */ + readonly scope?: cdk.IConstruct; +} + +/** + * Options for a grant operation to both identity and resource + */ +export interface GrantOnPrincipalAndResourceOptions extends CommonGrantOptions { + /** + * The resource with a resource policy + * + * The statement will always be added to the resource policy. + */ + readonly resource: IResourceWithPolicy; + + /** + * When referring to the resource in a resource policy, use this as ARN. + * + * (Depending on the resource type, this needs to be '*' in a resource policy). + * + * @default Same as regular resource ARNs + */ + readonly resourceSelfArns?: string[]; +} + +/** + * Result of a grant() operation + * + * This class is not instantiable by consumers on purpose, so that they will be + * required to call the Grant factory functions. + */ +export class Grant { + /** + * Grant the given permissions to the principal + * + * The permissions will be added to the principal policy primarily, falling + * back to the resource policy if necessary. The permissions must be granted + * somewhere. + * + * - Trying to grant permissions to a principal that does not admit adding to + * the principal policy while not providing a resource with a resource policy + * is an error. + * - Trying to grant permissions to an absent principal (possible in the + * case of imported resources) leads to a warning being added to the + * resource construct. + */ + public static addToPrincipalOrResource(options: GrantWithResourceOptions): Grant { + const result = Grant.addToPrincipal({ + ...options, + scope: options.resource + }); + + if (result.success) { return result; } + + const statement = new PolicyStatement() + .addActions(...options.actions) + .addResources(...(options.resourceSelfArns || options.resourceArns)) + .addPrincipal(options.grantee!.grantPrincipal); + + options.resource.addToResourcePolicy(statement); + + return new Grant({ resourceStatement: statement, options }); + } + + /** + * Try to grant the given permissions to the given principal + * + * Absence of a principal leads to a warning, but failing to add + * the permissions to a present principal is not an error. + */ + public static addToPrincipal(options: GrantOnPrincipalOptions): Grant { + const statement = new PolicyStatement() + .addActions(...options.actions) + .addResources(...options.resourceArns); + + const addedToPrincipal = options.grantee.grantPrincipal.addToPolicy(statement); + + return new Grant({ principalStatement: addedToPrincipal ? statement : undefined, options }); + } + + /** + * Add a grant both on the principal and on the resource + * + * As long as any principal is given, granting on the pricipal may fail (in + * case of a non-identity principal), but granting on the resource will + * never fail. + * + * Statement will be the resource statement. + */ + public static addToPrincipalAndResource(options: GrantOnPrincipalAndResourceOptions): Grant { + const result = Grant.addToPrincipal({ + ...options, + scope: options.resource, + }); + + const statement = new PolicyStatement() + .addActions(...options.actions) + .addResources(...(options.resourceSelfArns || options.resourceArns)) + .addPrincipal(options.grantee!.grantPrincipal); + + options.resource.addToResourcePolicy(statement); + + return new Grant({ principalStatement: statement, resourceStatement: result.resourceStatement, options }); + } + + /** + * The statement that was added to the principal's policy + * + * Can be accessed to (e.g.) add additional conditions to the statement. + */ + public readonly principalStatement?: PolicyStatement; + + /** + * The statement that was added to the resource policy + * + * Can be accessed to (e.g.) add additional conditions to the statement. + */ + public readonly resourceStatement?: PolicyStatement; + + /** + * The options originally used to set this result + * + * Private member doubles as a way to make it impossible for an object literal to + * be structurally the same as this class. + */ + private readonly options: CommonGrantOptions; + + private constructor(props: GrantProps) { + this.options = props.options; + this.principalStatement = props.principalStatement; + this.resourceStatement = props.resourceStatement; + } + + /** + * Whether the grant operation was successful + */ + public get success(): boolean { + return this.principalStatement !== undefined || this.resourceStatement !== undefined; + } + + /** + * Throw an error if this grant wasn't successful + */ + public assertSuccess(): void { + if (!this.success) { + // tslint:disable-next-line:max-line-length + throw new Error(`${describeGrant(this.options)} could not be added on either identity or resource policy.`); + } + } +} + +function describeGrant(options: CommonGrantOptions) { + return `Permissions for '${options.grantee}' to call '${options.actions}' on '${options.resourceArns}'`; +} + +interface GrantProps { + readonly options: CommonGrantOptions; + readonly principalStatement?: PolicyStatement; + readonly resourceStatement?: PolicyStatement; +} + +/** + * A resource with a resource policy that can be added to + */ +export interface IResourceWithPolicy extends cdk.IConstruct { + /** + * Add a statement to the resource's resource policy + */ + addToResourcePolicy(statement: PolicyStatement): void; +} diff --git a/packages/@aws-cdk/aws-iam/lib/group.ts b/packages/@aws-cdk/aws-iam/lib/group.ts index a8b82d68fab3f..f9e6360230e04 100644 --- a/packages/@aws-cdk/aws-iam/lib/group.ts +++ b/packages/@aws-cdk/aws-iam/lib/group.ts @@ -1,7 +1,9 @@ import { Construct } from '@aws-cdk/cdk'; import { CfnGroup } from './iam.generated'; -import { IPrincipal, Policy } from './policy'; -import { ArnPrincipal, PolicyPrincipal, PolicyStatement } from './policy-document'; +import { IIdentity } from './identity-base'; +import { Policy } from './policy'; +import { ArnPrincipal, PolicyStatement, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; import { User } from './user'; import { AttachedPolicies, undefinedIfEmpty } from './util'; @@ -18,23 +20,25 @@ export interface GroupProps { * * @default Generated by CloudFormation (recommended) */ - groupName?: string; + readonly groupName?: string; /** * A list of ARNs for managed policies associated with group. * @default No managed policies. */ - managedPolicyArns?: any[]; + readonly managedPolicyArns?: any[]; /** * The path to the group. For more information about paths, see [IAM * Identifiers](http://docs.aws.amazon.com/IAM/latest/UserGuide/index.html?Using_Identifiers.html) * in the IAM User Guide. */ - path?: string; + readonly path?: string; } -export class Group extends Construct implements IPrincipal { +export class Group extends Construct implements IIdentity { + public readonly grantPrincipal: IPrincipal = this; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; /** * The runtime name of this group. */ @@ -45,10 +49,7 @@ export class Group extends Construct implements IPrincipal { */ public readonly groupArn: string; - /** - * An "AWS" policy principal that represents this group. - */ - public readonly principal: PolicyPrincipal; + public readonly policyFragment: PrincipalPolicyFragment; private readonly managedPolicies: string[]; private readonly attachedPolicies = new AttachedPolicies(); @@ -67,7 +68,7 @@ export class Group extends Construct implements IPrincipal { this.groupName = group.groupName; this.groupArn = group.groupArn; - this.principal = new ArnPrincipal(this.groupArn); + this.policyFragment = new ArnPrincipal(this.groupArn).policyFragment; } /** @@ -97,12 +98,13 @@ export class Group extends Construct implements IPrincipal { /** * Adds an IAM statement to the default policy. */ - public addToPolicy(statement: PolicyStatement) { + public addToPolicy(statement: PolicyStatement): boolean { if (!this.defaultPolicy) { this.defaultPolicy = new Policy(this, 'DefaultPolicy'); this.defaultPolicy.attachToGroup(this); } this.defaultPolicy.addStatement(statement); + return true; } } diff --git a/packages/@aws-cdk/aws-iam/lib/identity-base.ts b/packages/@aws-cdk/aws-iam/lib/identity-base.ts new file mode 100644 index 0000000000000..9b0e2d93bd7f6 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/identity-base.ts @@ -0,0 +1,21 @@ +import cdk = require('@aws-cdk/cdk'); +import { Policy } from "./policy"; +import { IPrincipal } from "./principals"; + +/** + * A construct that represents an IAM principal, such as a user, group or role. + */ +export interface IIdentity extends IPrincipal, cdk.IConstruct { + /** + * Attaches an inline policy to this principal. + * This is the same as calling `policy.addToXxx(principal)`. + * @param policy The policy resource to attach to this principal. + */ + attachInlinePolicy(policy: Policy): void; + + /** + * Attaches a managed policy to this principal. + * @param arn The ARN of the managed policy + */ + attachManagedPolicy(arn: string): void; +} diff --git a/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts b/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts new file mode 100644 index 0000000000000..e4079c8c75694 --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/imported-resource-principal.ts @@ -0,0 +1,45 @@ +import cdk = require('@aws-cdk/cdk'); +import { PolicyStatement, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; + +/** + * Properties for an ImportedResourcePrincipal + */ +export interface ImportedResourcePrincipalProps { + /** + * The resource the role proxy is for + */ + readonly resource: cdk.IConstruct; +} + +/** + * A principal associated with an imported resource + * + * Some resources have roles associated with them which they assume, such as + * Lambda Functions, CodeBuild projects, StepFunctions machines, etc. + * + * When those resources are imported, their actual roles are not always + * imported with them. When that happens, we use an instance of this class + * instead, which will add user warnings when statements are attempted to be + * added to it. + */ +export class ImportedResourcePrincipal implements IPrincipal { + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + public readonly grantPrincipal: IPrincipal; + private readonly resource: cdk.IConstruct; + + constructor(props: ImportedResourcePrincipalProps) { + this.resource = props.resource; + this.grantPrincipal = this; + } + + public get policyFragment(): PrincipalPolicyFragment { + throw new Error(`Cannot get policy fragment of ${this.resource.node.path}, resource imported without a role`); + } + + public addToPolicy(statement: PolicyStatement): boolean { + const repr = JSON.stringify(this.resource.node.resolve(statement)); + this.resource.node.addWarning(`Add statement to this resource's role: ${repr}`); + return true; // Pretend we did the work. The human will do it for us, eventually. + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/lib/index.ts b/packages/@aws-cdk/aws-iam/lib/index.ts index 2301ccd5b6ae8..d8566a304f30f 100644 --- a/packages/@aws-cdk/aws-iam/lib/index.ts +++ b/packages/@aws-cdk/aws-iam/lib/index.ts @@ -5,6 +5,10 @@ export * from './policy'; export * from './user'; export * from './group'; export * from './lazy-role'; +export * from './principals'; +export * from './identity-base'; +export * from './grant'; +export * from './imported-resource-principal'; // AWS::IAM CloudFormation Resources: export * from './iam.generated'; diff --git a/packages/@aws-cdk/aws-iam/lib/lazy-role.ts b/packages/@aws-cdk/aws-iam/lib/lazy-role.ts index 846732dd985e5..9b4a290b9fa1f 100644 --- a/packages/@aws-cdk/aws-iam/lib/lazy-role.ts +++ b/packages/@aws-cdk/aws-iam/lib/lazy-role.ts @@ -1,6 +1,8 @@ import cdk = require('@aws-cdk/cdk'); -import { IPrincipal, Policy } from './policy'; -import { PolicyPrincipal, PolicyStatement } from './policy-document'; +import { Grant } from './grant'; +import { Policy } from './policy'; +import { PolicyStatement, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; import { IRole, Role, RoleImportProps, RoleProps } from './role'; /** @@ -13,6 +15,8 @@ import { IRole, Role, RoleImportProps, RoleProps } from './role'; * not be synthesized or deployed. */ export class LazyRole extends cdk.Construct implements IRole { + public readonly grantPrincipal: IPrincipal = this; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; private role?: Role; private readonly statements = new Array(); private readonly policies = new Array(); @@ -31,11 +35,12 @@ export class LazyRole extends cdk.Construct implements IRole { * If there is no default policy attached to this role, it will be created. * @param permission The permission statement to add to the policy document */ - public addToPolicy(statement: PolicyStatement): void { + public addToPolicy(statement: PolicyStatement): boolean { if (this.role) { - this.role.addToPolicy(statement); + return this.role.addToPolicy(statement); } else { this.statements.push(statement); + return true; } } @@ -78,24 +83,21 @@ export class LazyRole extends cdk.Construct implements IRole { return this.instantiate().roleName; } - /** - * Returns a Principal object representing the ARN of this role. - */ - public get principal(): PolicyPrincipal { - return this.instantiate().principal; + public get policyFragment(): PrincipalPolicyFragment { + return this.instantiate().policyFragment; } /** * Grant the actions defined in actions to the identity Principal on this resource. */ - public grant(identity?: IPrincipal, ...actions: string[]): void { + public grant(identity: IPrincipal, ...actions: string[]): Grant { return this.instantiate().grant(identity, ...actions); } /** * Grant permissions to the given principal to pass this role. */ - public grantPassRole(identity?: IPrincipal): void { + public grantPassRole(identity: IPrincipal): Grant { return this.instantiate().grantPassRole(identity); } diff --git a/packages/@aws-cdk/aws-iam/lib/policy-document.ts b/packages/@aws-cdk/aws-iam/lib/policy-document.ts index d761468b5643a..21022bb5ee64c 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-document.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-document.ts @@ -1,5 +1,7 @@ import cdk = require('@aws-cdk/cdk'); import { Default, RegionInfo } from '@aws-cdk/region-info'; +import { IPrincipal } from './principals'; +import { mergePrincipal } from './util'; export class PolicyDocument extends cdk.Token { private statements = new Array(); @@ -44,18 +46,37 @@ export class PolicyDocument extends cdk.Token { } /** - * Represents an IAM principal. + * Base class for policy principals */ -export abstract class PolicyPrincipal { +export abstract class PrincipalBase implements IPrincipal { + public readonly grantPrincipal: IPrincipal = this; + /** - * When this Principal is used in an AssumeRole policy, the action to use. + * Return the policy fragment that identifies this principal in a Policy. */ - public assumeRoleAction: string = 'sts:AssumeRole'; + public abstract readonly policyFragment: PrincipalPolicyFragment; /** - * Return the policy fragment that identifies this principal in a Policy. + * When this Principal is used in an AssumeRole policy, the action to use. */ - public abstract policyFragment(): PrincipalPolicyFragment; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + + public addToPolicy(_statement: PolicyStatement): boolean { + // This base class is used for non-identity principals. None of them + // have a PolicyDocument to add to. + return false; + } + + public toString() { + // This is a first pass to make the object readable. Descendant principals + // should return something nicer. + return JSON.stringify(this.policyFragment.principalJson); + } + + public toJSON() { + // Have to implement toJSON() because the default will lead to infinite recursion. + return this.policyFragment.principalJson; + } } /** @@ -71,37 +92,69 @@ export class PrincipalPolicyFragment { } } -export class ArnPrincipal extends PolicyPrincipal { +export class ArnPrincipal extends PrincipalBase { constructor(public readonly arn: string) { super(); } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { return new PrincipalPolicyFragment({ AWS: [ this.arn ] }); } + + public toString() { + return `ArnPrincipal(${this.arn})`; + } } export class AccountPrincipal extends ArnPrincipal { constructor(public readonly accountId: any) { super(new StackDependentToken(stack => `arn:${stack.partition}:iam::${accountId}:root`).toString()); } + + public toString() { + return `AccountPrincipal(${this.accountId})`; + } } /** * An IAM principal that represents an AWS service (i.e. sqs.amazonaws.com). */ -export class ServicePrincipal extends PolicyPrincipal { +export class ServicePrincipal extends PrincipalBase { constructor(public readonly service: string, private readonly opts: ServicePrincipalOpts = {}) { super(); } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { return new PrincipalPolicyFragment({ Service: [ new ServicePrincipalToken(this.service, this.opts).toString() ] }); } + + public toString() { + return `ServicePrincipal(${this.service})`; + } +} + +/** + * A principal that represents an AWS Organization + */ +export class OrganizationPrincipal extends PrincipalBase { + constructor(public readonly organizationId: string) { + super(); + } + + public get policyFragment(): PrincipalPolicyFragment { + return new PrincipalPolicyFragment( + { AWS: ['*'] }, + { StringEquals: { 'aws:PrincipalOrgID': this.organizationId } } + ); + } + + public toString() { + return `OrganizationPrincipal(${this.organizationId})`; + } } /** @@ -117,33 +170,49 @@ export class ServicePrincipal extends PolicyPrincipal { * for more details. * */ -export class CanonicalUserPrincipal extends PolicyPrincipal { +export class CanonicalUserPrincipal extends PrincipalBase { constructor(public readonly canonicalUserId: string) { super(); } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { return new PrincipalPolicyFragment({ CanonicalUser: [ this.canonicalUserId ] }); } + + public toString() { + return `CanonicalUserPrincipal(${this.canonicalUserId})`; + } } -export class FederatedPrincipal extends PolicyPrincipal { +export class FederatedPrincipal extends PrincipalBase { + public readonly assumeRoleAction: string; + constructor( public readonly federated: string, public readonly conditions: {[key: string]: any}, - public assumeRoleAction: string = 'sts:AssumeRole') { + assumeRoleAction: string = 'sts:AssumeRole') { super(); + + this.assumeRoleAction = assumeRoleAction; } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { return new PrincipalPolicyFragment({ Federated: [ this.federated ] }, this.conditions); } + + public toString() { + return `FederatedPrincipal(${this.federated})`; + } } export class AccountRootPrincipal extends AccountPrincipal { constructor() { super(new StackDependentToken(stack => stack.accountId).toString()); } + + public toString() { + return `AccountRootPrincipal()`; + } } /** @@ -153,6 +222,10 @@ export class AnyPrincipal extends ArnPrincipal { constructor() { super('*'); } + + public toString() { + return `AnyPrincipal()`; + } } /** @@ -161,17 +234,18 @@ export class AnyPrincipal extends ArnPrincipal { */ export class Anyone extends AnyPrincipal { } -export class CompositePrincipal extends PolicyPrincipal { - private readonly principals = new Array(); +export class CompositePrincipal extends PrincipalBase { + public readonly assumeRoleAction: string; + private readonly principals = new Array(); - constructor(principal: PolicyPrincipal, ...additionalPrincipals: PolicyPrincipal[]) { + constructor(principal: PrincipalBase, ...additionalPrincipals: PrincipalBase[]) { super(); this.assumeRoleAction = principal.assumeRoleAction; this.addPrincipals(principal); this.addPrincipals(...additionalPrincipals); } - public addPrincipals(...principals: PolicyPrincipal[]): this { + public addPrincipals(...principals: PrincipalBase[]): this { for (const p of principals) { if (p.assumeRoleAction !== this.assumeRoleAction) { throw new Error( @@ -179,7 +253,7 @@ export class CompositePrincipal extends PolicyPrincipal { `Expecting "${this.assumeRoleAction}", got "${p.assumeRoleAction}"`); } - const fragment = p.policyFragment(); + const fragment = p.policyFragment; if (fragment.conditions && Object.keys(fragment.conditions).length > 0) { throw new Error( `Components of a CompositePrincipal must not have conditions. ` + @@ -192,15 +266,19 @@ export class CompositePrincipal extends PolicyPrincipal { return this; } - public policyFragment(): PrincipalPolicyFragment { + public get policyFragment(): PrincipalPolicyFragment { const principalJson: { [key: string]: string[] } = { }; for (const p of this.principals) { - mergePrincipal(principalJson, p.policyFragment().principalJson); + mergePrincipal(principalJson, p.policyFragment.principalJson); } return new PrincipalPolicyFragment(principalJson); } + + public toString() { + return `CompositePrincipal(${this.principals})`; + } } /** @@ -244,8 +322,8 @@ export class PolicyStatement extends cdk.Token { return Object.keys(this.principal).length > 0; } - public addPrincipal(principal: PolicyPrincipal): this { - const fragment = principal.policyFragment(); + public addPrincipal(principal: IPrincipal): this { + const fragment = principal.policyFragment; mergePrincipal(this.principal, fragment.principalJson); this.addConditions(fragment.conditions); return this; @@ -446,21 +524,6 @@ export enum PolicyStatementEffect { Deny = 'Deny', } -function mergePrincipal(target: { [key: string]: string[] }, source: { [key: string]: string[] }) { - for (const key of Object.keys(source)) { - target[key] = target[key] || []; - - const value = source[key]; - if (!Array.isArray(value)) { - throw new Error(`Principal value must be an array (it will be normalized later): ${value}`); - } - - target[key].push(...value); - } - - return target; -} - /** * A lazy token that requires an instance of Stack to evaluate */ @@ -496,5 +559,5 @@ export interface ServicePrincipalOpts { * * @default the current Stack's region. */ - region?: string; + readonly region?: string; } diff --git a/packages/@aws-cdk/aws-iam/lib/policy.ts b/packages/@aws-cdk/aws-iam/lib/policy.ts index bafc0c5d3f340..6ac5a4e9d8c5f 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy.ts @@ -1,46 +1,11 @@ import { Construct, Token } from '@aws-cdk/cdk'; import { Group } from './group'; import { CfnPolicy } from './iam.generated'; -import { PolicyDocument, PolicyPrincipal, PolicyStatement } from './policy-document'; +import { PolicyDocument, PolicyStatement } from './policy-document'; import { IRole } from './role'; import { User } from './user'; import { generatePolicyName, undefinedIfEmpty } from './util'; -/** - * A construct that represents an IAM principal, such as a user, group or role. - */ -export interface IPrincipal { - /** - * The IAM principal of this identity (i.e. AWS principal, service principal, etc). - */ - readonly principal: PolicyPrincipal; - - /** - * Adds an IAM statement to the default inline policy associated with this - * principal. If a policy doesn't exist, it is created. - */ - addToPolicy(statement: PolicyStatement): void; - - /** - * Attaches an inline policy to this principal. - * This is the same as calling `policy.addToXxx(principal)`. - * @param policy The policy resource to attach to this principal. - */ - attachInlinePolicy(policy: Policy): void; - - /** - * Attaches a managed policy to this principal. - * @param arn The ARN of the managed policy - */ - attachManagedPolicy(arn: string): void; -} - -/** - * @deprecated Use IPrincipal - */ -// tslint:disable-next-line:no-empty-interface -export type IIdentityResource = IPrincipal; - export interface PolicyProps { /** * The name of the policy. If you specify multiple policies for an entity, @@ -50,31 +15,31 @@ export interface PolicyProps { * @default Uses the logical ID of the policy resource, which is ensured to * be unique within the stack. */ - policyName?: string; + readonly policyName?: string; /** * Users to attach this policy to. * You can also use `attachToUser(user)` to attach this policy to a user. */ - users?: User[]; + readonly users?: User[]; /** * Roles to attach this policy to. * You can also use `attachToRole(role)` to attach this policy to a role. */ - roles?: IRole[]; + readonly roles?: IRole[]; /** * Groups to attach this policy to. * You can also use `attachToGroup(group)` to attach this policy to a group. */ - groups?: Group[]; + readonly groups?: Group[]; /** * Initial set of permissions to add to this policy document. * You can also use `addPermission(statement)` to add permissions later. */ - statements?: PolicyStatement[]; + readonly statements?: PolicyStatement[]; } /** diff --git a/packages/@aws-cdk/aws-iam/lib/principals.ts b/packages/@aws-cdk/aws-iam/lib/principals.ts new file mode 100644 index 0000000000000..70bd154fc2e3c --- /dev/null +++ b/packages/@aws-cdk/aws-iam/lib/principals.ts @@ -0,0 +1,50 @@ +import { PolicyStatement, PrincipalPolicyFragment } from './policy-document'; + +/** + * Any object that has an associated principal that a permission can be granted to + */ +export interface IGrantable { + /** + * The principal to grant permissions to + */ + readonly grantPrincipal: IPrincipal; +} + +/** + * Represents a logical IAM principal. + * + * An IPrincipal describes a logical entity that can perform AWS API calls + * against sets of resources, optionally under certain conditions. + * + * Examples of simple principals are IAM objects that you create, such + * as Users or Roles. + * + * An example of a more complex principals is a `ServicePrincipal` (such as + * `new ServicePrincipal("sns.amazonaws.com")`, which represents the Simple + * Notifications Service). + * + * A single logical Principal may also map to a set of physical principals. + * For example, `new OrganizationPrincipal('o-1234')` represents all + * identities that are part of the given AWS Organization. + */ +export interface IPrincipal extends IGrantable { + /** + * When this Principal is used in an AssumeRole policy, the action to use. + */ + readonly assumeRoleAction: string; + + /** + * Return the policy fragment that identifies this principal in a Policy. + */ + readonly policyFragment: PrincipalPolicyFragment; + + /** + * Add to the policy of this principal. + * + * @returns true if the statement was added, false if the principal in + * question does not have a policy document to add the statement to. + */ + addToPolicy(statement: PolicyStatement): boolean; +} + +// FIXME: Move all principals here after approval of changing PR. \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/lib/role.ts b/packages/@aws-cdk/aws-iam/lib/role.ts index 4b7479abbc15b..dc20cdbf382d8 100644 --- a/packages/@aws-cdk/aws-iam/lib/role.ts +++ b/packages/@aws-cdk/aws-iam/lib/role.ts @@ -1,7 +1,10 @@ -import { CfnOutput, Construct, IConstruct } from '@aws-cdk/cdk'; +import { CfnOutput, Construct } from '@aws-cdk/cdk'; +import { Grant } from './grant'; import { CfnRole } from './iam.generated'; -import { IPrincipal, Policy } from './policy'; -import { ArnPrincipal, PolicyDocument, PolicyPrincipal, PolicyStatement } from './policy-document'; +import { IIdentity } from './identity-base'; +import { Policy } from './policy'; +import { ArnPrincipal, PolicyDocument, PolicyStatement, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; import { AttachedPolicies, undefinedIfEmpty } from './util'; export interface RoleProps { @@ -12,7 +15,7 @@ export interface RoleProps { * You can later modify the assume role policy document by accessing it via * the `assumeRolePolicy` property. */ - assumedBy: PolicyPrincipal; + readonly assumedBy: IPrincipal; /** * ID that the role assumer needs to provide when assuming this role @@ -22,14 +25,14 @@ export interface RoleProps { * * @default No external ID required */ - externalId?: string; + readonly externalId?: string; /** * A list of ARNs for managed policies associated with this role. * You can add managed policies later using `attachManagedPolicy(arn)`. * @default No managed policies. */ - managedPolicyArns?: string[]; + readonly managedPolicyArns?: string[]; /** * A list of named policies to inline into this role. These policies will be @@ -38,13 +41,13 @@ export interface RoleProps { * dependencies that could otherwise be introduced). * @default No policy is inlined in the Role resource. */ - inlinePolicies?: { [name: string]: PolicyDocument }; + readonly inlinePolicies?: { [name: string]: PolicyDocument }; /** * The path associated with this role. For information about IAM paths, see * Friendly Names and Paths in IAM User Guide. */ - path?: string; + readonly path?: string; /** * A name for the IAM role. For valid values, see the RoleName parameter for @@ -60,7 +63,7 @@ export interface RoleProps { * acknowledge your template's capabilities. For more information, see * Acknowledging IAM Resources in AWS CloudFormation Templates. */ - roleName?: string; + readonly roleName?: string; /** * The maximum session duration (in seconds) that you want to set for the @@ -81,7 +84,7 @@ export interface RoleProps { * * @link https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html */ - maxSessionDurationSec?: number; + readonly maxSessionDurationSec?: number; } /** @@ -98,6 +101,10 @@ export class Role extends Construct implements IRole { return new ImportedRole(scope, id, props); } + public readonly grantPrincipal: IPrincipal = this; + + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + /** * The assume role policy document associated with this role. */ @@ -120,9 +127,9 @@ export class Role extends Construct implements IRole { public readonly roleName: string; /** - * Returns the ARN of this role. + * Returns the role. */ - public readonly principal: PolicyPrincipal; + public readonly policyFragment: PrincipalPolicyFragment; private defaultPolicy?: Policy; private readonly managedPolicyArns: string[]; @@ -147,8 +154,8 @@ export class Role extends Construct implements IRole { this.roleId = role.roleId; this.roleArn = role.roleArn; - this.principal = new ArnPrincipal(this.roleArn); this.roleName = role.roleName; + this.policyFragment = new ArnPrincipal(this.roleArn).policyFragment; function _flatten(policies?: { [name: string]: PolicyDocument }) { if (policies == null || Object.keys(policies).length === 0) { @@ -175,12 +182,13 @@ export class Role extends Construct implements IRole { * If there is no default policy attached to this role, it will be created. * @param permission The permission statement to add to the policy document */ - public addToPolicy(statement: PolicyStatement) { + public addToPolicy(statement: PolicyStatement): boolean { if (!this.defaultPolicy) { this.defaultPolicy = new Policy(this, 'DefaultPolicy'); this.attachInlinePolicy(this.defaultPolicy); } this.defaultPolicy.addStatement(statement); + return true; } /** @@ -203,28 +211,27 @@ export class Role extends Construct implements IRole { /** * Grant the actions defined in actions to the identity Principal on this resource. */ - public grant(identity?: IPrincipal, ...actions: string[]) { - if (!identity) { - return; - } - - identity.addToPolicy(new PolicyStatement() - .addResource(this.roleArn) - .addActions(...actions)); + public grant(grantee: IPrincipal, ...actions: string[]) { + return Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [this.roleArn], + scope: this + }); } /** * Grant permissions to the given principal to pass this role. */ - public grantPassRole(identity?: IPrincipal) { - this.grant(identity, 'iam:PassRole'); + public grantPassRole(identity: IPrincipal) { + return this.grant(identity, 'iam:PassRole'); } } /** * A Role object */ -export interface IRole extends IConstruct, IPrincipal { +export interface IRole extends IIdentity { /** * Returns the ARN of this role. */ @@ -249,15 +256,15 @@ export interface IRole extends IConstruct, IPrincipal { /** * Grant the actions defined in actions to the identity Principal on this resource. */ - grant(identity?: IPrincipal, ...actions: string[]): void; + grant(grantee: IPrincipal, ...actions: string[]): Grant; /** * Grant permissions to the given principal to pass this role. */ - grantPassRole(identity?: IPrincipal): void; + grantPassRole(grantee: IPrincipal): Grant; } -function createAssumeRolePolicy(principal: PolicyPrincipal, externalId?: string) { +function createAssumeRolePolicy(principal: IPrincipal, externalId?: string) { const statement = new PolicyStatement(); statement .addPrincipal(principal) @@ -287,7 +294,7 @@ export interface RoleImportProps { /** * The role's ARN */ - roleArn: string; + readonly roleArn: string; /** * The stable and unique string identifying the role. For example, @@ -296,15 +303,17 @@ export interface RoleImportProps { * @default If "roleId" is not specified for an imported role, then * `role.roleId` will throw an exception. In most cases, role ID is not really needed. */ - roleId?: string; + readonly roleId?: string; } /** * A role that already exists */ class ImportedRole extends Construct implements IRole { + public readonly grantPrincipal: IPrincipal = this; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; + public readonly policyFragment: PrincipalPolicyFragment; public readonly roleArn: string; - public readonly principal: PolicyPrincipal; private readonly _roleId?: string; @@ -312,7 +321,7 @@ class ImportedRole extends Construct implements IRole { super(scope, id); this.roleArn = props.roleArn; this._roleId = props.roleId; - this.principal = new ArnPrincipal(this.roleArn); + this.policyFragment = new ArnPrincipal(this.roleArn).policyFragment; } public get roleId() { @@ -330,8 +339,9 @@ class ImportedRole extends Construct implements IRole { return this.props; } - public addToPolicy(_statement: PolicyStatement): void { - // FIXME: Add warning that we're ignoring this + public addToPolicy(_statement: PolicyStatement): boolean { + // Statement will be added to resource instead + return false; } public attachInlinePolicy(_policy: Policy): void { @@ -345,14 +355,19 @@ class ImportedRole extends Construct implements IRole { /** * Grant the actions defined in actions to the identity Principal on this resource. */ - public grant(_identity?: IPrincipal, ..._actions: string[]): void { - // FIXME: Add warning that we're ignoring this + public grant(grantee: IPrincipal, ...actions: string[]): Grant { + return Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [this.roleArn], + scope: this + }); } /** * Grant permissions to the given principal to pass this role. */ - public grantPassRole(_identity?: IPrincipal): void { - // FIXME: Add warning that we're ignoring this + public grantPassRole(identity: IPrincipal): Grant { + return this.grant(identity, 'iam:PassRole'); } } diff --git a/packages/@aws-cdk/aws-iam/lib/user.ts b/packages/@aws-cdk/aws-iam/lib/user.ts index 9ddca09349783..8613922888a03 100644 --- a/packages/@aws-cdk/aws-iam/lib/user.ts +++ b/packages/@aws-cdk/aws-iam/lib/user.ts @@ -1,8 +1,11 @@ -import { Construct } from '@aws-cdk/cdk'; +import { Construct, SecretValue } from '@aws-cdk/cdk'; import { Group } from './group'; import { CfnUser } from './iam.generated'; -import { IPrincipal, Policy } from './policy'; -import { ArnPrincipal, PolicyPrincipal, PolicyStatement } from './policy-document'; +import { IIdentity } from './identity-base'; +import { Policy } from './policy'; +import { PolicyStatement } from './policy-document'; +import { ArnPrincipal, PrincipalPolicyFragment } from './policy-document'; +import { IPrincipal } from './principals'; import { AttachedPolicies, undefinedIfEmpty } from './util'; export interface UserProps { @@ -10,20 +13,20 @@ export interface UserProps { * Groups to add this user to. You can also use `addToGroup` to add this * user to a group. */ - groups?: Group[]; + readonly groups?: Group[]; /** * A list of ARNs for managed policies attacherd to this user. * You can use `addManagedPolicy(arn)` to attach a managed policy to this user. * @default No managed policies. */ - managedPolicyArns?: any[]; + readonly managedPolicyArns?: any[]; /** * The path for the user name. For more information about paths, see IAM * Identifiers in the IAM User Guide. */ - path?: string; + readonly path?: string; /** * A name for the IAM user. For valid values, see the UserName parameter for @@ -41,15 +44,18 @@ export interface UserProps { * * @default Generated by CloudFormation (recommended) */ - userName?: string; + readonly userName?: string; /** * The password for the user. This is required so the user can access the * AWS Management Console. * + * You can use `SecretValue.plainText` to specify a password in plain text or + * use `secretsmanager.Secret.import` to reference a secret in Secrets Manager. + * * @default User won't be able to access the management console without a password. */ - password?: string; + readonly password?: SecretValue; /** * Specifies whether the user is required to set a new password the next @@ -59,10 +65,12 @@ export interface UserProps { * * @default false */ - passwordResetRequired?: boolean; + readonly passwordResetRequired?: boolean; } -export class User extends Construct implements IPrincipal { +export class User extends Construct implements IIdentity { + public readonly grantPrincipal: IPrincipal = this; + public readonly assumeRoleAction: string = 'sts:AssumeRole'; /** * An attribute that represents the user name. @@ -74,10 +82,7 @@ export class User extends Construct implements IPrincipal { */ public readonly userArn: string; - /** - * Returns the ARN of this user. - */ - public readonly principal: PolicyPrincipal; + public readonly policyFragment: PrincipalPolicyFragment; private readonly groups = new Array(); private readonly managedPolicyArns = new Array(); @@ -97,7 +102,7 @@ export class User extends Construct implements IPrincipal { this.userName = user.userName; this.userArn = user.userArn; - this.principal = new ArnPrincipal(this.userArn); + this.policyFragment = new ArnPrincipal(this.userArn).policyFragment; if (props.groups) { props.groups.forEach(g => this.addToGroup(g)); @@ -129,20 +134,23 @@ export class User extends Construct implements IPrincipal { /** * Adds an IAM statement to the default policy. + * + * @returns true */ - public addToPolicy(statement: PolicyStatement) { + public addToPolicy(statement: PolicyStatement): boolean { if (!this.defaultPolicy) { this.defaultPolicy = new Policy(this, 'DefaultPolicy'); this.defaultPolicy.attachToUser(this); } this.defaultPolicy.addStatement(statement); + return true; } private parseLoginProfile(props: UserProps): CfnUser.LoginProfileProperty | undefined { if (props.password) { return { - password: props.password, + password: props.password.toString(), passwordResetRequired: props.passwordResetRequired }; } diff --git a/packages/@aws-cdk/aws-iam/lib/util.ts b/packages/@aws-cdk/aws-iam/lib/util.ts index e3d2bae372627..8fc2a504f9557 100644 --- a/packages/@aws-cdk/aws-iam/lib/util.ts +++ b/packages/@aws-cdk/aws-iam/lib/util.ts @@ -44,3 +44,21 @@ export class AttachedPolicies { this.policies.push(policy); } } + +/** + * Merge two dictionaries that represent IAM principals + */ +export function mergePrincipal(target: { [key: string]: string[] }, source: { [key: string]: string[] }) { + for (const key of Object.keys(source)) { + target[key] = target[key] || []; + + const value = source[key]; + if (!Array.isArray(value)) { + throw new Error(`Principal value must be an array (it will be normalized later): ${value}`); + } + + target[key].push(...value); + } + + return target; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/package.json b/packages/@aws-cdk/aws-iam/package.json index a17ec6f4cc00d..ed37c5f76de38 100644 --- a/packages/@aws-cdk/aws-iam/package.json +++ b/packages/@aws-cdk/aws-iam/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-iam", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK routines for easily assigning correct and minimal IAM permissions", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-iam", + "module": "aws_cdk.aws_iam" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-iam" }, "scripts": { "build": "cdk-build", @@ -56,22 +61,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/region-info": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/region-info": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/region-info": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/region-info": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-iam/test/example.attaching.lit.ts b/packages/@aws-cdk/aws-iam/test/example.attaching.lit.ts index 78480ec54b3a8..1f01efb1a5004 100644 --- a/packages/@aws-cdk/aws-iam/test/example.attaching.lit.ts +++ b/packages/@aws-cdk/aws-iam/test/example.attaching.lit.ts @@ -1,4 +1,5 @@ import cdk = require('@aws-cdk/cdk'); +import { SecretValue } from '@aws-cdk/cdk'; import { Group, Policy, User } from '../lib'; export class ExampleConstruct extends cdk.Construct { @@ -6,7 +7,7 @@ export class ExampleConstruct extends cdk.Construct { super(scope, id); /// !show - const user = new User(this, 'MyUser', { password: '1234' }); + const user = new User(this, 'MyUser', { password: SecretValue.plainText('1234') }); const group = new Group(this, 'MyGroup'); const policy = new Policy(this, 'MyPolicy'); diff --git a/packages/@aws-cdk/aws-iam/test/integ.user.ts b/packages/@aws-cdk/aws-iam/test/integ.user.ts index ea1b19acd4429..e21c468177554 100644 --- a/packages/@aws-cdk/aws-iam/test/integ.user.ts +++ b/packages/@aws-cdk/aws-iam/test/integ.user.ts @@ -1,4 +1,4 @@ -import { App, Stack } from "@aws-cdk/cdk"; +import { App, SecretValue, Stack } from "@aws-cdk/cdk"; import { User } from "../lib"; const app = new App(); @@ -7,7 +7,7 @@ const stack = new Stack(app, 'aws-cdk-iam-user'); new User(stack, 'MyUser', { userName: 'benisrae', - password: '1234', + password: SecretValue.plainText('1234'), passwordResetRequired: true }); diff --git a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts index 64e051a66c647..6b730c272ac3d 100644 --- a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts +++ b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts @@ -1,6 +1,6 @@ import { Stack, Token } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { Anyone, AnyPrincipal, CanonicalUserPrincipal, PolicyDocument, PolicyPrincipal, PolicyStatement } from '../lib'; +import { Anyone, AnyPrincipal, CanonicalUserPrincipal, IPrincipal, PolicyDocument, PolicyStatement } from '../lib'; import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, PrincipalPolicyFragment, ServicePrincipal } from '../lib'; export = { @@ -278,9 +278,11 @@ export = { 'addPrincipal correctly merges array in'(test: Test) { const stack = new Stack(); - const arrayPrincipal: PolicyPrincipal = { + const arrayPrincipal: IPrincipal = { + get grantPrincipal() { return this; }, assumeRoleAction: 'sts:AssumeRole', - policyFragment: () => new PrincipalPolicyFragment({ AWS: ['foo', 'bar'] }), + policyFragment: new PrincipalPolicyFragment({ AWS: ['foo', 'bar'] }), + addToPolicy() { return false; } }; const s = new PolicyStatement().addAccountRootPrincipal() .addPrincipal(arrayPrincipal); diff --git a/packages/@aws-cdk/aws-iam/test/test.role.ts b/packages/@aws-cdk/aws-iam/test/test.role.ts index b5c5d0c38c25d..c8a7a7d266976 100644 --- a/packages/@aws-cdk/aws-iam/test/test.role.ts +++ b/packages/@aws-cdk/aws-iam/test/test.role.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike, SynthUtils } from '@aws-cdk/assert'; import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; @@ -87,10 +87,10 @@ export = { assumedBy: new ServicePrincipal('sns.amazonaws.com') }); - test.ok(!('MyRoleDefaultPolicyA36BE1DD' in stack._toCloudFormation().Resources), 'initially created without a policy'); + test.ok(!('MyRoleDefaultPolicyA36BE1DD' in SynthUtils.toCloudFormation(stack).Resources), 'initially created without a policy'); role.addToPolicy(new PolicyStatement().addResource('myresource').addAction('myaction')); - test.ok(stack._toCloudFormation().Resources.MyRoleDefaultPolicyA36BE1DD, 'policy resource created'); + test.ok(SynthUtils.toCloudFormation(stack).Resources.MyRoleDefaultPolicyA36BE1DD, 'policy resource created'); expect(stack).toMatch({ Resources: { MyRoleF48FFE04: diff --git a/packages/@aws-cdk/aws-iam/test/test.user.ts b/packages/@aws-cdk/aws-iam/test/test.user.ts index 4982a39749ce6..d483669635c26 100644 --- a/packages/@aws-cdk/aws-iam/test/test.user.ts +++ b/packages/@aws-cdk/aws-iam/test/test.user.ts @@ -1,4 +1,4 @@ -import { App, Stack } from '@aws-cdk/cdk'; +import { App, SecretValue, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { User } from '../lib'; @@ -17,7 +17,7 @@ export = { const app = new App(); const stack = new Stack(app, 'MyStack'); new User(stack, 'MyUser', { - password: '1234' + password: SecretValue.plainText('1234') }); test.deepEqual(app.synthesizeStack(stack.name).template, { Resources: { MyUserDC45028B: diff --git a/packages/@aws-cdk/aws-inspector/package.json b/packages/@aws-cdk/aws-inspector/package.json index 23183f6447090..f0f21f2f18892 100644 --- a/packages/@aws-cdk/aws-inspector/package.json +++ b/packages/@aws-cdk/aws-inspector/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-inspector", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Inspector", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-inspector", + "module": "aws_cdk.aws_inspector" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-inspector" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-iot/package.json b/packages/@aws-cdk/aws-iot/package.json index 9b3be52a2bb82..7f254fb301ed8 100644 --- a/packages/@aws-cdk/aws-iot/package.json +++ b/packages/@aws-cdk/aws-iot/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-iot", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::IoT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-iot", + "module": "aws_cdk.aws_iot" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-iot" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-iot1click/package.json b/packages/@aws-cdk/aws-iot1click/package.json index 706bd6b539b43..37d015698c5e1 100644 --- a/packages/@aws-cdk/aws-iot1click/package.json +++ b/packages/@aws-cdk/aws-iot1click/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-iot1click", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::IoT1Click", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "iot1click" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-iot1click", + "module": "aws_cdk.aws_iot1click" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-iot1click" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -55,18 +60,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-iotanalytics/package.json b/packages/@aws-cdk/aws-iotanalytics/package.json index 95c79547a4a92..32ed2ffb3087e 100644 --- a/packages/@aws-cdk/aws-iotanalytics/package.json +++ b/packages/@aws-cdk/aws-iotanalytics/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-iotanalytics", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::IoTAnalytics", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "iotanalytics" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-iotanalytics", + "module": "aws_cdk.aws_iotanalytics" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-iotanalytics" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -56,18 +61,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-kinesis/lib/stream.ts b/packages/@aws-cdk/aws-kinesis/lib/stream.ts index 4a79da7e65ca1..1e86c62ad84bd 100644 --- a/packages/@aws-cdk/aws-kinesis/lib/stream.ts +++ b/packages/@aws-cdk/aws-kinesis/lib/stream.ts @@ -32,7 +32,7 @@ export interface IStream extends cdk.IConstruct, logs.ILogSubscriptionDestinatio * If an encryption key is used, permission to ues the key to decrypt the * contents of the stream will also be granted. */ - grantRead(identity?: iam.IPrincipal): void; + grantRead(grantee: iam.IGrantable): iam.Grant; /** * Grant write permissions for this stream and its contents to an IAM @@ -41,7 +41,7 @@ export interface IStream extends cdk.IConstruct, logs.ILogSubscriptionDestinatio * If an encryption key is used, permission to ues the key to encrypt the * contents of the stream will also be granted. */ - grantWrite(identity?: iam.IPrincipal): void; + grantWrite(grantee: iam.IGrantable): iam.Grant; /** * Grants read/write permissions for this stream and its contents to an IAM @@ -50,7 +50,7 @@ export interface IStream extends cdk.IConstruct, logs.ILogSubscriptionDestinatio * If an encryption key is used, permission to use the key for * encrypt/decrypt will also be granted. */ - grantReadWrite(identity?: iam.IPrincipal): void; + grantReadWrite(grantee: iam.IGrantable): iam.Grant; } /** @@ -62,12 +62,12 @@ export interface StreamImportProps { /** * The ARN of the stream. */ - streamArn: string; + readonly streamArn: string; /** * The KMS key securing the contents of the stream if encryption is enabled. */ - encryptionKey?: kms.EncryptionKeyImportProps; + readonly encryptionKey?: kms.EncryptionKeyImportProps; } /** @@ -117,23 +117,14 @@ export abstract class StreamBase extends cdk.Construct implements IStream { * If an encryption key is used, permission to ues the key to decrypt the * contents of the stream will also be granted. */ - public grantRead(identity?: iam.IPrincipal) { - if (!identity) { - return; + public grantRead(grantee: iam.IGrantable) { + const ret = this.grant(grantee, 'kinesis:DescribeStream', 'kinesis:GetRecords', 'kinesis:GetShardIterator'); + + if (this.encryptionKey) { + this.encryptionKey.grantDecrypt(grantee); } - this.grant( - identity, - { - streamActions: [ - 'kinesis:DescribeStream', - 'kinesis:GetRecords', - 'kinesis:GetShardIterator' - ], - keyActions: [ - 'kms:Decrypt' - ] - } - ); + + return ret; } /** @@ -143,25 +134,14 @@ export abstract class StreamBase extends cdk.Construct implements IStream { * If an encryption key is used, permission to ues the key to decrypt the * contents of the stream will also be granted. */ - public grantWrite(identity?: iam.IPrincipal) { - if (!identity) { - return; + public grantWrite(grantee: iam.IGrantable) { + const ret = this.grant(grantee, 'kinesis:DescribeStream', 'kinesis:PutRecord', 'kinesis:PutRecords'); + + if (this.encryptionKey) { + this.encryptionKey.grantEncrypt(grantee); } - this.grant( - identity, - { - streamActions: [ - 'kinesis:DescribeStream', - 'kinesis:PutRecord', - 'kinesis:PutRecords' - ], - keyActions: [ - 'kms:GenerateDataKey', - 'kms:Encrypt' - ] - } - ); + return ret; } /** @@ -171,27 +151,20 @@ export abstract class StreamBase extends cdk.Construct implements IStream { * If an encryption key is used, permission to use the key for * encrypt/decrypt will also be granted. */ - public grantReadWrite(identity?: iam.IPrincipal) { - if (!identity) { - return; + public grantReadWrite(grantee: iam.IGrantable) { + const ret = this.grant( + grantee, + 'kinesis:DescribeStream', + 'kinesis:GetRecords', + 'kinesis:GetShardIterator', + 'kinesis:PutRecord', + 'kinesis:PutRecords'); + + if (this.encryptionKey) { + this.encryptionKey.grantEncryptDecrypt(grantee); } - this.grant( - identity, - { - streamActions: [ - 'kinesis:DescribeStream', - 'kinesis:GetRecords', - 'kinesis:GetShardIterator', - 'kinesis:PutRecord', - 'kinesis:PutRecords' - ], - keyActions: [ - 'kms:Decrypt', - 'kms:GenerateDataKey', - 'kms:Encrypt' - ] - } - ); + + return ret; } public logSubscriptionDestination(sourceLogGroup: logs.ILogGroup): logs.LogSubscriptionDestination { @@ -254,17 +227,13 @@ export abstract class StreamBase extends cdk.Construct implements IStream { return dest.logSubscriptionDestination(sourceLogGroup); } - private grant(identity: iam.IPrincipal, actions: { streamActions: string[], keyActions: string[] }) { - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.streamArn) - .addActions(...actions.streamActions)); - - // grant key permissions if there's an associated key. - if (this.encryptionKey) { - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.encryptionKey.keyArn) - .addActions(...actions.keyActions)); - } + private grant(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [this.streamArn], + scope: this, + }); } } @@ -273,19 +242,19 @@ export interface StreamProps { * Enforces a particular physical stream name. * @default */ - streamName?: string; + readonly streamName?: string; /** * The number of hours for the data records that are stored in shards to remain accessible. * @default 24 */ - retentionPeriodHours?: number; + readonly retentionPeriodHours?: number; /** * The number of shards for the stream. * @default 1 */ - shardCount?: number; + readonly shardCount?: number; /** * The kind of server-side encryption to apply to this stream. @@ -295,7 +264,7 @@ export interface StreamProps { * * @default Unencrypted */ - encryption?: StreamEncryption; + readonly encryption?: StreamEncryption; /** * External KMS key to use for stream encryption. @@ -305,7 +274,7 @@ export interface StreamProps { * @default If encryption is set to "Kms" and this property is undefined, a * new KMS key will be created and associated with this stream. */ - encryptionKey?: kms.IEncryptionKey; + readonly encryptionKey?: kms.IEncryptionKey; } /** diff --git a/packages/@aws-cdk/aws-kinesis/package.json b/packages/@aws-cdk/aws-kinesis/package.json index b895b1fcf1ed7..7625a3806905b 100644 --- a/packages/@aws-cdk/aws-kinesis/package.json +++ b/packages/@aws-cdk/aws-kinesis/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-kinesis", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS Kinesis", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-kinesis", + "module": "aws_cdk.aws_kinesis" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-kinesis" }, "scripts": { "build": "cdk-build", @@ -53,25 +58,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-logs": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-logs": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts index 5e5d8a1a872ce..4612cf0f3c14f 100644 --- a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts +++ b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts @@ -305,6 +305,19 @@ export = { } }, "Resource": "*" + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "MyUserDC45028B", + "Arn" + ] + } + }, + "Resource": "*" } ], "Version": "2012-10-17" @@ -428,6 +441,23 @@ export = { } }, "Resource": "*" + }, + { + "Action": [ + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "MyUserDC45028B", + "Arn" + ] + } + }, + "Resource": "*" } ], "Version": "2012-10-17" @@ -475,8 +505,9 @@ export = { }, { "Action": [ - "kms:GenerateDataKey", - "kms:Encrypt" + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", ], "Effect": "Allow", "Resource": { @@ -554,6 +585,24 @@ export = { } }, "Resource": "*" + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "MyUserDC45028B", + "Arn" + ] + } + }, + "Resource": "*" } ], "Version": "2012-10-17" @@ -604,8 +653,9 @@ export = { { "Action": [ "kms:Decrypt", - "kms:GenerateDataKey", - "kms:Encrypt" + "kms:Encrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*" ], "Effect": "Allow", "Resource": { diff --git a/packages/@aws-cdk/aws-kinesisanalytics/package.json b/packages/@aws-cdk/aws-kinesisanalytics/package.json index 5843f6786ecb5..912734619573b 100644 --- a/packages/@aws-cdk/aws-kinesisanalytics/package.json +++ b/packages/@aws-cdk/aws-kinesisanalytics/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-kinesisanalytics", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::KinesisAnalytics", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-kinesisanalytics", + "module": "aws_cdk.aws_kinesisanalytics" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-kinesisanalytics" }, "scripts": { "build": "cdk-build", @@ -57,19 +62,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-kinesisfirehose/package.json b/packages/@aws-cdk/aws-kinesisfirehose/package.json index cd9685ceb0c6b..f96e34fdedeae 100644 --- a/packages/@aws-cdk/aws-kinesisfirehose/package.json +++ b/packages/@aws-cdk/aws-kinesisfirehose/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-kinesisfirehose", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::KinesisFirehose", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-kinesisfirehose", + "module": "aws_cdk.aws_kinesisfirehose" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-kinesisfirehose" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-kms/lib/alias.ts b/packages/@aws-cdk/aws-kms/lib/alias.ts index d0a522a0b9748..d0a47a5922734 100644 --- a/packages/@aws-cdk/aws-kms/lib/alias.ts +++ b/packages/@aws-cdk/aws-kms/lib/alias.ts @@ -11,14 +11,14 @@ export interface EncryptionKeyAliasProps { * forward slash, such as alias/. You can't specify aliases that begin with * alias/AWS. These aliases are reserved. */ - alias: string; + readonly alias: string; /** * The ID of the key for which you are creating the alias. Specify the key's * globally unique identifier or Amazon Resource Name (ARN). You can't * specify another alias. */ - key: IEncryptionKey; + readonly key: IEncryptionKey; } /** diff --git a/packages/@aws-cdk/aws-kms/lib/index.ts b/packages/@aws-cdk/aws-kms/lib/index.ts index 52d9014cc7b9c..5affbeb43c54c 100644 --- a/packages/@aws-cdk/aws-kms/lib/index.ts +++ b/packages/@aws-cdk/aws-kms/lib/index.ts @@ -1,5 +1,6 @@ export * from './key'; export * from './alias'; +export * from './via-service-principal'; // AWS::KMS CloudFormation Resources: export * from './kms.generated'; diff --git a/packages/@aws-cdk/aws-kms/lib/key.ts b/packages/@aws-cdk/aws-kms/lib/key.ts index 6e773948d14f6..cf4c7584c061c 100644 --- a/packages/@aws-cdk/aws-kms/lib/key.ts +++ b/packages/@aws-cdk/aws-kms/lib/key.ts @@ -1,3 +1,4 @@ +import iam = require('@aws-cdk/aws-iam'); import { PolicyDocument, PolicyStatement } from '@aws-cdk/aws-iam'; import { CfnOutput, Construct, DeletionPolicy, IConstruct } from '@aws-cdk/cdk'; import { EncryptionKeyAlias } from './alias'; @@ -28,13 +29,33 @@ export interface IEncryptionKey extends IConstruct { * @returns a key ref which can be used in a call to `EncryptionKey.import(ref)`. */ export(): EncryptionKeyImportProps; + + /** + * Grant the indicated permissions on this key to the given principal + */ + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; + + /** + * Grant decryption permisisons using this key to the given principal + */ + grantDecrypt(grantee: iam.IGrantable): iam.Grant; + + /** + * Grant encryption permisisons using this key to the given principal + */ + grantEncrypt(grantee: iam.IGrantable): iam.Grant; + + /** + * Grant encryption and decryption permisisons using this key to the given principal + */ + grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant; } export interface EncryptionKeyImportProps { /** * The ARN of the external KMS key. */ - keyArn: string; + readonly keyArn: string; } export abstract class EncryptionKeyBase extends Construct implements IEncryptionKey { @@ -75,6 +96,55 @@ export abstract class EncryptionKeyBase extends Construct implements IEncryption } public abstract export(): EncryptionKeyImportProps; + + /** + * Grant the indicated permissions on this key to the given principal + * + * This modifies both the principal's policy as well as the resource policy, + * since the default CloudFormation setup for KMS keys is that the policy + * must not be empty and so default grants won't work. + */ + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { + return iam.Grant.addToPrincipalAndResource({ + grantee, + actions, + resourceArns: [this.keyArn], + resource: this, + resourceSelfArns: ['*'] + }); + } + + /** + * Grant decryption permisisons using this key to the given principal + */ + public grantDecrypt(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, + 'kms:Decrypt', + ); + } + + /** + * Grant encryption permisisons using this key to the given principal + */ + public grantEncrypt(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*' + ); + } + + /** + * Grant encryption and decryption permisisons using this key to the given principal + */ + public grantEncryptDecrypt(grantee: iam.IGrantable): iam.Grant { + return this.grant(grantee, + 'kms:Decrypt', + 'kms:Encrypt', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*' + ); + } } /** @@ -85,19 +155,19 @@ export interface EncryptionKeyProps { * A description of the key. Use a description that helps your users decide * whether the key is appropriate for a particular task. */ - description?: string; + readonly description?: string; /** * Indicates whether AWS KMS rotates the key. * @default false */ - enableKeyRotation?: boolean; + readonly enableKeyRotation?: boolean; /** * Indicates whether the key is available for use. * @default Key is enabled */ - enabled?: boolean; + readonly enabled?: boolean; /** * Custom policy document to attach to the KMS key. @@ -105,7 +175,7 @@ export interface EncryptionKeyProps { * @default A policy document with permissions for the account root to * administer the key will be created. */ - policy?: PolicyDocument; + readonly policy?: PolicyDocument; /** * Whether the encryption key should be retained when it is removed from the Stack. This is useful when one wants to @@ -113,7 +183,7 @@ export interface EncryptionKeyProps { * * @default true */ - retain?: boolean; + readonly retain?: boolean; } /** diff --git a/packages/@aws-cdk/aws-kms/lib/via-service-principal.ts b/packages/@aws-cdk/aws-kms/lib/via-service-principal.ts new file mode 100644 index 0000000000000..08029a64d400f --- /dev/null +++ b/packages/@aws-cdk/aws-kms/lib/via-service-principal.ts @@ -0,0 +1,27 @@ +import iam = require('@aws-cdk/aws-iam'); + +/** + * A principal to allow access to a key if it's being used through another AWS service + */ +export class ViaServicePrincipal extends iam.PrincipalBase { + private readonly basePrincipal: iam.IPrincipal; + + constructor(private readonly serviceName: string, basePrincipal?: iam.IPrincipal) { + super(); + this.basePrincipal = basePrincipal ? basePrincipal : new iam.AnyPrincipal(); + } + + public get policyFragment(): iam.PrincipalPolicyFragment { + // Make a copy of the base policyFragment to add a condition to it + const base = this.basePrincipal.policyFragment; + const conditions = Object.assign({}, base.conditions); + + if (conditions.StringEquals) { + conditions.StringEquals = Object.assign({ 'kms:ViaService': this.serviceName }, conditions.StringEquals); + } else { + conditions.StringEquals = { 'kms:ViaService': this.serviceName }; + } + + return { principalJson: base.principalJson, conditions }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-kms/package.json b/packages/@aws-cdk/aws-kms/package.json index c708a4d430367..0a9475142f75f 100644 --- a/packages/@aws-cdk/aws-kms/package.json +++ b/packages/@aws-cdk/aws-kms/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-kms", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS KMS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-kms", + "module": "aws_cdk.aws_kms" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-kms" }, "scripts": { "build": "cdk-build", @@ -54,22 +59,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-kms/test/test.key.ts b/packages/@aws-cdk/aws-kms/test/test.key.ts index fe537b8984dd8..b0afeab8c0978 100644 --- a/packages/@aws-cdk/aws-kms/test/test.key.ts +++ b/packages/@aws-cdk/aws-kms/test/test.key.ts @@ -1,5 +1,5 @@ import { exactlyMatchTemplate, expect, haveResource, ResourcePart } from '@aws-cdk/assert'; -import { PolicyDocument, PolicyStatement } from '@aws-cdk/aws-iam'; +import { PolicyDocument, PolicyStatement, User } from '@aws-cdk/aws-iam'; import { App, Stack, Tag } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { EncryptionKey } from '../lib'; @@ -321,6 +321,55 @@ export = { test.done(); }, + 'grant decrypt on a key'(test: Test) { + // GIVEN + const stack = new Stack(); + const key = new EncryptionKey(stack, 'Key'); + const user = new User(stack, 'User'); + + // WHEN + key.grantDecrypt(user); + + // THEN + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + // This one is there by default + { + // tslint:disable-next-line:max-line-length + Action: [ "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion" ], + Effect: "Allow", + Principal: { AWS: { "Fn::Join": [ "", [ "arn:", { Ref: "AWS::Partition" }, ":iam::", { Ref: "AWS::AccountId" }, ":root" ] ] } }, + Resource: "*" + }, + // This is the interesting one + { + Action: "kms:Decrypt", + Effect: "Allow", + Principal: { AWS: { "Fn::GetAtt": [ "User00B015A1", "Arn" ] } }, + Resource: "*" + } + ], + Version: "2012-10-17" + } + })); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "kms:Decrypt", + Effect: "Allow", + Resource: { "Fn::GetAtt": [ "Key961B73FD", "Arn" ] } + } + ], + Version: "2012-10-17" + }, + })); + + test.done(); + }, + 'import/export can be used to bring in an existing key'(test: Test) { const stack1 = new Stack(); const policy = new PolicyDocument(); diff --git a/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts b/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts new file mode 100644 index 0000000000000..308bdd996b829 --- /dev/null +++ b/packages/@aws-cdk/aws-kms/test/test.via-service-principal.ts @@ -0,0 +1,55 @@ +import iam = require('@aws-cdk/aws-iam'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import kms = require('../lib'); + +export = { + 'Via service, any principal'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const statement = new iam.PolicyStatement() + .addAction('abc:call') + .addPrincipal(new kms.ViaServicePrincipal('bla.amazonaws.com')) + .addResource('*'); + + // THEN + test.deepEqual(stack.node.resolve(statement), { + Action: 'abc:call', + Condition: { StringEquals: { 'kms:ViaService': 'bla.amazonaws.com' } }, + Effect: 'Allow', + Principal: '*', + Resource: '*' + }); + + test.done(); + }, + + 'Via service, principal with conditions'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const statement = new iam.PolicyStatement() + .addAction('abc:call') + .addPrincipal(new kms.ViaServicePrincipal('bla.amazonaws.com', new iam.OrganizationPrincipal('o-1234'))) + .addResource('*'); + + // THEN + test.deepEqual(stack.node.resolve(statement), { + Action: 'abc:call', + Condition: { + StringEquals: { + 'kms:ViaService': 'bla.amazonaws.com', + 'aws:PrincipalOrgID': 'o-1234' + } + }, + Effect: 'Allow', + Principal: '*', + Resource: '*' + }); + + test.done(); + }, +}; \ No newline at end of file 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 e1b8ac388f3c3..156ceea800db7 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts @@ -11,12 +11,12 @@ export interface DynamoEventSourceProps { * * @default 100 */ - batchSize?: number; + readonly batchSize?: number; /** * Where to begin consuming the DynamoDB stream. */ - startingPosition: lambda.StartingPosition; + readonly startingPosition: lambda.StartingPosition; } /** @@ -37,7 +37,7 @@ export class DynamoEventSource implements lambda.IEventSource { startingPosition: this.props.startingPosition }); - this.table.grantStreamRead(target.role); - dynamodb.Table.grantListStreams(target.role); + this.table.grantStreamRead(target); + dynamodb.Table.grantListStreams(target); } } 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 0c495eab819ea..5893935ab375b 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts @@ -11,12 +11,12 @@ export interface KinesisEventSourceProps { * * @default 100 */ - batchSize?: number; + readonly batchSize?: number; /** * Where to begin consuming the Kinesis stream. */ - startingPosition: lambda.StartingPosition; + readonly startingPosition: lambda.StartingPosition; } /** @@ -37,6 +37,6 @@ export class KinesisEventSource implements lambda.IEventSource { eventSourceArn: this.stream.streamArn, }); - this.stream.grantRead(target.role); + this.stream.grantRead(target); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/s3.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/s3.ts index 49e4fe8e9d4a9..be8a2253caf75 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/s3.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/s3.ts @@ -5,7 +5,7 @@ export interface S3EventSourceProps { /** * The s3 event types that will trigger the notification. */ - events: s3.EventType[]; + readonly events: s3.EventType[]; /** * S3 object key filter rules to determine which objects trigger this event. @@ -13,7 +13,7 @@ export interface S3EventSourceProps { * against the s3 object key. Refer to the S3 Developer Guide for details * about allowed filter rules. */ - filters?: s3.NotificationKeyFilter[]; + readonly filters?: s3.NotificationKeyFilter[]; } /** 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 a02dd4fb430fb..d372116fe49f2 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts @@ -11,7 +11,7 @@ export interface SqsEventSourceProps { * * @default 10 */ - batchSize?: number; + readonly batchSize?: number; } /** @@ -31,6 +31,6 @@ export class SqsEventSource implements lambda.IEventSource { eventSourceArn: this.queue.queueArn, }); - this.queue.grantConsumeMessages(target.role); + this.queue.grantConsumeMessages(target); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-event-sources/package.json b/packages/@aws-cdk/aws-lambda-event-sources/package.json index 666afed6f9f26..84352368ac838 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/package.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-lambda-event-sources", - "version": "0.26.0", + "version": "0.28.0", "description": "Event sources for AWS Lambda", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-lambda-event-sources", + "module": "aws_cdk.aws_lambda_event_sources" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-lambda-event-sources" }, "scripts": { "build": "cdk-build", @@ -50,34 +55,34 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-apigateway": "^0.26.0", - "@aws-cdk/aws-dynamodb": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kinesis": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/aws-sqs": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-apigateway": "^0.28.0", + "@aws-cdk/aws-dynamodb": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kinesis": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-apigateway": "^0.26.0", - "@aws-cdk/aws-dynamodb": "^0.26.0", - "@aws-cdk/aws-kinesis": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/aws-sqs": "^0.26.0" + "@aws-cdk/aws-apigateway": "^0.28.0", + "@aws-cdk/aws-dynamodb": "^0.28.0", + "@aws-cdk/aws-kinesis": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json index 3f9d5ad835291..615e8c3652d8a 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.s3.expected.json @@ -65,7 +65,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "FC4345940" + "Fn::GetAtt": [ + "FC4345940", + "Arn" + ] }, "Principal": "s3.amazonaws.com", "SourceAccount": { @@ -205,4 +208,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json index ccb9bb05bb9af..6a50e98575458 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sns.expected.json @@ -80,7 +80,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "FC4345940" + "Fn::GetAtt": [ + "FC4345940", + "Arn" + ] }, "Principal": "sns.amazonaws.com", "SourceArn": { @@ -92,4 +95,4 @@ "Type": "AWS::SNS::Topic" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sqs.expected.json b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sqs.expected.json index 4ae6c580fe0e9..53cbc327dc4ad 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sqs.expected.json +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/integ.sqs.expected.json @@ -114,4 +114,4 @@ "Type": "AWS::SQS::Queue" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts index bb9f5c1f11a4d..bcac2b353c278 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.sns.ts @@ -21,7 +21,10 @@ export = { expect(stack).to(haveResource('AWS::Lambda::Permission', { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "Fn9270CBC0" + "Fn::GetAtt": [ + "Fn9270CBC0", + "Arn" + ] }, "Principal": "sns.amazonaws.com", "SourceArn": { diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 2bf3c4a7bef4e..f81166476bc88 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -72,54 +72,6 @@ fn.addEventSource(new S3EventSource(bucket, { See the documentation for the __@aws-cdk/aws-lambda-event-sources__ module for more details. -### Lambda in CodePipeline - -This module also contains an Action that allows you to invoke a Lambda function from CodePipeline: - -```ts -import codepipeline = require('@aws-cdk/aws-codepipeline'); - -const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const lambdaAction = new lambda.PipelineInvokeAction({ - actionName: 'Lambda', - lambda: fn, -}); -pipeline.addStage({ - actionName: 'Lambda', - actions: [lambdaAction], -}); -``` - -You can also create the action from the Lambda directly: - -```ts -// equivalent to the code above: -const lambdaAction = fn.toCodePipelineInvokeAction({ actionName: 'Lambda' }); -``` - -The Lambda Action can have up to 5 inputs, -and up to 5 outputs: - -```typescript -const lambdaAction = fn.toCodePipelineInvokeAction({ - actionName: 'Lambda', - inputArtifacts: [ - sourceAction.outputArtifact, - buildAction.outputArtifact, - ], - outputArtifactNames: [ - 'Out1', - 'Out2', - ], -}); - -lambdaAction.outputArtifacts(); // returns the list of output Artifacts -lambdaAction.outputArtifact('Out2'); // returns the named output Artifact, or throws an exception if not found -``` - -See [the AWS documentation](https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html) -on how to write a Lambda function invoked from CodePipeline. - ### Lambda with DLQ ```ts diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index 15c4818102c05..b074878fd167d 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -1,9 +1,8 @@ -import iam = require('@aws-cdk/aws-iam'); +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import { FunctionBase, FunctionImportProps, IFunction } from './function-base'; import { Version } from './lambda-version'; import { CfnAlias } from './lambda.generated'; -import { Permission } from './permission'; /** * Properties for a new Lambda alias @@ -14,19 +13,19 @@ export interface AliasProps { * * @default No description */ - description?: string; + readonly description?: string; /** * Function version this alias refers to * * Use lambda.addVersion() to obtain a new lambda version to refer to. */ - version: Version; + readonly version: Version; /** * Name of this alias */ - aliasName: string; + readonly aliasName: string; /** * Additional versions with individual weights this alias points to @@ -45,13 +44,17 @@ export interface AliasProps { * * @default No additional versions */ - additionalVersions?: VersionWeight[]; + readonly additionalVersions?: VersionWeight[]; } /** * A new alias to a particular version of a Lambda function. */ export class Alias extends FunctionBase { + /** + * Name of this alias. + */ + public readonly aliasName: string; /** * ARN of this alias * @@ -68,12 +71,7 @@ export class Alias extends FunctionBase { */ public readonly functionArn: string; - /** - * Role associated with this alias - */ - public readonly role?: iam.IRole | undefined; - - protected readonly canCreatePermissions: boolean = true; // Not used anyway + protected readonly canCreatePermissions: boolean = true; /** * The actual Lambda function object that this Alias is pointing to @@ -83,10 +81,10 @@ export class Alias extends FunctionBase { constructor(scope: cdk.Construct, id: string, props: AliasProps) { super(scope, id); + this.aliasName = props.aliasName; this.underlyingLambda = props.version.lambda; - this.role = this.underlyingLambda.role; - new CfnAlias(this, 'Resource', { + const alias = new CfnAlias(this, 'Resource', { name: props.aliasName, description: props.description, functionName: this.underlyingLambda.functionName, @@ -94,10 +92,36 @@ export class Alias extends FunctionBase { routingConfig: this.determineRoutingConfig(props) }); - // Not actually the name, but an ARN can be used in all places - // where the name is expected, and an ARN can refer to an Alias. - this.functionName = `${props.version.lambda.functionArn}:${props.aliasName}`; - this.functionArn = `${props.version.lambda.functionArn}:${props.aliasName}`; + // ARN parsing splits on `:`, so we can only get the function's name from the ARN as resourceName... + // And we're parsing it out (instead of using the underlying function directly) in order to have use of it incur + // an implicit dependency on the resource. + this.functionName = `${this.node.stack.parseArn(alias.aliasArn, ":").resourceName!}:${props.aliasName}`; + this.functionArn = alias.aliasArn; + } + + /** + * Role associated with this alias + */ + public get role() { + return this.underlyingLambda.role; + } + + public get grantPrincipal() { + return this.underlyingLambda.grantPrincipal; + } + + public metric(metricName: string, props: cloudwatch.MetricCustomization = {}): cloudwatch.Metric { + // Metrics on Aliases need the "bare" function name, and the alias' ARN, this differes from the base behavior. + return super.metric(metricName, { + dimensions: { + FunctionName: this.underlyingLambda.functionName, + // construct the ARN from the underlying lambda so that alarms on an alias + // don't cause a circular dependency with CodeDeploy + // see: https://github.com/awslabs/aws-cdk/issues/2231 + Resource: `${this.underlyingLambda.functionArn}:${this.aliasName}` + }, + ...props + }); } public export(): FunctionImportProps { @@ -106,11 +130,6 @@ export class Alias extends FunctionBase { }; } - public addPermission(name: string, permission: Permission) { - // Forward addPermission() to the underlying Lambda object - this.underlyingLambda.addPermission(name, permission); - } - /** * Calculate the routingConfig parameter from the input props */ diff --git a/packages/@aws-cdk/aws-lambda/lib/code.ts b/packages/@aws-cdk/aws-lambda/lib/code.ts index 3ebee37f33b62..0c0ddc19e3bf1 100644 --- a/packages/@aws-cdk/aws-lambda/lib/code.ts +++ b/packages/@aws-cdk/aws-lambda/lib/code.ts @@ -11,7 +11,7 @@ export abstract class Code { * @param key The object key * @param objectVersion Optional S3 object version */ - public static bucket(bucket: s3.IBucket, key: string, objectVersion?: string) { + public static bucket(bucket: s3.IBucket, key: string, objectVersion?: string): S3Code { return new S3Code(bucket, key, objectVersion); } @@ -19,7 +19,7 @@ export abstract class Code { * @returns `LambdaInlineCode` with inline code. * @param code The actual handler code (limited to 4KiB) */ - public static inline(code: string) { + public static inline(code: string): InlineCode { return new InlineCode(code); } @@ -27,7 +27,7 @@ export abstract class Code { * Loads the function code from a local disk asset. * @param path Either a directory with the Lambda code bundle or a .zip file */ - public static asset(path: string) { + public static asset(path: string): AssetCode { return new AssetCode(path); } @@ -37,7 +37,7 @@ export abstract class Code { * @param directoryToZip The directory to zip * @deprecated use `lambda.Code.asset(path)` (no need to specify if it's a file or a directory) */ - public static directory(directoryToZip: string) { + public static directory(directoryToZip: string): AssetCode { return new AssetCode(directoryToZip, assets.AssetPackaging.ZipDirectory); } @@ -46,10 +46,20 @@ export abstract class Code { * @param filePath The file path * @deprecated use `lambda.Code.asset(path)` (no need to specify if it's a file or a directory) */ - public static file(filePath: string) { + public static file(filePath: string): AssetCode { return new AssetCode(filePath, assets.AssetPackaging.File); } + /** + * Creates a new Lambda source defined using CloudFormation parameters. + * + * @returns a new instance of `CfnParametersCode` + * @param props optional construction properties of {@link CfnParametersCode} + */ + public static cfnParameters(props?: CfnParametersCodeProps): CfnParametersCode { + return new CfnParametersCode(props); + } + /** * Determines whether this Code is inline code or not. */ @@ -60,6 +70,8 @@ export abstract class Code { * Lambda function. * * @param resource the resource to which the code will be attached (a CfnFunction, or a CfnLayerVersion). + * + * @internal */ public abstract _toJSON(resource?: cdk.CfnResource): CfnFunction.CodeProperty; @@ -89,6 +101,9 @@ export class S3Code extends Code { this.bucketName = bucket.bucketName; } + /** + * @internal + */ public _toJSON(_?: cdk.CfnResource): CfnFunction.CodeProperty { return { s3Bucket: this.bucketName, @@ -119,6 +134,9 @@ export class InlineCode extends Code { } } + /** + * @internal + */ public _toJSON(_?: cdk.CfnResource): CfnFunction.CodeProperty { return { zipFile: this.code @@ -150,8 +168,8 @@ export class AssetCode extends Code { this.packaging = packaging; } else { this.packaging = fs.lstatSync(path).isDirectory() - ? assets.AssetPackaging.ZipDirectory - : assets.AssetPackaging.File; + ? assets.AssetPackaging.ZipDirectory + : assets.AssetPackaging.File; } } @@ -169,15 +187,117 @@ export class AssetCode extends Code { } } + /** + * @internal + */ public _toJSON(resource?: cdk.CfnResource): CfnFunction.CodeProperty { if (resource) { // https://github.com/awslabs/aws-cdk/issues/1432 this.asset!.addResourceMetadata(resource, 'Code'); } - return { + return { s3Bucket: this.asset!.s3BucketName, s3Key: this.asset!.s3ObjectKey }; } } + +/** + * Construction properties for {@link CfnParametersCode}. + */ +export interface CfnParametersCodeProps { + /** + * The CloudFormation parameter that represents the name of the S3 Bucket + * where the Lambda code will be located in. + * Must be of type 'String'. + * + * @default a new parameter will be created + */ + readonly bucketNameParam?: cdk.CfnParameter; + + /** + * The CloudFormation parameter that represents the path inside the S3 Bucket + * where the Lambda code will be located at. + * Must be of type 'String'. + * + * @default a new parameter will be created + */ + readonly objectKeyParam?: cdk.CfnParameter; +} + +/** + * Lambda code defined using 2 CloudFormation parameters. + * Useful when you don't have access to the code of your Lambda from your CDK code, so you can't use Assets, + * and you want to deploy the Lambda in a CodePipeline, using CloudFormation Actions - + * you can fill the parameters using the {@link #assign} method. + */ +export class CfnParametersCode extends Code { + public readonly isInline = false; + private _bucketNameParam?: cdk.CfnParameter; + private _objectKeyParam?: cdk.CfnParameter; + + constructor(props: CfnParametersCodeProps = {}) { + super(); + + this._bucketNameParam = props.bucketNameParam; + this._objectKeyParam = props.objectKeyParam; + } + + public bind(construct: cdk.Construct) { + if (!this._bucketNameParam) { + this._bucketNameParam = new cdk.CfnParameter(construct, 'LambdaSourceBucketNameParameter', { + type: 'String', + }); + } + + if (!this._objectKeyParam) { + this._objectKeyParam = new cdk.CfnParameter(construct, 'LambdaSourceObjectKeyParameter', { + type: 'String', + }); + } + } + + /** + * Create a parameters map from this instance's CloudFormation parameters. + * + * It returns a map with 2 keys that correspond to the names of the parameters defined in this Lambda code, + * and as values it contains the appropriate expressions pointing at the provided S3 coordinates + * (most likely, obtained from a CodePipeline Artifact by calling the `artifact.s3Coordinates` method). + * The result should be provided to the CloudFormation Action + * that is deploying the Stack that the Lambda with this code is part of, + * in the `parameterOverrides` property. + * + * @param coordinates the coordinates of the object in S3 that represents the Lambda code + */ + public assign(coordinates: s3.Coordinates): { [name: string]: any } { + const ret: { [name: string]: any } = {}; + ret[this.bucketNameParam] = coordinates.bucketName; + ret[this.objectKeyParam] = coordinates.objectKey; + return ret; + } + + /** @internal */ + public _toJSON(_?: cdk.CfnResource): CfnFunction.CodeProperty { + return { + s3Bucket: this._bucketNameParam!.stringValue, + s3Key: this._objectKeyParam!.stringValue, + }; + } + + public get bucketNameParam(): string { + if (this._bucketNameParam) { + return this._bucketNameParam.logicalId; + } else { + throw new Error('Pass CfnParametersCode to a Lambda Function before accessing the bucketNameParam property'); + } + } + + public get objectKeyParam(): string { + if (this._objectKeyParam) { + return this._objectKeyParam.logicalId; + } else { + throw new Error('Pass CfnParametersCode to a Lambda Function before accessing the objectKeyParam property'); + } + } +} diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts index 5c75e956d3daf..d22c53dbc3efc 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts @@ -7,12 +7,12 @@ export interface EventSourceMappingProps { * The Amazon Resource Name (ARN) of the event source. Any record added to * this stream can invoke the Lambda function. */ - eventSourceArn: string; + readonly eventSourceArn: string; /** * The target AWS Lambda function. */ - target: IFunction; + readonly target: IFunction; /** * The largest number of records that AWS Lambda will retrieve from your event @@ -24,14 +24,14 @@ export interface EventSourceMappingProps { * @default The default for Amazon Kinesis and Amazon DynamoDB is 100 records. * Both the default and maximum for Amazon SQS are 10 messages. */ - batchSize?: number; + readonly batchSize?: number; /** * Set to false to disable the event source upon creation. * * @default true */ - enabled?: boolean; + readonly enabled?: boolean; /** * The position in the DynamoDB or Kinesis stream where AWS Lambda should @@ -39,7 +39,7 @@ export interface EventSourceMappingProps { * * @see https://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetShardIterator.html#Kinesis-GetShardIterator-request-ShardIteratorType */ - startingPosition?: StartingPosition + readonly startingPosition?: StartingPosition } /** diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 06e42ea0f4599..0fb8dd77cd6bd 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -9,10 +9,9 @@ import cdk = require('@aws-cdk/cdk'); import { IEventSource } from './event-source'; import { CfnPermission } from './lambda.generated'; import { Permission } from './permission'; -import { CommonPipelineInvokeActionProps, PipelineInvokeAction } from './pipeline-action'; export interface IFunction extends cdk.IConstruct, events.IEventRuleTarget, logs.ILogSubscriptionDestination, - s3n.IBucketNotificationDestination, ec2.IConnectable, stepfunctions.IStepFunctionsTaskResource { + s3n.IBucketNotificationDestination, ec2.IConnectable, stepfunctions.IStepFunctionsTaskResource, iam.IGrantable { /** * Logical ID of this Function. @@ -47,20 +46,12 @@ export interface IFunction extends cdk.IConstruct, events.IEventRuleTarget, logs */ addPermission(id: string, permission: Permission): void; - /** - * Convenience method for creating a new {@link PipelineInvokeAction}. - * - * @param props the construction properties of the new Action - * @returns the newly created {@link PipelineInvokeAction} - */ - toCodePipelineInvokeAction(props: CommonPipelineInvokeActionProps): PipelineInvokeAction; - addToRolePolicy(statement: iam.PolicyStatement): void; /** * Grant the given identity permissions to invoke this Lambda */ - grantInvoke(identity?: iam.IPrincipal): void; + grantInvoke(identity: iam.IGrantable): iam.Grant; /** * Return the given named metric for this Lambda @@ -105,14 +96,14 @@ export interface FunctionImportProps { * * Format: arn::lambda:::function: */ - functionArn: string; + readonly functionArn: string; /** * The IAM execution role associated with this function. * * If the role is not specified, any role-related operations will no-op. */ - role?: iam.IRole; + readonly role?: iam.IRole; /** * Id of the securityGroup for this Lambda, if in a VPC. @@ -120,10 +111,14 @@ export interface FunctionImportProps { * This needs to be given in order to support allowing connections * to this Lambda. */ - securityGroupId?: string; + readonly securityGroupId?: string; } export abstract class FunctionBase extends cdk.Construct implements IFunction { + /** + * The principal this Lambda Function is running as + */ + public abstract readonly grantPrincipal: iam.IPrincipal; /** * The name of the function. @@ -137,6 +132,8 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { /** * The IAM role associated with this function. + * + * Undefined if the function was imported without a role. */ public abstract readonly role?: iam.IRole; @@ -151,6 +148,7 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { * Actual connections object for this Lambda * * May be unset, in which case this Lambda is not configured use in a VPC. + * @internal */ protected _connections?: ec2.Connections; @@ -175,7 +173,7 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { new CfnPermission(this, id, { action, principal, - functionName: this.functionName, + functionName: this.functionArn, eventSourceToken: permission.eventSourceToken, sourceAccount: permission.sourceAccount, sourceArn: permission.sourceArn, @@ -186,13 +184,6 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { return this.node.id; } - public toCodePipelineInvokeAction(props: CommonPipelineInvokeActionProps): PipelineInvokeAction { - return new PipelineInvokeAction({ - ...props, - lambda: this, - }); - } - public addToRolePolicy(statement: iam.PolicyStatement) { if (!this.role) { return; @@ -246,12 +237,27 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { /** * Grant the given identity permissions to invoke this Lambda */ - public grantInvoke(identity?: iam.IPrincipal) { - if (identity) { - identity.addToPolicy(new iam.PolicyStatement() - .addAction('lambda:InvokeFunction') - .addResource(this.functionArn)); - } + public grantInvoke(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipalOrResource({ + grantee, + actions: ['lambda:InvokeFunction'], + resourceArns: [this.functionArn], + + // Fake resource-like object on which to call addToResourcePolicy(), which actually + // calls addPermission() + resource: { + addToResourcePolicy: (_statement) => { + // Couldn't add permissions to the principal, so add them locally. + const identifier = 'Invoke' + JSON.stringify(grantee!.grantPrincipal.policyFragment.principalJson); + this.addPermission(identifier, { + principal: grantee.grantPrincipal!, + action: 'lambda:InvokeFunction', + }); + }, + dependencyRoots: [], + node: this.node, + }, + }); } public logSubscriptionDestination(sourceLogGroup: logs.ILogGroup): logs.LogSubscriptionDestination { @@ -330,11 +336,10 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { source.bind(this); } - private parsePermissionPrincipal(principal?: iam.PolicyPrincipal) { + private parsePermissionPrincipal(principal?: iam.IPrincipal) { if (!principal) { return undefined; } - // use duck-typing, not instance of if ('accountId' in principal) { @@ -345,7 +350,7 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { return (principal as iam.ServicePrincipal).service; } - throw new Error(`Invalid principal type for Lambda permission statement: ${JSON.stringify(this.node.resolve(principal))}. ` + + throw new Error(`Invalid principal type for Lambda permission statement: ${this.node.resolve(principal.toString())}. ` + 'Supported: AccountPrincipal, ServicePrincipal'); } } diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 657219c575cfc..324783d9605e8 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -1,6 +1,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); +import logs = require('@aws-cdk/aws-logs'); import sqs = require('@aws-cdk/aws-sqs'); import cdk = require('@aws-cdk/cdk'); import { Code } from './code'; @@ -9,6 +10,7 @@ import { FunctionBase, FunctionImportProps, IFunction } from './function-base'; import { Version } from './lambda-version'; import { CfnFunction } from './lambda.generated'; import { ILayerVersion } from './layers'; +import { LogRetention } from './log-retention'; import { Runtime } from './runtime'; /** @@ -37,12 +39,12 @@ export interface FunctionProps { * Amazon Simple Storage Service (Amazon S3) bucket or specify your source * code as inline text. */ - code: Code; + readonly code: Code; /** * A description of the function. */ - description?: string; + readonly description?: string; /** * The name of the function (within your source code) that Lambda calls to @@ -53,7 +55,7 @@ export interface FunctionProps { * ZipFile property within the Code property, specify index.function_name as * the handler. */ - handler: string; + readonly handler: string; /** * The function execution time (in seconds) after which Lambda terminates @@ -62,7 +64,7 @@ export interface FunctionProps { * * @default 3 seconds. */ - timeout?: number; + readonly timeout?: number; /** * Key-value pairs that Lambda caches and makes available for your Lambda @@ -70,21 +72,21 @@ export interface FunctionProps { * as test and production environment configurations, without changing your * Lambda function source code. */ - environment?: { [key: string]: any }; + readonly environment?: { [key: string]: any }; /** * The runtime environment for the Lambda function that you are uploading. * For valid values, see the Runtime property in the AWS Lambda Developer * Guide. */ - runtime: Runtime; + readonly runtime: Runtime; /** * A name for the function. If you don't specify a name, AWS CloudFormation * generates a unique physical ID and uses that ID for the function's name. * For more information, see Name Type. */ - functionName?: string; + readonly functionName?: string; /** * The amount of memory, in MB, that is allocated to your Lambda function. @@ -94,14 +96,14 @@ export interface FunctionProps { * * @default The default value is 128 MB */ - memorySize?: number; + readonly memorySize?: number; /** * Initial policy statements to add to the created Lambda Role. * * You can call `addToRolePolicy` to the created lambda to add statements post creation. */ - initialPolicy?: iam.PolicyStatement[]; + readonly initialPolicy?: iam.PolicyStatement[]; /** * Lambda execution role. @@ -113,14 +115,14 @@ export interface FunctionProps { * @default a unique role will be generated for this lambda function. * Both supplied and generated roles can always be changed by calling `addToRolePolicy`. */ - role?: iam.IRole; + readonly role?: iam.IRole; /** * VPC network to place Lambda network interfaces * * Specify this if the Lambda function needs to access resources in a VPC. */ - vpc?: ec2.IVpcNetwork; + readonly vpc?: ec2.IVpcNetwork; /** * Where to place the network interfaces within the VPC. @@ -130,7 +132,7 @@ export interface FunctionProps { * * @default All private subnets */ - vpcPlacement?: ec2.VpcPlacementStrategy; + readonly vpcSubnets?: ec2.SubnetSelection; /** * What security group to associate with the Lambda's network interfaces. @@ -141,7 +143,7 @@ export interface FunctionProps { * not specified, a dedicated security group will be created for this * function. */ - securityGroup?: ec2.ISecurityGroup; + readonly securityGroup?: ec2.ISecurityGroup; /** * Whether to allow the Lambda to send all network traffic @@ -151,7 +153,7 @@ export interface FunctionProps { * * @default true */ - allowAllOutbound?: boolean; + readonly allowAllOutbound?: boolean; /** * Enabled DLQ. If `deadLetterQueue` is undefined, @@ -159,21 +161,21 @@ export interface FunctionProps { * * @default false unless `deadLetterQueue` is set, which implies DLQ is enabled */ - deadLetterQueueEnabled?: boolean; + readonly deadLetterQueueEnabled?: boolean; /** * The SQS queue to use if DLQ is enabled. * * @default SQS queue with 14 day retention period if `deadLetterQueueEnabled` is `true` */ - deadLetterQueue?: sqs.IQueue; + readonly deadLetterQueue?: sqs.IQueue; /** * Enable AWS X-Ray Tracing for Lambda Function. * * @default undefined X-Ray tracing disabled */ - tracing?: Tracing; + readonly tracing?: Tracing; /** * A list of layers to add to the function's execution environment. You can configure your Lambda function to pull in @@ -182,7 +184,7 @@ export interface FunctionProps { * * @default no layers */ - layers?: ILayerVersion[]; + readonly layers?: ILayerVersion[]; /** * The maximum of concurrent executions you want to reserve for the function. @@ -190,14 +192,23 @@ export interface FunctionProps { * @default no specific limit - account limit * @see https://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html */ - reservedConcurrentExecutions?: number; + readonly reservedConcurrentExecutions?: number; /** * Event sources for this function. * * You can also add event sources using `addEventSource`. */ - events?: IEventSource[]; + readonly events?: IEventSource[]; + + /** + * The number of days log events are kept in CloudWatch Logs. When updating + * this property, unsetting it doesn't remove the log retention policy. To + * remove the retention policy, set the value to `Infinity`. + * + * @default logs never expire + */ + readonly logRetentionDays?: logs.RetentionDays; } /** @@ -322,6 +333,11 @@ export class Function extends FunctionBase { */ public readonly handler: string; + /** + * The principal this Lambda Function is running as + */ + public readonly grantPrincipal: iam.IPrincipal; + protected readonly canCreatePermissions = true; private readonly layers: ILayerVersion[] = []; @@ -350,6 +366,7 @@ export class Function extends FunctionBase { assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicyArns, }); + this.grantPrincipal = this.role; for (const statement of (props.initialPolicy || [])) { this.role.addToPolicy(statement); @@ -395,6 +412,14 @@ export class Function extends FunctionBase { for (const event of props.events || []) { this.addEventSource(event); } + + // Log retention + if (props.logRetentionDays) { + new LogRetention(this, 'LogRetention', { + logGroupName: `/aws/lambda/${this.functionName}`, + retentionDays: props.logRetentionDays + }); + } } /** @@ -467,6 +492,25 @@ export class Function extends FunctionBase { }); } + /** + * Add a new version for this Lambda, always with a different name. + * + * This is similar to the {@link addVersion} method, + * but useful when deploying this Lambda through CodePipeline with blue/green deployments. + * When using {@link addVersion}, + * your Alias will not be updated until you change the name passed to {@link addVersion} in your CDK code. + * When deploying through a Pipeline, + * that might lead to a situation where a change to your Lambda application code will never be activated, + * even though it traveled through the entire Pipeline, + * because the Alias is still pointing to an old Version. + * This method creates a new, unique Version every time the CDK code is executed, + * and so prevents that from happening. + */ + public newVersion(): Version { + const now = new Date(); + return this.addVersion(now.toISOString()); + } + private renderEnvironment() { if (!this.environment || Object.keys(this.environment).length === 0) { return undefined; @@ -504,15 +548,22 @@ export class Function extends FunctionBase { // Pick subnets, make sure they're not Public. Routing through an IGW // won't work because the ENIs don't get a Public IP. - const subnets = props.vpc.subnets(props.vpcPlacement); - for (const subnet of subnets) { - if (props.vpc.isPublicSubnet(subnet)) { + // Why are we not simply forcing vpcSubnets? Because you might still be choosing + // Isolated networks or selecting among 2 sets of Private subnets by name. + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); + const publicSubnetIds = new Set(props.vpc.publicSubnets.map(s => s.subnetId)); + for (const subnetId of subnetIds) { + if (publicSubnetIds.has(subnetId)) { throw new Error('Not possible to place Lambda Functions in a Public subnet'); } } + // List can't be empty here, if we got this far you intended to put your Lambda + // in subnets. We're going to guarantee that we get the nice error message by + // making VpcNetwork do the selection again. + return { - subnetIds: subnets.map(s => s.subnetId), + subnetIds, securityGroupIds: [securityGroup.securityGroupId] }; } @@ -555,6 +606,7 @@ export class Function extends FunctionBase { } export class ImportedFunction extends FunctionBase { + public readonly grantPrincipal: iam.IPrincipal; public readonly functionName: string; public readonly functionArn: string; public readonly role?: iam.IRole; @@ -567,6 +619,7 @@ export class ImportedFunction extends FunctionBase { this.functionArn = props.functionArn; this.functionName = extractNameFromArn(props.functionArn); this.role = props.role; + this.grantPrincipal = this.role || new iam.ImportedResourcePrincipal({ resource: this } ); if (props.securityGroupId) { this._connections = new ec2.Connections({ diff --git a/packages/@aws-cdk/aws-lambda/lib/index.ts b/packages/@aws-cdk/aws-lambda/lib/index.ts index c4cb9c7a7cdc0..8def6e736af0e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/index.ts @@ -3,13 +3,13 @@ export * from './function-base'; export * from './function'; export * from './layers'; export * from './permission'; -export * from './pipeline-action'; export * from './runtime'; export * from './code'; export * from './lambda-version'; export * from './singleton-lambda'; export * from './event-source'; export * from './event-source-mapping'; +export * from './log-retention'; // AWS::Lambda CloudFormation Resources: export * from './lambda.generated'; diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index 8470d9d0e3451..de1e1924bd569 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -13,19 +13,19 @@ export interface VersionProps { * * @default No validation is performed */ - codeSha256?: string; + readonly codeSha256?: string; /** * Description of the version * * @default Description of the Lambda */ - description?: string; + readonly description?: string; /** * Function to get the value of */ - lambda: IFunction; + readonly lambda: IFunction; } /** diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index b42cd8930403d..43d9d7979976c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -9,30 +9,30 @@ export interface LayerVersionProps { * * @default All runtimes are supported */ - compatibleRuntimes?: Runtime[]; + readonly compatibleRuntimes?: Runtime[]; /** * The content of this Layer. Using *inline* (per ``code.isInline``) code is not permitted. */ - code: Code; + readonly code: Code; /** * The description the this Lambda Layer. */ - description?: string; + readonly description?: string; /** * The SPDX licence identifier or URL to the license file for this layer. * * @default no license information will be recorded. */ - license?: string; + readonly license?: string; /** * The name of the layer. * @default a name will be generated. */ - name?: string; + readonly name?: string; } export interface ILayerVersion extends cdk.IConstruct { @@ -101,14 +101,14 @@ export interface LayerVersionUsageGrantee { * The AWS Account id of the account that is authorized to use a Lambda Layer Version. The wild-card ``'*'`` can be * used to grant access to "any" account (or any account in an organization when ``organizationId`` is specified). */ - accountId: string; + readonly accountId: string; /** * The ID of the AWS Organization to hwich the grant is restricted. * * Can only be specified if ``accountId`` is ``'*'`` */ - organizationId?: string; + readonly organizationId?: string; } /** @@ -118,12 +118,12 @@ export interface LayerVersionImportProps { /** * The ARN of the LayerVersion. */ - layerVersionArn: string; + readonly layerVersionArn: string; /** * The list of compatible runtimes with this Layer. */ - compatibleRuntimes?: Runtime[]; + readonly compatibleRuntimes?: Runtime[]; } /** @@ -194,7 +194,7 @@ export interface SingletonLayerVersionProps extends LayerVersionProps { * The identifier should be unique across all layer providers. * We recommend generating a UUID per provider. */ - uuid: string; + readonly uuid: string; } /** diff --git a/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts b/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts new file mode 100644 index 0000000000000..56d71ea19cd68 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/lib/log-retention-provider/index.ts @@ -0,0 +1,104 @@ +// tslint:disable:no-console +import AWS = require('aws-sdk'); + +/** + * Creates a log group and doesn't throw if it exists. + * + * @param logGroupName the name of the log group to create + */ +async function createLogGroupSafe(logGroupName: string) { + try { // Try to create the log group + const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28' }); + await cloudwatchlogs.createLogGroup({ logGroupName }).promise(); + } catch (e) { + if (e.code !== 'ResourceAlreadyExistsException') { + throw e; + } + } +} + +/** + * Puts or deletes a retention policy on a log group. + * + * @param logGroupName the name of the log group to create + * @param retentionInDays the number of days to retain the log events in the specified log group. + */ +async function setRetentionPolicy(logGroupName: string, retentionInDays?: number) { + const cloudwatchlogs = new AWS.CloudWatchLogs({ apiVersion: '2014-03-28' }); + if (!retentionInDays) { + await cloudwatchlogs.deleteRetentionPolicy({ logGroupName }).promise(); + } else { + await cloudwatchlogs.putRetentionPolicy({ logGroupName, retentionInDays }).promise(); + } +} + +export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent, context: AWSLambda.Context) { + try { + console.log(JSON.stringify(event)); + + // The target log group + const logGroupName = event.ResourceProperties.LogGroupName; + + if (event.RequestType === 'Create' || event.RequestType === 'Update') { + // Act on the target log group + await createLogGroupSafe(logGroupName); + await setRetentionPolicy(logGroupName, parseInt(event.ResourceProperties.RetentionInDays, 10)); + + if (event.RequestType === 'Create') { + // Set a retention policy of 1 day on the logs of this function. The log + // group for this function should already exist at this stage because we + // already logged the event but due to the async nature of Lambda logging + // there could be a race condition. So we also try to create the log group + // of this function first. If multiple LogRetention constructs are present + // in the stack, they will try to act on this function's log group at the + // same time. This can sometime result in an OperationAbortedException. To + // avoid this and because this operation is not critical we catch all errors. + try { + await createLogGroupSafe(`/aws/lambda/${context.functionName}`); + await setRetentionPolicy(`/aws/lambda/${context.functionName}`, 1); + } catch (e) { + console.log(e); + } + } + } + + await respond('SUCCESS', 'OK', logGroupName); + } catch (e) { + console.log(e); + + await respond('FAILED', e.message, event.ResourceProperties.LogGroupName); + } + + function respond(responseStatus: string, reason: string, physicalResourceId: string) { + const responseBody = JSON.stringify({ + Status: responseStatus, + Reason: reason, + PhysicalResourceId: physicalResourceId, + StackId: event.StackId, + RequestId: event.RequestId, + LogicalResourceId: event.LogicalResourceId, + Data: {} + }); + + console.log('Responding', responseBody); + + const parsedUrl = require('url').parse(event.ResponseURL); + const requestOptions = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length } + }; + + return new Promise((resolve, reject) => { + try { + const request = require('https').request(requestOptions, resolve); + request.on('error', reject); + request.write(responseBody); + request.end(); + } catch (e) { + reject(e); + } + }); + } +} diff --git a/packages/@aws-cdk/aws-lambda/lib/log-retention.ts b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts new file mode 100644 index 0000000000000..036fb38f9a719 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/lib/log-retention.ts @@ -0,0 +1,64 @@ +import iam = require('@aws-cdk/aws-iam'); +import logs = require('@aws-cdk/aws-logs'); +import cdk = require('@aws-cdk/cdk'); +import path = require('path'); +import { Code } from './code'; +import { Runtime } from './runtime'; +import { SingletonFunction } from './singleton-lambda'; + +/** + * Construction properties for a LogRetention. + */ +export interface LogRetentionProps { + /** + * The log group name. + */ + readonly logGroupName: string; + + /** + * The number of days log events are kept in CloudWatch Logs. + */ + readonly retentionDays: logs.RetentionDays; +} + +/** + * Creates a custom resource to control the retention policy of a CloudWatch Logs + * log group. The log group is created if it doesn't already exist. The policy + * is removed when `retentionDays` is `undefined` or equal to `Infinity`. + */ +export class LogRetention extends cdk.Construct { + constructor(scope: cdk.Construct, id: string, props: LogRetentionProps) { + super(scope, id); + + // Custom resource provider + const provider = new SingletonFunction(this, 'Provider', { + code: Code.asset(path.join(__dirname, 'log-retention-provider')), + runtime: Runtime.NodeJS810, + handler: 'index.handler', + uuid: 'aae0aa3c-5b4d-4f87-b02d-85b201efdd8a', + lambdaPurpose: 'LogRetention', + }); + + if (provider.role && !provider.role.node.tryFindChild('DefaultPolicy')) { // Avoid duplicate statements + provider.role.addToPolicy( + new iam.PolicyStatement() + .addActions('logs:PutRetentionPolicy', 'logs:DeleteRetentionPolicy') + // We need '*' here because we will also put a retention policy on + // the log group of the provider function. Referencing it's name + // creates a CF circular dependency. + .addAllResources() + ); + } + + // Need to use a CfnResource here to prevent lerna dependency cycles + // @aws-cdk/aws-cloudformation -> @aws-cdk/aws-lambda -> @aws-cdk/aws-cloudformation + new cdk.CfnResource(this, 'Resource', { + type: 'Custom::LogRetention', + properties: { + ServiceToken: provider.functionArn, + LogGroupName: props.logGroupName, + RetentionInDays: props.retentionDays === Infinity ? undefined : props.retentionDays + } + }); + } +} diff --git a/packages/@aws-cdk/aws-lambda/lib/permission.ts b/packages/@aws-cdk/aws-lambda/lib/permission.ts index de4ab91c6794d..e2a3fcbee57d3 100644 --- a/packages/@aws-cdk/aws-lambda/lib/permission.ts +++ b/packages/@aws-cdk/aws-lambda/lib/permission.ts @@ -1,4 +1,4 @@ -import { PolicyPrincipal } from '@aws-cdk/aws-iam'; +import iam = require('@aws-cdk/aws-iam'); /** * Represents a permission statement that can be added to a Lambda's resource policy @@ -14,7 +14,7 @@ export interface Permission { * * @default 'lambda:InvokeFunction' */ - action?: string; + readonly action?: string; /** * A unique token that must be supplied by the principal invoking the @@ -22,7 +22,7 @@ export interface Permission { * * @default The caller would not need to present a token. */ - eventSourceToken?: string; + readonly eventSourceToken?: string; /** * The entity for which you are granting permission to invoke the Lambda @@ -34,7 +34,7 @@ export interface Permission { * * The principal can be either an AccountPrincipal or a ServicePrincipal. */ - principal: PolicyPrincipal; + readonly principal: iam.IPrincipal; /** * The AWS account ID (without hyphens) of the source owner. For example, if @@ -42,7 +42,7 @@ export interface Permission { * bucket owner's account ID. You can use this property to ensure that all * source principals are owned by a specific account. */ - sourceAccount?: string; + readonly sourceAccount?: string; /** * The ARN of a resource that is invoking your function. When granting @@ -52,5 +52,5 @@ export interface Permission { * any bucket from any AWS account that creates a mapping to your function, * can invoke the function. */ - sourceArn?: string; + readonly sourceArn?: string; } diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index 2de0a98e9bcaf..23c4ec38f01e2 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -3,7 +3,7 @@ export interface LambdaRuntimeProps { * Whether the ``ZipFile`` (aka inline code) property can be used with this runtime. * @default false */ - supportsInlineCode?: boolean; + readonly supportsInlineCode?: boolean; } export enum RuntimeFamily { diff --git a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts index 9b00fbda319f9..fbecb0b0e997e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/lib/singleton-lambda.ts @@ -14,7 +14,7 @@ export interface SingletonFunctionProps extends FunctionProps { * The identifier should be unique across all custom resource providers. * We recommend generating a UUID per provider. */ - uuid: string; + readonly uuid: string; /** * A descriptive name for the purpose of this Lambda. @@ -25,7 +25,7 @@ export interface SingletonFunctionProps extends FunctionProps { * * @default SingletonLambda */ - lambdaPurpose?: string; + readonly lambdaPurpose?: string; } /** @@ -35,9 +35,10 @@ export interface SingletonFunctionProps extends FunctionProps { * for every SingletonLambda you create. */ export class SingletonFunction extends FunctionBase { + public readonly grantPrincipal: iam.IPrincipal; public readonly functionName: string; public readonly functionArn: string; - public readonly role?: iam.IRole | undefined; + public readonly role?: iam.IRole; protected readonly canCreatePermissions: boolean; private lambdaFunction: IFunction; @@ -49,6 +50,7 @@ export class SingletonFunction extends FunctionBase { this.functionArn = this.lambdaFunction.functionArn; this.functionName = this.lambdaFunction.functionName; this.role = this.lambdaFunction.role; + this.grantPrincipal = this.lambdaFunction.grantPrincipal; this.canCreatePermissions = true; // Doesn't matter, addPermission is overriden anyway } diff --git a/packages/@aws-cdk/aws-lambda/package-lock.json b/packages/@aws-cdk/aws-lambda/package-lock.json new file mode 100644 index 0000000000000..7da9748305bf3 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/package-lock.json @@ -0,0 +1,424 @@ +{ + "name": "@aws-cdk/aws-lambda", + "version": "0.27.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@sinonjs/commons": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", + "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@types/aws-lambda": { + "version": "8.10.23", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.23.tgz", + "integrity": "sha512-erfexxfuc1+T7b4OswooKwpIjpdgEOVz6ZrDDWSR+3v7Kjhs4EVowfUkF9KuLKhpcjz+VVHQ/pWIl7zSVbKbFQ==", + "dev": true + }, + "@types/nock": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@types/nock/-/nock-9.3.1.tgz", + "integrity": "sha512-eOVHXS5RnWOjTVhu3deCM/ruy9E6JCgeix2g7wpFiekQh3AaEAK1cz43tZDukKmtSmQnwvSySq7ubijCA32I7Q==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", + "dev": true + }, + "@types/sinon": { + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.10.tgz", + "integrity": "sha512-4w7SvsiUOtd4mUfund9QROPSJ5At/GQskDpqd87pJIRI6ULWSJqHI3GIZE337wQuN3aznroJGr94+o8fwvL37Q==", + "dev": true + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "aws-sdk": { + "version": "2.425.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.425.0.tgz", + "integrity": "sha512-SM2qZJPlZUKVzSSqNuCvONOhJ2kcFvU+hAwutjQeje2VKpSAbUbFCFWl6cki2FjiyGZYEPfl0Q+3ANJO8gx9BA==", + "dev": true, + "requires": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.8", + "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-mock": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/aws-sdk-mock/-/aws-sdk-mock-4.3.1.tgz", + "integrity": "sha512-uOaf7/Tq9kSoRc2/EQfAn24AAwU6UwvR8xSFSg0vTRxK0xHHEZ5UB/KF6ibF2gj0I4977lM35237E5sbzhRxKA==", + "dev": true, + "requires": { + "aws-sdk": "^2.369.0", + "sinon": "^7.1.1", + "traverse": "^0.6.6" + } + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "dev": true + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lolex": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-3.1.0.tgz", + "integrity": "sha512-zFo5MgCJ0rZ7gQg69S4pqBsLURbFw11X68C18OcJjJQbqaXm2NoTrGl1IMM3TIz0/BnN1tIs2tzmmqvCsOMMjw==", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "nise": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", + "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "lolex": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", + "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", + "dev": true + } + } + }, + "nock": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", + "integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==", + "dev": true, + "requires": { + "chai": "^4.1.2", + "debug": "^4.1.0", + "deep-equal": "^1.0.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.5", + "mkdirp": "^0.5.0", + "propagate": "^1.0.0", + "qs": "^6.5.1", + "semver": "^5.5.0" + } + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "propagate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", + "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", + "dev": true + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "sinon": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.0.tgz", + "integrity": "sha512-0pYvgRv46fODzT/PByqb79MVNpyxsxf38WEiXTABOF8RfIMcIARfZ+1ORuxwAmHkreZ/jST3UDBdKCRhUy/e1A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.0", + "diff": "^3.5.0", + "lolex": "^3.1.0", + "nise": "^1.4.10", + "supports-color": "^5.5.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", + "dev": true + } + } +} diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index e804d9f979383..16b968b017767 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-lambda", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS Lambda", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-lambda", + "module": "aws_cdk.aws_lambda" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-lambda" }, "scripts": { "build": "cdk-build", @@ -58,43 +63,53 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "@types/aws-lambda": "^8.10.23", + "@types/nock": "^9.3.1", + "@types/sinon": "^7.0.10", + "aws-sdk": "^2.425.0", + "aws-sdk-mock": "^4.3.1", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "nock": "^10.0.6", + "pkglint": "^0.28.0", + "sinon": "^7.3.0" }, "dependencies": { - "@aws-cdk/assets": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-logs": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/aws-s3-notifications": "^0.26.0", - "@aws-cdk/aws-sqs": "^0.26.0", - "@aws-cdk/aws-stepfunctions": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/assets": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/aws-s3-notifications": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0", + "@aws-cdk/aws-stepfunctions": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/assets": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-logs": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/aws-s3-notifications": "^0.26.0", - "@aws-cdk/aws-sqs": "^0.26.0", - "@aws-cdk/aws-stepfunctions": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/assets": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/aws-s3-notifications": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0", + "@aws-cdk/aws-stepfunctions": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" + }, + "awslint": { + "exclude": [ + "grant-result:@aws-cdk/aws-lambda.LayerVersionBase.grantUsage" + ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda/test/integ.bucket-notifications.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.bucket-notifications.expected.json index e82d57546a874..738b14605a43c 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.bucket-notifications.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.bucket-notifications.expected.json @@ -110,7 +110,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyFunction3BAA72D1" + "Fn::GetAtt": [ + "MyFunction3BAA72D1", + "Arn" + ] }, "Principal": "s3.amazonaws.com", "SourceAccount": { @@ -129,7 +132,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyFunction3BAA72D1" + "Fn::GetAtt": [ + "MyFunction3BAA72D1", + "Arn" + ] }, "Principal": "s3.amazonaws.com", "SourceAccount": { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.events.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.events.expected.json index e68a46f29a6d5..f71081e3ff084 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.events.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.events.expected.json @@ -65,7 +65,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyFunc8A243A2C" + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] }, "Principal": "events.amazonaws.com", "SourceArn": { @@ -81,7 +84,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyFunc8A243A2C" + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] }, "Principal": "events.amazonaws.com", "SourceArn": { diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json index 2a80228e6fd81..eac87632a5aa2 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.expected.json @@ -104,6 +104,16 @@ }, "Name": "prod" } + }, + "AliasAliasPermissionAF30F9E8": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "Alias325C5727" + }, + "Principal": "cloudformation.amazonaws.com" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts b/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts index 4f1630042bea8..e76b4f90121a8 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.lambda.ts @@ -16,9 +16,12 @@ fn.addToRolePolicy(new iam.PolicyStatement().addAllResources().addAction('*')); const version = fn.addVersion('1'); -new lambda.Alias(stack, 'Alias', { +const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version, }); +alias.addPermission('AliasPermission', { + principal: new iam.ServicePrincipal('cloudformation.amazonaws.com') +}); app.run(); diff --git a/packages/@aws-cdk/aws-lambda/test/integ.log-retention.expected.json b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.expected.json new file mode 100644 index 0000000000000..9ef732e1d267b --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.expected.json @@ -0,0 +1,383 @@ +{ + "Resources": { + "OneWeekServiceRole05A6F9F8": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "OneWeekFE56F6A4": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = (event) => console.log(JSON.stringify(event));" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "OneWeekServiceRole05A6F9F8", + "Arn" + ] + }, + "Runtime": "nodejs8.10" + }, + "DependsOn": [ + "OneWeekServiceRole05A6F9F8" + ] + }, + "OneWeekLogRetention8E8911C1": { + "Type": "Custom::LogRetention", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/lambda/", + { + "Ref": "OneWeekFE56F6A4" + } + ] + ] + }, + "RetentionInDays": 7 + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:PutRetentionPolicy", + "logs:DeleteRetentionPolicy" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "Roles": [ + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + } + ] + } + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3BucketB81211B5" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3VersionKey10C1B354" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3VersionKey10C1B354" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB", + "Arn" + ] + }, + "Runtime": "nodejs8.10" + }, + "DependsOn": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + ] + }, + "OneMonthServiceRoleFBD1064F": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "OneMonth64E966BF": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = (event) => console.log(JSON.stringify(event));" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "OneMonthServiceRoleFBD1064F", + "Arn" + ] + }, + "Runtime": "nodejs8.10" + }, + "DependsOn": [ + "OneMonthServiceRoleFBD1064F" + ] + }, + "OneMonthLogRetention814A40D9": { + "Type": "Custom::LogRetention", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/lambda/", + { + "Ref": "OneMonth64E966BF" + } + ] + ] + }, + "RetentionInDays": 30 + } + }, + "OneYearServiceRole24D47762": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "OneYearA82EBDA9": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = (event) => console.log(JSON.stringify(event));" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "OneYearServiceRole24D47762", + "Arn" + ] + }, + "Runtime": "nodejs8.10" + }, + "DependsOn": [ + "OneYearServiceRole24D47762" + ] + }, + "OneYearLogRetentionBD83A067": { + "Type": "Custom::LogRetention", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": { + "Fn::Join": [ + "", + [ + "/aws/lambda/", + { + "Ref": "OneYearA82EBDA9" + } + ] + ] + }, + "RetentionInDays": 365 + } + } + }, + "Parameters": { + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3BucketB81211B5": { + "Type": "String", + "Description": "S3 bucket for asset \"aws-cdk-lambda-log-retention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code\"" + }, + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aCodeS3VersionKey10C1B354": { + "Type": "String", + "Description": "S3 key for asset version \"aws-cdk-lambda-log-retention/LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8a/Code\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts new file mode 100644 index 0000000000000..08475cc79d5b8 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/integ.log-retention.ts @@ -0,0 +1,30 @@ +import logs = require('@aws-cdk/aws-logs'); +import cdk = require('@aws-cdk/cdk'); +import lambda = require('../lib'); + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-lambda-log-retention'); + +new lambda.Function(stack, 'OneWeek', { + code: new lambda.InlineCode('exports.handler = (event) => console.log(JSON.stringify(event));'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810, + logRetentionDays: logs.RetentionDays.OneWeek +}); + +new lambda.Function(stack, 'OneMonth', { + code: new lambda.InlineCode('exports.handler = (event) => console.log(JSON.stringify(event));'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810, + logRetentionDays: logs.RetentionDays.OneMonth +}); + +new lambda.Function(stack, 'OneYear', { + code: new lambda.InlineCode('exports.handler = (event) => console.log(JSON.stringify(event));'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS810, + logRetentionDays: logs.RetentionDays.OneYear +}); + +app.run(); diff --git a/packages/@aws-cdk/aws-lambda/test/test.alias.ts b/packages/@aws-cdk/aws-lambda/test/test.alias.ts index 7ff54f0a80c59..1e340495f691b 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.alias.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.alias.ts @@ -1,5 +1,5 @@ -import { beASupersetOfTemplate, expect, haveResource } from '@aws-cdk/assert'; -import { AccountPrincipal, PolicyStatement } from '@aws-cdk/aws-iam'; +import { beASupersetOfTemplate, expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import lambda = require('../lib'); @@ -40,6 +40,33 @@ export = { test.done(); }, + 'can use newVersion to create a new Version'(test: Test) { + const stack = new Stack(); + const fn = new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('hello()'), + handler: 'index.hello', + runtime: lambda.Runtime.NodeJS610, + }); + + const version = fn.newVersion(); + + new lambda.Alias(stack, 'Alias', { + aliasName: 'prod', + version, + }); + + expect(stack).to(haveResourceLike('AWS::Lambda::Version', { + FunctionName: { Ref: "MyLambdaCCE802FB" }, + })); + + expect(stack).to(haveResourceLike('AWS::Lambda::Alias', { + FunctionName: { Ref: "MyLambdaCCE802FB" }, + Name: "prod" + })); + + test.done(); + }, + 'can add additional versions to alias'(test: Test) { const stack = new Stack(); @@ -103,7 +130,7 @@ export = { test.done(); }, - 'addPermission() on alias forward to real Lambda'(test: Test) { + 'metric adds Resource: aliasArn to dimensions'(test: Test) { const stack = new Stack(); // GIVEN @@ -117,14 +144,37 @@ export = { const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // WHEN - alias.addPermission('Perm', { - principal: new AccountPrincipal('123456') + new cloudwatch.Alarm(stack, 'Alarm', { + metric: alias.metric('Test'), + alarmName: 'Test', + threshold: 1, + evaluationPeriods: 1 }); // THEN - expect(stack).to(haveResource('AWS::Lambda::Permission', { - FunctionName: stack.node.resolve(fn.functionName), - Principal: "123456" + expect(stack).to(haveResource('AWS::CloudWatch::Alarm', { + Dimensions: [{ + Name: "FunctionName", + Value: { + Ref: "MyLambdaCCE802FB" + } + }, { + Name: "Resource", + Value: { + 'Fn::Join': [ + '', + [ + { + "Fn::GetAtt": [ + "MyLambdaCCE802FB", + "Arn" + ] + }, + ':prod' + ] + ] + } + }] })); test.done(); @@ -149,7 +199,7 @@ export = { test.done(); }, - 'addToRolePolicy on alias forwards to real Lambda'(test: Test) { + 'functionName is derived from the aliasArn so that dependencies are sound'(test: Test) { const stack = new Stack(); // GIVEN @@ -163,26 +213,27 @@ export = { const alias = new lambda.Alias(stack, 'Alias', { aliasName: 'prod', version }); // WHEN - alias.addToRolePolicy(new PolicyStatement() - .addAction('s3:GetObject') - .addAllResources()); - test.equals(alias.role, fn.role); - - // THEN - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [{ - Action: "s3:GetObject", - Effect: "Allow", - Resource: "*" - }], - Version: "2012-10-17" - }, - PolicyName: "MyLambdaServiceRoleDefaultPolicy5BBC6F68", - Roles: [{ - Ref: "MyLambdaServiceRole4539ECB6" - }] - })); + test.deepEqual(stack.node.resolve(alias.functionName), { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 6, + { + "Fn::Split": [ + ":", + { + Ref: "Alias325C5727" + } + ] + } + ] + }, + ":prod" + ] + ] + }); test.done(); } diff --git a/packages/@aws-cdk/aws-lambda/test/test.code.ts b/packages/@aws-cdk/aws-lambda/test/test.code.ts index ea08dd02d83e3..f74809a71fc37 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.code.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.code.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike, ResourcePart } from '@aws-cdk/assert'; import assets = require('@aws-cdk/assets'); import cdk = require('@aws-cdk/cdk'); import cxapi = require('@aws-cdk/cx-api'); @@ -6,6 +6,8 @@ import { Test } from 'nodeunit'; import path = require('path'); import lambda = require('../lib'); +// tslint:disable:no-string-literal + export = { 'lambda.Code.inline': { 'fails if used with unsupported runtimes'(test: Test) { @@ -93,7 +95,111 @@ export = { }, ResourcePart.CompleteDefinition)); test.done(); } - } + }, + + 'lambda.Code.cfnParameters': { + "automatically creates the Bucket and Key parameters when it's used in a Function"(test: Test) { + const stack = new cdk.Stack(); + const code = new lambda.CfnParametersCode(); + new lambda.Function(stack, 'Function', { + code, + runtime: lambda.Runtime.NodeJS810, + handler: 'index.handler', + }); + + expect(stack).to(haveResourceLike('AWS::Lambda::Function', { + Code: { + S3Bucket: { + Ref: "FunctionLambdaSourceBucketNameParameter9E9E108F", + }, + S3Key: { + Ref: "FunctionLambdaSourceObjectKeyParameter1C7AED11", + }, + }, + })); + + test.equal(stack.node.resolve(code.bucketNameParam), 'FunctionLambdaSourceBucketNameParameter9E9E108F'); + test.equal(stack.node.resolve(code.objectKeyParam), 'FunctionLambdaSourceObjectKeyParameter1C7AED11'); + + test.done(); + }, + + 'does not allow accessing the Parameter properties before being used in a Function'(test: Test) { + const code = new lambda.CfnParametersCode(); + + test.throws(() => { + test.notEqual(code.bucketNameParam, undefined); + }, /bucketNameParam/); + + test.throws(() => { + test.notEqual(code.objectKeyParam, undefined); + }, /objectKeyParam/); + + test.done(); + }, + + 'allows passing custom Parameters when creating it'(test: Test) { + const stack = new cdk.Stack(); + const bucketNameParam = new cdk.CfnParameter(stack, 'BucketNameParam', { + type: 'String', + }); + const bucketKeyParam = new cdk.CfnParameter(stack, 'ObjectKeyParam', { + type: 'String', + }); + + const code = lambda.Code.cfnParameters({ + bucketNameParam, + objectKeyParam: bucketKeyParam, + }); + + test.equal(stack.node.resolve(code.bucketNameParam), 'BucketNameParam'); + test.equal(stack.node.resolve(code.objectKeyParam), 'ObjectKeyParam'); + + new lambda.Function(stack, 'Function', { + code, + runtime: lambda.Runtime.NodeJS810, + handler: 'index.handler', + }); + + expect(stack).to(haveResourceLike('AWS::Lambda::Function', { + Code: { + S3Bucket: { + Ref: "BucketNameParam", + }, + S3Key: { + Ref: "ObjectKeyParam", + }, + }, + })); + + test.done(); + }, + + 'can assign parameters'(test: Test) { + // given + const stack = new cdk.Stack(); + const code = new lambda.CfnParametersCode({ + bucketNameParam: new cdk.CfnParameter(stack, 'BucketNameParam', { + type: 'String', + }), + objectKeyParam: new cdk.CfnParameter(stack, 'ObjectKeyParam', { + type: 'String', + }), + }); + + // when + const overrides = stack.node.resolve(code.assign({ + bucketName: 'SomeBucketName', + objectKey: 'SomeObjectKey', + })); + + // then + test.equal(overrides['BucketNameParam'], 'SomeBucketName'); + test.equal(overrides['ObjectKeyParam'], 'SomeObjectKey'); + + test.done(); + }, + }, }; function defineFunction(code: lambda.Code, runtime: lambda.Runtime = lambda.Runtime.NodeJS810) { diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts index b24c2b0d81844..23b588681b04f 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts @@ -1,6 +1,7 @@ import { countResources, expect, haveResource, MatchStyle, ResourcePart } from '@aws-cdk/assert'; import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); +import logs = require('@aws-cdk/aws-logs'); import sqs = require('@aws-cdk/aws-sqs'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; @@ -153,8 +154,8 @@ export = { "Handler": "bar", "Role": { "Fn::GetAtt": [ - "MyLambdaServiceRole4539ECB6", - "Arn" + "MyLambdaServiceRole4539ECB6", + "Arn" ] }, "Runtime": "python2.7" @@ -168,7 +169,10 @@ export = { "Properties": { "Action": "lambda:*", "FunctionName": { - "Ref": "MyLambdaCCE802FB" + "Fn::GetAtt": [ + "MyLambdaCCE802FB", + "Arn" + ] }, "Principal": "s3.amazonaws.com", "SourceAccount": { @@ -269,14 +273,24 @@ export = { expect(stack).to(haveResource('AWS::Lambda::Permission', { "Action": "lambda:InvokeFunction", - "FunctionName": { "Ref": lambdaId }, + "FunctionName": { + "Fn::GetAtt": [ + lambdaId, + "Arn" + ] + }, "Principal": "events.amazonaws.com", "SourceArn": { "Fn::GetAtt": [ "Rule4C995B7F", "Arn" ] } })); expect(stack).to(haveResource('AWS::Lambda::Permission', { "Action": "lambda:InvokeFunction", - "FunctionName": { "Ref": "MyLambdaCCE802FB" }, + "FunctionName": { + "Fn::GetAtt": [ + lambdaId, + "Arn" + ] + }, "Principal": "events.amazonaws.com", "SourceArn": { "Fn::GetAtt": [ "Rule270732244", "Arn" ] } })); @@ -1296,7 +1310,38 @@ export = { test.equal(rt.supportsInlineCode, false); test.done(); - } + }, + + 'specify log retention'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NodeJS, + logRetentionDays: logs.RetentionDays.OneMonth + }); + + // THEN + expect(stack).to(haveResource('Custom::LogRetention', { + 'LogGroupName': { + 'Fn::Join': [ + '', + [ + '/aws/lambda/', + { + Ref: 'MyLambdaCCE802FB' + } + ] + ] + }, + 'RetentionInDays': 30 + })); + + test.done(); + } }; function newTestLambda(scope: cdk.Construct) { diff --git a/packages/@aws-cdk/aws-lambda/test/test.log-retention-provider.ts b/packages/@aws-cdk/aws-lambda/test/test.log-retention-provider.ts new file mode 100644 index 0000000000000..385f95f1dd613 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/test.log-retention-provider.ts @@ -0,0 +1,265 @@ +import AWSSDK = require('aws-sdk'); +import AWS = require('aws-sdk-mock'); +import nock = require('nock'); +import { Test } from 'nodeunit'; +import sinon = require('sinon'); +import provider = require('../lib/log-retention-provider'); + +const eventCommon = { + ServiceToken: 'token', + ResponseURL: 'https://localhost', + StackId: 'stackId', + RequestId: 'requestId', + LogicalResourceId: 'logicalResourceId', + PhysicalResourceId: 'group', + ResourceType: 'Custom::LogRetention', +}; + +const context = { + functionName: 'provider' +} as AWSLambda.Context; + +function createRequest(type: string) { + return nock('https://localhost') + .put('/', (body: AWSLambda.CloudFormationCustomResourceResponse) => body.Status === type && body.PhysicalResourceId === 'group') + .reply(200); +} + +export = { + 'tearDown'(callback: any) { + AWS.restore(); + nock.cleanAll(); + callback(); + }, + + async 'create event'(test: Test) { + const createLogGroupFake = sinon.fake.resolves({}); + const putRetentionPolicyFake = sinon.fake.resolves({}); + const deleteRetentionPolicyFake = sinon.fake.resolves({}); + + AWS.mock('CloudWatchLogs', 'createLogGroup', createLogGroupFake); + AWS.mock('CloudWatchLogs', 'putRetentionPolicy', putRetentionPolicyFake); + AWS.mock('CloudWatchLogs', 'deleteRetentionPolicy', deleteRetentionPolicyFake); + + const event = { + ...eventCommon, + RequestType: 'Create', + ResourceProperties: { + ServiceToken: 'token', + RetentionInDays: '30', + LogGroupName: 'group' + } + }; + + const request = createRequest('SUCCESS'); + + await provider.handler(event as AWSLambda.CloudFormationCustomResourceCreateEvent, context); + + sinon.assert.calledWith(createLogGroupFake, { + logGroupName: 'group' + }); + + sinon.assert.calledWith(putRetentionPolicyFake, { + logGroupName: 'group', + retentionInDays: 30 + }); + + sinon.assert.calledWith(createLogGroupFake, { + logGroupName: '/aws/lambda/provider' + }); + + sinon.assert.calledWith(putRetentionPolicyFake, { + logGroupName: '/aws/lambda/provider', + retentionInDays: 1 + }); + + sinon.assert.notCalled(deleteRetentionPolicyFake); + + test.equal(request.isDone(), true); + + test.done(); + }, + + async 'update event with new log retention'(test: Test) { + const error = new Error() as NodeJS.ErrnoException; + error.code = 'ResourceAlreadyExistsException'; + + const createLogGroupFake = sinon.fake.rejects(error); + const putRetentionPolicyFake = sinon.fake.resolves({}); + const deleteRetentionPolicyFake = sinon.fake.resolves({}); + + AWS.mock('CloudWatchLogs', 'createLogGroup', createLogGroupFake); + AWS.mock('CloudWatchLogs', 'putRetentionPolicy', putRetentionPolicyFake); + AWS.mock('CloudWatchLogs', 'deleteRetentionPolicy', deleteRetentionPolicyFake); + + const event = { + ...eventCommon, + RequestType: 'Update', + ResourceProperties: { + ServiceToken: 'token', + RetentionInDays: '365', + LogGroupName: 'group' + }, + OldResourceProperties: { + ServiceToken: 'token', + LogGroupName: 'group', + RetentionInDays: '30' + } + }; + + const request = createRequest('SUCCESS'); + + await provider.handler(event as AWSLambda.CloudFormationCustomResourceUpdateEvent, context); + + sinon.assert.calledWith(createLogGroupFake, { + logGroupName: 'group' + }); + + sinon.assert.calledWith(putRetentionPolicyFake, { + logGroupName: 'group', + retentionInDays: 365 + }); + + sinon.assert.notCalled(deleteRetentionPolicyFake); + + test.equal(request.isDone(), true); + + test.done(); + }, + + async 'update event with log retention undefined'(test: Test) { + const error = new Error() as NodeJS.ErrnoException; + error.code = 'ResourceAlreadyExistsException'; + + const createLogGroupFake = sinon.fake.rejects(error); + const putRetentionPolicyFake = sinon.fake.resolves({}); + const deleteRetentionPolicyFake = sinon.fake.resolves({}); + + AWS.mock('CloudWatchLogs', 'createLogGroup', createLogGroupFake); + AWS.mock('CloudWatchLogs', 'putRetentionPolicy', putRetentionPolicyFake); + AWS.mock('CloudWatchLogs', 'deleteRetentionPolicy', deleteRetentionPolicyFake); + + const event = { + ...eventCommon, + RequestType: 'Update', + PhysicalResourceId: 'group', + ResourceProperties: { + ServiceToken: 'token', + LogGroupName: 'group' + }, + OldResourceProperties: { + ServiceToken: 'token', + LogGroupName: 'group', + RetentionInDays: '365' + } + }; + + const request = createRequest('SUCCESS'); + + await provider.handler(event as AWSLambda.CloudFormationCustomResourceUpdateEvent, context); + + sinon.assert.calledWith(createLogGroupFake, { + logGroupName: 'group' + }); + + sinon.assert.calledWith(deleteRetentionPolicyFake, { + logGroupName: 'group' + }); + + test.equal(request.isDone(), true); + + test.done(); + }, + + async 'delete event'(test: Test) { + const createLogGroupFake = sinon.fake.resolves({}); + const putRetentionPolicyFake = sinon.fake.resolves({}); + const deleteRetentionPolicyFake = sinon.fake.resolves({}); + + AWS.mock('CloudWatchLogs', 'createLogGroup', createLogGroupFake); + AWS.mock('CloudWatchLogs', 'putRetentionPolicy', putRetentionPolicyFake); + AWS.mock('CloudWatchLogs', 'deleteRetentionPolicy', deleteRetentionPolicyFake); + + const event = { + ...eventCommon, + RequestType: 'Delete', + PhysicalResourceId: 'group', + ResourceProperties: { + ServiceToken: 'token', + LogGroupName: 'group' + } + }; + + const request = createRequest('SUCCESS'); + + await provider.handler(event as AWSLambda.CloudFormationCustomResourceDeleteEvent, context); + + sinon.assert.notCalled(createLogGroupFake); + + sinon.assert.notCalled(putRetentionPolicyFake); + + sinon.assert.notCalled(deleteRetentionPolicyFake); + + test.equal(request.isDone(), true); + + test.done(); + }, + + async 'responds with FAILED on error'(test: Test) { + const createLogGroupFake = sinon.fake.rejects(new Error('UnkownError')); + + AWS.mock('CloudWatchLogs', 'createLogGroup', createLogGroupFake); + + const event = { + ...eventCommon, + RequestType: 'Create', + ResourceProperties: { + ServiceToken: 'token', + RetentionInDays: '30', + LogGroupName: 'group' + } + }; + + const request = createRequest('FAILED'); + + await provider.handler(event as AWSLambda.CloudFormationCustomResourceCreateEvent, context); + + test.equal(request.isDone(), true); + + test.done(); + }, + + async 'does not fail when operations on provider log group fail'(test: Test) { + const createLogGroupFake = (params: AWSSDK.CloudWatchLogs.CreateLogGroupRequest) => { + if (params.logGroupName === '/aws/lambda/provider') { + return Promise.reject(new Error('OperationAbortedException')); + } + return Promise.resolve({}); + }; + + const putRetentionPolicyFake = sinon.fake.resolves({}); + const deleteRetentionPolicyFake = sinon.fake.resolves({}); + + AWS.mock('CloudWatchLogs', 'createLogGroup', createLogGroupFake); + AWS.mock('CloudWatchLogs', 'putRetentionPolicy', putRetentionPolicyFake); + AWS.mock('CloudWatchLogs', 'deleteRetentionPolicy', deleteRetentionPolicyFake); + + const event = { + ...eventCommon, + RequestType: 'Create', + ResourceProperties: { + ServiceToken: 'token', + RetentionInDays: '30', + LogGroupName: 'group' + } + }; + + const request = createRequest('SUCCESS'); + + await provider.handler(event as AWSLambda.CloudFormationCustomResourceCreateEvent, context); + + test.equal(request.isDone(), true); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts b/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts new file mode 100644 index 0000000000000..be223b42148d7 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/test/test.log-retention.ts @@ -0,0 +1,57 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import logs = require('@aws-cdk/aws-logs'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import lambda = require('../lib'); + +// tslint:disable:object-literal-key-quotes + +export = { + 'log retention construct'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new lambda.LogRetention(stack, 'MyLambda', { + logGroupName: 'group', + retentionDays: logs.RetentionDays.OneMonth + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:PutRetentionPolicy", + "logs:DeleteRetentionPolicy" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "Roles": [ + { + "Ref": "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRole9741ECFB" + } + ] + })); + + expect(stack).to(haveResource('Custom::LogRetention', { + "ServiceToken": { + "Fn::GetAtt": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A", + "Arn" + ] + }, + "LogGroupName": "group", + "RetentionInDays": 30 + })); + + test.done(); + + } +}; diff --git a/packages/@aws-cdk/aws-lambda/test/test.subscriptiondestination.ts b/packages/@aws-cdk/aws-lambda/test/test.subscriptiondestination.ts index b9e5065858a22..f07817c5c22c1 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.subscriptiondestination.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.subscriptiondestination.ts @@ -30,7 +30,12 @@ export = { // THEN: Lambda has permissions to be invoked by CWL expect(stack).to(haveResource('AWS::Lambda::Permission', { Action: "lambda:InvokeFunction", - FunctionName: { Ref: "MyLambdaCCE802FB" }, + FunctionName: { + "Fn::GetAtt": [ + "MyLambdaCCE802FB", + "Arn" + ] + }, Principal: { "Fn::Join": ["", ["logs.", {Ref: "AWS::Region"}, ".amazonaws.com"]] } })); diff --git a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts index 878dc3c3efc51..b069c11a8d2cc 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts @@ -137,7 +137,7 @@ export = { handler: 'index.handler', runtime: lambda.Runtime.NodeJS610, vpc, - vpcPlacement: { subnetsToUse: ec2.SubnetType.Public } + vpcSubnets: { subnetType: ec2.SubnetType.Public } }); }); diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index c67852c02a381..b14448e2c7afd 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -10,19 +10,19 @@ export interface CrossAccountDestinationProps { * * @default Automatically generated */ - destinationName?: string; + readonly destinationName?: string; /** * The role to assume that grants permissions to write to 'target'. * * The role must be assumable by 'logs.{REGION}.amazonaws.com'. */ - role: iam.Role; + readonly role: iam.Role; /** * The log destination target's ARN */ - targetArn: string; + readonly targetArn: string; } /** diff --git a/packages/@aws-cdk/aws-logs/lib/log-group.ts b/packages/@aws-cdk/aws-logs/lib/log-group.ts index c0df6719e0cc6..9453cfa67c448 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-group.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-group.ts @@ -69,19 +69,19 @@ export interface ILogGroup extends cdk.IConstruct { /** * Give permissions to write to create and write to streams in this log group */ - grantWrite(principal?: iam.IPrincipal): void; + grantWrite(grantee: iam.IGrantable): iam.Grant; /** * Give the indicated permissions on this log group and all streams */ - grant(principal?: iam.IPrincipal, ...actions: string[]): void; + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; } /** * Properties for importing a LogGroup */ export interface LogGroupImportProps { - logGroupArn: string; + readonly logGroupArn: string; } /** @@ -171,24 +171,115 @@ export abstract class LogGroupBase extends cdk.Construct implements ILogGroup { /** * Give permissions to write to create and write to streams in this log group */ - public grantWrite(principal?: iam.IPrincipal) { - this.grant(principal, 'logs:CreateLogStream', 'logs:PutLogEvents'); + public grantWrite(grantee: iam.IGrantable) { + return this.grant(grantee, 'logs:CreateLogStream', 'logs:PutLogEvents'); } /** * Give the indicated permissions on this log group and all streams */ - public grant(principal?: iam.IPrincipal, ...actions: string[]) { - if (!principal) { return; } - - principal.addToPolicy(new iam.PolicyStatement() - .addActions(...actions) - // This ARN includes a ':*' at the end to include the log streams. + public grant(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipal({ + grantee, + actions, + // A LogGroup ARN out of CloudFormation already includes a ':*' at the end to include the log streams under the group. // See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#w2ab1c21c10c63c43c11 - .addResource(`${this.logGroupArn}`)); + resourceArns: [this.logGroupArn], + scope: this, + }); } } +/** + * How long, in days, the log contents will be retained. + */ +export enum RetentionDays { + /** + * 1 day + */ + OneDay = 1, + + /** + * 3 days + */ + ThreeDays = 3, + + /** + * 5 days + */ + FiveDays = 5, + + /** + * 1 week + */ + OneWeek = 7, + + /** + * 2 weeks + */ + TwoWeeks = 14, + + /** + * 1 month + */ + OneMonth = 30, + + /** + * 2 months + */ + TwoMonths = 60, + + /** + * 3 months + */ + ThreeMonths = 90, + + /** + * 4 months + */ + FourMonths = 120, + + /** + * 5 months + */ + FiveMonths = 150, + + /** + * 6 months + */ + SixMonths = 180, + + /** + * 1 year + */ + OneYear = 365, + + /** + * 13 months + */ + ThirteenMonths = 400, + + /** + * 18 months + */ + EighteenMonths = 545, + + /** + * 2 years + */ + TwoYears = 731, + + /** + * 5 years + */ + FiveYears = 1827, + + /** + * 10 years + */ + TenYears = 3653 +} + /** * Properties for a LogGroup */ @@ -198,7 +289,7 @@ export interface LogGroupProps { * * @default Automatically generated */ - logGroupName?: string; + readonly logGroupName?: string; /** * How long, in days, the log contents will be retained. @@ -207,7 +298,7 @@ export interface LogGroupProps { * * @default 731 days (2 years) */ - retentionDays?: number; + readonly retentionDays?: RetentionDays; /** * Retain the log group if the stack or containing construct ceases to exist @@ -219,7 +310,7 @@ export interface LogGroupProps { * * @default true */ - retainLogGroup?: boolean; + readonly retainLogGroup?: boolean; } /** @@ -247,7 +338,7 @@ export class LogGroup extends LogGroupBase { super(scope, id); let retentionInDays = props.retentionDays; - if (retentionInDays === undefined) { retentionInDays = 731; } + if (retentionInDays === undefined) { retentionInDays = RetentionDays.TwoYears; } if (retentionInDays === Infinity) { retentionInDays = undefined; } if (retentionInDays !== undefined && retentionInDays <= 0) { @@ -317,7 +408,7 @@ export interface NewLogStreamProps { * * @default Automatically generated */ - logStreamName?: string; + readonly logStreamName?: string; } /** @@ -329,12 +420,12 @@ export interface NewSubscriptionFilterProps { * * For example, a Kinesis stream or a Lambda function. */ - destination: ILogSubscriptionDestination; + readonly destination: ILogSubscriptionDestination; /** * Log events matching this pattern will be sent to the destination. */ - filterPattern: IFilterPattern; + readonly filterPattern: IFilterPattern; } /** @@ -344,17 +435,17 @@ export interface NewMetricFilterProps { /** * Pattern to search for log events. */ - filterPattern: IFilterPattern; + readonly filterPattern: IFilterPattern; /** * The namespace of the metric to emit. */ - metricNamespace: string; + readonly metricNamespace: string; /** * The name of the metric to emit. */ - metricName: string; + readonly metricName: string; /** * The value to emit for the metric. @@ -371,12 +462,12 @@ export interface NewMetricFilterProps { * * @default "1" */ - metricValue?: string; + readonly metricValue?: string; /** * The value to emit if the pattern does not match a particular event. * * @default No metric emitted. */ - defaultValue?: number; + readonly defaultValue?: number; } diff --git a/packages/@aws-cdk/aws-logs/lib/log-stream.ts b/packages/@aws-cdk/aws-logs/lib/log-stream.ts index a40885e430f63..2e4d1772d1526 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-stream.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-stream.ts @@ -18,7 +18,7 @@ export interface ILogStream extends cdk.IConstruct { * Properties for importing a LogStream */ export interface LogStreamImportProps { - logStreamName: string; + readonly logStreamName: string; } /** @@ -28,7 +28,7 @@ export interface LogStreamProps { /** * The log group to create a log stream for. */ - logGroup: ILogGroup; + readonly logGroup: ILogGroup; /** * The name of the log stream to create. @@ -37,7 +37,7 @@ export interface LogStreamProps { * * @default Automatically generated */ - logStreamName?: string; + readonly logStreamName?: string; /** * Retain the log stream if the stack or containing construct ceases to exist @@ -50,7 +50,7 @@ export interface LogStreamProps { * * @default true */ - retainLogStream?: boolean; + readonly retainLogStream?: boolean; } /** diff --git a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts index b8cfb2832d1d7..eb335d5f8fb6d 100644 --- a/packages/@aws-cdk/aws-logs/lib/metric-filter.ts +++ b/packages/@aws-cdk/aws-logs/lib/metric-filter.ts @@ -10,22 +10,22 @@ export interface MetricFilterProps { /** * The log group to create the filter on. */ - logGroup: ILogGroup; + readonly logGroup: ILogGroup; /** * Pattern to search for log events. */ - filterPattern: IFilterPattern; + readonly filterPattern: IFilterPattern; /** * The namespace of the metric to emit. */ - metricNamespace: string; + readonly metricNamespace: string; /** * The name of the metric to emit. */ - metricName: string; + readonly metricName: string; /** * The value to emit for the metric. @@ -42,14 +42,14 @@ export interface MetricFilterProps { * * @default "1" */ - metricValue?: string; + readonly metricValue?: string; /** * The value to emit if the pattern does not match a particular event. * * @default No metric emitted. */ - defaultValue?: number; + readonly defaultValue?: number; } /** diff --git a/packages/@aws-cdk/aws-logs/lib/pattern.ts b/packages/@aws-cdk/aws-logs/lib/pattern.ts index b2f18fe32d2b6..f667bd679787a 100644 --- a/packages/@aws-cdk/aws-logs/lib/pattern.ts +++ b/packages/@aws-cdk/aws-logs/lib/pattern.ts @@ -371,21 +371,21 @@ export interface ColumnRestriction { /** * Comparison operator to use */ - comparison: string; + readonly comparison: string; /** * String value to compare to * * Exactly one of 'stringValue' and 'numberValue' must be set. */ - stringValue?: string; + readonly stringValue?: string; /** * Number value to compare to * * Exactly one of 'stringValue' and 'numberValue' must be set. */ - numberValue?: number; + readonly numberValue?: number; } /** diff --git a/packages/@aws-cdk/aws-logs/lib/subscription-filter.ts b/packages/@aws-cdk/aws-logs/lib/subscription-filter.ts index f6c9528777e3f..a0d6ebae5f117 100644 --- a/packages/@aws-cdk/aws-logs/lib/subscription-filter.ts +++ b/packages/@aws-cdk/aws-logs/lib/subscription-filter.ts @@ -45,19 +45,19 @@ export interface SubscriptionFilterProps { /** * The log group to create the subscription on. */ - logGroup: ILogGroup; + readonly logGroup: ILogGroup; /** * The destination to send the filtered events to. * * For example, a Kinesis stream or a Lambda function. */ - destination: ILogSubscriptionDestination; + readonly destination: ILogSubscriptionDestination; /** * Log events matching this pattern will be sent to the destination. */ - filterPattern: IFilterPattern; + readonly filterPattern: IFilterPattern; } /** diff --git a/packages/@aws-cdk/aws-logs/package.json b/packages/@aws-cdk/aws-logs/package.json index b7ca8c4df73d1..ab59961c7c55f 100644 --- a/packages/@aws-cdk/aws-logs/package.json +++ b/packages/@aws-cdk/aws-logs/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-logs", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Logs", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-logs", + "module": "aws_cdk.aws_logs" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-logs" }, "scripts": { "build": "cdk-build", @@ -54,24 +59,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-logs/test/example.retention.lit.ts b/packages/@aws-cdk/aws-logs/test/example.retention.lit.ts index 72e7a180c6267..ed0fd084e18d4 100644 --- a/packages/@aws-cdk/aws-logs/test/example.retention.lit.ts +++ b/packages/@aws-cdk/aws-logs/test/example.retention.lit.ts @@ -1,5 +1,5 @@ import { Stack } from '@aws-cdk/cdk'; -import { LogGroup } from '../lib'; +import { LogGroup, RetentionDays } from '../lib'; const stack = new Stack(); @@ -7,7 +7,7 @@ function shortLogGroup() { /// !show // Configure log group for short retention const logGroup = new LogGroup(stack, 'LogGroup', { - retentionDays: 7 + retentionDays: RetentionDays.OneWeek }); /// !hide return logGroup; diff --git a/packages/@aws-cdk/aws-logs/test/test.loggroup.ts b/packages/@aws-cdk/aws-logs/test/test.loggroup.ts index f23d3b16f8002..418197a35839f 100644 --- a/packages/@aws-cdk/aws-logs/test/test.loggroup.ts +++ b/packages/@aws-cdk/aws-logs/test/test.loggroup.ts @@ -2,7 +2,7 @@ import { expect, haveResource, matchTemplate } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; -import { LogGroup } from '../lib'; +import { LogGroup, RetentionDays } from '../lib'; export = { 'fixed retention'(test: Test) { @@ -11,7 +11,7 @@ export = { // WHEN new LogGroup(stack, 'LogGroup', { - retentionDays: 7 + retentionDays: RetentionDays.OneWeek }); // THEN diff --git a/packages/@aws-cdk/aws-neptune/package.json b/packages/@aws-cdk/aws-neptune/package.json index 575a5294583ee..6591f1f960e8c 100644 --- a/packages/@aws-cdk/aws-neptune/package.json +++ b/packages/@aws-cdk/aws-neptune/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-neptune", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Neptune", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "neptune" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-neptune", + "module": "aws_cdk.aws_neptune" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-neptune" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -55,18 +60,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-opsworks/package.json b/packages/@aws-cdk/aws-opsworks/package.json index 27263d816c247..5771b7e3374da 100644 --- a/packages/@aws-cdk/aws-opsworks/package.json +++ b/packages/@aws-cdk/aws-opsworks/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-opsworks", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::OpsWorks", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-opsworks", + "module": "aws_cdk.aws_opsworks" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-opsworks" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-opsworkscm/package.json b/packages/@aws-cdk/aws-opsworkscm/package.json index 10d56351955df..5c06987302a16 100644 --- a/packages/@aws-cdk/aws-opsworkscm/package.json +++ b/packages/@aws-cdk/aws-opsworkscm/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-opsworkscm", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::OpsWorksCM", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "opsworkscm" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-opsworkscm", + "module": "aws_cdk.aws_opsworkscm" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-opsworkscm" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -56,18 +61,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-quickstarts/lib/database.ts b/packages/@aws-cdk/aws-quickstarts/lib/database.ts index 4b142bb413c15..d9fc933f5e1b0 100644 --- a/packages/@aws-cdk/aws-quickstarts/lib/database.ts +++ b/packages/@aws-cdk/aws-quickstarts/lib/database.ts @@ -3,14 +3,14 @@ import rds = require('@aws-cdk/aws-rds'); import cdk = require('@aws-cdk/cdk'); export interface SqlServerProps { - instanceClass?: string; - engine?: string; - engineVersion?: string; - licenseModel?: string; - masterUsername: string; - masterPassword: string; - allocatedStorage?: number; - vpc: ec2.IVpcNetwork; + readonly instanceClass?: string; + readonly engine?: string; + readonly engineVersion?: string; + readonly licenseModel?: string; + readonly masterUsername: string; + readonly masterPassword: string; + readonly allocatedStorage?: number; + readonly vpc: ec2.IVpcNetwork; } /** diff --git a/packages/@aws-cdk/aws-quickstarts/lib/rdgw.ts b/packages/@aws-cdk/aws-quickstarts/lib/rdgw.ts index 4d959b272138a..ae4912f7beb12 100644 --- a/packages/@aws-cdk/aws-quickstarts/lib/rdgw.ts +++ b/packages/@aws-cdk/aws-quickstarts/lib/rdgw.ts @@ -3,18 +3,18 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); export interface RemoteDesktopGatewayProps { - rdgwCIDR: string; - vpc: ec2.IVpcNetwork; - keyPairName: string; - - adminPassword: string; - adminUser?: string; - - domainDNSName?: string; - numberOfRDGWHosts?: number; - qss3BucketName?: string; - qss3KeyPrefix?: string; - rdgwInstanceType?: string; + readonly rdgwCIDR: string; + readonly vpc: ec2.IVpcNetwork; + readonly keyPairName: string; + + readonly adminPassword: string; + readonly adminUser?: string; + + readonly domainDNSName?: string; + readonly numberOfRDGWHosts?: number; + readonly qss3BucketName?: string; + readonly qss3KeyPrefix?: string; + readonly rdgwInstanceType?: string; } /** diff --git a/packages/@aws-cdk/aws-quickstarts/package.json b/packages/@aws-cdk/aws-quickstarts/package.json index 4e64427d844e7..782ee19cc2d22 100644 --- a/packages/@aws-cdk/aws-quickstarts/package.json +++ b/packages/@aws-cdk/aws-quickstarts/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-quickstarts", - "version": "0.26.0", + "version": "0.28.0", "description": "AWS Quickstarts for the CDK", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-quickstarts", + "module": "aws_cdk.aws_quickstarts" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-quickstarts" }, "scripts": { "build": "cdk-build", @@ -49,22 +54,22 @@ }, "license": "Apache-2.0", "devDependencies": { - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-rds": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-rds": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ram/package.json b/packages/@aws-cdk/aws-ram/package.json index 4e5594db49654..4af58bd3f3390 100644 --- a/packages/@aws-cdk/aws-ram/package.json +++ b/packages/@aws-cdk/aws-ram/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ram", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::RAM", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "ram" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-ram", + "module": "aws_cdk.aws_ram" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-ram" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -56,18 +61,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 9fd10331d0170..958fe1e3f4988 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -2,6 +2,13 @@ The `aws-cdk-rds` package contains Constructs for setting up RDS instances. +> Note: the functionality this package is currently limited, as the CDK team is +> focusing on other use cases first. If your use case is not listed below, you +> will have to use achieve it using CloudFormation resources. +> +> If you would like to help improve the state of this library, Pull Requests are +> welcome. + Supported: * Clustered databases @@ -15,25 +22,25 @@ Not supported: ### Starting a Clustered Database To set up a clustered database (like Aurora), create an instance of `DatabaseCluster`. You must -always launch a database in a VPC. Use the `vpcPlacement` attribute to control whether +always launch a database in a VPC. Use the `vpcSubnets` attribute to control whether your instances will be launched privately or publicly: ```ts const cluster = new DatabaseCluster(this, 'Database', { engine: DatabaseClusterEngine.Aurora, masterUser: { - username: 'admin', - password: '7959866cacc02c2d243ecfe177464fe6', + username: 'admin' }, instanceProps: { instanceType: new InstanceTypePair(InstanceClass.Burstable2, InstanceSize.Small), - vpcPlacement: { - subnetsToUse: ec2.SubnetType.Public, + vpcSubnets: { + subnetType: ec2.SubnetType.Public, }, vpc } }); ``` +By default, the master password will be generated and stored in AWS Secrets Manager. Your cluster will be empty by default. To add a default database upon construction, specify the `defaultDatabaseName` attribute. @@ -53,3 +60,30 @@ attributes: ```ts const writeAddress = cluster.clusterEndpoint.socketAddress; // "HOSTNAME:PORT" ``` + +### Rotating master password +When the master password is generated and stored in AWS Secrets Manager, it can be rotated automatically: + +[example of setting up master password rotation](test/integ.cluster-rotation.lit.ts) + +Rotation of the master password is also supported for an existing cluster: +```ts +new RotationSingleUser(stack, 'Rotation', { + secret: importedSecret, + engine: DatabaseEngine.Oracle, + target: importedCluster, + vpc: importedVpc, +}) +``` + +The `importedSecret` must be a JSON string with the following format: +```json +{ + "engine": "", + "host": "", + "username": "", + "password": "", + "dbname": "", + "port": "" +} +``` diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts index 55ccfabf5fb60..3f8338a770409 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-parameter-group.ts @@ -21,7 +21,7 @@ export interface IClusterParameterGroup extends cdk.IConstruct { * Properties to reference a cluster parameter group */ export interface ClusterParameterGroupImportProps { - parameterGroupName: string; + readonly parameterGroupName: string; } /** @@ -31,17 +31,17 @@ export interface ClusterParameterGroupProps { /** * Database family of this parameter group */ - family: string; + readonly family: string; /** * Description for this parameter group */ - description: string; + readonly description: string; /** * The parameters in this parameter group */ - parameters?: Parameters; + readonly parameters?: Parameters; } /** diff --git a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts index 3a6803c587ecb..41b32ff24f5e7 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster-ref.ts @@ -1,10 +1,11 @@ import ec2 = require('@aws-cdk/aws-ec2'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); import cdk = require('@aws-cdk/cdk'); /** * Create a clustered database with a given number of instances. */ -export interface IDatabaseCluster extends cdk.IConstruct, ec2.IConnectable { +export interface IDatabaseCluster extends cdk.IConstruct, ec2.IConnectable, secretsmanager.ISecretAttachmentTarget { /** * Identifier of the cluster */ @@ -48,38 +49,38 @@ export interface DatabaseClusterImportProps { /** * The database port */ - port: string; + readonly port: string; /** * The security group for this database cluster */ - securityGroupId: string; + readonly securityGroupId: string; /** * Identifier for the cluster */ - clusterIdentifier: string; + readonly clusterIdentifier: string; /** * Identifier for the instances */ - instanceIdentifiers: string[]; + readonly instanceIdentifiers: string[]; // Actual underlying type: DBInstanceId[], but we have to type it more loosely for Java's benefit. /** * Cluster endpoint address */ - clusterEndpointAddress: string; + readonly clusterEndpointAddress: string; /** * Reader endpoint address */ - readerEndpointAddress: string; + readonly readerEndpointAddress: string; /** * Endpoint addresses of individual instances */ - instanceEndpointAddresses: string[]; + readonly instanceEndpointAddresses: string[]; } /** diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 5510bfafebc1c..13b81137ad220 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -1,9 +1,13 @@ import ec2 = require('@aws-cdk/aws-ec2'); +import kms = require('@aws-cdk/aws-kms'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); import cdk = require('@aws-cdk/cdk'); import { IClusterParameterGroup } from './cluster-parameter-group'; import { DatabaseClusterImportProps, Endpoint, IDatabaseCluster } from './cluster-ref'; +import { DatabaseSecret } from './database-secret'; import { BackupProps, DatabaseClusterEngine, InstanceProps, Login } from './props'; import { CfnDBCluster, CfnDBInstance, CfnDBSubnetGroup } from './rds.generated'; +import { DatabaseEngine, RotationSingleUser, RotationSingleUserOptions } from './rotation-single-user'; /** * Properties for a new database cluster @@ -12,7 +16,7 @@ export interface DatabaseClusterProps { /** * What kind of database to start */ - engine: DatabaseClusterEngine; + readonly engine: DatabaseClusterEngine; /** * How many replicas/instances to create @@ -21,36 +25,36 @@ export interface DatabaseClusterProps { * * @default 2 */ - instances?: number; + readonly instances?: number; /** * Settings for the individual instances that are launched */ - instanceProps: InstanceProps; + readonly instanceProps: InstanceProps; /** * Username and password for the administrative user */ - masterUser: Login; + readonly masterUser: Login; /** * Backup settings */ - backup?: BackupProps; + readonly backup?: BackupProps; /** * What port to listen on * * If not supplied, the default for the engine is used. */ - port?: number; + readonly port?: number; /** * An optional identifier for the cluster * * If not supplied, a name is automatically generated. */ - clusterIdentifier?: string; + readonly clusterIdentifier?: string; /** * Base identifier for instances @@ -61,17 +65,27 @@ export interface DatabaseClusterProps { * * If clusterIdentifier is also not given, the identifier is automatically generated. */ - instanceIdentifierBase?: string; + readonly instanceIdentifierBase?: string; /** * Name of a database which is automatically created inside the cluster */ - defaultDatabaseName?: string; + readonly defaultDatabaseName?: string; /** - * ARN of KMS key if you want to enable storage encryption + * Whether to enable storage encryption + * + * @default false + */ + readonly storageEncrypted?: boolean + + /** + * The KMS key for storage encryption. If specified `storageEncrypted` + * will be set to `true`. + * + * @default default master key */ - kmsKeyArn?: string; + readonly kmsKey?: kms.IEncryptionKey; /** * A daily time range in 24-hours UTC format in which backups preferably execute. @@ -80,20 +94,28 @@ export interface DatabaseClusterProps { * * Example: '01:00-02:00' */ - preferredMaintenanceWindow?: string; + readonly preferredMaintenanceWindow?: string; /** * Additional parameters to pass to the database engine * * @default No parameter group */ - parameterGroup?: IClusterParameterGroup; + readonly parameterGroup?: IClusterParameterGroup; + + /** + * The CloudFormation policy to apply when the cluster and its instances + * are removed from the stack or replaced during an update. + * + * @default Retain + */ + readonly deleteReplacePolicy?: cdk.DeletionPolicy } /** - * Create a clustered database with a given number of instances. + * A new or imported clustered database. */ -export class DatabaseCluster extends cdk.Construct implements IDatabaseCluster { +export abstract class DatabaseClusterBase extends cdk.Construct implements IDatabaseCluster { /** * Import an existing DatabaseCluster from properties */ @@ -101,6 +123,57 @@ export class DatabaseCluster extends cdk.Construct implements IDatabaseCluster { return new ImportedDatabaseCluster(scope, id, props); } + /** + * Identifier of the cluster + */ + public abstract readonly clusterIdentifier: string; + /** + * Identifiers of the replicas + */ + public abstract readonly instanceIdentifiers: string[]; + + /** + * The endpoint to use for read/write operations + */ + public abstract readonly clusterEndpoint: Endpoint; + + /** + * Endpoint to use for load-balanced read-only operations. + */ + public abstract readonly readerEndpoint: Endpoint; + + /** + * Endpoints which address each individual replica. + */ + public abstract readonly instanceEndpoints: Endpoint[]; + + /** + * Access to the network connections + */ + public abstract readonly connections: ec2.Connections; + + /** + * Security group identifier of this database + */ + public abstract readonly securityGroupId: string; + + public abstract export(): DatabaseClusterImportProps; + + /** + * Renders the secret attachment target specifications. + */ + public asSecretAttachmentTarget(): secretsmanager.SecretAttachmentTargetProps { + return { + targetId: this.clusterIdentifier, + targetType: secretsmanager.AttachmentTargetType.Cluster + }; + } +} + +/** + * Create a clustered database with a given number of instances. + */ +export class DatabaseCluster extends DatabaseClusterBase implements IDatabaseCluster { /** * Identifier of the cluster */ @@ -136,19 +209,42 @@ export class DatabaseCluster extends cdk.Construct implements IDatabaseCluster { */ public readonly securityGroupId: string; + /** + * The secret attached to this cluster + */ + public readonly secret?: secretsmanager.ISecret; + + /** + * The database engine of this cluster + */ + public readonly engine: DatabaseClusterEngine; + + /** + * The VPC where the DB subnet group is created. + */ + private readonly vpc: ec2.IVpcNetwork; + + /** + * The subnets used by the DB subnet group. + */ + private readonly vpcSubnets?: ec2.SubnetSelection; + constructor(scope: cdk.Construct, id: string, props: DatabaseClusterProps) { super(scope, id); - const subnets = props.instanceProps.vpc.subnets(props.instanceProps.vpcPlacement); + this.vpc = props.instanceProps.vpc; + this.vpcSubnets = props.instanceProps.vpcSubnets; + + const { subnetIds } = props.instanceProps.vpc.selectSubnets(props.instanceProps.vpcSubnets); // Cannot test whether the subnets are in different AZs, but at least we can test the amount. - if (subnets.length < 2) { - throw new Error(`Cluster requires at least 2 subnets, got ${subnets.length}`); + if (subnetIds.length < 2) { + throw new Error(`Cluster requires at least 2 subnets, got ${subnetIds.length}`); } const subnetGroup = new CfnDBSubnetGroup(this, 'Subnets', { dbSubnetGroupDescription: `Subnets for ${id} database`, - subnetIds: subnets.map(s => s.subnetId) + subnetIds, }); const securityGroup = props.instanceProps.securityGroup !== undefined ? @@ -158,35 +254,61 @@ export class DatabaseCluster extends cdk.Construct implements IDatabaseCluster { }); this.securityGroupId = securityGroup.securityGroupId; + let secret; + if (!props.masterUser.password) { + secret = new DatabaseSecret(this, 'Secret', { + username: props.masterUser.username, + encryptionKey: props.masterUser.kmsKey + }); + } + + this.engine = props.engine; + const cluster = new CfnDBCluster(this, 'Resource', { // Basic - engine: props.engine, + engine: this.engine, dbClusterIdentifier: props.clusterIdentifier, dbSubnetGroupName: subnetGroup.ref, vpcSecurityGroupIds: [this.securityGroupId], port: props.port, dbClusterParameterGroupName: props.parameterGroup && props.parameterGroup.parameterGroupName, // Admin - masterUsername: props.masterUser.username, - masterUserPassword: props.masterUser.password, + masterUsername: secret ? secret.secretJsonValue('username').toString() : props.masterUser.username, + masterUserPassword: secret + ? secret.secretJsonValue('password').toString() + : (props.masterUser.password + ? props.masterUser.password.toString() + : undefined), backupRetentionPeriod: props.backup && props.backup.retentionDays, preferredBackupWindow: props.backup && props.backup.preferredWindow, preferredMaintenanceWindow: props.preferredMaintenanceWindow, databaseName: props.defaultDatabaseName, // Encryption - kmsKeyId: props.kmsKeyArn, - storageEncrypted: props.kmsKeyArn ? true : false, + kmsKeyId: props.kmsKey && props.kmsKey.keyArn, + storageEncrypted: props.kmsKey ? true : props.storageEncrypted }); + const deleteReplacePolicy = props.deleteReplacePolicy || cdk.DeletionPolicy.Retain; + cluster.options.deletionPolicy = deleteReplacePolicy; + cluster.options.updateReplacePolicy = deleteReplacePolicy; + this.clusterIdentifier = cluster.ref; this.clusterEndpoint = new Endpoint(cluster.dbClusterEndpointAddress, cluster.dbClusterEndpointPort); this.readerEndpoint = new Endpoint(cluster.dbClusterReadEndpointAddress, cluster.dbClusterEndpointPort); + if (secret) { + this.secret = secret.addTargetAttachment('AttachedSecret', { + target: this + }); + } + const instanceCount = props.instances != null ? props.instances : 2; if (instanceCount < 1) { throw new Error('At least one instance is required'); } + // Get the actual subnet objects so we can depend on internet connectivity. + const internetConnected = props.instanceProps.vpc.selectSubnets(props.instanceProps.vpcSubnets).internetConnectedDependency; for (let i = 0; i < instanceCount; i++) { const instanceIndex = i + 1; @@ -194,7 +316,7 @@ export class DatabaseCluster extends cdk.Construct implements IDatabaseCluster { props.clusterIdentifier != null ? `${props.clusterIdentifier}instance${instanceIndex}` : undefined; - const publiclyAccessible = props.instanceProps.vpcPlacement && props.instanceProps.vpcPlacement.subnetsToUse === ec2.SubnetType.Public; + const publiclyAccessible = props.instanceProps.vpcSubnets && props.instanceProps.vpcSubnets.subnetType === ec2.SubnetType.Public; const instance = new CfnDBInstance(this, `Instance${instanceIndex}`, { // Link to cluster @@ -208,9 +330,12 @@ export class DatabaseCluster extends cdk.Construct implements IDatabaseCluster { dbSubnetGroupName: subnetGroup.ref, }); + instance.options.deletionPolicy = deleteReplacePolicy; + instance.options.updateReplacePolicy = deleteReplacePolicy; + // We must have a dependency on the NAT gateway provider here to create // things in the right order. - instance.node.addDependency(...subnets.map(s => s.internetConnectivityEstablished)); + instance.node.addDependency(internetConnected); this.instanceIdentifiers.push(instance.ref); this.instanceEndpoints.push(new Endpoint(instance.dbInstanceEndpointAddress, instance.dbInstanceEndpointPort)); @@ -220,6 +345,23 @@ export class DatabaseCluster extends cdk.Construct implements IDatabaseCluster { this.connections = new ec2.Connections({ securityGroups: [securityGroup], defaultPortRange }); } + /** + * Adds the single user rotation of the master password to this cluster. + */ + public addRotationSingleUser(id: string, options: RotationSingleUserOptions = {}): RotationSingleUser { + if (!this.secret) { + throw new Error('Cannot add single user rotation for a cluster without secret.'); + } + return new RotationSingleUser(this, id, { + secret: this.secret, + engine: toDatabaseEngine(this.engine), + vpc: this.vpc, + vpcSubnets: this.vpcSubnets, + target: this, + ...options + }); + } + /** * Export a Database Cluster for importing in another stack */ @@ -248,7 +390,7 @@ function databaseInstanceType(instanceType: ec2.InstanceType) { /** * An imported Database Cluster */ -class ImportedDatabaseCluster extends cdk.Construct implements IDatabaseCluster { +class ImportedDatabaseCluster extends DatabaseClusterBase implements IDatabaseCluster { /** * Default port to connect to this database */ @@ -308,3 +450,20 @@ class ImportedDatabaseCluster extends cdk.Construct implements IDatabaseCluster return this.props; } } + +/** + * Transforms a DatbaseClusterEngine to a DatabaseEngine. + * + * @param engine the engine to transform + */ +function toDatabaseEngine(engine: DatabaseClusterEngine): DatabaseEngine { + switch (engine) { + case DatabaseClusterEngine.Aurora: + case DatabaseClusterEngine.AuroraMysql: + return DatabaseEngine.Mysql; + case DatabaseClusterEngine.AuroraPostgresql: + return DatabaseEngine.Postgres; + default: + throw new Error('Unknown engine'); + } +} diff --git a/packages/@aws-cdk/aws-rds/lib/database-secret.ts b/packages/@aws-cdk/aws-rds/lib/database-secret.ts new file mode 100644 index 0000000000000..875801c6695b5 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/database-secret.ts @@ -0,0 +1,37 @@ +import kms = require('@aws-cdk/aws-kms'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); +import cdk = require('@aws-cdk/cdk'); + +/** + * Construction properties for a DatabaseSecret. + */ +export interface DatabaseSecretProps { + /** + * The username. + */ + readonly username: string; + + /** + * The KMS key to use to encrypt the secret. + * + * @default default master key + */ + readonly encryptionKey?: kms.IEncryptionKey; +} + +/** + * A database secret. + */ +export class DatabaseSecret extends secretsmanager.Secret { + constructor(scope: cdk.Construct, id: string, props: DatabaseSecretProps) { + super(scope, id, { + encryptionKey: props.encryptionKey, + generateSecretString: { + passwordLength: 30, // Oracle password cannot have more than 30 characters + secretStringTemplate: JSON.stringify({ username: props.username }), + generateStringKey: 'password', + excludeCharacters: '"@/\\' + } + }); + } +} diff --git a/packages/@aws-cdk/aws-rds/lib/index.ts b/packages/@aws-cdk/aws-rds/lib/index.ts index 4a0f2ed04ee88..149610e4fe159 100644 --- a/packages/@aws-cdk/aws-rds/lib/index.ts +++ b/packages/@aws-cdk/aws-rds/lib/index.ts @@ -1,8 +1,9 @@ export * from './cluster'; export * from './cluster-ref'; -export * from './instance'; export * from './props'; export * from './cluster-parameter-group'; +export * from './rotation-single-user'; +export * from './database-secret'; // AWS::RDS CloudFormation Resources: export * from './rds.generated'; diff --git a/packages/@aws-cdk/aws-rds/lib/instance.ts b/packages/@aws-cdk/aws-rds/lib/instance.ts deleted file mode 100644 index d8f52ffd68b13..0000000000000 --- a/packages/@aws-cdk/aws-rds/lib/instance.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Construct } from '@aws-cdk/cdk'; - -/** - * Create a database instance - * - * This can be a standalone database instance, or part of a cluster. - */ -export class DatabaseInstance extends Construct { -} diff --git a/packages/@aws-cdk/aws-rds/lib/props.ts b/packages/@aws-cdk/aws-rds/lib/props.ts index 366260c82791f..0a414f1e3c834 100644 --- a/packages/@aws-cdk/aws-rds/lib/props.ts +++ b/packages/@aws-cdk/aws-rds/lib/props.ts @@ -1,4 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); +import kms = require('@aws-cdk/aws-kms'); +import { SecretValue } from '@aws-cdk/cdk'; /** * The engine for the database cluster @@ -17,24 +19,24 @@ export interface InstanceProps { /** * What type of instance to start for the replicas */ - instanceType: ec2.InstanceType; + readonly instanceType: ec2.InstanceType; /** * What subnets to run the RDS instances in. * * Must be at least 2 subnets in two different AZs. */ - vpc: ec2.IVpcNetwork; + readonly vpc: ec2.IVpcNetwork; /** * Where to place the instances within the VPC */ - vpcPlacement?: ec2.VpcPlacementStrategy; + readonly vpcSubnets?: ec2.SubnetSelection; /** * Security group. If not specified a new one will be created. */ - securityGroup?: ec2.ISecurityGroup; + readonly securityGroup?: ec2.ISecurityGroup; } /** @@ -45,7 +47,7 @@ export interface BackupProps { /** * How many days to retain the backup */ - retentionDays: number; + readonly retentionDays: number; /** * A daily time range in 24-hours UTC format in which backups preferably execute. @@ -54,7 +56,7 @@ export interface BackupProps { * * Example: '01:00-02:00' */ - preferredWindow?: string; + readonly preferredWindow?: string; } /** @@ -64,15 +66,23 @@ export interface Login { /** * Username */ - username: string; + readonly username: string; /** * Password * - * Do not put passwords in your CDK code directly. Import it from a Stack - * Parameter or the SSM Parameter Store instead. + * Do not put passwords in your CDK code directly. + * + * @default a Secrets Manager generated password + */ + readonly password?: SecretValue; + + /** + * KMS encryption key to encrypt the generated secret. + * + * @default default master key */ - password: string; + readonly kmsKey?: kms.IEncryptionKey; } /** diff --git a/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts new file mode 100644 index 0000000000000..357e9504a0fd2 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/lib/rotation-single-user.ts @@ -0,0 +1,204 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import lambda = require('@aws-cdk/aws-lambda'); +import serverless = require('@aws-cdk/aws-sam'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); +import cdk = require('@aws-cdk/cdk'); + +/** + * A serverless application location. + */ +export class ServerlessApplicationLocation { + public static readonly MariaDbRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSMariaDBRotationSingleUser', '1.0.46'); + public static readonly MysqlRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSMySQLRotationSingleUser', '1.0.74'); + public static readonly OracleRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSOracleRotationSingleUser', '1.0.45'); + public static readonly PostgresRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSPostgreSQLRotationSingleUser', '1.0.75'); + public static readonly SqlServerRotationSingleUser = new ServerlessApplicationLocation('SecretsManagerRDSSQLServerRotationSingleUser', '1.0.74'); + + public readonly applicationId: string; + public readonly semanticVersion: string; + + constructor(applicationId: string, semanticVersion: string) { + this.applicationId = `arn:aws:serverlessrepo:us-east-1:297356227824:applications/${applicationId}`; + this.semanticVersion = semanticVersion; + } +} + +/** + * The RDS database engine + */ +export enum DatabaseEngine { + /** + * MariaDB + */ + MariaDb = 'mariadb', + + /** + * MySQL + */ + Mysql = 'mysql', + + /** + * Oracle + */ + Oracle = 'oracle', + + /** + * PostgreSQL + */ + Postgres = 'postgres', + + /** + * SQL Server + */ + SqlServer = 'sqlserver' +} + +/** + * Options to add single user rotation to a database instance or cluster. + */ +export interface RotationSingleUserOptions { + /** + * Specifies the number of days after the previous rotation before + * Secrets Manager triggers the next automatic rotation. + * + * @default 30 days + */ + readonly automaticallyAfterDays?: number; + + /** + * The location of the serverless application for the rotation. + * + * @default derived from the target's engine + */ + readonly serverlessApplicationLocation?: ServerlessApplicationLocation +} + +/** + * Construction properties for a RotationSingleUser. + */ +export interface RotationSingleUserProps extends RotationSingleUserOptions { + /** + * The secret to rotate. It must be a JSON string with the following format: + * { + * 'engine': , + * 'host': , + * 'username': , + * 'password': , + * 'dbname': , + * 'port': + * } + * + * This is typically the case for a secret referenced from an AWS::SecretsManager::SecretTargetAttachment + * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secrettargetattachment.html + */ + readonly secret: secretsmanager.ISecret; + + /** + * The database engine. Either `serverlessApplicationLocation` or `engine` must be specified. + * + * @default no engine specified + */ + readonly engine?: DatabaseEngine; + + /** + * The VPC where the Lambda rotation function will run. + */ + readonly vpc: ec2.IVpcNetwork; + + /** + * The type of subnets in the VPC where the Lambda rotation function will run. + * + * @default private subnets + */ + readonly vpcSubnets?: ec2.SubnetSelection; + + /** + * The target database cluster or instance + */ + readonly target: ec2.IConnectable; +} + +/** + * Single user secret rotation for a database instance or cluster. + */ +export class RotationSingleUser extends cdk.Construct { + constructor(scope: cdk.Construct, id: string, props: RotationSingleUserProps) { + super(scope, id); + + if (!props.serverlessApplicationLocation && !props.engine) { + throw new Error('Either `serverlessApplicationLocation` or `engine` must be specified.'); + } + + if (!props.target.connections.defaultPortRange) { + throw new Error('The `target` connections must have a default port range.'); + } + + const rotationFunctionName = this.node.uniqueId; + + const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { + vpc: props.vpc + }); + + const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); + + props.target.connections.allowDefaultPortFrom(securityGroup); + + const application = new serverless.CfnApplication(this, 'Resource', { + location: props.serverlessApplicationLocation || getApplicationLocation(props.engine), + parameters: { + endpoint: `https://secretsmanager.${this.node.stack.region}.${this.node.stack.urlSuffix}`, + functionName: rotationFunctionName, + vpcSecurityGroupIds: securityGroup.securityGroupId, + vpcSubnetIds: subnetIds.join(',') + } + }); + + // Dummy import to reference this function in the rotation schedule + const rotationLambda = lambda.Function.import(this, 'RotationLambda', { + functionArn: this.node.stack.formatArn({ + service: 'lambda', + resource: 'function', + sep: ':', + resourceName: rotationFunctionName + }), + }); + + // Cannot use rotationLambda.addPermission because it currently does not + // return a cdk.Construct and we need to add a dependency. + const permission = new lambda.CfnPermission(this, 'Permission', { + action: 'lambda:InvokeFunction', + functionName: rotationFunctionName, + principal: `secretsmanager.${this.node.stack.urlSuffix}` + }); + permission.node.addDependency(application); // Add permission after application is deployed + + const rotationSchedule = props.secret.addRotationSchedule('RotationSchedule', { + rotationLambda, + automaticallyAfterDays: props.automaticallyAfterDays + }); + rotationSchedule.node.addDependency(permission); // Cannot rotate without permission + } +} + +/** + * Returns the location for the rotation single user application. + * + * @param engine the database engine + * @throws if the engine is not supported + */ +function getApplicationLocation(engine: string = ''): ServerlessApplicationLocation { + switch (engine) { + case DatabaseEngine.MariaDb: + return ServerlessApplicationLocation.MariaDbRotationSingleUser; + case DatabaseEngine.Mysql: + return ServerlessApplicationLocation.MysqlRotationSingleUser; + case DatabaseEngine.Oracle: + return ServerlessApplicationLocation.OracleRotationSingleUser; + case DatabaseEngine.Postgres: + return ServerlessApplicationLocation.PostgresRotationSingleUser; + case DatabaseEngine.SqlServer: + return ServerlessApplicationLocation.SqlServerRotationSingleUser; + default: + throw new Error(`Engine ${engine} not supported for single user rotation.`); + } +} diff --git a/packages/@aws-cdk/aws-rds/package.json b/packages/@aws-cdk/aws-rds/package.json index 3dc5f1d3ba3b4..48586de0e0d4b 100644 --- a/packages/@aws-cdk/aws-rds/package.json +++ b/packages/@aws-cdk/aws-rds/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-rds", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS RDS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-rds", + "module": "aws_cdk.aws_rds" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-rds" }, "scripts": { "build": "cdk-build", @@ -54,24 +59,29 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-sam": "^0.28.0", + "@aws-cdk/aws-secretsmanager": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-secretsmanager": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json new file mode 100644 index 0000000000000..774d57074bb82 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.expected.json @@ -0,0 +1,795 @@ +{ + "Transform": "AWS::Serverless-2016-10-31", + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/VPC/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/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" + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/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": "Name", + "Value": "aws-cdk-rds-cluster-rotation/VPC/PublicSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/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" + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/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": "Name", + "Value": "aws-cdk-rds-cluster-rotation/VPC/PublicSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/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" + } + }, + "VPCPublicSubnet3NATGatewayD3048F5C": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet3EIPAD4BC883", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/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": "Name", + "Value": "aws-cdk-rds-cluster-rotation/VPC/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/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": "Name", + "Value": "aws-cdk-rds-cluster-rotation/VPC/PrivateSubnet2" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/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": "Name", + "Value": "aws-cdk-rds-cluster-rotation/VPC/PrivateSubnet3" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-cdk-rds-cluster-rotation/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": "aws-cdk-rds-cluster-rotation/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "DatabaseSubnets56F17B9A": { + "Type": "AWS::RDS::DBSubnetGroup", + "Properties": { + "DBSubnetGroupDescription": "Subnets for Database database", + "SubnetIds": [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ] + } + }, + "DatabaseSecurityGroup5C91FDCB": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "RDS security group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "DatabaseSecurityGroupfromawscdkrdsclusterrotationDatabaseRotationSecurityGroup35913E19IndirectPort12DA2942": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "from awscdkrdsclusterrotationDatabaseRotationSecurityGroup35913E19:{IndirectPort}", + "FromPort": { + "Fn::GetAtt": [ + "DatabaseB269D8BB", + "Endpoint.Port" + ] + }, + "GroupId": { + "Fn::GetAtt": [ + "DatabaseSecurityGroup5C91FDCB", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "DatabaseRotationSecurityGroup17736B63", + "GroupId" + ] + }, + "ToPort": { + "Fn::GetAtt": [ + "DatabaseB269D8BB", + "Endpoint.Port" + ] + } + } + }, + "DatabaseSecret3B817195": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": { + "ExcludeCharacters": "\"@/\\", + "GenerateStringKey": "password", + "PasswordLength": 30, + "SecretStringTemplate": "{\"username\":\"admin\"}" + } + } + }, + "DatabaseSecretAttachedSecretE6CAC445": { + "Type": "AWS::SecretsManager::SecretTargetAttachment", + "Properties": { + "SecretId": { + "Ref": "DatabaseSecret3B817195" + }, + "TargetId": { + "Ref": "DatabaseB269D8BB" + }, + "TargetType": "AWS::RDS::DBCluster" + } + }, + "DatabaseSecretAttachedSecretRotationSchedule93D67FF7": { + "Type": "AWS::SecretsManager::RotationSchedule", + "Properties": { + "SecretId": { + "Ref": "DatabaseSecretAttachedSecretE6CAC445" + }, + "RotationLambdaARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":lambda:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":function:awscdkrdsclusterrotationDatabaseRotation30042AAE" + ] + ] + }, + "RotationRules": { + "AutomaticallyAfterDays": 30 + } + }, + "DependsOn": [ + "DatabaseRotationPermission64416CB0" + ] + }, + "DatabaseB269D8BB": { + "Type": "AWS::RDS::DBCluster", + "Properties": { + "Engine": "aurora", + "DBSubnetGroupName": { + "Ref": "DatabaseSubnets56F17B9A" + }, + "MasterUsername": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "DatabaseSecret3B817195" + }, + ":SecretString:username::}}" + ] + ] + }, + "MasterUserPassword": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "DatabaseSecret3B817195" + }, + ":SecretString:password::}}" + ] + ] + }, + "VpcSecurityGroupIds": [ + { + "Fn::GetAtt": [ + "DatabaseSecurityGroup5C91FDCB", + "GroupId" + ] + } + ] + }, + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain" + }, + "DatabaseInstance1844F58FD": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBInstanceClass": "db.t2.small", + "DBClusterIdentifier": { + "Ref": "DatabaseB269D8BB" + }, + "DBSubnetGroupName": { + "Ref": "DatabaseSubnets56F17B9A" + }, + "Engine": "aurora" + }, + "DependsOn": [ + "VPCPrivateSubnet1DefaultRouteAE1D6490", + "VPCPrivateSubnet2DefaultRouteF4F5CFD2", + "VPCPrivateSubnet3DefaultRoute27F311AE" + ], + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain" + }, + "DatabaseInstance2AA380DEE": { + "Type": "AWS::RDS::DBInstance", + "Properties": { + "DBInstanceClass": "db.t2.small", + "DBClusterIdentifier": { + "Ref": "DatabaseB269D8BB" + }, + "DBSubnetGroupName": { + "Ref": "DatabaseSubnets56F17B9A" + }, + "Engine": "aurora" + }, + "DependsOn": [ + "VPCPrivateSubnet1DefaultRouteAE1D6490", + "VPCPrivateSubnet2DefaultRouteF4F5CFD2", + "VPCPrivateSubnet3DefaultRoute27F311AE" + ], + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain" + }, + "DatabaseRotationSecurityGroup17736B63": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-cdk-rds-cluster-rotation/Database/Rotation/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "DatabaseRotation6B6E1D86": { + "Type": "AWS::Serverless::Application", + "Properties": { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser", + "SemanticVersion": "1.0.74" + }, + "Parameters": { + "endpoint": { + "Fn::Join": [ + "", + [ + "https://secretsmanager.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + }, + "functionName": "awscdkrdsclusterrotationDatabaseRotation30042AAE", + "vpcSecurityGroupIds": { + "Fn::GetAtt": [ + "DatabaseRotationSecurityGroup17736B63", + "GroupId" + ] + }, + "vpcSubnetIds": { + "Fn::Join": [ + "", + [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + ",", + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + ",", + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ] + ] + } + } + } + }, + "DatabaseRotationPermission64416CB0": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": "awscdkrdsclusterrotationDatabaseRotation30042AAE", + "Principal": { + "Fn::Join": [ + "", + [ + "secretsmanager.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + }, + "DependsOn": [ + "DatabaseRotation6B6E1D86" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts new file mode 100644 index 0000000000000..bcd41ecf298d4 --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster-rotation.lit.ts @@ -0,0 +1,25 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import rds = require('../lib'); + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-cdk-rds-cluster-rotation'); + +const vpc = new ec2.VpcNetwork(stack, 'VPC'); + +/// !show +const cluster = new rds.DatabaseCluster(stack, 'Database', { + engine: rds.DatabaseClusterEngine.Aurora, + masterUser: { + username: 'admin' + }, + instanceProps: { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + vpc + } +}); + +cluster.addRotationSingleUser('Rotation'); +/// !hide + +app.run(); diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json index d8bb54664a09b..6f09d047317fe 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.expected.json @@ -484,7 +484,9 @@ ] } ] - } + }, + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain" }, "DatabaseInstance1844F58FD": { "Type": "AWS::RDS::DBInstance", @@ -502,7 +504,9 @@ "DependsOn": [ "VPCPublicSubnet1DefaultRoute91CEF279", "VPCPublicSubnet2DefaultRouteB7481BBA" - ] + ], + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain" }, "DatabaseInstance2AA380DEE": { "Type": "AWS::RDS::DBInstance", @@ -520,7 +524,9 @@ "DependsOn": [ "VPCPublicSubnet1DefaultRoute91CEF279", "VPCPublicSubnet2DefaultRouteB7481BBA" - ] + ], + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain" } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts index 468b1d442ce77..a6eea21795b90 100644 --- a/packages/@aws-cdk/aws-rds/test/integ.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/integ.cluster.ts @@ -1,6 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import kms = require('@aws-cdk/aws-kms'); import cdk = require('@aws-cdk/cdk'); +import { SecretValue } from '@aws-cdk/cdk'; import { DatabaseCluster, DatabaseClusterEngine } from '../lib'; import { ClusterParameterGroup } from '../lib/cluster-parameter-group'; @@ -20,15 +21,15 @@ const cluster = new DatabaseCluster(stack, 'Database', { engine: DatabaseClusterEngine.Aurora, masterUser: { username: 'admin', - password: '7959866cacc02c2d243ecfe177464fe6', + password: SecretValue.plainText('7959866cacc02c2d243ecfe177464fe6'), }, instanceProps: { instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), - vpcPlacement: { subnetsToUse: ec2.SubnetType.Public }, + vpcSubnets: { subnetType: ec2.SubnetType.Public }, vpc }, parameterGroup: params, - kmsKeyArn: kmsKey.keyArn, + kmsKey, }); cluster.connections.allowDefaultPortFromAnyIpv4('Open to the world'); diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index fb48ebec7817d..124efb19cf9bb 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -1,6 +1,8 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { expect, haveResource, ResourcePart } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); +import kms = require('@aws-cdk/aws-kms'); import cdk = require('@aws-cdk/cdk'); +import { SecretValue } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { ClusterParameterGroup, DatabaseCluster, DatabaseClusterEngine } from '../lib'; @@ -15,7 +17,7 @@ export = { engine: DatabaseClusterEngine.Aurora, masterUser: { username: 'admin', - password: 'tooshort', + password: SecretValue.plainText('tooshort'), }, instanceProps: { instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), @@ -25,12 +27,21 @@ export = { // THEN expect(stack).to(haveResource('AWS::RDS::DBCluster', { - Engine: "aurora", - DBSubnetGroupName: { Ref: "DatabaseSubnets56F17B9A" }, - MasterUsername: "admin", - MasterUserPassword: "tooshort", - VpcSecurityGroupIds: [ {"Fn::GetAtt": ["DatabaseSecurityGroup5C91FDCB", "GroupId"]}] - })); + Properties: { + Engine: "aurora", + DBSubnetGroupName: { Ref: "DatabaseSubnets56F17B9A" }, + MasterUsername: "admin", + MasterUserPassword: "tooshort", + VpcSecurityGroupIds: [ {"Fn::GetAtt": ["DatabaseSecurityGroup5C91FDCB", "GroupId"]}] + }, + DeletionPolicy: 'Retain', + UpdateReplacePolicy: 'Retain' + }, ResourcePart.CompleteDefinition)); + + expect(stack).to(haveResource('AWS::RDS::DBInstance', { + DeletionPolicy: 'Retain', + UpdateReplacePolicy: 'Retain' + }, ResourcePart.CompleteDefinition)); test.done(); }, @@ -43,7 +54,7 @@ export = { engine: DatabaseClusterEngine.Aurora, masterUser: { username: 'admin', - password: 'tooshort', + password: SecretValue.plainText('tooshort'), }, instanceProps: { instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), @@ -69,7 +80,7 @@ export = { instances: 1, masterUser: { username: 'admin', - password: 'tooshort', + password: SecretValue.plainText('tooshort'), }, instanceProps: { instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), @@ -105,7 +116,7 @@ export = { instances: 1, masterUser: { username: 'admin', - password: 'tooshort', + password: SecretValue.plainText('tooshort'), }, instanceProps: { instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), @@ -143,7 +154,7 @@ export = { engine: DatabaseClusterEngine.Aurora, masterUser: { username: 'admin', - password: 'tooshort', + password: SecretValue.plainText('tooshort'), }, instanceProps: { instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), @@ -175,6 +186,94 @@ export = { // THEN test.deepEqual(stack.node.resolve(exported), { parameterGroupName: { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' } }); test.deepEqual(stack.node.resolve(imported.parameterGroupName), { 'Fn::ImportValue': 'Stack:ParamsParameterGroupNameA6B808D7' }); + test.done(); + }, + + 'creates a secret when master credentials are not specified'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AuroraMysql, + masterUser: { + username: 'admin' + }, + instanceProps: { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + vpc + } + }); + + // THEN + expect(stack).to(haveResource('AWS::RDS::DBCluster', { + MasterUsername: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:', + { + Ref: 'DatabaseSecret3B817195' + }, + ':SecretString:username::}}' + ] + ] + }, + MasterUserPassword: { + 'Fn::Join': [ + '', + [ + '{{resolve:secretsmanager:', + { + Ref: 'DatabaseSecret3B817195' + }, + ':SecretString:password::}}' + ] + ] + }, + })); + + expect(stack).to(haveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: { + ExcludeCharacters: '\"@/\\', + GenerateStringKey: 'password', + PasswordLength: 30, + SecretStringTemplate: '{"username":"admin"}' + } + })); + + test.done(); + }, + + 'create an encrypted cluster with custom KMS key'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AuroraMysql, + masterUser: { + username: 'admin' + }, + instanceProps: { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + vpc + }, + kmsKey: new kms.EncryptionKey(stack, 'Key') + }); + + // THEN + expect(stack).to(haveResource('AWS::RDS::DBCluster', { + KmsKeyId: { + 'Fn::GetAtt': [ + 'Key961B73FD', + 'Arn' + ] + } + })); + test.done(); } }; diff --git a/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts b/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts new file mode 100644 index 0000000000000..0c64f5759707a --- /dev/null +++ b/packages/@aws-cdk/aws-rds/test/test.rotation-single-user.ts @@ -0,0 +1,232 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import ec2 = require('@aws-cdk/aws-ec2'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); +import cdk = require('@aws-cdk/cdk'); +import { SecretValue } from '@aws-cdk/cdk'; +import { Test } from 'nodeunit'; +import rds = require('../lib'); + +// tslint:disable:object-literal-key-quotes + +export = { + 'add a rds rotation single user to a cluster'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const cluster = new rds.DatabaseCluster(stack, 'Database', { + engine: rds.DatabaseClusterEngine.AuroraMysql, + masterUser: { + username: 'admin' + }, + instanceProps: { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + vpc + } + }); + + // WHEN + cluster.addRotationSingleUser('Rotation'); + + // THEN + expect(stack).to(haveResource('AWS::EC2::SecurityGroupIngress', { + "IpProtocol": "tcp", + "Description": "from DatabaseRotationSecurityGroup1C5A8031:{IndirectPort}", + "FromPort": { + "Fn::GetAtt": [ + "DatabaseB269D8BB", + "Endpoint.Port" + ] + }, + "GroupId": { + "Fn::GetAtt": [ + "DatabaseSecurityGroup5C91FDCB", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "DatabaseRotationSecurityGroup17736B63", + "GroupId" + ] + }, + "ToPort": { + "Fn::GetAtt": [ + "DatabaseB269D8BB", + "Endpoint.Port" + ] + } + })); + + expect(stack).to(haveResource('AWS::SecretsManager::RotationSchedule', { + "SecretId": { + "Ref": "DatabaseSecretAttachedSecretE6CAC445" + }, + "RotationLambdaARN": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":lambda:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":function:DatabaseRotation0D47EBD2" + ] + ] + }, + "RotationRules": { + "AutomaticallyAfterDays": 30 + } + })); + + expect(stack).to(haveResource('AWS::EC2::SecurityGroup', { + "GroupDescription": "Database/Rotation/SecurityGroup" + })); + + expect(stack).to(haveResource('AWS::Serverless::Application', { + "Location": { + "ApplicationId": "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSMySQLRotationSingleUser", + "SemanticVersion": "1.0.74" + }, + "Parameters": { + "endpoint": { + "Fn::Join": [ + "", + [ + "https://secretsmanager.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + }, + "functionName": "DatabaseRotation0D47EBD2", + "vpcSecurityGroupIds": { + "Fn::GetAtt": [ + "DatabaseRotationSecurityGroup17736B63", + "GroupId" + ] + }, + "vpcSubnetIds": { + "Fn::Join": [ + "", + [ + { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + ",", + { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + }, + ",", + { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + ] + ] + } + } + })); + + expect(stack).to(haveResource('AWS::Lambda::Permission', { + "Action": "lambda:InvokeFunction", + "FunctionName": "DatabaseRotation0D47EBD2", + "Principal": { + "Fn::Join": [ + "", + [ + "secretsmanager.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + })); + + test.done(); + }, + + 'throws when trying to add rotation to a cluster without secret'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + + // WHEN + const cluster = new rds.DatabaseCluster(stack, 'Database', { + engine: rds.DatabaseClusterEngine.AuroraMysql, + masterUser: { + username: 'admin', + password: SecretValue.plainText('tooshort') + }, + instanceProps: { + instanceType: new ec2.InstanceTypePair(ec2.InstanceClass.Burstable2, ec2.InstanceSize.Small), + vpc + } + }); + + // THEN + test.throws(() => cluster.addRotationSingleUser('Rotation'), /without secret/); + + test.done(); + }, + + 'throws when both application location and engine are not specified'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { + vpc, + }); + const target = new ec2.Connections({ + defaultPortRange: new ec2.TcpPort(1521), + securityGroups: [securityGroup] + }); + const secret = new secretsmanager.Secret(stack, 'Secret'); + + // THEN + test.throws(() => new rds.RotationSingleUser(stack, 'Rotation', { + secret, + vpc, + target + }), /`serverlessApplicationLocation`.+`engine`/); + + test.done(); + }, + + 'throws when connections object has no default port range'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.VpcNetwork(stack, 'VPC'); + const secret = new secretsmanager.Secret(stack, 'Secret'); + const securityGroup = new ec2.SecurityGroup(stack, 'SecurityGroup', { + vpc, + }); + + // WHEN + const target = new ec2.Connections({ + securityGroups: [securityGroup] + }); + + // THEN + test.throws(() => new rds.RotationSingleUser(stack, 'Rotation', { + secret, + engine: rds.DatabaseEngine.Mysql, + vpc, + target + }), /`target`.+default port range/); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-redshift/package.json b/packages/@aws-cdk/aws-redshift/package.json index 4a02f5ee5a98c..6cec43e6917a0 100644 --- a/packages/@aws-cdk/aws-redshift/package.json +++ b/packages/@aws-cdk/aws-redshift/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-redshift", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Redshift", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-redshift", + "module": "aws_cdk.aws_redshift" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-redshift" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-robomaker/package.json b/packages/@aws-cdk/aws-robomaker/package.json index 05718c8249d88..d458a10313986 100644 --- a/packages/@aws-cdk/aws-robomaker/package.json +++ b/packages/@aws-cdk/aws-robomaker/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-robomaker", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::RoboMaker", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "robomaker" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-robomaker", + "module": "aws_cdk.aws_robomaker" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-robomaker" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -56,18 +61,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts index c2f4d221e9724..53f88d27d8b6a 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone-provider.ts @@ -10,17 +10,17 @@ export interface HostedZoneProviderProps { /** * The zone domain e.g. example.com */ - domainName: string; + readonly domainName: string; /** * Is this a private zone */ - privateZone?: boolean; + readonly privateZone?: boolean; /** * If this is a private zone which VPC is assocaitated */ - vpcId?: string; + readonly vpcId?: string; } const DEFAULT_HOSTED_ZONE: HostedZoneContextResponse = { diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone-ref.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone-ref.ts index 33a7b0e295fb9..09818d8176dce 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone-ref.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone-ref.ts @@ -35,10 +35,10 @@ export interface HostedZoneImportProps { /** * Identifier of the hosted zone */ - hostedZoneId: string; + readonly hostedZoneId: string; /** * Name of the hosted zone */ - zoneName: string; + readonly zoneName: string; } diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts index ef198625d057b..a297c95b95618 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts @@ -10,21 +10,21 @@ export interface CommonHostedZoneProps { * The name of the domain. For resource record types that include a domain * name, specify a fully qualified domain name. */ - zoneName: string; + readonly zoneName: string; /** * Any comments that you want to include about the hosted zone. * * @default none */ - comment?: string; + readonly comment?: string; /** * The Amazon Resource Name (ARN) for the log group that you want Amazon Route 53 to send query logs to. * * @default disabled */ - queryLogsLogGroupArn?: string; + readonly queryLogsLogGroupArn?: string; } /** @@ -39,7 +39,7 @@ export interface HostedZoneProps extends CommonHostedZoneProps { * * @default public (no VPCs associated) */ - vpcs?: ec2.IVpcNetwork[]; + readonly vpcs?: ec2.IVpcNetwork[]; } export class HostedZone extends cdk.Construct implements IHostedZone { @@ -140,14 +140,14 @@ export interface ZoneDelegationOptions { * * @default none */ - comment?: string; + readonly comment?: string; /** * The TTL (Time To Live) of the DNS delegation record in DNS caches. * * @default 172800 */ - ttl?: number; + readonly ttl?: number; } export interface PrivateHostedZoneProps extends CommonHostedZoneProps { @@ -157,7 +157,7 @@ export interface PrivateHostedZoneProps extends CommonHostedZoneProps { * Private hosted zones must be associated with at least one VPC. You can * associated additional VPCs using `addVpc(vpc)`. */ - vpc: ec2.IVpcNetwork; + readonly vpc: ec2.IVpcNetwork; } /** diff --git a/packages/@aws-cdk/aws-route53/lib/records/alias.ts b/packages/@aws-cdk/aws-route53/lib/records/alias.ts index ba892113e1478..1e16e41933a94 100644 --- a/packages/@aws-cdk/aws-route53/lib/records/alias.ts +++ b/packages/@aws-cdk/aws-route53/lib/records/alias.ts @@ -21,28 +21,28 @@ export interface AliasRecordTargetProps { /** * Hosted zone ID of the target */ - hostedZoneId: string; + readonly hostedZoneId: string; /** * DNS name of the target */ - dnsName: string; + readonly dnsName: string; } export interface AliasRecordProps { /** * The zone in which this alias should be defined. */ - zone: IHostedZone; + readonly zone: IHostedZone; /** * Name for the record. This can be the FQDN for the record (foo.example.com) or * a subdomain of the parent hosted zone (foo, with example.com as the hosted zone). */ - recordName: string; + readonly recordName: string; /** * Target for the alias record */ - target: IAliasRecordTarget; + readonly target: IAliasRecordTarget; } /** diff --git a/packages/@aws-cdk/aws-route53/lib/records/cname.ts b/packages/@aws-cdk/aws-route53/lib/records/cname.ts index e41bda2f515d8..55e9a7bd3f32a 100644 --- a/packages/@aws-cdk/aws-route53/lib/records/cname.ts +++ b/packages/@aws-cdk/aws-route53/lib/records/cname.ts @@ -7,24 +7,24 @@ export interface CnameRecordProps { /** * The hosted zone in which to define the new TXT record. */ - zone: IHostedZone; + readonly zone: IHostedZone; /** * The domain name for this record set. */ - recordName: string; + readonly recordName: string; /** * The value for this record set. */ - recordValue: string; + readonly recordValue: string; /** * The resource record cache time to live (TTL) in seconds. * * @default 1800 seconds */ - ttl?: number; + readonly ttl?: number; } /** diff --git a/packages/@aws-cdk/aws-route53/lib/records/txt.ts b/packages/@aws-cdk/aws-route53/lib/records/txt.ts index a4cafcbcea50b..64bd769848640 100644 --- a/packages/@aws-cdk/aws-route53/lib/records/txt.ts +++ b/packages/@aws-cdk/aws-route53/lib/records/txt.ts @@ -7,24 +7,24 @@ export interface TxtRecordProps { /** * The hosted zone in which to define the new TXT record. */ - zone: IHostedZone; + readonly zone: IHostedZone; /** * The domain name for this record set. */ - recordName: string; + readonly recordName: string; /** * The value for this record set. */ - recordValue: string; + readonly recordValue: string; /** * The resource record cache time to live (TTL) in seconds. * * @default 1800 seconds */ - ttl?: number; + readonly ttl?: number; } /** diff --git a/packages/@aws-cdk/aws-route53/lib/records/zone-delegation.ts b/packages/@aws-cdk/aws-route53/lib/records/zone-delegation.ts index 82f3764dc35b2..d67716756f430 100644 --- a/packages/@aws-cdk/aws-route53/lib/records/zone-delegation.ts +++ b/packages/@aws-cdk/aws-route53/lib/records/zone-delegation.ts @@ -8,16 +8,16 @@ export interface ZoneDelegationRecordProps extends ZoneDelegationOptions { /** * The zone in which this delegate is defined. */ - zone: IHostedZone; + readonly zone: IHostedZone; /** * The name of the zone that delegation is made to. */ - delegatedZoneName: string; + readonly delegatedZoneName: string; /** * The name servers to report in the delegation records. */ - nameServers: string[]; + readonly nameServers: string[]; } /** diff --git a/packages/@aws-cdk/aws-route53/package-lock.json b/packages/@aws-cdk/aws-route53/package-lock.json index 91e62829c871b..d9189f3b693a0 100644 --- a/packages/@aws-cdk/aws-route53/package-lock.json +++ b/packages/@aws-cdk/aws-route53/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-route53", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 6fb96c433b3b9..6637f48dcd57f 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-route53", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS Route53", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-route53", + "module": "aws_cdk.aws_route53" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-route53" }, "scripts": { "build": "cdk-build", @@ -54,25 +59,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-logs": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-route53resolver/package.json b/packages/@aws-cdk/aws-route53resolver/package.json index 5463a6cbd9d0e..143aae8a2dfaa 100644 --- a/packages/@aws-cdk/aws-route53resolver/package.json +++ b/packages/@aws-cdk/aws-route53resolver/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-route53resolver", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::Route53Resolver", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "route53resolver" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-route53resolver", + "module": "aws_cdk.aws_route53resolver" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-route53resolver" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -55,18 +60,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index cb2c3cc4dd61f..950143b8100ca 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -11,19 +11,19 @@ export interface BucketDeploymentProps { /** * The source from which to deploy the contents of this bucket. */ - source: ISource; + readonly source: ISource; /** * The S3 bucket to sync the contents of the zip file to. */ - destinationBucket: s3.IBucket; + readonly destinationBucket: s3.IBucket; /** * Key prefix in the destination bucket. * * @default "/" (unzip to root of the destination bucket) */ - destinationKeyPrefix?: string; + readonly destinationKeyPrefix?: string; /** * If this is set to "false", the destination files will be deleted when the @@ -36,7 +36,7 @@ export interface BucketDeploymentProps { * * @default true - when resource is deleted/updated, files are retained */ - retainOnDelete?: boolean; + readonly retainOnDelete?: boolean; } export class BucketDeployment extends cdk.Construct { @@ -54,8 +54,8 @@ export class BucketDeployment extends cdk.Construct { const source = props.source.bind(this); - source.bucket.grantRead(handler.role); - props.destinationBucket.grantReadWrite(handler.role); + source.bucket.grantRead(handler); + props.destinationBucket.grantReadWrite(handler); new cloudformation.CustomResource(this, 'CustomResource', { lambdaProvider: handler, diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/source.ts b/packages/@aws-cdk/aws-s3-deployment/lib/source.ts index 61507865c4840..145c2e8a90388 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/source.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/source.ts @@ -7,12 +7,12 @@ export interface SourceProps { /** * The source bucket to deploy from. */ - bucket: s3.IBucket; + readonly bucket: s3.IBucket; /** * An S3 object key in the source bucket that points to a zip file. */ - zipObjectKey: string; + readonly zipObjectKey: string; } /** diff --git a/packages/@aws-cdk/aws-s3-deployment/package.json b/packages/@aws-cdk/aws-s3-deployment/package.json index 6c2277aa7e16a..4a9d005acbf10 100644 --- a/packages/@aws-cdk/aws-s3-deployment/package.json +++ b/packages/@aws-cdk/aws-s3-deployment/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-s3-deployment", - "version": "0.26.0", + "version": "0.28.0", "description": "Constructs for deploying contents to S3 buckets", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-s3-deployment", + "module": "aws_cdk.aws_s3_deployment" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-s3-deployment" }, "scripts": { "build": "cdk-build", @@ -68,25 +73,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/assets": "^0.26.0", - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/assets": "^0.28.0", + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-s3-notifications/lib/destination.ts b/packages/@aws-cdk/aws-s3-notifications/lib/destination.ts index 7a079878875c9..ac7ef9a967d72 100644 --- a/packages/@aws-cdk/aws-s3-notifications/lib/destination.ts +++ b/packages/@aws-cdk/aws-s3-notifications/lib/destination.ts @@ -22,18 +22,18 @@ export interface BucketNotificationDestinationProps { /** * The notification type. */ - type: BucketNotificationDestinationType; + readonly type: BucketNotificationDestinationType; /** * The ARN of the destination (i.e. Lambda, SNS, SQS). */ - arn: string; + readonly arn: string; /** * Any additional dependencies that should be resolved before the bucket notification * can be configured (for example, the SNS Topic Policy resource). */ - dependencies?: cdk.IDependable[] + readonly dependencies?: cdk.IDependable[] } /** diff --git a/packages/@aws-cdk/aws-s3-notifications/package.json b/packages/@aws-cdk/aws-s3-notifications/package.json index dc80505e3b956..ab5b87bebb992 100644 --- a/packages/@aws-cdk/aws-s3-notifications/package.json +++ b/packages/@aws-cdk/aws-s3-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-s3-notifications", - "version": "0.26.0", + "version": "0.28.0", "description": "Bucket Notifications API for AWS S3", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-s3-notifications", + "module": "aws_cdk.aws_s3_notifications" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-s3-notifications" }, "scripts": { "build": "cdk-build", @@ -50,17 +55,17 @@ }, "license": "Apache-2.0", "devDependencies": { - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index b89b3b32971a0..06a02424dd7d9 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -82,98 +82,6 @@ bucket.grantReadWrite(lambda.role); Will give the Lambda's execution role permissions to read and write from the bucket. -### Buckets as sources in CodePipeline - -This package also defines an Action that allows you to use a -Bucket as a source in CodePipeline: - -```ts -import codepipeline = require('@aws-cdk/aws-codepipeline'); -import s3 = require('@aws-cdk/aws-s3'); - -const sourceBucket = new s3.Bucket(this, 'MyBucket', { - versioned: true, // a Bucket used as a source in CodePipeline must be versioned -}); - -const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const sourceAction = new s3.PipelineSourceAction({ - actionName: 'S3Source', - bucket: sourceBucket, - bucketKey: 'path/to/file.zip', -}); -pipeline.addStage({ - name: 'Source', - actions: [sourceAction], -}); -``` - -You can also create the action from the Bucket directly: - -```ts -// equivalent to the code above: -const sourceAction = sourceBucket.toCodePipelineSourceAction({ - actionName: 'S3Source', - bucketKey: 'path/to/file.zip', -}); -``` - -By default, the Pipeline will poll the Bucket to detect changes. -You can change that behavior to use CloudWatch Events by setting the `pollForSourceChanges` -property to `false` (it's `true` by default). -If you do that, make sure the source Bucket is part of an AWS CloudTrail Trail - -otherwise, the CloudWatch Events will not be emitted, -and your Pipeline will not react to changes in the Bucket. -You can do it through the CDK: - -```typescript -import cloudtrail = require('@aws-cdk/aws-cloudtrail'); - -const key = 'some/key.zip'; -const trail = new cloudtrail.CloudTrail(this, 'CloudTrail'); -trail.addS3EventSelector([sourceBucket.arnForObjects(key)], cloudtrail.ReadWriteType.WriteOnly); -const sourceAction = sourceBucket.toCodePipelineSourceAction({ - actionName: 'S3Source', - bucketKey: key, - pollForSourceChanges: false, // default: true -}); -``` - -### Buckets as deploy targets in CodePipeline - -This package also defines an Action that allows you to use a -Bucket as a deployment target in CodePipeline: - -```ts -import codepipeline = require('@aws-cdk/aws-codepipeline'); -import s3 = require('@aws-cdk/aws-s3'); - -const targetBucket = new s3.Bucket(this, 'MyBucket', {}); - -const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const deployAction = new s3.PipelineDeployAction({ - actionName: 'S3Deploy', - stage: deployStage, - bucket: targetBucket, - inputArtifact: sourceAction.outputArtifact, -}); -const deployStage = pipeline.addStage({ - name: 'Deploy', - actions: [deployAction], -}); -``` - -You can also create the action from the Bucket directly: - -```ts -// equivalent to the code above: -const deployAction = targetBucket.toCodePipelineDeployAction({ - actionName: 'S3Deploy', - extract: false, // default: true - objectKey: 'path/in/bucket', // required if extract is false - inputArtifact: sourceAction.outputArtifact, -}); -``` - ### Sharing buckets between stacks To use a bucket in a different stack in the same CDK application, pass the object to the other stack: diff --git a/packages/@aws-cdk/aws-s3/lib/bucket-policy.ts b/packages/@aws-cdk/aws-s3/lib/bucket-policy.ts index c56ce1982bd2c..e0b61dc8c5cbd 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket-policy.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket-policy.ts @@ -7,7 +7,7 @@ export interface BucketPolicyProps { /** * The Amazon S3 bucket that the policy applies to. */ - bucket: IBucket; + readonly bucket: IBucket; } /** diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index adfb0a8f0100b..5357dbe754dc4 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -6,10 +6,6 @@ import cdk = require('@aws-cdk/cdk'); import { BucketPolicy } from './bucket-policy'; import { BucketNotifications } from './notifications-resource'; import perms = require('./perms'); -import { - CommonPipelineDeployActionProps, CommonPipelineSourceActionProps, - PipelineDeployAction, PipelineSourceAction -} from './pipeline-actions'; import { LifecycleRule } from './rule'; import { CfnBucket } from './s3.generated'; import { parseBucketArn, parseBucketName } from './util'; @@ -55,22 +51,6 @@ export interface IBucket extends cdk.IConstruct { */ export(): BucketImportProps; - /** - * Convenience method for creating a new {@link PipelineSourceAction}. - * - * @param props the construction properties of the new Action - * @returns the newly created {@link PipelineSourceAction} - */ - toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction; - - /** - * Convenience method for creating a new {@link PipelineDeployAction}. - * - * @param props the construction properties of the new Action - * @returns the newly created {@link PipelineDeployAction} - */ - toCodePipelineDeployAction(props: CommonPipelineDeployActionProps): PipelineDeployAction; - /** * Adds a statement to the resource policy for a principal (i.e. * account/role/service) to perform actions on this bucket and/or it's @@ -111,7 +91,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantRead(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantRead(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Grant write permissions to this bucket to an IAM principal. @@ -122,7 +102,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantWrite(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantWrite(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Grants s3:PutObject* and s3:Abort* permissions for this bucket to an IAM principal. @@ -132,7 +112,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantPut(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantPut(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Grants s3:DeleteObject* permission to an IAM pricipal for objects @@ -141,7 +121,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantDelete(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantDelete(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Grants read/write permissions for this bucket and it's contents to an IAM @@ -153,7 +133,7 @@ export interface IBucket extends cdk.IConstruct { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - grantReadWrite(identity?: iam.IPrincipal, objectsKeyPattern?: any): void; + grantReadWrite(identity: iam.IGrantable, objectsKeyPattern?: any): iam.Grant; /** * Allows unrestricted access to objects from this bucket. @@ -178,7 +158,7 @@ export interface IBucket extends cdk.IConstruct { * @param allowedActions the set of S3 actions to allow. Default is "s3:GetObject". * @returns The `iam.PolicyStatement` object, which can be used to apply e.g. conditions. */ - grantPublicAccess(keyPrefix?: string, ...allowedActions: string[]): iam.PolicyStatement; + grantPublicAccess(keyPrefix?: string, ...allowedActions: string[]): iam.Grant; /** * Defines a CloudWatch Event Rule that triggers upon putting an object into the Bucket. @@ -201,7 +181,7 @@ export interface BucketImportProps { * The ARN of the bucket. At least one of bucketArn or bucketName must be * defined in order to initialize a bucket ref. */ - bucketArn?: string; + readonly bucketArn?: string; /** * The name of the bucket. If the underlying value of ARN is a string, the @@ -209,21 +189,21 @@ export interface BucketImportProps { * some features that require the bucket name such as auto-creating a bucket * policy, won't work. */ - bucketName?: string; + readonly bucketName?: string; /** * The domain name of the bucket. * * @default Inferred from bucket name */ - bucketDomainName?: string; + readonly bucketDomainName?: string; /** * The website URL of the bucket (if static web hosting is enabled). * * @default Inferred from bucket name */ - bucketWebsiteUrl?: string; + readonly bucketWebsiteUrl?: string; /** * The format of the website URL of the bucket. This should be true for @@ -231,7 +211,7 @@ export interface BucketImportProps { * * @default false */ - bucketWebsiteNewUrlFormat?: boolean; + readonly bucketWebsiteNewUrlFormat?: boolean; } /** @@ -296,20 +276,6 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { */ public abstract export(): BucketImportProps; - public toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction { - return new PipelineSourceAction({ - ...props, - bucket: this, - }); - } - - public toCodePipelineDeployAction(props: CommonPipelineDeployActionProps): PipelineDeployAction { - return new PipelineDeployAction({ - ...props, - bucket: this, - }); - } - public onPutObject(name: string, target?: events.IEventRuleTarget, path?: string): events.EventRule { const eventRule = new events.EventRule(this, name, { eventPattern: { @@ -409,8 +375,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantRead(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { - this.grant(identity, perms.BUCKET_READ_ACTIONS, perms.KEY_READ_ACTIONS, + public grantRead(identity: iam.IGrantable, objectsKeyPattern: any = '*') { + return this.grant(identity, perms.BUCKET_READ_ACTIONS, perms.KEY_READ_ACTIONS, this.bucketArn, this.arnForObjects(objectsKeyPattern)); } @@ -424,8 +390,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantWrite(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { - this.grant(identity, perms.BUCKET_WRITE_ACTIONS, perms.KEY_WRITE_ACTIONS, + public grantWrite(identity: iam.IGrantable, objectsKeyPattern: any = '*') { + return this.grant(identity, perms.BUCKET_WRITE_ACTIONS, perms.KEY_WRITE_ACTIONS, this.bucketArn, this.arnForObjects(objectsKeyPattern)); } @@ -438,8 +404,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantPut(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { - this.grant(identity, perms.BUCKET_PUT_ACTIONS, perms.KEY_WRITE_ACTIONS, + public grantPut(identity: iam.IGrantable, objectsKeyPattern: any = '*') { + return this.grant(identity, perms.BUCKET_PUT_ACTIONS, perms.KEY_WRITE_ACTIONS, this.arnForObjects(objectsKeyPattern)); } @@ -450,8 +416,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantDelete(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { - this.grant(identity, perms.BUCKET_DELETE_ACTIONS, [], + public grantDelete(identity: iam.IGrantable, objectsKeyPattern: any = '*') { + return this.grant(identity, perms.BUCKET_DELETE_ACTIONS, [], this.arnForObjects(objectsKeyPattern)); } @@ -465,11 +431,11 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * @param identity The principal * @param objectsKeyPattern Restrict the permission to a certain key pattern (default '*') */ - public grantReadWrite(identity?: iam.IPrincipal, objectsKeyPattern: any = '*') { + public grantReadWrite(identity: iam.IGrantable, objectsKeyPattern: any = '*') { const bucketActions = perms.BUCKET_READ_ACTIONS.concat(perms.BUCKET_WRITE_ACTIONS); const keyActions = perms.KEY_READ_ACTIONS.concat(perms.KEY_WRITE_ACTIONS); - this.grant(identity, + return this.grant(identity, bucketActions, keyActions, this.bucketArn, @@ -497,51 +463,40 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { * * @param keyPrefix the prefix of S3 object keys (e.g. `home/*`). Default is "*". * @param allowedActions the set of S3 actions to allow. Default is "s3:GetObject". - * @returns The `iam.PolicyStatement` object, which can be used to apply e.g. conditions. */ - public grantPublicAccess(keyPrefix = '*', ...allowedActions: string[]): iam.PolicyStatement { + public grantPublicAccess(keyPrefix = '*', ...allowedActions: string[]) { if (this.disallowPublicAccess) { throw new Error("Cannot grant public access when 'blockPublicPolicy' is enabled"); } allowedActions = allowedActions.length > 0 ? allowedActions : [ 's3:GetObject' ]; - const statement = new iam.PolicyStatement() - .addActions(...allowedActions) - .addResource(this.arnForObjects(keyPrefix)) - .addPrincipal(new iam.Anyone()); - - this.addToResourcePolicy(statement); - return statement; + return iam.Grant.addToPrincipalOrResource({ + actions: allowedActions, + resourceArns: [this.arnForObjects(keyPrefix)], + grantee: new iam.Anyone(), + resource: this, + }); } - private grant(identity: iam.IPrincipal | undefined, + private grant(grantee: iam.IGrantable, bucketActions: string[], keyActions: string[], resourceArn: string, ...otherResourceArns: string[]) { - - if (!identity) { - return; - } - const resources = [ resourceArn, ...otherResourceArns ]; - identity.addToPolicy(new iam.PolicyStatement() - .addResources(...resources) - .addActions(...bucketActions)); + const ret = iam.Grant.addToPrincipalOrResource({ + grantee, + actions: bucketActions, + resourceArns: resources, + resource: this, + }); - // grant key permissions if there's an associated key. if (this.encryptionKey) { - // KMS permissions need to be granted both directions - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.encryptionKey.keyArn) - .addActions(...keyActions)); - - this.encryptionKey.addToResourcePolicy(new iam.PolicyStatement() - .addAllResources() - .addPrincipal(identity.principal) - .addActions(...keyActions)); + this.encryptionKey.grant(grantee, ...keyActions); } + + return ret; } } @@ -551,28 +506,28 @@ export interface BlockPublicAccessOptions { * * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-options */ - blockPublicAcls?: boolean; + readonly blockPublicAcls?: boolean; /** * Whether to block public policy * * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-options */ - blockPublicPolicy?: boolean; + readonly blockPublicPolicy?: boolean; /** * Whether to ignore public ACLs * * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-options */ - ignorePublicAcls?: boolean; + readonly ignorePublicAcls?: boolean; /** * Whether to restrict public access * * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html#access-control-block-public-access-options */ - restrictPublicBuckets?: boolean; + readonly restrictPublicBuckets?: boolean; } export class BlockPublicAccess { @@ -610,7 +565,7 @@ export interface BucketProps { * * @default Unencrypted */ - encryption?: BucketEncryption; + readonly encryption?: BucketEncryption; /** * External KMS key to use for bucket encryption. @@ -622,60 +577,60 @@ export interface BucketProps { * @default If encryption is set to "Kms" and this property is undefined, a * new KMS key will be created and associated with this bucket. */ - encryptionKey?: kms.IEncryptionKey; + readonly encryptionKey?: kms.IEncryptionKey; /** * Physical name of this bucket. * * @default Assigned by CloudFormation (recommended) */ - bucketName?: string; + readonly bucketName?: string; /** * Policy to apply when the bucket is removed from this stack. * * @default The bucket will be orphaned */ - removalPolicy?: cdk.RemovalPolicy; + readonly removalPolicy?: cdk.RemovalPolicy; /** * Whether this bucket should have versioning turned on or not. * * @default false */ - versioned?: boolean; + readonly versioned?: boolean; /** * Rules that define how Amazon S3 manages objects during their lifetime. * * @default No lifecycle rules */ - lifecycleRules?: LifecycleRule[]; + readonly lifecycleRules?: LifecycleRule[]; /** * The name of the index document (e.g. "index.html") for the website. Enables static website * hosting for this bucket. */ - websiteIndexDocument?: string; + readonly websiteIndexDocument?: string; /** * The name of the error document (e.g. "404.html") for the website. * `websiteIndexDocument` must also be set if this is set. */ - websiteErrorDocument?: string; + readonly websiteErrorDocument?: string; /** * Grants public read access to all objects in the bucket. * Similar to calling `bucket.grantPublicAccess()` */ - publicReadAccess?: boolean; + readonly publicReadAccess?: boolean; /** * The block public access configuration of this bucket. * * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/access-control-block-public-access.html */ - blockPublicAccess?: BlockPublicAccess; + readonly blockPublicAccess?: BlockPublicAccess; } /** @@ -1079,12 +1034,12 @@ export interface NotificationKeyFilter { /** * S3 keys must have the specified prefix. */ - prefix?: string; + readonly prefix?: string; /** * S3 keys must have the specified suffix. */ - suffix?: string; + readonly suffix?: string; } class ImportedBucket extends BucketBase { diff --git a/packages/@aws-cdk/aws-s3/lib/coordinates.ts b/packages/@aws-cdk/aws-s3/lib/coordinates.ts new file mode 100644 index 0000000000000..6b096762fd573 --- /dev/null +++ b/packages/@aws-cdk/aws-s3/lib/coordinates.ts @@ -0,0 +1,14 @@ +/** + * An interface that represents the coordinates of a specific object in an S3 Bucket. + */ +export interface Coordinates { + /** + * The name of the S3 Bucket the object is in. + */ + readonly bucketName: string; + + /** + * The path inside the Bucket where the object is located at. + */ + readonly objectKey: string; +} diff --git a/packages/@aws-cdk/aws-s3/lib/index.ts b/packages/@aws-cdk/aws-s3/lib/index.ts index f7d6f0d21696e..ca25e093b7613 100644 --- a/packages/@aws-cdk/aws-s3/lib/index.ts +++ b/packages/@aws-cdk/aws-s3/lib/index.ts @@ -1,6 +1,6 @@ export * from './bucket'; export * from './bucket-policy'; -export * from './pipeline-actions'; +export * from './coordinates'; export * from './rule'; // AWS::S3 CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-s3/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-s3/lib/pipeline-actions.ts deleted file mode 100644 index db16f68fe7599..0000000000000 --- a/packages/@aws-cdk/aws-s3/lib/pipeline-actions.ts +++ /dev/null @@ -1,144 +0,0 @@ -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); -import cdk = require('@aws-cdk/cdk'); -import { IBucket } from './bucket'; - -/** - * Common properties for creating {@link PipelineSourceAction} - - * either directly, through its constructor, - * or through {@link IBucket#toCodePipelineSourceAction}. - */ -export interface CommonPipelineSourceActionProps extends codepipeline.CommonActionProps { - /** - * The name of the source's output artifact. CfnOutput artifacts are used by CodePipeline as - * inputs into other actions. - * - * @default a name will be auto-generated - */ - outputArtifactName?: string; - - /** - * The key within the S3 bucket that stores the source code. - * - * @example 'path/to/file.zip' - */ - bucketKey: string; - - /** - * Whether AWS CodePipeline should poll for source changes. - * If this is `false`, the Pipeline will use CloudWatch Events to detect source changes instead. - * Note that if this is `false`, you need to make sure to include the source Bucket in a CloudTrail Trail, - * as otherwise the CloudWatch Events will not be emitted. - * - * @default true - * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/log-s3-data-events.html - */ - pollForSourceChanges?: boolean; -} - -/** - * Construction properties of the {@link PipelineSourceAction S3 source Action}. - */ -export interface PipelineSourceActionProps extends CommonPipelineSourceActionProps { - /** - * The Amazon S3 bucket that stores the source code - */ - bucket: IBucket; -} - -/** - * Source that is provided by a specific Amazon S3 object. - */ -export class PipelineSourceAction extends codepipeline.SourceAction { - private readonly props: PipelineSourceActionProps; - - constructor(props: PipelineSourceActionProps) { - super({ - ...props, - provider: 'S3', - outputArtifactName: props.outputArtifactName || `Artifact_${props.actionName}_${props.bucket.node.uniqueId}`, - configuration: { - S3Bucket: props.bucket.bucketName, - S3ObjectKey: props.bucketKey, - PollForSourceChanges: props.pollForSourceChanges, - }, - }); - - this.props = props; - } - - protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { - if (this.props.pollForSourceChanges === false) { - this.props.bucket.onPutObject(stage.pipeline.node.uniqueId + 'SourceEventRule', - stage.pipeline, this.props.bucketKey); - } - - // pipeline needs permissions to read from the S3 bucket - this.props.bucket.grantRead(stage.pipeline.role); - } -} - -/** - * Common properties for creating {@link PipelineDeployAction} - - * either directly, through its constructor, - * or through {@link IBucket#toCodePipelineDeployAction}. - */ -export interface CommonPipelineDeployActionProps extends codepipeline.CommonActionProps { - /** - * Should the deploy action extract the artifact before deploying to Amazon S3. - * - * @default true - */ - extract?: boolean; - - /** - * The key of the target object. This is required if extract is false. - */ - objectKey?: string; - - /** - * The inputArtifact to deploy to Amazon S3. - */ - inputArtifact: codepipeline.Artifact; -} - -/** - * Construction properties of the {@link PipelineDeployAction S3 deploy Action}. - */ -export interface PipelineDeployActionProps extends CommonPipelineDeployActionProps { - /** - * The Amazon S3 bucket that is the deploy target. - */ - bucket: IBucket; -} - -/** - * Deploys the sourceArtifact to Amazon S3. - */ -export class PipelineDeployAction extends codepipeline.DeployAction { - private readonly bucket: IBucket; - - constructor(props: PipelineDeployActionProps) { - super({ - ...props, - provider: 'S3', - artifactBounds: { - minInputs: 1, - maxInputs: 1, - minOutputs: 0, - maxOutputs: 0, - }, - configuration: { - BucketName: props.bucket.bucketName, - Extract: (props.extract === false) ? 'false' : 'true', - ObjectKey: props.objectKey, - }, - }); - - this.bucket = props.bucket; - } - - protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { - // pipeline needs permissions to write to the S3 bucket - this.bucket.grantWrite(stage.pipeline.role); - } -} diff --git a/packages/@aws-cdk/aws-s3/lib/rule.ts b/packages/@aws-cdk/aws-s3/lib/rule.ts index 835d92aaa1991..186469dd0048a 100644 --- a/packages/@aws-cdk/aws-s3/lib/rule.ts +++ b/packages/@aws-cdk/aws-s3/lib/rule.ts @@ -5,14 +5,14 @@ export interface LifecycleRule { /** * A unique identifier for this rule. The value cannot be more than 255 characters. */ - id?: string; + readonly id?: string; /** * Whether this rule is enabled. * * @default true */ - enabled?: boolean; + readonly enabled?: boolean; /** * Specifies a lifecycle rule that aborts incomplete multipart uploads to an Amazon S3 bucket. @@ -24,7 +24,7 @@ export interface LifecycleRule { * * @default Incomplete uploads are never aborted */ - abortIncompleteMultipartUploadAfterDays?: number; + readonly abortIncompleteMultipartUploadAfterDays?: number; /** * Indicates when objects are deleted from Amazon S3 and Amazon Glacier. @@ -37,7 +37,7 @@ export interface LifecycleRule { * * @default No expiration date */ - expirationDate?: Date; + readonly expirationDate?: Date; /** * Indicates the number of days after creation when objects are deleted from Amazon S3 and Amazon Glacier. @@ -48,7 +48,7 @@ export interface LifecycleRule { * * @default No expiration timeout */ - expirationInDays?: number; + readonly expirationInDays?: number; /** * Time between when a new version of the object is uploaded to the bucket and when old versions of the object expire. @@ -62,7 +62,7 @@ export interface LifecycleRule { * * @default No noncurrent version expiration */ - noncurrentVersionExpirationInDays?: number; + readonly noncurrentVersionExpirationInDays?: number; /** * One or more transition rules that specify when non-current objects transition to a specified storage class. @@ -72,7 +72,7 @@ export interface LifecycleRule { * If you specify a transition and expiration time, the expiration time * must be later than the transition time. */ - noncurrentVersionTransitions?: NoncurrentVersionTransition[]; + readonly noncurrentVersionTransitions?: NoncurrentVersionTransition[]; /** * One or more transition rules that specify when an object transitions to a specified storage class. @@ -83,21 +83,21 @@ export interface LifecycleRule { * * @default No transition rules */ - transitions?: Transition[]; + readonly transitions?: Transition[]; /** * Object key prefix that identifies one or more objects to which this rule applies. * * @default Rule applies to all objects */ - prefix?: string; + readonly prefix?: string; /** * The TagFilter property type specifies tags to use to identify a subset of objects for an Amazon S3 bucket. * * @default Rule applies to all objects */ - tagFilters?: {[tag: string]: any}; + readonly tagFilters?: {[tag: string]: any}; } /** @@ -107,7 +107,7 @@ export interface Transition { /** * The storage class to which you want the object to transition. */ - storageClass: StorageClass; + readonly storageClass: StorageClass; /** * Indicates when objects are transitioned to the specified storage class. @@ -116,14 +116,14 @@ export interface Transition { * * @default No transition date. */ - transitionDate?: Date; + readonly transitionDate?: Date; /** * Indicates the number of days after creation when objects are transitioned to the specified storage class. * * @default No transition count. */ - transitionInDays?: number; + readonly transitionInDays?: number; } /** @@ -133,14 +133,14 @@ export interface NoncurrentVersionTransition { /** * The storage class to which you want the object to transition. */ - storageClass: StorageClass; + readonly storageClass: StorageClass; /** * Indicates the number of days after creation when objects are transitioned to the specified storage class. * * @default No transition count. */ - transitionInDays: number; + readonly transitionInDays: number; } /** diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index 5460c458b4896..b1057fc4dc627 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-s3", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS S3", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-s3", + "module": "aws_cdk.aws_s3" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-s3" }, "scripts": { "build": "cdk-build", @@ -54,28 +59,26 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-s3-notifications": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-s3-notifications": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-s3-notifications": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-s3-notifications": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -89,4 +92,4 @@ "resource-interface:@aws-cdk/aws-s3.IBucketPolicy" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket.ts b/packages/@aws-cdk/aws-s3/test/test.bucket.ts index f23f69336f6db..8789d97ceb91e 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/test.bucket.ts @@ -1,4 +1,4 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { expect, haveResource, SynthUtils } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import cdk = require('@aws-cdk/cdk'); @@ -444,7 +444,7 @@ export = { test.deepEqual(bucket.bucketArn, bucketArn); test.deepEqual(bucket.node.resolve(bucket.bucketName), 'my-bucket'); - test.deepEqual(stack._toCloudFormation(), {}, 'the ref is not a real resource'); + test.deepEqual(SynthUtils.toCloudFormation(stack), {}, 'the ref is not a real resource'); test.done(); }, @@ -743,6 +743,65 @@ export = { test.done(); }, + 'grant permissions to non-identity principal'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Kms }); + + // WHEN + bucket.grantRead(new iam.OrganizationPrincipal('o-1234')); + + // THEN + expect(stack).to(haveResource('AWS::S3::BucketPolicy', { + PolicyDocument: { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ "s3:GetObject*", "s3:GetBucket*", "s3:List*" ], + "Condition": { "StringEquals": { "aws:PrincipalOrgID": "o-1234" } }, + "Effect": "Allow", + "Principal": "*", + "Resource": [ + { "Fn::GetAtt": [ "MyBucketF68F3FF0", "Arn" ] }, + { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "MyBucketF68F3FF0", "Arn" ] }, "/*" ] ] } + ] + } + ] + } + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + "KeyPolicy": { + "Statement": [ + { + "Action": [ "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*", + "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion" ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ "", [ + "arn:", { "Ref": "AWS::Partition" }, ":iam::", { "Ref": "AWS::AccountId" }, ":root" + ]] + } + }, + "Resource": "*" + }, + { + "Action": [ "kms:Decrypt", "kms:DescribeKey" ], + "Effect": "Allow", + "Resource": "*", + "Principal": "*", + "Condition": { "StringEquals": { "aws:PrincipalOrgID": "o-1234" } }, + } + ], + "Version": "2012-10-17" + }, + + })); + + test.done(); + }, + 'if an encryption key is included, encrypt/decrypt permissions are also added both ways'(test: Test) { const stack = new cdk.Stack(); const bucket = new s3.Bucket(stack, 'MyBucket', { encryption: s3.BucketEncryption.Kms }); @@ -925,7 +984,7 @@ export = { bucket.grantWrite(writer); bucket.grantDelete(deleter); - const resources = stack._toCloudFormation().Resources; + const resources = SynthUtils.toCloudFormation(stack).Resources; const actions = (id: string) => resources[id].Properties.PolicyDocument.Statement[0].Action; test.deepEqual(actions('WriterDefaultPolicyDC585BCE'), [ 's3:DeleteObject*', 's3:PutObject*', 's3:Abort*' ]); @@ -1214,8 +1273,8 @@ export = { const bucket = new s3.Bucket(stack, 'b'); // WHEN - const statement = bucket.grantPublicAccess(); - statement.addCondition('IpAddress', { "aws:SourceIp": "54.240.143.0/24" }); + const result = bucket.grantPublicAccess(); + result.resourceStatement!.addCondition('IpAddress', { "aws:SourceIp": "54.240.143.0/24" }); // THEN expect(stack).to(haveResource('AWS::S3::BucketPolicy', { diff --git a/packages/@aws-cdk/aws-s3/test/test.notifications.ts b/packages/@aws-cdk/aws-s3/test/test.notifications.ts index 8009213fbf00e..5f44d8777b79e 100644 --- a/packages/@aws-cdk/aws-s3/test/test.notifications.ts +++ b/packages/@aws-cdk/aws-s3/test/test.notifications.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike, SynthUtils } from '@aws-cdk/assert'; import s3n = require('@aws-cdk/aws-s3-notifications'); import cdk = require('@aws-cdk/cdk'); import { Stack } from '@aws-cdk/cdk'; @@ -307,7 +307,7 @@ export = { bucket.onObjectCreated(dest); stack.node.prepareTree(); - test.deepEqual(stack._toCloudFormation().Resources.BucketNotifications8F2E257D, { + test.deepEqual(SynthUtils.toCloudFormation(stack).Resources.BucketNotifications8F2E257D, { Type: 'Custom::S3BucketNotifications', Properties: { ServiceToken: { 'Fn::GetAtt': [ 'BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691', 'Arn' ] }, diff --git a/packages/@aws-cdk/aws-sagemaker/package.json b/packages/@aws-cdk/aws-sagemaker/package.json index 3e2c0442931a9..e433e399ac782 100644 --- a/packages/@aws-cdk/aws-sagemaker/package.json +++ b/packages/@aws-cdk/aws-sagemaker/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sagemaker", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::SageMaker", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-sagemaker", + "module": "aws_cdk.aws_sagemaker" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-sagemaker" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -55,18 +60,18 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-sam/.gitignore b/packages/@aws-cdk/aws-sam/.gitignore new file mode 100644 index 0000000000000..648b05328eba1 --- /dev/null +++ b/packages/@aws-cdk/aws-sam/.gitignore @@ -0,0 +1,15 @@ +*.d.ts +*.generated.ts +*.js +*.js.map +.jsii +.LAST_BUILD +.LAST_PACKAGE +.nycrc +.nyc_output +coverage +dist +tsconfig.json +tslint.json + +*.snk \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sam/.npmignore b/packages/@aws-cdk/aws-sam/.npmignore new file mode 100644 index 0000000000000..aedb60356c270 --- /dev/null +++ b/packages/@aws-cdk/aws-sam/.npmignore @@ -0,0 +1,21 @@ +# The basics +*.ts +*.tgz +!*.d.ts +!*.js + +# Coverage +coverage +.nyc_output +.nycrc + +# Build gear +dist +.LAST_BUILD +.LAST_PACKAGE +.jsii + +*.snk + +# Include .jsii +!.jsii diff --git a/packages/@aws-cdk/aws-sam/LICENSE b/packages/@aws-cdk/aws-sam/LICENSE new file mode 100644 index 0000000000000..46c185646b439 --- /dev/null +++ b/packages/@aws-cdk/aws-sam/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-2019 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-sam/NOTICE b/packages/@aws-cdk/aws-sam/NOTICE new file mode 100644 index 0000000000000..8585168af8b7d --- /dev/null +++ b/packages/@aws-cdk/aws-sam/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-sam/README.md b/packages/@aws-cdk/aws-sam/README.md new file mode 100644 index 0000000000000..d67b26a50e3a8 --- /dev/null +++ b/packages/@aws-cdk/aws-sam/README.md @@ -0,0 +1,7 @@ +## AWS Serverless Application Model Construct Library + +```ts +const sam = require('@aws-cdk/aws-sam'); +``` + +Note: we recommended to use `@aws-cdk/aws-lambda`, `aws-cdk/aws-lambda-event-sources` and `@aws-cdk/aws-apigateway` packages to build 'serverless' applications with the CDK instead of the `AWS::Serverless` resources exposes by `@aws-cdk/aws-sam`. diff --git a/packages/@aws-cdk/aws-sam/lib/index.ts b/packages/@aws-cdk/aws-sam/lib/index.ts new file mode 100644 index 0000000000000..7431d9e9031b7 --- /dev/null +++ b/packages/@aws-cdk/aws-sam/lib/index.ts @@ -0,0 +1,2 @@ +// AWS::Serverless CloudFormation Resources: +export * from './sam.generated'; diff --git a/packages/@aws-cdk/aws-sam/package.json b/packages/@aws-cdk/aws-sam/package.json new file mode 100644 index 0000000000000..dbb63fa3f8f1b --- /dev/null +++ b/packages/@aws-cdk/aws-sam/package.json @@ -0,0 +1,77 @@ +{ + "name": "@aws-cdk/aws-sam", + "version": "0.28.0", + "description": "The CDK Construct Library for the AWS Serverless Application Model (SAM) resources", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "dotnet": { + "namespace": "Amazon.CDK.AWS.SAM", + "packageId": "Amazon.CDK.AWS.SAM", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk" + }, + "java": { + "package": "software.amazon.awscdk.services.sam", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "sam" + } + }, + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-sam", + "module": "aws_cdk.aws_sam" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-sam" + }, + "homepage": "https://github.com/awslabs/aws-cdk", + "scripts": { + "build": "cdk-build", + "integ": "cdk-integ", + "lint": "cdk-lint", + "package": "cdk-package", + "pkglint": "pkglint -f", + "test": "cdk-test", + "watch": "cdk-watch", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" + }, + "cdk-build": { + "cloudformation": "AWS::Serverless" + }, + "keywords": [ + "aws", + "cdk", + "constructs", + "aws-sam" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" + }, + "dependencies": { + "@aws-cdk/cdk": "^0.28.0" + }, + "peerDependencies": { + "@aws-cdk/cdk": "^0.28.0" + }, + "engines": { + "node": ">= 8.10.0" + } +} diff --git a/packages/@aws-cdk/aws-serverless/test/test.serverless.ts b/packages/@aws-cdk/aws-sam/test/test.sam.ts similarity index 100% rename from packages/@aws-cdk/aws-serverless/test/test.serverless.ts rename to packages/@aws-cdk/aws-sam/test/test.sam.ts diff --git a/packages/@aws-cdk/aws-sdb/package.json b/packages/@aws-cdk/aws-sdb/package.json index f53dd68b0fdcb..7ed3e20e65311 100644 --- a/packages/@aws-cdk/aws-sdb/package.json +++ b/packages/@aws-cdk/aws-sdb/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sdb", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::SDB", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-sdb", + "module": "aws_cdk.aws_sdb" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-sdb" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-secretsmanager/README.md b/packages/@aws-cdk/aws-secretsmanager/README.md index a5c71b8a2b6f1..3acc6c5c1f8a4 100644 --- a/packages/@aws-cdk/aws-secretsmanager/README.md +++ b/packages/@aws-cdk/aws-secretsmanager/README.md @@ -28,4 +28,19 @@ const secret = Secret.import(scope, 'ImportedSecret', { ``` SecretsManager secret values can only be used in select set of properties. For the -list of properties, see [the CloudFormation Dynamic References documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.htm). \ No newline at end of file +list of properties, see [the CloudFormation Dynamic References documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.htm). + +### Rotating a Secret +A rotation schedule can be added to a Secret: +```ts +const fn = new lambda.Function(...); +const secret = new secretsManager.Secret(this, 'Secret'); + +secret.addRotationSchedule('RotationSchedule', { + rotationLambda: fn, + automaticallyAfterDays: 15 +}); +``` +See [Overview of the Lambda Rotation Function](https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html) on how to implement a Lambda Rotation Function. + +For RDS credentials rotation, see [aws-rds](https://github.com/awslabs/aws-cdk/blob/master/packages/%40aws-cdk/aws-rds/README.md). diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/index.ts b/packages/@aws-cdk/aws-secretsmanager/lib/index.ts index 0f425ff7d53ed..79568e657c24c 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/index.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/index.ts @@ -1,5 +1,5 @@ export * from './secret'; -export * from './secret-string'; +export * from './rotation-schedule'; // AWS::SecretsManager CloudFormation Resources: export * from './secretsmanager.generated'; diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts b/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts new file mode 100644 index 0000000000000..99389ce7233fa --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/lib/rotation-schedule.ts @@ -0,0 +1,49 @@ +import lambda = require('@aws-cdk/aws-lambda'); +import cdk = require('@aws-cdk/cdk'); +import { ISecret } from './secret'; +import { CfnRotationSchedule } from './secretsmanager.generated'; + +/** + * Options to add a rotation schedule to a secret. + */ +export interface RotationScheduleOptions { + /** + * THe Lambda function that can rotate the secret. + */ + readonly rotationLambda: lambda.IFunction; + + /** + * Specifies the number of days after the previous rotation before + * Secrets Manager triggers the next automatic rotation. + * + * @default 30 + */ + readonly automaticallyAfterDays?: number; +} + +/** + * Construction properties for a RotationSchedule. + */ +export interface RotationScheduleProps extends RotationScheduleOptions { + /** + * The secret to rotate. + */ + readonly secret: ISecret; +} + +/** + * A rotation schedule. + */ +export class RotationSchedule extends cdk.Construct { + constructor(scope: cdk.Construct, id: string, props: RotationScheduleProps) { + super(scope, id); + + new CfnRotationSchedule(this, 'Resource', { + secretId: props.secret.secretArn, + rotationLambdaArn: props.rotationLambda.functionArn, + rotationRules: { + automaticallyAfterDays: props.automaticallyAfterDays || 30 + } + }); + } +} diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret-string.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret-string.ts deleted file mode 100644 index 1ece79d6c3bc2..0000000000000 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret-string.ts +++ /dev/null @@ -1,69 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); - -/** - * Properties for a SecretString - */ -export interface SecretStringProps { - /** - * Unique identifier or ARN of the secret - */ - secretId: string; - - /** - * Specifies the secret version that you want to retrieve by the staging label attached to the version. - * - * Can specify at most one of versionId and versionStage. - * - * @default AWSCURRENT - */ - versionStage?: string; - - /** - * Specifies the unique identifier of the version of the secret that you want to use in stack operations. - * - * Can specify at most one of versionId and versionStage. - * - * @default AWSCURRENT - */ - versionId?: string; -} - -/** - * References a secret string in Secrets Manager - * - * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-secretsmanager-secret.html - */ -export class SecretString extends cdk.DynamicReference { - constructor(scope: cdk.Construct, id: string, private readonly props: SecretStringProps) { - super(scope, id, { - service: cdk.DynamicReferenceService.SecretsManager, - referenceKey: '', - }); - } - - /** - * Return the full value of the secret - */ - public get stringValue(): string { - return this.resolveStringForJsonKey(''); - } - - /** - * Interpret the secret as a JSON object and return a field's value from it - */ - public jsonFieldValue(key: string) { - return this.resolveStringForJsonKey(key); - } - - private resolveStringForJsonKey(jsonKey: string) { - const parts = [ - this.props.secretId, - 'SecretString', - jsonKey, - this.props.versionStage || '', - this.props.versionId || '' - ]; - - return this.makeResolveValue(cdk.DynamicReferenceService.SecretsManager, parts.join(':')); - } -} diff --git a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts index 142592b8a6a29..4c7d14021d7a9 100644 --- a/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/lib/secret.ts @@ -1,13 +1,13 @@ import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); -import cdk = require('@aws-cdk/cdk'); -import { SecretString } from './secret-string'; +import { Construct, IConstruct, SecretValue } from '@aws-cdk/cdk'; +import { RotationSchedule, RotationScheduleOptions } from './rotation-schedule'; import secretsmanager = require('./secretsmanager.generated'); /** * A secret in AWS Secrets Manager. */ -export interface ISecret extends cdk.IConstruct { +export interface ISecret extends IConstruct { /** * The customer-managed encryption key that is used to encrypt this secret, if any. When not specified, the default * KMS key for the account and region is being used. @@ -20,21 +20,14 @@ export interface ISecret extends cdk.IConstruct { readonly secretArn: string; /** - * Returns a SecretString corresponding to this secret. - * - * SecretString represents the value of the Secret. - */ - readonly secretString: SecretString; - - /** - * Retrieve the value of the Secret, as a string. + * Retrieve the value of the stored secret as a `SecretValue`. */ - readonly stringValue: string; + readonly secretValue: SecretValue; /** - * Interpret the secret as a JSON object and return a field's value from it + * Interpret the secret as a JSON object and return a field's value from it as a `SecretValue`. */ - jsonFieldValue(key: string): string; + secretJsonValue(key: string): SecretValue; /** * Exports this secret. @@ -50,7 +43,12 @@ export interface ISecret extends cdk.IConstruct { * @param versionStages the version stages the grant is limited to. If not specified, no restriction on the version * stages is applied. */ - grantRead(grantee: iam.IPrincipal, versionStages?: string[]): void; + grantRead(grantee: iam.IGrantable, versionStages?: string[]): iam.Grant; + + /** + * Adds a rotation schedule to the secret. + */ + addRotationSchedule(id: string, options: RotationScheduleOptions): RotationSchedule; } /** @@ -60,14 +58,14 @@ export interface SecretProps { /** * An optional, human-friendly description of the secret. */ - description?: string; + readonly description?: string; /** * The customer-managed encryption key to use for encrypting the secret value. * * @default a default KMS key for the account and region is used. */ - encryptionKey?: kms.IEncryptionKey; + readonly encryptionKey?: kms.IEncryptionKey; /** * Configuration for how to generate a secret value. @@ -75,15 +73,15 @@ export interface SecretProps { * @default 32 characters with upper-case letters, lower-case letters, punctuation and numbers (at least one from each * category), per the default values of ``SecretStringGenerator``. */ - generateSecretString?: SecretStringGenerator; + readonly generateSecretString?: SecretStringGenerator; /** * A name for the secret. Note that deleting secrets from SecretsManager does not happen immediately, but after a 7 to * 30 days blackout period. During that period, it is not possible to create another secret that shares the same name. * - * @default a name is generated by CloudFormation. + * @default - a name is generated by CloudFormation. */ - name?: string; + readonly name?: string; } /** @@ -93,62 +91,61 @@ export interface SecretImportProps { /** * The encryption key that is used to encrypt the secret, unless the default SecretsManager key is used. */ - encryptionKey?: kms.IEncryptionKey; + readonly encryptionKey?: kms.IEncryptionKey; /** * The ARN of the secret in SecretsManager. */ - secretArn: string; + readonly secretArn: string; } /** * The common behavior of Secrets. Users should not use this class directly, and instead use ``Secret``. */ -export abstract class SecretBase extends cdk.Construct implements ISecret { +export abstract class SecretBase extends Construct implements ISecret { public abstract readonly encryptionKey?: kms.IEncryptionKey; public abstract readonly secretArn: string; - private _secretString?: SecretString; - public abstract export(): SecretImportProps; - public grantRead(grantee: iam.IPrincipal, versionStages?: string[]): void { + public grantRead(grantee: iam.IGrantable, versionStages?: string[]): iam.Grant { // @see https://docs.aws.amazon.com/fr_fr/secretsmanager/latest/userguide/auth-and-access_identity-based-policies.html - const statement = new iam.PolicyStatement() - .allow() - .addAction('secretsmanager:GetSecretValue') - .addResource(this.secretArn); - if (versionStages != null) { - statement.addCondition('ForAnyValue:StringEquals', { + + const result = iam.Grant.addToPrincipal({ + grantee, + actions: ['secretsmanager:GetSecretValue'], + resourceArns: [this.secretArn], + scope: this + }); + if (versionStages != null && result.principalStatement) { + result.principalStatement.addCondition('ForAnyValue:StringEquals', { 'secretsmanager:VersionStage': versionStages }); } - grantee.addToPolicy(statement); if (this.encryptionKey) { // @see https://docs.aws.amazon.com/fr_fr/kms/latest/developerguide/services-secrets-manager.html - this.encryptionKey.addToResourcePolicy(new iam.PolicyStatement() - .allow() - .addPrincipal(grantee.principal) - .addAction('kms:Decrypt') - .addAllResources() - .addCondition('StringEquals', { - 'kms:ViaService': `secretsmanager.${this.node.stack.region}.amazonaws.com` - })); + this.encryptionKey.grantDecrypt( + new kms.ViaServicePrincipal(`secretsmanager.${this.node.stack.region}.amazonaws.com`, grantee.grantPrincipal) + ); } + + return result; } - public get secretString() { - this._secretString = this._secretString || new SecretString(this, 'SecretString', { secretId: this.secretArn }); - return this._secretString; + public get secretValue() { + return this.secretJsonValue(''); } - public get stringValue() { - return this.secretString.stringValue; + public secretJsonValue(jsonField: string) { + return SecretValue.secretsManager(this.secretArn, { jsonField }); } - public jsonFieldValue(key: string): string { - return this.secretString.jsonFieldValue(key); + public addRotationSchedule(id: string, options: RotationScheduleOptions): RotationSchedule { + return new RotationSchedule(this, id, { + secret: this, + ...options + }); } } @@ -163,16 +160,22 @@ export class Secret extends SecretBase { * @param id the ID of the imported Secret in the construct tree. * @param props the attributes of the imported secret. */ - public static import(scope: cdk.Construct, id: string, props: SecretImportProps): ISecret { + public static import(scope: Construct, id: string, props: SecretImportProps): ISecret { return new ImportedSecret(scope, id, props); } public readonly encryptionKey?: kms.IEncryptionKey; public readonly secretArn: string; - constructor(scope: cdk.Construct, id: string, props: SecretProps = {}) { + constructor(scope: Construct, id: string, props: SecretProps = {}) { super(scope, id); + if (props.generateSecretString && + (props.generateSecretString.secretStringTemplate || props.generateSecretString.generateStringKey) && + !(props.generateSecretString.secretStringTemplate && props.generateSecretString.generateStringKey)) { + throw new Error('`secretStringTemplate` and `generateStringKey` must be specified together.'); + } + const resource = new secretsmanager.CfnSecret(this, 'Resource', { description: props.description, kmsKeyId: props.encryptionKey && props.encryptionKey.keyArn, @@ -184,6 +187,108 @@ export class Secret extends SecretBase { this.secretArn = resource.secretArn; } + /** + * Adds a target attachment to the secret. + * + * @returns an AttachedSecret + */ + public addTargetAttachment(id: string, options: AttachedSecretOptions): AttachedSecret { + return new AttachedSecret(this, id, { + secret: this, + ...options + }); + } + + public export(): SecretImportProps { + return { + encryptionKey: this.encryptionKey, + secretArn: this.secretArn, + }; + } +} + +/** + * A secret attachment target. + */ +export interface ISecretAttachmentTarget { + /** + * Renders the target specifications. + */ + asSecretAttachmentTarget(): SecretAttachmentTargetProps; +} + +/** + * The type of service or database that's being associated with the secret. + */ +export enum AttachmentTargetType { + /** + * A database instance + */ + Instance = 'AWS::RDS::DBInstance', + + /** + * A database cluster + */ + Cluster = 'AWS::RDS::DBCluster' +} + +/** + * Attachment target specifications. + */ +export interface SecretAttachmentTargetProps { + /** + * The id of the target to attach the secret to. + */ + readonly targetId: string; + + /** + * The type of the target to attach the secret to. + */ + readonly targetType: AttachmentTargetType; +} + +/** + * Options to add a secret attachment to a secret. + */ +export interface AttachedSecretOptions { + /** + * The target to attach the secret to. + */ + readonly target: ISecretAttachmentTarget; +} + +/** + * Construction properties for an AttachedSecret. + */ +export interface AttachedSecretProps extends AttachedSecretOptions { + /** + * The secret to attach to the target. + */ + readonly secret: ISecret; +} + +/** + * An attached secret. + */ +export class AttachedSecret extends SecretBase implements ISecret { + public readonly encryptionKey?: kms.IEncryptionKey; + public readonly secretArn: string; + + constructor(scope: Construct, id: string, props: AttachedSecretProps) { + super(scope, id); + + const attachment = new secretsmanager.CfnSecretTargetAttachment(this, 'Resource', { + secretId: props.secret.secretArn, + targetId: props.target.asSecretAttachmentTarget().targetId, + targetType: props.target.asSecretAttachmentTarget().targetType + }); + + this.encryptionKey = props.secret.encryptionKey; + + // This allows to reference the secret after attachment (dependency). + this.secretArn = attachment.secretTargetAttachmentSecretArn; + } + public export(): SecretImportProps { return { encryptionKey: this.encryptionKey, @@ -201,21 +306,21 @@ export interface SecretStringGenerator { * * @default false */ - excludeUppercase?: boolean; + readonly excludeUppercase?: boolean; /** * Specifies whether the generated password must include at least one of every allowed character type. * * @default true */ - requireEachIncludedType?: boolean; + readonly requireEachIncludedType?: boolean; /** * Specifies that the generated password can include the space character. * * @default false */ - includeSpace?: boolean; + readonly includeSpace?: boolean; /** * A string that includes characters that shouldn't be included in the generated password. The string can be a minimum @@ -223,60 +328,57 @@ export interface SecretStringGenerator { * * @default no exclusions */ - excludeCharacters?: string; + readonly excludeCharacters?: string; /** * The desired length of the generated password. * * @default 32 */ - passwordLength?: number; + readonly passwordLength?: number; /** * Specifies that the generated password shouldn't include punctuation characters. * * @default false */ - excludePunctuation?: boolean; + readonly excludePunctuation?: boolean; /** * Specifies that the generated password shouldn't include lowercase letters. * * @default false */ - excludeLowercase?: boolean; + readonly excludeLowercase?: boolean; /** * Specifies that the generated password shouldn't include digits. * * @default false */ - excludeNumbers?: boolean; -} + readonly excludeNumbers?: boolean; -/** - * Configuration to generate secrets such as passwords automatically, and include them in a JSON object template. - */ -export interface TemplatedSecretStringGenerator extends SecretStringGenerator { /** - * The JSON key name that's used to add the generated password to the JSON structure specified by the - * ``secretStringTemplate`` parameter. + * A properly structured JSON string that the generated password can be added to. The ``generateStringKey`` is + * combined with the generated random string and inserted into the JSON structure that's specified by this parameter. + * The merged JSON string is returned as the completed SecretString of the secret. If you specify ``secretStringTemplate`` + * then ``generateStringKey`` must be also be specified. */ - generateStringKey: string; + readonly secretStringTemplate?: string; /** - * A properly structured JSON string that the generated password can be added to. The ``generateStringKey`` is - * combined with the generated random string and inserted into the JSON structure that's specified by this parameter. - * The merged JSON string is returned as the completed SecretString of the secret. + * The JSON key name that's used to add the generated password to the JSON structure specified by the + * ``secretStringTemplate`` parameter. If you specify ``generateStringKey`` then ``secretStringTemplate`` + * must be also be specified. */ - secretStringTemplate: string; + readonly generateStringKey?: string; } class ImportedSecret extends SecretBase { public readonly encryptionKey?: kms.IEncryptionKey; public readonly secretArn: string; - constructor(scope: cdk.Construct, id: string, private readonly props: SecretImportProps) { + constructor(scope: Construct, id: string, private readonly props: SecretImportProps) { super(scope, id); this.encryptionKey = props.encryptionKey; diff --git a/packages/@aws-cdk/aws-secretsmanager/package.json b/packages/@aws-cdk/aws-secretsmanager/package.json index b07fd8924e93d..4608b3c54520c 100644 --- a/packages/@aws-cdk/aws-secretsmanager/package.json +++ b/packages/@aws-cdk/aws-secretsmanager/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-secretsmanager", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::SecretsManager", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "artifactId": "secretsmanager" } }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-secretsmanager", + "module": "aws_cdk.aws_secretsmanager" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-secretsmanager" }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { @@ -55,23 +60,27 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-secretsmanager/test/example.app-with-secret.lit.ts b/packages/@aws-cdk/aws-secretsmanager/test/example.app-with-secret.lit.ts index bcaca6be74842..f5b15b4a6b2b5 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/example.app-with-secret.lit.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/example.app-with-secret.lit.ts @@ -7,14 +7,14 @@ class ExampleStack extends cdk.Stack { super(scope, id); /// !show - const loginSecret = new secretsmanager.SecretString(this, 'Secret', { - secretId: 'SomeLogin' + const loginSecret = secretsmanager.Secret.import(this, 'Secret', { + secretArn: 'SomeLogin' }); new iam.User(this, 'User', { // Get the 'password' field from the secret that looks like // { "username": "XXXX", "password": "YYYY" } - password: loginSecret.jsonFieldValue('password') + password: loginSecret.secretJsonValue('password') }); /// !hide diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json index 2eb92c11cc1b1..b09235155139e 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.expected.json @@ -79,6 +79,46 @@ } } } + }, + "TemplatedSecret3D98B577": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": { + "GenerateStringKey": "password", + "SecretStringTemplate": "{\"username\":\"user\"}" + } + } + }, + "OtherUser6093621C": { + "Type": "AWS::IAM::User", + "Properties": { + "LoginProfile": { + "Password": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "TemplatedSecret3D98B577" + }, + ":SecretString:password::}}" + ] + ] + } + }, + "UserName": { + "Fn::Join": [ + "", + [ + "{{resolve:secretsmanager:", + { + "Ref": "TemplatedSecret3D98B577" + }, + ":SecretString:username::}}" + ] + ] + } + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts index 4a90c8f6ef424..4553d0042c87f 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/integ.secret.lit.ts @@ -9,11 +9,25 @@ class SecretsManagerStack extends cdk.Stack { const role = new iam.Role(this, 'TestRole', { assumedBy: new iam.AccountRootPrincipal() }); /// !show + // Default secret const secret = new secretsManager.Secret(this, 'Secret'); secret.grantRead(role); new iam.User(this, 'User', { - password: secret.stringValue + password: secret.secretValue + }); + + // Templated secret + const templatedSecret = new secretsManager.Secret(this, 'TemplatedSecret', { + generateSecretString: { + secretStringTemplate: JSON.stringify({ username: 'user' }), + generateStringKey: 'password' + } + }); + + new iam.User(this, 'OtherUser', { + userName: templatedSecret.secretJsonValue('username').toString(), + password: templatedSecret.secretJsonValue('password') }); /// !hide } diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.rotation-schedule.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.rotation-schedule.ts new file mode 100644 index 0000000000000..c009d60e57722 --- /dev/null +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.rotation-schedule.ts @@ -0,0 +1,42 @@ +import { expect, haveResource } from '@aws-cdk/assert'; +import lambda = require('@aws-cdk/aws-lambda'); +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import secretsmanager = require('../lib'); + +export = { + 'create a rotation schedule'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const secret = new secretsmanager.Secret(stack, 'Secret'); + const rotationLambda = new lambda.Function(stack, 'Lambda', { + runtime: lambda.Runtime.NodeJS810, + code: lambda.Code.inline('export.handler = event => event;'), + handler: 'index.handler' + }); + + // WHEN + new secretsmanager.RotationSchedule(stack, 'RotationSchedule', { + secret, + rotationLambda + }); + + // THEN + expect(stack).to(haveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'SecretA720EF05' + }, + RotationLambdaARN: { + 'Fn::GetAtt': [ + 'LambdaD247545B', + 'Arn' + ] + }, + RotationRules: { + AutomaticallyAfterDays: 30 + } + })); + + test.done(); + }, +}; diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-string.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret-string.ts deleted file mode 100644 index 51f2677b41a20..0000000000000 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret-string.ts +++ /dev/null @@ -1,35 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { Test } from 'nodeunit'; -import secretsmanager = require('../lib'); - -export = { - 'can reference Secrets Manager Value'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const ref = new secretsmanager.SecretString(stack, 'Ref', { - secretId: 'SomeSecret', - }); - - // THEN - test.equal(ref.node.resolve(ref.stringValue), '{{resolve:secretsmanager:SomeSecret:SecretString:::}}'); - - test.done(); - }, - - 'can reference jsonkey in Secrets Manager Value'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const ref = new secretsmanager.SecretString(stack, 'Ref', { - secretId: 'SomeSecret', - }); - - // THEN - test.equal(ref.node.resolve(ref.jsonFieldValue('subkey')), '{{resolve:secretsmanager:SomeSecret:SecretString:subkey::}}'); - - test.done(); - }, -}; diff --git a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts index b45e670fec303..91f361f756d53 100644 --- a/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts +++ b/packages/@aws-cdk/aws-secretsmanager/test/test.secret.ts @@ -1,7 +1,9 @@ import { expect, haveResource } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); +import lambda = require('@aws-cdk/aws-lambda'); import cdk = require('@aws-cdk/cdk'); +import { SecretValue, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import secretsmanager = require('../lib'); @@ -21,6 +23,52 @@ export = { test.done(); }, + 'secret with generate secret string options'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + excludeUppercase: true, + passwordLength: 20 + } + }); + + // THEN + expect(stack).to(haveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: { + ExcludeUppercase: true, + PasswordLength: 20 + } + })); + + test.done(); + }, + + 'templated secret string'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + secretStringTemplate: JSON.stringify({ username: 'username' }), + generateStringKey: 'password' + } + }); + + // THEN + expect(stack).to(haveResource('AWS::SecretsManager::Secret', { + GenerateSecretString: { + SecretStringTemplate: '{"username":"username"}', + GenerateStringKey: 'password' + } + })); + + test.done(); + }, + 'grantRead'(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -214,7 +262,7 @@ export = { test.done(); }, - 'toSecretString'(test: Test) { + 'secretValue'(test: Test) { // GIVEN const stack = new cdk.Stack(); const key = new kms.EncryptionKey(stack, 'KMS'); @@ -224,7 +272,7 @@ export = { new cdk.CfnResource(stack, 'FakeResource', { type: 'CDK::Phony::Resource', properties: { - value: secret.stringValue + value: secret.secretValue } }); @@ -255,6 +303,107 @@ export = { // THEN test.equals(secret.secretArn, secretArn); test.same(secret.encryptionKey, encryptionKey); + test.deepEqual(stack.node.resolve(secret.secretValue), '{{resolve:secretsmanager:arn::of::a::secret:SecretString:::}}'); + test.deepEqual(stack.node.resolve(secret.secretJsonValue('password')), '{{resolve:secretsmanager:arn::of::a::secret:SecretString:password::}}'); + test.done(); + }, + + 'attached secret'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const secret = new secretsmanager.Secret(stack, 'Secret'); + const target: secretsmanager.ISecretAttachmentTarget = { + asSecretAttachmentTarget: () => ({ + targetId: 'instance', + targetType: secretsmanager.AttachmentTargetType.Instance + }) + }; + + // WHEN + secret.addTargetAttachment('AttachedSecret', { target }); + + // THEN + expect(stack).to(haveResource('AWS::SecretsManager::SecretTargetAttachment', { + SecretId: { + Ref: 'SecretA720EF05' + }, + TargetId: 'instance', + TargetType: 'AWS::RDS::DBInstance' + })); + + test.done(); + }, + + 'add a rotation schedule to an attached secret'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const secret = new secretsmanager.Secret(stack, 'Secret'); + const target: secretsmanager.ISecretAttachmentTarget = { + asSecretAttachmentTarget: () => ({ + targetId: 'cluster', + targetType: secretsmanager.AttachmentTargetType.Cluster + }) + }; + const attachedSecret = secret.addTargetAttachment('AttachedSecret', { target }); + const rotationLambda = new lambda.Function(stack, 'Lambda', { + runtime: lambda.Runtime.NodeJS810, + code: lambda.Code.inline('export.handler = event => event;'), + handler: 'index.handler' + }); + + // WHEN + attachedSecret.addRotationSchedule('RotationSchedule', { + rotationLambda + }); + + // THEN + expect(stack).to(haveResource('AWS::SecretsManager::RotationSchedule', { + SecretId: { + Ref: 'SecretAttachedSecret94145316' // The secret returned by the attachment, not the secret itself. + } + })); + + test.done(); + }, + + 'throws when specifying secretStringTemplate but not generateStringKey'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // THEN + test.throws(() => new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + secretStringTemplate: JSON.stringify({ username: 'username' }) + } + }), /`secretStringTemplate`.+`generateStringKey`/); + + test.done(); + }, + + 'throws when specifying generateStringKey but not secretStringTemplate'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // THEN + test.throws(() => new secretsmanager.Secret(stack, 'Secret', { + generateSecretString: { + generateStringKey: 'password' + } + }), /`secretStringTemplate`.+`generateStringKey`/); + + test.done(); + }, + + 'equivalence of SecretValue and Secret.import'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const imported = secretsmanager.Secret.import(stack, 'Imported', { secretArn: 'my-secret-arn' }).secretJsonValue('password'); + const value = SecretValue.secretsManager('my-secret-arn', { jsonField: 'password' }); + + // THEN + test.deepEqual(stack.node.resolve(imported), stack.node.resolve(value)); test.done(); } }; diff --git a/packages/@aws-cdk/aws-serverless/.gitignore b/packages/@aws-cdk/aws-serverless/.gitignore index 648b05328eba1..f50d54a345a3f 100644 --- a/packages/@aws-cdk/aws-serverless/.gitignore +++ b/packages/@aws-cdk/aws-serverless/.gitignore @@ -1,15 +1,4 @@ -*.d.ts -*.generated.ts -*.js -*.js.map .jsii -.LAST_BUILD -.LAST_PACKAGE -.nycrc -.nyc_output -coverage -dist tsconfig.json -tslint.json - -*.snk \ No newline at end of file +*.d.ts +*.js diff --git a/packages/@aws-cdk/aws-serverless/.npmignore b/packages/@aws-cdk/aws-serverless/.npmignore index aedb60356c270..79bf59f3834e3 100644 --- a/packages/@aws-cdk/aws-serverless/.npmignore +++ b/packages/@aws-cdk/aws-serverless/.npmignore @@ -1,21 +1,9 @@ -# The basics *.ts -*.tgz !*.d.ts -!*.js -# Coverage -coverage -.nyc_output -.nycrc -# Build gear +# Exclude jsii outdir dist -.LAST_BUILD -.LAST_PACKAGE -.jsii - -*.snk # Include .jsii !.jsii diff --git a/packages/@aws-cdk/aws-serverless/README.md b/packages/@aws-cdk/aws-serverless/README.md index 86d576684fc5e..42ffd142e7470 100644 --- a/packages/@aws-cdk/aws-serverless/README.md +++ b/packages/@aws-cdk/aws-serverless/README.md @@ -1,5 +1,4 @@ ## AWS Serverless Construct Library -```ts -const serverless = require('@aws-cdk/aws-serverless'); -``` +The last version published for this module was `0.26.0`. Since then, this module +was renamed to `@aws-cdk/aws-sam` - please upgrade your dependencies accordingly. diff --git a/packages/@aws-cdk/aws-serverless/lib/index.ts b/packages/@aws-cdk/aws-serverless/lib/index.ts index eb26f203a6065..08e253902ed40 100644 --- a/packages/@aws-cdk/aws-serverless/lib/index.ts +++ b/packages/@aws-cdk/aws-serverless/lib/index.ts @@ -1,2 +1,4 @@ -// AWS::Serverless CloudFormation Resources: -export * from './serverless.generated'; +export * from '@aws-cdk/aws-sam'; + +// tslint:disable-next-line: no-console +console.error('WARNING: The @aws-cdk/serverless package was renamed to @aws-cdk/sam. Please update your dependencies!'); diff --git a/packages/@aws-cdk/aws-serverless/lib/serverless.generated.ts b/packages/@aws-cdk/aws-serverless/lib/serverless.generated.ts new file mode 100644 index 0000000000000..d45bee28444ee --- /dev/null +++ b/packages/@aws-cdk/aws-serverless/lib/serverless.generated.ts @@ -0,0 +1,2035 @@ +// Copyright 2012-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Generated from the AWS CloudFormation Resource Specification +// See: docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html +// @cfn2ts:meta@ {"generated":"2019-03-21T09:12:23.615Z","fingerprint":"UJ42fezBazlHPOkm0pNitOMM9peubfGEd2/XcTwSlsI="} + +// tslint:disable:max-line-length | This is generated code - line lengths are difficult to control + +import cdk = require('@aws-cdk/cdk'); + +/** + * Properties for defining a `AWS::Serverless::Api` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ +export interface CfnApiProps { + /** + * `AWS::Serverless::Api.StageName` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + stageName: string; + /** + * `AWS::Serverless::Api.Auth` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + auth?: CfnApi.AuthProperty | cdk.Token; + /** + * `AWS::Serverless::Api.BinaryMediaTypes` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + binaryMediaTypes?: string[]; + /** + * `AWS::Serverless::Api.CacheClusterEnabled` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + cacheClusterEnabled?: boolean | cdk.Token; + /** + * `AWS::Serverless::Api.CacheClusterSize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + cacheClusterSize?: string; + /** + * `AWS::Serverless::Api.Cors` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + cors?: string; + /** + * `AWS::Serverless::Api.DefinitionBody` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + definitionBody?: object | cdk.Token; + /** + * `AWS::Serverless::Api.DefinitionUri` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + definitionUri?: CfnApi.S3LocationProperty | string | cdk.Token; + /** + * `AWS::Serverless::Api.EndpointConfiguration` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + endpointConfiguration?: string; + /** + * `AWS::Serverless::Api.MethodSettings` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + methodSettings?: object | cdk.Token; + /** + * `AWS::Serverless::Api.Name` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + name?: string; + /** + * `AWS::Serverless::Api.Variables` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ + variables?: { [key: string]: (string) } | cdk.Token; +} + +/** + * Determine whether the given properties match those of a `CfnApiProps` + * + * @param properties - the TypeScript properties of a `CfnApiProps` + * + * @returns the result of the validation. + */ +function CfnApiPropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('auth', CfnApi_AuthPropertyValidator)(properties.auth)); + errors.collect(cdk.propertyValidator('binaryMediaTypes', cdk.listValidator(cdk.validateString))(properties.binaryMediaTypes)); + errors.collect(cdk.propertyValidator('cacheClusterEnabled', cdk.validateBoolean)(properties.cacheClusterEnabled)); + errors.collect(cdk.propertyValidator('cacheClusterSize', cdk.validateString)(properties.cacheClusterSize)); + errors.collect(cdk.propertyValidator('cors', cdk.validateString)(properties.cors)); + errors.collect(cdk.propertyValidator('definitionBody', cdk.validateObject)(properties.definitionBody)); + errors.collect(cdk.propertyValidator('definitionUri', cdk.unionValidator(CfnApi_S3LocationPropertyValidator, cdk.validateString))(properties.definitionUri)); + errors.collect(cdk.propertyValidator('endpointConfiguration', cdk.validateString)(properties.endpointConfiguration)); + errors.collect(cdk.propertyValidator('methodSettings', cdk.validateObject)(properties.methodSettings)); + errors.collect(cdk.propertyValidator('name', cdk.validateString)(properties.name)); + errors.collect(cdk.propertyValidator('stageName', cdk.requiredValidator)(properties.stageName)); + errors.collect(cdk.propertyValidator('stageName', cdk.validateString)(properties.stageName)); + errors.collect(cdk.propertyValidator('variables', cdk.hashValidator(cdk.validateString))(properties.variables)); + return errors.wrap('supplied properties not correct for "CfnApiProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Api` resource + * + * @param properties - the TypeScript properties of a `CfnApiProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Api` resource. + */ +// @ts-ignore TS6133 +function cfnApiPropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApiPropsValidator(properties).assertSuccess(); + return { + StageName: cdk.stringToCloudFormation(properties.stageName), + Auth: cfnApiAuthPropertyToCloudFormation(properties.auth), + BinaryMediaTypes: cdk.listMapper(cdk.stringToCloudFormation)(properties.binaryMediaTypes), + CacheClusterEnabled: cdk.booleanToCloudFormation(properties.cacheClusterEnabled), + CacheClusterSize: cdk.stringToCloudFormation(properties.cacheClusterSize), + Cors: cdk.stringToCloudFormation(properties.cors), + DefinitionBody: cdk.objectToCloudFormation(properties.definitionBody), + DefinitionUri: cdk.unionMapper([CfnApi_S3LocationPropertyValidator, cdk.validateString], [cfnApiS3LocationPropertyToCloudFormation, cdk.stringToCloudFormation])(properties.definitionUri), + EndpointConfiguration: cdk.stringToCloudFormation(properties.endpointConfiguration), + MethodSettings: cdk.objectToCloudFormation(properties.methodSettings), + Name: cdk.stringToCloudFormation(properties.name), + Variables: cdk.hashMapper(cdk.stringToCloudFormation)(properties.variables), + }; +} + +/** + * A CloudFormation `AWS::Serverless::Api` + * + * @cloudformationResource AWS::Serverless::Api + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi + */ +export class CfnApi extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::Api"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly apiName: string; + + /** + * Create a new `AWS::Serverless::Api`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props: CfnApiProps) { + super(scope, id, { type: CfnApi.resourceTypeName, properties: props }); + cdk.requireProperty(props, 'stageName', this); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnApi.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnApi.requiredTransform)} transform is required when using CfnApi, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnApi.requiredTransform; + this.apiName = this.ref.toString(); + } + + public get propertyOverrides(): CfnApiProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnApiPropsToCloudFormation(this.node.resolve(properties)); + } +} + +export namespace CfnApi { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api-auth-object + */ + export interface AuthProperty { + /** + * `CfnApi.AuthProperty.Authorizers` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api-auth-object + */ + authorizers?: object | cdk.Token; + /** + * `CfnApi.AuthProperty.DefaultAuthorizer` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api-auth-object + */ + defaultAuthorizer?: string; + } +} + +/** + * Determine whether the given properties match those of a `AuthProperty` + * + * @param properties - the TypeScript properties of a `AuthProperty` + * + * @returns the result of the validation. + */ +function CfnApi_AuthPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('authorizers', cdk.validateObject)(properties.authorizers)); + errors.collect(cdk.propertyValidator('defaultAuthorizer', cdk.validateString)(properties.defaultAuthorizer)); + return errors.wrap('supplied properties not correct for "AuthProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Api.Auth` resource + * + * @param properties - the TypeScript properties of a `AuthProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Api.Auth` resource. + */ +// @ts-ignore TS6133 +function cfnApiAuthPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApi_AuthPropertyValidator(properties).assertSuccess(); + return { + Authorizers: cdk.objectToCloudFormation(properties.authorizers), + DefaultAuthorizer: cdk.stringToCloudFormation(properties.defaultAuthorizer), + }; +} + +export namespace CfnApi { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3-location-object + */ + export interface S3LocationProperty { + /** + * `CfnApi.S3LocationProperty.Bucket` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + bucket: string; + /** + * `CfnApi.S3LocationProperty.Key` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + key: string; + /** + * `CfnApi.S3LocationProperty.Version` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + version: number | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `S3LocationProperty` + * + * @param properties - the TypeScript properties of a `S3LocationProperty` + * + * @returns the result of the validation. + */ +function CfnApi_S3LocationPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('bucket', cdk.requiredValidator)(properties.bucket)); + errors.collect(cdk.propertyValidator('bucket', cdk.validateString)(properties.bucket)); + errors.collect(cdk.propertyValidator('key', cdk.requiredValidator)(properties.key)); + errors.collect(cdk.propertyValidator('key', cdk.validateString)(properties.key)); + errors.collect(cdk.propertyValidator('version', cdk.requiredValidator)(properties.version)); + errors.collect(cdk.propertyValidator('version', cdk.validateNumber)(properties.version)); + return errors.wrap('supplied properties not correct for "S3LocationProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Api.S3Location` resource + * + * @param properties - the TypeScript properties of a `S3LocationProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Api.S3Location` resource. + */ +// @ts-ignore TS6133 +function cfnApiS3LocationPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApi_S3LocationPropertyValidator(properties).assertSuccess(); + return { + Bucket: cdk.stringToCloudFormation(properties.bucket), + Key: cdk.stringToCloudFormation(properties.key), + Version: cdk.numberToCloudFormation(properties.version), + }; +} + +/** + * Properties for defining a `AWS::Serverless::Application` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ +export interface CfnApplicationProps { + /** + * `AWS::Serverless::Application.Location` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + location: CfnApplication.ApplicationLocationProperty | string | cdk.Token; + /** + * `AWS::Serverless::Application.NotificationArns` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + notificationArns?: string[]; + /** + * `AWS::Serverless::Application.Parameters` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + parameters?: { [key: string]: (string) } | cdk.Token; + /** + * `AWS::Serverless::Application.Tags` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + tags?: { [key: string]: (string) }; + /** + * `AWS::Serverless::Application.TimeoutInMinutes` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + timeoutInMinutes?: number | cdk.Token; +} + +/** + * Determine whether the given properties match those of a `CfnApplicationProps` + * + * @param properties - the TypeScript properties of a `CfnApplicationProps` + * + * @returns the result of the validation. + */ +function CfnApplicationPropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('location', cdk.requiredValidator)(properties.location)); + errors.collect(cdk.propertyValidator('location', cdk.unionValidator(CfnApplication_ApplicationLocationPropertyValidator, cdk.validateString))(properties.location)); + errors.collect(cdk.propertyValidator('notificationArns', cdk.listValidator(cdk.validateString))(properties.notificationArns)); + errors.collect(cdk.propertyValidator('parameters', cdk.hashValidator(cdk.validateString))(properties.parameters)); + errors.collect(cdk.propertyValidator('tags', cdk.hashValidator(cdk.validateString))(properties.tags)); + errors.collect(cdk.propertyValidator('timeoutInMinutes', cdk.validateNumber)(properties.timeoutInMinutes)); + return errors.wrap('supplied properties not correct for "CfnApplicationProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Application` resource + * + * @param properties - the TypeScript properties of a `CfnApplicationProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Application` resource. + */ +// @ts-ignore TS6133 +function cfnApplicationPropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApplicationPropsValidator(properties).assertSuccess(); + return { + Location: cdk.unionMapper([CfnApplication_ApplicationLocationPropertyValidator, cdk.validateString], [cfnApplicationApplicationLocationPropertyToCloudFormation, cdk.stringToCloudFormation])(properties.location), + NotificationArns: cdk.listMapper(cdk.stringToCloudFormation)(properties.notificationArns), + Parameters: cdk.hashMapper(cdk.stringToCloudFormation)(properties.parameters), + Tags: cdk.hashMapper(cdk.stringToCloudFormation)(properties.tags), + TimeoutInMinutes: cdk.numberToCloudFormation(properties.timeoutInMinutes), + }; +} + +/** + * A CloudFormation `AWS::Serverless::Application` + * + * @cloudformationResource AWS::Serverless::Application + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ +export class CfnApplication extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::Application"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly applicationName: string; + + /** + * The `TagManager` handles setting, removing and formatting tags + * + * Tags should be managed either passing them as properties during + * initiation or by calling methods on this object. If both techniques are + * used only the tags from the TagManager will be used. `Tag` (aspect) + * will use the manager. + */ + public readonly tags: cdk.TagManager; + + /** + * Create a new `AWS::Serverless::Application`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props: CfnApplicationProps) { + super(scope, id, { type: CfnApplication.resourceTypeName, properties: props }); + cdk.requireProperty(props, 'location', this); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnApplication.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnApplication.requiredTransform)} transform is required when using CfnApplication, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnApplication.requiredTransform; + this.applicationName = this.ref.toString(); + const tags = props === undefined ? undefined : props.tags; + this.tags = new cdk.TagManager(cdk.TagType.Map, "AWS::Serverless::Application", tags); + } + + public get propertyOverrides(): CfnApplicationProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnApplicationPropsToCloudFormation(this.node.resolve(properties)); + } +} + +export namespace CfnApplication { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + export interface ApplicationLocationProperty { + /** + * `CfnApplication.ApplicationLocationProperty.ApplicationId` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + applicationId: string; + /** + * `CfnApplication.ApplicationLocationProperty.SemanticVersion` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapplication + */ + semanticVersion: string; + } +} + +/** + * Determine whether the given properties match those of a `ApplicationLocationProperty` + * + * @param properties - the TypeScript properties of a `ApplicationLocationProperty` + * + * @returns the result of the validation. + */ +function CfnApplication_ApplicationLocationPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('applicationId', cdk.requiredValidator)(properties.applicationId)); + errors.collect(cdk.propertyValidator('applicationId', cdk.validateString)(properties.applicationId)); + errors.collect(cdk.propertyValidator('semanticVersion', cdk.requiredValidator)(properties.semanticVersion)); + errors.collect(cdk.propertyValidator('semanticVersion', cdk.validateString)(properties.semanticVersion)); + return errors.wrap('supplied properties not correct for "ApplicationLocationProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Application.ApplicationLocation` resource + * + * @param properties - the TypeScript properties of a `ApplicationLocationProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Application.ApplicationLocation` resource. + */ +// @ts-ignore TS6133 +function cfnApplicationApplicationLocationPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnApplication_ApplicationLocationPropertyValidator(properties).assertSuccess(); + return { + ApplicationId: cdk.stringToCloudFormation(properties.applicationId), + SemanticVersion: cdk.stringToCloudFormation(properties.semanticVersion), + }; +} + +/** + * Properties for defining a `AWS::Serverless::Function` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ +export interface CfnFunctionProps { + /** + * `AWS::Serverless::Function.CodeUri` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + codeUri: CfnFunction.S3LocationProperty | string | cdk.Token; + /** + * `AWS::Serverless::Function.Handler` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + handler: string; + /** + * `AWS::Serverless::Function.Runtime` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + runtime: string; + /** + * `AWS::Serverless::Function.AutoPublishAlias` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + autoPublishAlias?: string; + /** + * `AWS::Serverless::Function.DeadLetterQueue` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + deadLetterQueue?: CfnFunction.DeadLetterQueueProperty | cdk.Token; + /** + * `AWS::Serverless::Function.DeploymentPreference` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + deploymentPreference?: CfnFunction.DeploymentPreferenceProperty | cdk.Token; + /** + * `AWS::Serverless::Function.Description` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + description?: string; + /** + * `AWS::Serverless::Function.Environment` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + environment?: CfnFunction.FunctionEnvironmentProperty | cdk.Token; + /** + * `AWS::Serverless::Function.Events` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + events?: { [key: string]: (CfnFunction.EventSourceProperty | cdk.Token) } | cdk.Token; + /** + * `AWS::Serverless::Function.FunctionName` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + functionName?: string; + /** + * `AWS::Serverless::Function.KmsKeyArn` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + kmsKeyArn?: string; + /** + * `AWS::Serverless::Function.Layers` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + layers?: string[]; + /** + * `AWS::Serverless::Function.MemorySize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + memorySize?: number | cdk.Token; + /** + * `AWS::Serverless::Function.Policies` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + policies?: Array | CfnFunction.IAMPolicyDocumentProperty | string | cdk.Token; + /** + * `AWS::Serverless::Function.ReservedConcurrentExecutions` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + reservedConcurrentExecutions?: number | cdk.Token; + /** + * `AWS::Serverless::Function.Role` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + role?: string; + /** + * `AWS::Serverless::Function.Tags` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + tags?: { [key: string]: (string) }; + /** + * `AWS::Serverless::Function.Timeout` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + timeout?: number | cdk.Token; + /** + * `AWS::Serverless::Function.Tracing` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + tracing?: string; + /** + * `AWS::Serverless::Function.VpcConfig` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + vpcConfig?: CfnFunction.VpcConfigProperty | cdk.Token; +} + +/** + * Determine whether the given properties match those of a `CfnFunctionProps` + * + * @param properties - the TypeScript properties of a `CfnFunctionProps` + * + * @returns the result of the validation. + */ +function CfnFunctionPropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('autoPublishAlias', cdk.validateString)(properties.autoPublishAlias)); + errors.collect(cdk.propertyValidator('codeUri', cdk.requiredValidator)(properties.codeUri)); + errors.collect(cdk.propertyValidator('codeUri', cdk.unionValidator(CfnFunction_S3LocationPropertyValidator, cdk.validateString))(properties.codeUri)); + errors.collect(cdk.propertyValidator('deadLetterQueue', CfnFunction_DeadLetterQueuePropertyValidator)(properties.deadLetterQueue)); + errors.collect(cdk.propertyValidator('deploymentPreference', CfnFunction_DeploymentPreferencePropertyValidator)(properties.deploymentPreference)); + errors.collect(cdk.propertyValidator('description', cdk.validateString)(properties.description)); + errors.collect(cdk.propertyValidator('environment', CfnFunction_FunctionEnvironmentPropertyValidator)(properties.environment)); + errors.collect(cdk.propertyValidator('events', cdk.hashValidator(CfnFunction_EventSourcePropertyValidator))(properties.events)); + errors.collect(cdk.propertyValidator('functionName', cdk.validateString)(properties.functionName)); + errors.collect(cdk.propertyValidator('handler', cdk.requiredValidator)(properties.handler)); + errors.collect(cdk.propertyValidator('handler', cdk.validateString)(properties.handler)); + errors.collect(cdk.propertyValidator('kmsKeyArn', cdk.validateString)(properties.kmsKeyArn)); + errors.collect(cdk.propertyValidator('layers', cdk.listValidator(cdk.validateString))(properties.layers)); + errors.collect(cdk.propertyValidator('memorySize', cdk.validateNumber)(properties.memorySize)); + errors.collect(cdk.propertyValidator('policies', cdk.unionValidator(cdk.unionValidator(CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString), cdk.listValidator(cdk.unionValidator(CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString))))(properties.policies)); + errors.collect(cdk.propertyValidator('reservedConcurrentExecutions', cdk.validateNumber)(properties.reservedConcurrentExecutions)); + errors.collect(cdk.propertyValidator('role', cdk.validateString)(properties.role)); + errors.collect(cdk.propertyValidator('runtime', cdk.requiredValidator)(properties.runtime)); + errors.collect(cdk.propertyValidator('runtime', cdk.validateString)(properties.runtime)); + errors.collect(cdk.propertyValidator('tags', cdk.hashValidator(cdk.validateString))(properties.tags)); + errors.collect(cdk.propertyValidator('timeout', cdk.validateNumber)(properties.timeout)); + errors.collect(cdk.propertyValidator('tracing', cdk.validateString)(properties.tracing)); + errors.collect(cdk.propertyValidator('vpcConfig', CfnFunction_VpcConfigPropertyValidator)(properties.vpcConfig)); + return errors.wrap('supplied properties not correct for "CfnFunctionProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function` resource + * + * @param properties - the TypeScript properties of a `CfnFunctionProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionPropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunctionPropsValidator(properties).assertSuccess(); + return { + CodeUri: cdk.unionMapper([CfnFunction_S3LocationPropertyValidator, cdk.validateString], [cfnFunctionS3LocationPropertyToCloudFormation, cdk.stringToCloudFormation])(properties.codeUri), + Handler: cdk.stringToCloudFormation(properties.handler), + Runtime: cdk.stringToCloudFormation(properties.runtime), + AutoPublishAlias: cdk.stringToCloudFormation(properties.autoPublishAlias), + DeadLetterQueue: cfnFunctionDeadLetterQueuePropertyToCloudFormation(properties.deadLetterQueue), + DeploymentPreference: cfnFunctionDeploymentPreferencePropertyToCloudFormation(properties.deploymentPreference), + Description: cdk.stringToCloudFormation(properties.description), + Environment: cfnFunctionFunctionEnvironmentPropertyToCloudFormation(properties.environment), + Events: cdk.hashMapper(cfnFunctionEventSourcePropertyToCloudFormation)(properties.events), + FunctionName: cdk.stringToCloudFormation(properties.functionName), + KmsKeyArn: cdk.stringToCloudFormation(properties.kmsKeyArn), + Layers: cdk.listMapper(cdk.stringToCloudFormation)(properties.layers), + MemorySize: cdk.numberToCloudFormation(properties.memorySize), + Policies: cdk.unionMapper([cdk.unionValidator(CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString), cdk.listValidator(cdk.unionValidator(CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString))], [cdk.unionMapper([CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString], [cfnFunctionIAMPolicyDocumentPropertyToCloudFormation, cdk.stringToCloudFormation]), cdk.listMapper(cdk.unionMapper([CfnFunction_IAMPolicyDocumentPropertyValidator, cdk.validateString], [cfnFunctionIAMPolicyDocumentPropertyToCloudFormation, cdk.stringToCloudFormation]))])(properties.policies), + ReservedConcurrentExecutions: cdk.numberToCloudFormation(properties.reservedConcurrentExecutions), + Role: cdk.stringToCloudFormation(properties.role), + Tags: cdk.hashMapper(cdk.stringToCloudFormation)(properties.tags), + Timeout: cdk.numberToCloudFormation(properties.timeout), + Tracing: cdk.stringToCloudFormation(properties.tracing), + VpcConfig: cfnFunctionVpcConfigPropertyToCloudFormation(properties.vpcConfig), + }; +} + +/** + * A CloudFormation `AWS::Serverless::Function` + * + * @cloudformationResource AWS::Serverless::Function + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ +export class CfnFunction extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::Function"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly functionName: string; + + /** + * The `TagManager` handles setting, removing and formatting tags + * + * Tags should be managed either passing them as properties during + * initiation or by calling methods on this object. If both techniques are + * used only the tags from the TagManager will be used. `Tag` (aspect) + * will use the manager. + */ + public readonly tags: cdk.TagManager; + + /** + * Create a new `AWS::Serverless::Function`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props: CfnFunctionProps) { + super(scope, id, { type: CfnFunction.resourceTypeName, properties: props }); + cdk.requireProperty(props, 'codeUri', this); + cdk.requireProperty(props, 'handler', this); + cdk.requireProperty(props, 'runtime', this); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnFunction.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnFunction.requiredTransform)} transform is required when using CfnFunction, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnFunction.requiredTransform; + this.functionName = this.ref.toString(); + const tags = props === undefined ? undefined : props.tags; + this.tags = new cdk.TagManager(cdk.TagType.Map, "AWS::Serverless::Function", tags); + } + + public get propertyOverrides(): CfnFunctionProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnFunctionPropsToCloudFormation(this.node.resolve(properties)); + } +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#alexaskill + */ + export interface AlexaSkillEventProperty { + /** + * `CfnFunction.AlexaSkillEventProperty.Variables` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#alexaskill + */ + variables?: { [key: string]: (string) } | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `AlexaSkillEventProperty` + * + * @param properties - the TypeScript properties of a `AlexaSkillEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_AlexaSkillEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('variables', cdk.hashValidator(cdk.validateString))(properties.variables)); + return errors.wrap('supplied properties not correct for "AlexaSkillEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.AlexaSkillEvent` resource + * + * @param properties - the TypeScript properties of a `AlexaSkillEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.AlexaSkillEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionAlexaSkillEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_AlexaSkillEventPropertyValidator(properties).assertSuccess(); + return { + Variables: cdk.hashMapper(cdk.stringToCloudFormation)(properties.variables), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + */ + export interface ApiEventProperty { + /** + * `CfnFunction.ApiEventProperty.Method` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + */ + method: string; + /** + * `CfnFunction.ApiEventProperty.Path` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + */ + path: string; + /** + * `CfnFunction.ApiEventProperty.RestApiId` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + */ + restApiId?: string; + } +} + +/** + * Determine whether the given properties match those of a `ApiEventProperty` + * + * @param properties - the TypeScript properties of a `ApiEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_ApiEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('method', cdk.requiredValidator)(properties.method)); + errors.collect(cdk.propertyValidator('method', cdk.validateString)(properties.method)); + errors.collect(cdk.propertyValidator('path', cdk.requiredValidator)(properties.path)); + errors.collect(cdk.propertyValidator('path', cdk.validateString)(properties.path)); + errors.collect(cdk.propertyValidator('restApiId', cdk.validateString)(properties.restApiId)); + return errors.wrap('supplied properties not correct for "ApiEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.ApiEvent` resource + * + * @param properties - the TypeScript properties of a `ApiEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.ApiEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionApiEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_ApiEventPropertyValidator(properties).assertSuccess(); + return { + Method: cdk.stringToCloudFormation(properties.method), + Path: cdk.stringToCloudFormation(properties.path), + RestApiId: cdk.stringToCloudFormation(properties.restApiId), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + */ + export interface CloudWatchEventEventProperty { + /** + * `CfnFunction.CloudWatchEventEventProperty.Input` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + */ + input?: string; + /** + * `CfnFunction.CloudWatchEventEventProperty.InputPath` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#cloudwatchevent + */ + inputPath?: string; + /** + * `CfnFunction.CloudWatchEventEventProperty.Pattern` + * @see http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/CloudWatchEventsandEventPatterns.html + */ + pattern: object | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `CloudWatchEventEventProperty` + * + * @param properties - the TypeScript properties of a `CloudWatchEventEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_CloudWatchEventEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('input', cdk.validateString)(properties.input)); + errors.collect(cdk.propertyValidator('inputPath', cdk.validateString)(properties.inputPath)); + errors.collect(cdk.propertyValidator('pattern', cdk.requiredValidator)(properties.pattern)); + errors.collect(cdk.propertyValidator('pattern', cdk.validateObject)(properties.pattern)); + return errors.wrap('supplied properties not correct for "CloudWatchEventEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.CloudWatchEventEvent` resource + * + * @param properties - the TypeScript properties of a `CloudWatchEventEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.CloudWatchEventEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionCloudWatchEventEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_CloudWatchEventEventPropertyValidator(properties).assertSuccess(); + return { + Input: cdk.stringToCloudFormation(properties.input), + InputPath: cdk.stringToCloudFormation(properties.inputPath), + Pattern: cdk.objectToCloudFormation(properties.pattern), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deadletterqueue-object + */ + export interface DeadLetterQueueProperty { + /** + * `CfnFunction.DeadLetterQueueProperty.TargetArn` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + targetArn: string; + /** + * `CfnFunction.DeadLetterQueueProperty.Type` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + type: string; + } +} + +/** + * Determine whether the given properties match those of a `DeadLetterQueueProperty` + * + * @param properties - the TypeScript properties of a `DeadLetterQueueProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_DeadLetterQueuePropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('targetArn', cdk.requiredValidator)(properties.targetArn)); + errors.collect(cdk.propertyValidator('targetArn', cdk.validateString)(properties.targetArn)); + errors.collect(cdk.propertyValidator('type', cdk.requiredValidator)(properties.type)); + errors.collect(cdk.propertyValidator('type', cdk.validateString)(properties.type)); + return errors.wrap('supplied properties not correct for "DeadLetterQueueProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.DeadLetterQueue` resource + * + * @param properties - the TypeScript properties of a `DeadLetterQueueProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.DeadLetterQueue` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionDeadLetterQueuePropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_DeadLetterQueuePropertyValidator(properties).assertSuccess(); + return { + TargetArn: cdk.stringToCloudFormation(properties.targetArn), + Type: cdk.stringToCloudFormation(properties.type), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/docs/safe_lambda_deployments.rst + */ + export interface DeploymentPreferenceProperty { + /** + * `CfnFunction.DeploymentPreferenceProperty.Enabled` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + enabled: boolean | cdk.Token; + /** + * `CfnFunction.DeploymentPreferenceProperty.Type` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + type: string; + /** + * `CfnFunction.DeploymentPreferenceProperty.Alarms` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + alarms?: string[]; + /** + * `CfnFunction.DeploymentPreferenceProperty.Hooks` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#deploymentpreference-object + */ + hooks?: string[]; + } +} + +/** + * Determine whether the given properties match those of a `DeploymentPreferenceProperty` + * + * @param properties - the TypeScript properties of a `DeploymentPreferenceProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_DeploymentPreferencePropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('enabled', cdk.requiredValidator)(properties.enabled)); + errors.collect(cdk.propertyValidator('enabled', cdk.validateBoolean)(properties.enabled)); + errors.collect(cdk.propertyValidator('type', cdk.requiredValidator)(properties.type)); + errors.collect(cdk.propertyValidator('type', cdk.validateString)(properties.type)); + errors.collect(cdk.propertyValidator('alarms', cdk.listValidator(cdk.validateString))(properties.alarms)); + errors.collect(cdk.propertyValidator('hooks', cdk.listValidator(cdk.validateString))(properties.hooks)); + return errors.wrap('supplied properties not correct for "DeploymentPreferenceProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.DeploymentPreference` resource + * + * @param properties - the TypeScript properties of a `DeploymentPreferenceProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.DeploymentPreference` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionDeploymentPreferencePropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_DeploymentPreferencePropertyValidator(properties).assertSuccess(); + return { + Enabled: cdk.booleanToCloudFormation(properties.enabled), + Type: cdk.stringToCloudFormation(properties.type), + Alarms: cdk.listMapper(cdk.stringToCloudFormation)(properties.alarms), + Hooks: cdk.listMapper(cdk.stringToCloudFormation)(properties.hooks), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb + */ + export interface DynamoDBEventProperty { + /** + * `CfnFunction.DynamoDBEventProperty.BatchSize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb + */ + batchSize: number | cdk.Token; + /** + * `CfnFunction.DynamoDBEventProperty.StartingPosition` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb + */ + startingPosition: string; + /** + * `CfnFunction.DynamoDBEventProperty.Stream` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb + */ + stream: string; + } +} + +/** + * Determine whether the given properties match those of a `DynamoDBEventProperty` + * + * @param properties - the TypeScript properties of a `DynamoDBEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_DynamoDBEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('batchSize', cdk.requiredValidator)(properties.batchSize)); + errors.collect(cdk.propertyValidator('batchSize', cdk.validateNumber)(properties.batchSize)); + errors.collect(cdk.propertyValidator('startingPosition', cdk.requiredValidator)(properties.startingPosition)); + errors.collect(cdk.propertyValidator('startingPosition', cdk.validateString)(properties.startingPosition)); + errors.collect(cdk.propertyValidator('stream', cdk.requiredValidator)(properties.stream)); + errors.collect(cdk.propertyValidator('stream', cdk.validateString)(properties.stream)); + return errors.wrap('supplied properties not correct for "DynamoDBEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.DynamoDBEvent` resource + * + * @param properties - the TypeScript properties of a `DynamoDBEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.DynamoDBEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionDynamoDBEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_DynamoDBEventPropertyValidator(properties).assertSuccess(); + return { + BatchSize: cdk.numberToCloudFormation(properties.batchSize), + StartingPosition: cdk.stringToCloudFormation(properties.startingPosition), + Stream: cdk.stringToCloudFormation(properties.stream), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#event-source-object + */ + export interface EventSourceProperty { + /** + * `CfnFunction.EventSourceProperty.Properties` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#event-source-types + */ + properties: CfnFunction.S3EventProperty | CfnFunction.SNSEventProperty | CfnFunction.SQSEventProperty | CfnFunction.KinesisEventProperty | CfnFunction.DynamoDBEventProperty | CfnFunction.ApiEventProperty | CfnFunction.ScheduleEventProperty | CfnFunction.CloudWatchEventEventProperty | CfnFunction.IoTRuleEventProperty | CfnFunction.AlexaSkillEventProperty | cdk.Token; + /** + * `CfnFunction.EventSourceProperty.Type` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#event-source-object + */ + type: string; + } +} + +/** + * Determine whether the given properties match those of a `EventSourceProperty` + * + * @param properties - the TypeScript properties of a `EventSourceProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_EventSourcePropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('properties', cdk.requiredValidator)(properties.properties)); + errors.collect(cdk.propertyValidator('properties', cdk.unionValidator(CfnFunction_S3EventPropertyValidator, CfnFunction_SNSEventPropertyValidator, CfnFunction_SQSEventPropertyValidator, CfnFunction_KinesisEventPropertyValidator, CfnFunction_DynamoDBEventPropertyValidator, CfnFunction_ApiEventPropertyValidator, CfnFunction_ScheduleEventPropertyValidator, CfnFunction_CloudWatchEventEventPropertyValidator, CfnFunction_IoTRuleEventPropertyValidator, CfnFunction_AlexaSkillEventPropertyValidator))(properties.properties)); + errors.collect(cdk.propertyValidator('type', cdk.requiredValidator)(properties.type)); + errors.collect(cdk.propertyValidator('type', cdk.validateString)(properties.type)); + return errors.wrap('supplied properties not correct for "EventSourceProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.EventSource` resource + * + * @param properties - the TypeScript properties of a `EventSourceProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.EventSource` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionEventSourcePropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_EventSourcePropertyValidator(properties).assertSuccess(); + return { + Properties: cdk.unionMapper([CfnFunction_S3EventPropertyValidator, CfnFunction_SNSEventPropertyValidator, CfnFunction_SQSEventPropertyValidator, CfnFunction_KinesisEventPropertyValidator, CfnFunction_DynamoDBEventPropertyValidator, CfnFunction_ApiEventPropertyValidator, CfnFunction_ScheduleEventPropertyValidator, CfnFunction_CloudWatchEventEventPropertyValidator, CfnFunction_IoTRuleEventPropertyValidator, CfnFunction_AlexaSkillEventPropertyValidator], [cfnFunctionS3EventPropertyToCloudFormation, cfnFunctionSNSEventPropertyToCloudFormation, cfnFunctionSQSEventPropertyToCloudFormation, cfnFunctionKinesisEventPropertyToCloudFormation, cfnFunctionDynamoDBEventPropertyToCloudFormation, cfnFunctionApiEventPropertyToCloudFormation, cfnFunctionScheduleEventPropertyToCloudFormation, cfnFunctionCloudWatchEventEventPropertyToCloudFormation, cfnFunctionIoTRuleEventPropertyToCloudFormation, cfnFunctionAlexaSkillEventPropertyToCloudFormation])(properties.properties), + Type: cdk.stringToCloudFormation(properties.type), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + */ + export interface FunctionEnvironmentProperty { + /** + * `CfnFunction.FunctionEnvironmentProperty.Variables` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#environment-object + */ + variables: { [key: string]: (string) } | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `FunctionEnvironmentProperty` + * + * @param properties - the TypeScript properties of a `FunctionEnvironmentProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_FunctionEnvironmentPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('variables', cdk.requiredValidator)(properties.variables)); + errors.collect(cdk.propertyValidator('variables', cdk.hashValidator(cdk.validateString))(properties.variables)); + return errors.wrap('supplied properties not correct for "FunctionEnvironmentProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.FunctionEnvironment` resource + * + * @param properties - the TypeScript properties of a `FunctionEnvironmentProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.FunctionEnvironment` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionFunctionEnvironmentPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_FunctionEnvironmentPropertyValidator(properties).assertSuccess(); + return { + Variables: cdk.hashMapper(cdk.stringToCloudFormation)(properties.variables), + }; +} + +export namespace CfnFunction { + /** + * @see http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html + */ + export interface IAMPolicyDocumentProperty { + /** + * `CfnFunction.IAMPolicyDocumentProperty.Statement` + * @see http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html + */ + statement: object | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `IAMPolicyDocumentProperty` + * + * @param properties - the TypeScript properties of a `IAMPolicyDocumentProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_IAMPolicyDocumentPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('statement', cdk.requiredValidator)(properties.statement)); + errors.collect(cdk.propertyValidator('statement', cdk.validateObject)(properties.statement)); + return errors.wrap('supplied properties not correct for "IAMPolicyDocumentProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.IAMPolicyDocument` resource + * + * @param properties - the TypeScript properties of a `IAMPolicyDocumentProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.IAMPolicyDocument` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionIAMPolicyDocumentPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_IAMPolicyDocumentPropertyValidator(properties).assertSuccess(); + return { + Statement: cdk.objectToCloudFormation(properties.statement), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#iotrule + */ + export interface IoTRuleEventProperty { + /** + * `CfnFunction.IoTRuleEventProperty.AwsIotSqlVersion` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#iotrule + */ + awsIotSqlVersion?: string; + /** + * `CfnFunction.IoTRuleEventProperty.Sql` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#iotrule + */ + sql: string; + } +} + +/** + * Determine whether the given properties match those of a `IoTRuleEventProperty` + * + * @param properties - the TypeScript properties of a `IoTRuleEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_IoTRuleEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('awsIotSqlVersion', cdk.validateString)(properties.awsIotSqlVersion)); + errors.collect(cdk.propertyValidator('sql', cdk.requiredValidator)(properties.sql)); + errors.collect(cdk.propertyValidator('sql', cdk.validateString)(properties.sql)); + return errors.wrap('supplied properties not correct for "IoTRuleEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.IoTRuleEvent` resource + * + * @param properties - the TypeScript properties of a `IoTRuleEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.IoTRuleEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionIoTRuleEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_IoTRuleEventPropertyValidator(properties).assertSuccess(); + return { + AwsIotSqlVersion: cdk.stringToCloudFormation(properties.awsIotSqlVersion), + Sql: cdk.stringToCloudFormation(properties.sql), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis + */ + export interface KinesisEventProperty { + /** + * `CfnFunction.KinesisEventProperty.BatchSize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis + */ + batchSize?: number | cdk.Token; + /** + * `CfnFunction.KinesisEventProperty.StartingPosition` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis + */ + startingPosition: string; + /** + * `CfnFunction.KinesisEventProperty.Stream` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis + */ + stream: string; + } +} + +/** + * Determine whether the given properties match those of a `KinesisEventProperty` + * + * @param properties - the TypeScript properties of a `KinesisEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_KinesisEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('batchSize', cdk.validateNumber)(properties.batchSize)); + errors.collect(cdk.propertyValidator('startingPosition', cdk.requiredValidator)(properties.startingPosition)); + errors.collect(cdk.propertyValidator('startingPosition', cdk.validateString)(properties.startingPosition)); + errors.collect(cdk.propertyValidator('stream', cdk.requiredValidator)(properties.stream)); + errors.collect(cdk.propertyValidator('stream', cdk.validateString)(properties.stream)); + return errors.wrap('supplied properties not correct for "KinesisEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.KinesisEvent` resource + * + * @param properties - the TypeScript properties of a `KinesisEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.KinesisEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionKinesisEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_KinesisEventPropertyValidator(properties).assertSuccess(); + return { + BatchSize: cdk.numberToCloudFormation(properties.batchSize), + StartingPosition: cdk.stringToCloudFormation(properties.startingPosition), + Stream: cdk.stringToCloudFormation(properties.stream), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3 + */ + export interface S3EventProperty { + /** + * `CfnFunction.S3EventProperty.Bucket` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3 + */ + bucket: string; + /** + * `CfnFunction.S3EventProperty.Events` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3 + */ + events: string[] | string | cdk.Token; + /** + * `CfnFunction.S3EventProperty.Filter` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3 + */ + filter?: CfnFunction.S3NotificationFilterProperty | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `S3EventProperty` + * + * @param properties - the TypeScript properties of a `S3EventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_S3EventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('bucket', cdk.requiredValidator)(properties.bucket)); + errors.collect(cdk.propertyValidator('bucket', cdk.validateString)(properties.bucket)); + errors.collect(cdk.propertyValidator('events', cdk.requiredValidator)(properties.events)); + errors.collect(cdk.propertyValidator('events', cdk.unionValidator(cdk.unionValidator(cdk.validateString), cdk.listValidator(cdk.unionValidator(cdk.validateString))))(properties.events)); + errors.collect(cdk.propertyValidator('filter', CfnFunction_S3NotificationFilterPropertyValidator)(properties.filter)); + return errors.wrap('supplied properties not correct for "S3EventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.S3Event` resource + * + * @param properties - the TypeScript properties of a `S3EventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.S3Event` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionS3EventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_S3EventPropertyValidator(properties).assertSuccess(); + return { + Bucket: cdk.stringToCloudFormation(properties.bucket), + Events: cdk.unionMapper([cdk.unionValidator(cdk.validateString), cdk.listValidator(cdk.unionValidator(cdk.validateString))], [cdk.unionMapper([cdk.validateString], [cdk.stringToCloudFormation]), cdk.listMapper(cdk.unionMapper([cdk.validateString], [cdk.stringToCloudFormation]))])(properties.events), + Filter: cfnFunctionS3NotificationFilterPropertyToCloudFormation(properties.filter), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#s3-location-object + */ + export interface S3LocationProperty { + /** + * `CfnFunction.S3LocationProperty.Bucket` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + bucket: string; + /** + * `CfnFunction.S3LocationProperty.Key` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + key: string; + /** + * `CfnFunction.S3LocationProperty.Version` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + */ + version?: number | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `S3LocationProperty` + * + * @param properties - the TypeScript properties of a `S3LocationProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_S3LocationPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('bucket', cdk.requiredValidator)(properties.bucket)); + errors.collect(cdk.propertyValidator('bucket', cdk.validateString)(properties.bucket)); + errors.collect(cdk.propertyValidator('key', cdk.requiredValidator)(properties.key)); + errors.collect(cdk.propertyValidator('key', cdk.validateString)(properties.key)); + errors.collect(cdk.propertyValidator('version', cdk.validateNumber)(properties.version)); + return errors.wrap('supplied properties not correct for "S3LocationProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.S3Location` resource + * + * @param properties - the TypeScript properties of a `S3LocationProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.S3Location` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionS3LocationPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_S3LocationPropertyValidator(properties).assertSuccess(); + return { + Bucket: cdk.stringToCloudFormation(properties.bucket), + Key: cdk.stringToCloudFormation(properties.key), + Version: cdk.numberToCloudFormation(properties.version), + }; +} + +export namespace CfnFunction { + /** + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-notificationconfiguration-config-filter.html + */ + export interface S3NotificationFilterProperty { + /** + * `CfnFunction.S3NotificationFilterProperty.S3Key` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-notificationconfiguration-config-filter.html + */ + s3Key: string; + } +} + +/** + * Determine whether the given properties match those of a `S3NotificationFilterProperty` + * + * @param properties - the TypeScript properties of a `S3NotificationFilterProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_S3NotificationFilterPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('s3Key', cdk.requiredValidator)(properties.s3Key)); + errors.collect(cdk.propertyValidator('s3Key', cdk.validateString)(properties.s3Key)); + return errors.wrap('supplied properties not correct for "S3NotificationFilterProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.S3NotificationFilter` resource + * + * @param properties - the TypeScript properties of a `S3NotificationFilterProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.S3NotificationFilter` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionS3NotificationFilterPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_S3NotificationFilterPropertyValidator(properties).assertSuccess(); + return { + S3Key: cdk.stringToCloudFormation(properties.s3Key), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sns + */ + export interface SNSEventProperty { + /** + * `CfnFunction.SNSEventProperty.Topic` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sns + */ + topic: string; + } +} + +/** + * Determine whether the given properties match those of a `SNSEventProperty` + * + * @param properties - the TypeScript properties of a `SNSEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_SNSEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('topic', cdk.requiredValidator)(properties.topic)); + errors.collect(cdk.propertyValidator('topic', cdk.validateString)(properties.topic)); + return errors.wrap('supplied properties not correct for "SNSEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.SNSEvent` resource + * + * @param properties - the TypeScript properties of a `SNSEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.SNSEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionSNSEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_SNSEventPropertyValidator(properties).assertSuccess(); + return { + Topic: cdk.stringToCloudFormation(properties.topic), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sqs + */ + export interface SQSEventProperty { + /** + * `CfnFunction.SQSEventProperty.BatchSize` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sqs + */ + batchSize?: number | cdk.Token; + /** + * `CfnFunction.SQSEventProperty.Queue` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sqs + */ + queue: string; + } +} + +/** + * Determine whether the given properties match those of a `SQSEventProperty` + * + * @param properties - the TypeScript properties of a `SQSEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_SQSEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('batchSize', cdk.validateNumber)(properties.batchSize)); + errors.collect(cdk.propertyValidator('queue', cdk.requiredValidator)(properties.queue)); + errors.collect(cdk.propertyValidator('queue', cdk.validateString)(properties.queue)); + return errors.wrap('supplied properties not correct for "SQSEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.SQSEvent` resource + * + * @param properties - the TypeScript properties of a `SQSEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.SQSEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionSQSEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_SQSEventPropertyValidator(properties).assertSuccess(); + return { + BatchSize: cdk.numberToCloudFormation(properties.batchSize), + Queue: cdk.stringToCloudFormation(properties.queue), + }; +} + +export namespace CfnFunction { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule + */ + export interface ScheduleEventProperty { + /** + * `CfnFunction.ScheduleEventProperty.Input` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule + */ + input?: string; + /** + * `CfnFunction.ScheduleEventProperty.Schedule` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#schedule + */ + schedule: string; + } +} + +/** + * Determine whether the given properties match those of a `ScheduleEventProperty` + * + * @param properties - the TypeScript properties of a `ScheduleEventProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_ScheduleEventPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('input', cdk.validateString)(properties.input)); + errors.collect(cdk.propertyValidator('schedule', cdk.requiredValidator)(properties.schedule)); + errors.collect(cdk.propertyValidator('schedule', cdk.validateString)(properties.schedule)); + return errors.wrap('supplied properties not correct for "ScheduleEventProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.ScheduleEvent` resource + * + * @param properties - the TypeScript properties of a `ScheduleEventProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.ScheduleEvent` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionScheduleEventPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_ScheduleEventPropertyValidator(properties).assertSuccess(); + return { + Input: cdk.stringToCloudFormation(properties.input), + Schedule: cdk.stringToCloudFormation(properties.schedule), + }; +} + +export namespace CfnFunction { + /** + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html + */ + export interface VpcConfigProperty { + /** + * `CfnFunction.VpcConfigProperty.SecurityGroupIds` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html + */ + securityGroupIds: string[]; + /** + * `CfnFunction.VpcConfigProperty.SubnetIds` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html + */ + subnetIds: string[]; + } +} + +/** + * Determine whether the given properties match those of a `VpcConfigProperty` + * + * @param properties - the TypeScript properties of a `VpcConfigProperty` + * + * @returns the result of the validation. + */ +function CfnFunction_VpcConfigPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('securityGroupIds', cdk.requiredValidator)(properties.securityGroupIds)); + errors.collect(cdk.propertyValidator('securityGroupIds', cdk.listValidator(cdk.validateString))(properties.securityGroupIds)); + errors.collect(cdk.propertyValidator('subnetIds', cdk.requiredValidator)(properties.subnetIds)); + errors.collect(cdk.propertyValidator('subnetIds', cdk.listValidator(cdk.validateString))(properties.subnetIds)); + return errors.wrap('supplied properties not correct for "VpcConfigProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::Function.VpcConfig` resource + * + * @param properties - the TypeScript properties of a `VpcConfigProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::Function.VpcConfig` resource. + */ +// @ts-ignore TS6133 +function cfnFunctionVpcConfigPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnFunction_VpcConfigPropertyValidator(properties).assertSuccess(); + return { + SecurityGroupIds: cdk.listMapper(cdk.stringToCloudFormation)(properties.securityGroupIds), + SubnetIds: cdk.listMapper(cdk.stringToCloudFormation)(properties.subnetIds), + }; +} + +/** + * Properties for defining a `AWS::Serverless::LayerVersion` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ +export interface CfnLayerVersionProps { + /** + * `AWS::Serverless::LayerVersion.CompatibleRuntimes` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + compatibleRuntimes?: string[]; + /** + * `AWS::Serverless::LayerVersion.ContentUri` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + contentUri?: string; + /** + * `AWS::Serverless::LayerVersion.Description` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + description?: string; + /** + * `AWS::Serverless::LayerVersion.LayerName` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + layerName?: string; + /** + * `AWS::Serverless::LayerVersion.LicenseInfo` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + licenseInfo?: string; + /** + * `AWS::Serverless::LayerVersion.RetentionPolicy` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ + retentionPolicy?: string; +} + +/** + * Determine whether the given properties match those of a `CfnLayerVersionProps` + * + * @param properties - the TypeScript properties of a `CfnLayerVersionProps` + * + * @returns the result of the validation. + */ +function CfnLayerVersionPropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('compatibleRuntimes', cdk.listValidator(cdk.validateString))(properties.compatibleRuntimes)); + errors.collect(cdk.propertyValidator('contentUri', cdk.validateString)(properties.contentUri)); + errors.collect(cdk.propertyValidator('description', cdk.validateString)(properties.description)); + errors.collect(cdk.propertyValidator('layerName', cdk.validateString)(properties.layerName)); + errors.collect(cdk.propertyValidator('licenseInfo', cdk.validateString)(properties.licenseInfo)); + errors.collect(cdk.propertyValidator('retentionPolicy', cdk.validateString)(properties.retentionPolicy)); + return errors.wrap('supplied properties not correct for "CfnLayerVersionProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::LayerVersion` resource + * + * @param properties - the TypeScript properties of a `CfnLayerVersionProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::LayerVersion` resource. + */ +// @ts-ignore TS6133 +function cfnLayerVersionPropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnLayerVersionPropsValidator(properties).assertSuccess(); + return { + CompatibleRuntimes: cdk.listMapper(cdk.stringToCloudFormation)(properties.compatibleRuntimes), + ContentUri: cdk.stringToCloudFormation(properties.contentUri), + Description: cdk.stringToCloudFormation(properties.description), + LayerName: cdk.stringToCloudFormation(properties.layerName), + LicenseInfo: cdk.stringToCloudFormation(properties.licenseInfo), + RetentionPolicy: cdk.stringToCloudFormation(properties.retentionPolicy), + }; +} + +/** + * A CloudFormation `AWS::Serverless::LayerVersion` + * + * @cloudformationResource AWS::Serverless::LayerVersion + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesslayerversion + */ +export class CfnLayerVersion extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::LayerVersion"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly layerVersionArn: string; + + /** + * Create a new `AWS::Serverless::LayerVersion`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props?: CfnLayerVersionProps) { + super(scope, id, { type: CfnLayerVersion.resourceTypeName, properties: props }); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnLayerVersion.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnLayerVersion.requiredTransform)} transform is required when using CfnLayerVersion, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnLayerVersion.requiredTransform; + this.layerVersionArn = this.ref.toString(); + } + + public get propertyOverrides(): CfnLayerVersionProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnLayerVersionPropsToCloudFormation(this.node.resolve(properties)); + } +} + +/** + * Properties for defining a `AWS::Serverless::SimpleTable` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ +export interface CfnSimpleTableProps { + /** + * `AWS::Serverless::SimpleTable.PrimaryKey` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#primary-key-object + */ + primaryKey?: CfnSimpleTable.PrimaryKeyProperty | cdk.Token; + /** + * `AWS::Serverless::SimpleTable.ProvisionedThroughput` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-provisionedthroughput.html + */ + provisionedThroughput?: CfnSimpleTable.ProvisionedThroughputProperty | cdk.Token; + /** + * `AWS::Serverless::SimpleTable.SSESpecification` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ + sseSpecification?: CfnSimpleTable.SSESpecificationProperty | cdk.Token; + /** + * `AWS::Serverless::SimpleTable.TableName` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ + tableName?: string; + /** + * `AWS::Serverless::SimpleTable.Tags` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ + tags?: { [key: string]: (string) }; +} + +/** + * Determine whether the given properties match those of a `CfnSimpleTableProps` + * + * @param properties - the TypeScript properties of a `CfnSimpleTableProps` + * + * @returns the result of the validation. + */ +function CfnSimpleTablePropsValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('primaryKey', CfnSimpleTable_PrimaryKeyPropertyValidator)(properties.primaryKey)); + errors.collect(cdk.propertyValidator('provisionedThroughput', CfnSimpleTable_ProvisionedThroughputPropertyValidator)(properties.provisionedThroughput)); + errors.collect(cdk.propertyValidator('sseSpecification', CfnSimpleTable_SSESpecificationPropertyValidator)(properties.sseSpecification)); + errors.collect(cdk.propertyValidator('tableName', cdk.validateString)(properties.tableName)); + errors.collect(cdk.propertyValidator('tags', cdk.hashValidator(cdk.validateString))(properties.tags)); + return errors.wrap('supplied properties not correct for "CfnSimpleTableProps"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable` resource + * + * @param properties - the TypeScript properties of a `CfnSimpleTableProps` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable` resource. + */ +// @ts-ignore TS6133 +function cfnSimpleTablePropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnSimpleTablePropsValidator(properties).assertSuccess(); + return { + PrimaryKey: cfnSimpleTablePrimaryKeyPropertyToCloudFormation(properties.primaryKey), + ProvisionedThroughput: cfnSimpleTableProvisionedThroughputPropertyToCloudFormation(properties.provisionedThroughput), + SSESpecification: cfnSimpleTableSSESpecificationPropertyToCloudFormation(properties.sseSpecification), + TableName: cdk.stringToCloudFormation(properties.tableName), + Tags: cdk.hashMapper(cdk.stringToCloudFormation)(properties.tags), + }; +} + +/** + * A CloudFormation `AWS::Serverless::SimpleTable` + * + * @cloudformationResource AWS::Serverless::SimpleTable + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlesssimpletable + */ +export class CfnSimpleTable extends cdk.CfnResource { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly resourceTypeName = "AWS::Serverless::SimpleTable"; + /** + * The `Transform` a template must use in order to use this resource + */ + public static readonly requiredTransform = "AWS::Serverless-2016-10-31"; + public readonly simpleTableName: string; + + /** + * The `TagManager` handles setting, removing and formatting tags + * + * Tags should be managed either passing them as properties during + * initiation or by calling methods on this object. If both techniques are + * used only the tags from the TagManager will be used. `Tag` (aspect) + * will use the manager. + */ + public readonly tags: cdk.TagManager; + + /** + * Create a new `AWS::Serverless::SimpleTable`. + * + * @param scope - scope in which this resource is defined + * @param id - scoped id of the resource + * @param props - resource properties + */ + constructor(scope: cdk.Construct, id: string, props?: CfnSimpleTableProps) { + super(scope, id, { type: CfnSimpleTable.resourceTypeName, properties: props }); + // If a different transform than the required one is in use, this resource cannot be used + if (this.node.stack.templateOptions.transform && this.node.stack.templateOptions.transform !== CfnSimpleTable.requiredTransform) { + throw new Error(`The ${JSON.stringify(CfnSimpleTable.requiredTransform)} transform is required when using CfnSimpleTable, but the ${JSON.stringify(this.node.stack.templateOptions.transform)} is used.`); + } + // Automatically configure the required transform + this.node.stack.templateOptions.transform = CfnSimpleTable.requiredTransform; + this.simpleTableName = this.ref.toString(); + const tags = props === undefined ? undefined : props.tags; + this.tags = new cdk.TagManager(cdk.TagType.Map, "AWS::Serverless::SimpleTable", tags); + } + + public get propertyOverrides(): CfnSimpleTableProps { + return this.untypedPropertyOverrides; + } + protected renderProperties(properties: any): { [key: string]: any } { + return cfnSimpleTablePropsToCloudFormation(this.node.resolve(properties)); + } +} + +export namespace CfnSimpleTable { + /** + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#primary-key-object + */ + export interface PrimaryKeyProperty { + /** + * `CfnSimpleTable.PrimaryKeyProperty.Name` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#primary-key-object + */ + name?: string; + /** + * `CfnSimpleTable.PrimaryKeyProperty.Type` + * @see https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#primary-key-object + */ + type: string; + } +} + +/** + * Determine whether the given properties match those of a `PrimaryKeyProperty` + * + * @param properties - the TypeScript properties of a `PrimaryKeyProperty` + * + * @returns the result of the validation. + */ +function CfnSimpleTable_PrimaryKeyPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('name', cdk.validateString)(properties.name)); + errors.collect(cdk.propertyValidator('type', cdk.requiredValidator)(properties.type)); + errors.collect(cdk.propertyValidator('type', cdk.validateString)(properties.type)); + return errors.wrap('supplied properties not correct for "PrimaryKeyProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.PrimaryKey` resource + * + * @param properties - the TypeScript properties of a `PrimaryKeyProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.PrimaryKey` resource. + */ +// @ts-ignore TS6133 +function cfnSimpleTablePrimaryKeyPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnSimpleTable_PrimaryKeyPropertyValidator(properties).assertSuccess(); + return { + Name: cdk.stringToCloudFormation(properties.name), + Type: cdk.stringToCloudFormation(properties.type), + }; +} + +export namespace CfnSimpleTable { + /** + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-provisionedthroughput.html + */ + export interface ProvisionedThroughputProperty { + /** + * `CfnSimpleTable.ProvisionedThroughputProperty.ReadCapacityUnits` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-provisionedthroughput.html + */ + readCapacityUnits?: number | cdk.Token; + /** + * `CfnSimpleTable.ProvisionedThroughputProperty.WriteCapacityUnits` + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-provisionedthroughput.html + */ + writeCapacityUnits: number | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `ProvisionedThroughputProperty` + * + * @param properties - the TypeScript properties of a `ProvisionedThroughputProperty` + * + * @returns the result of the validation. + */ +function CfnSimpleTable_ProvisionedThroughputPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('readCapacityUnits', cdk.validateNumber)(properties.readCapacityUnits)); + errors.collect(cdk.propertyValidator('writeCapacityUnits', cdk.requiredValidator)(properties.writeCapacityUnits)); + errors.collect(cdk.propertyValidator('writeCapacityUnits', cdk.validateNumber)(properties.writeCapacityUnits)); + return errors.wrap('supplied properties not correct for "ProvisionedThroughputProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.ProvisionedThroughput` resource + * + * @param properties - the TypeScript properties of a `ProvisionedThroughputProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.ProvisionedThroughput` resource. + */ +// @ts-ignore TS6133 +function cfnSimpleTableProvisionedThroughputPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnSimpleTable_ProvisionedThroughputPropertyValidator(properties).assertSuccess(); + return { + ReadCapacityUnits: cdk.numberToCloudFormation(properties.readCapacityUnits), + WriteCapacityUnits: cdk.numberToCloudFormation(properties.writeCapacityUnits), + }; +} + +export namespace CfnSimpleTable { + /** + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-ssespecification.html + */ + export interface SSESpecificationProperty { + /** + * `CfnSimpleTable.SSESpecificationProperty.SSEEnabled` + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-ssespecification.html + */ + sseEnabled?: boolean | cdk.Token; + } +} + +/** + * Determine whether the given properties match those of a `SSESpecificationProperty` + * + * @param properties - the TypeScript properties of a `SSESpecificationProperty` + * + * @returns the result of the validation. + */ +function CfnSimpleTable_SSESpecificationPropertyValidator(properties: any): cdk.ValidationResult { + if (!cdk.canInspect(properties)) { return cdk.VALIDATION_SUCCESS; } + const errors = new cdk.ValidationResults(); + errors.collect(cdk.propertyValidator('sseEnabled', cdk.validateBoolean)(properties.sseEnabled)); + return errors.wrap('supplied properties not correct for "SSESpecificationProperty"'); +} + +/** + * Renders the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.SSESpecification` resource + * + * @param properties - the TypeScript properties of a `SSESpecificationProperty` + * + * @returns the AWS CloudFormation properties of an `AWS::Serverless::SimpleTable.SSESpecification` resource. + */ +// @ts-ignore TS6133 +function cfnSimpleTableSSESpecificationPropertyToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) { return properties; } + CfnSimpleTable_SSESpecificationPropertyValidator(properties).assertSuccess(); + return { + SSEEnabled: cdk.booleanToCloudFormation(properties.sseEnabled), + }; +} diff --git a/packages/@aws-cdk/aws-serverless/package-lock.json b/packages/@aws-cdk/aws-serverless/package-lock.json new file mode 100644 index 0000000000000..cb6dd241cad10 --- /dev/null +++ b/packages/@aws-cdk/aws-serverless/package-lock.json @@ -0,0 +1,742 @@ +{ + "name": "@aws-cdk/aws-serverless", + "version": "0.27.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "camelcase": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.2.0.tgz", + "integrity": "sha512-IXFsBS2pC+X0j0N/GE7Dm7j3bsEBp+oTpb7F50dwEVX7rf3IgwO9XatnegTsDtniKCUtEJH4fSU6Asw7uoVLfQ==", + "dev": true + }, + "case": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/case/-/case-1.6.1.tgz", + "integrity": "sha512-N0rDB5ftMDKANGsIBRWPWcG0VIKtirgqcXb2vKFi66ySAjXVEwbfCN7ass1mkdXO8fbol3RfbWlQ9KyBX2F/Gg==", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + } + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "codemaker": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/codemaker/-/codemaker-0.8.2.tgz", + "integrity": "sha512-agUY157vcCXFnJ9gDM8/iKbq62PWHkccnztbeuS1NZ8Pm9Z13Z9FHMuXWrh/zOQjggXpr5y3pPBpCHOlDYnHLw==", + "dev": true, + "requires": { + "camelcase": "^5.2.0", + "decamelize": "^1.2.0", + "fs-extra": "^7.0.1" + } + }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "date-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.0.0.tgz", + "integrity": "sha512-M6UqVvZVgFYqZL1SfHsRGIQSz3ZL+qgbsV5Lp1Vj61LZVYuEwcMXYay7DRDtYs2HQQBK5hQtQ0fD9aEJ89V0LA==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flatted": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", + "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==", + "dev": true + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "jsii": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/jsii/-/jsii-0.8.2.tgz", + "integrity": "sha512-VbRwBFWlmWDoiNoNreX96ii37LEwviFvfSyTY37tMZFXs8QSG5NIrR66JmNaF/ejbQpubqAKxW/Nb/YWCqhRbg==", + "dev": true, + "requires": { + "case": "^1.6.1", + "colors": "^1.3.3", + "deep-equal": "^1.0.1", + "fs-extra": "^7.0.1", + "jsii-spec": "^0.8.2", + "log4js": "^4.0.2", + "semver": "^5.6.0", + "sort-json": "^2.0.0", + "spdx-license-list": "^5.0.0", + "typescript": "^3.3.3333", + "yargs": "^13.2.2" + } + }, + "jsii-pacmak": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/jsii-pacmak/-/jsii-pacmak-0.8.2.tgz", + "integrity": "sha512-guqjntPr9uVkGGR+4fAralIoHpPsdCLsQsDf7Kh3GCRjgLn20JeHBgv4vT6Wym0nqjqL/5fY4nsXI/9w/OVZ0A==", + "dev": true, + "requires": { + "clone": "^2.1.2", + "codemaker": "^0.8.2", + "escape-string-regexp": "^1.0.5", + "fs-extra": "^7.0.1", + "jsii-spec": "^0.8.2", + "spdx-license-list": "^5.0.0", + "xmlbuilder": "^11.0.0", + "yargs": "^13.2.2" + } + }, + "jsii-spec": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/jsii-spec/-/jsii-spec-0.8.2.tgz", + "integrity": "sha512-eDL96xDOMwk5tzSBqbOi1Hh17Y42eLyovoo4CEcwaHpBRR0mSccPyjXyjAEWJVMg1yyA2UEX0DrPgJa2yY7oeQ==", + "dev": true, + "requires": { + "jsonschema": "^1.2.4" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonschema": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.4.tgz", + "integrity": "sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "log4js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.1.0.tgz", + "integrity": "sha512-eDa+zZPeVEeK6QGJAePyXM6pg4P3n3TO5rX9iZMVY48JshsTyLJZLIL5HipI1kQ2qLsSyOpUqNND/C5H4WhhiA==", + "dev": true, + "requires": { + "date-format": "^2.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.0", + "rfdc": "^1.1.2", + "streamroller": "^1.0.4" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.2.0.tgz", + "integrity": "sha512-5fJxa68urlY0Ir8ijatKa3eRz5lwXnRCTvo9+TbTGAuTFJOwpGcY0X05moBd0nW45965Njt4CDI2GFQoG8DvqA==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "mimic-fn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.0.0.tgz", + "integrity": "sha512-jbex9Yd/3lmICXwYT6gA/j2mNQGU48wCh/VzRd+/Y/PjYQtlg1gLMdZqvu9s/xH7qKvngxRObl56XZR609IMbA==", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz", + "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.1.0.tgz", + "integrity": "sha512-H2RyIJ7+A3rjkwKC2l5GGtU4H1vkxKCAGsWasNVd0Set+6i4znxbWy6/j16YDPJDWxhsgZiKAstMEP8wCdSpjA==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "rfdc": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.2.tgz", + "integrity": "sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sort-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-json/-/sort-json-2.0.0.tgz", + "integrity": "sha512-OgXPErPJM/rBK5OhzIJ+etib/BmLQ1JY55Nb/ElhoWUec62pXNF/X6DrecHq3NW5OAGX0KxYD7m0HtgB9dvGeA==", + "dev": true, + "requires": { + "detect-indent": "^5.0.0", + "detect-newline": "^2.1.0", + "minimist": "^1.2.0" + } + }, + "spdx-license-list": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-list/-/spdx-license-list-5.0.0.tgz", + "integrity": "sha512-N5u9tEFRBUzQDjMKRRt8SHxC/UaqYApPmdF4MMFnICQg3z52onNbnneuro/sWw2rd+eGu9agQOzUbD671Xia7Q==", + "dev": true + }, + "streamroller": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.4.tgz", + "integrity": "sha512-Wc2Gm5ygjSX8ZpW9J7Y9FwiSzTlKSvcl0FTTMd3rn7RoxDXpBW+xD9TY5sWL2n0UR61COB0LG1BQvN6nTUQbLQ==", + "dev": true, + "requires": { + "async": "^2.6.1", + "date-format": "^2.0.0", + "debug": "^3.1.0", + "fs-extra": "^7.0.0", + "lodash": "^4.17.10" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "typescript": { + "version": "3.3.4000", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.4000.tgz", + "integrity": "sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==", + "dev": true + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/packages/@aws-cdk/aws-serverless/package.json b/packages/@aws-cdk/aws-serverless/package.json index 43888b177757f..d3de4148cae51 100644 --- a/packages/@aws-cdk/aws-serverless/package.json +++ b/packages/@aws-cdk/aws-serverless/package.json @@ -1,7 +1,8 @@ { "name": "@aws-cdk/aws-serverless", - "version": "0.26.0", - "description": "The CDK Construct Library for AWS::Serverless", + "version": "0.28.0", + "deprecated": true, + "description": "This module was renamed to @aws-cdk/aws-sam, use this instead!", "main": "lib/index.js", "types": "lib/index.d.ts", "jsii": { @@ -29,18 +30,8 @@ }, "homepage": "https://github.com/awslabs/aws-cdk", "scripts": { - "build": "cdk-build", - "integ": "cdk-integ", - "lint": "cdk-lint", - "package": "cdk-package", - "pkglint": "pkglint -f", - "test": "cdk-test", - "watch": "cdk-watch", - "awslint": "cdk-awslint", - "cfn2ts": "cfn2ts" - }, - "cdk-build": { - "cloudformation": "AWS::Serverless" + "build": "jsii", + "package": "jsii-pacmak" }, "keywords": [ "aws", @@ -55,16 +46,16 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "jsii": "^0.8.2", + "jsii-pacmak": "^0.8.2" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-sam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-sam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" diff --git a/packages/@aws-cdk/aws-servicecatalog/package.json b/packages/@aws-cdk/aws-servicecatalog/package.json index 4648d55d9f3a7..7aac0a3e3a87a 100644 --- a/packages/@aws-cdk/aws-servicecatalog/package.json +++ b/packages/@aws-cdk/aws-servicecatalog/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-servicecatalog", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::ServiceCatalog", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-servicecatalog", + "module": "aws_cdk.aws_servicecatalog" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-servicecatalog" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} 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 2d5cafd623fe0..e2eaf06efb0c8 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/alias-target-instance.ts @@ -11,12 +11,12 @@ export interface AliasTargetInstanceProps extends BaseInstanceProps { /** * DNS name of the target */ - dnsName: string; + readonly dnsName: string; /** * The Cloudmap service this resource is registered to. */ - service: IService; + readonly service: IService; } /* diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts index c2df134d045fe..b465a59f7c22d 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts @@ -13,7 +13,7 @@ export interface CnameInstanceBaseProps extends BaseInstanceProps { * return in response to DNS queries, for example, example.com. This value is required if the * service specified by ServiceId includes settings for an CNAME record. */ - instanceCname: string; + readonly instanceCname: string; } /* @@ -23,7 +23,7 @@ export interface CnameInstanceProps extends CnameInstanceBaseProps { /** * The Cloudmap service this resource is registered to. */ - service: IService; + readonly service: IService; } /* diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts index 0d064ed48760c..8e8f7e05ba406 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/instance.ts @@ -23,14 +23,14 @@ export interface BaseInstanceProps { * * @default Automatically generated name */ - instanceId?: string; + readonly instanceId?: string; /** * Custom attributes of the instance. * * @default none */ - customAttributes?: { [key: string]: string }; + readonly customAttributes?: { [key: string]: string }; } export abstract class InstanceBase extends cdk.Construct implements IInstance { diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts index 64600d73608d2..799875309cc93 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/ip-instance.ts @@ -14,7 +14,7 @@ export interface IpInstanceBaseProps extends BaseInstanceProps { * * @default 80 */ - port?: number; + readonly port?: number; /** * If the service that you specify contains a template for an A record, the IPv4 address that you want AWS Cloud @@ -22,7 +22,7 @@ export interface IpInstanceBaseProps extends BaseInstanceProps { * * @default none */ - ipv4?: string; + readonly ipv4?: string; /** * If the service that you specify contains a template for an AAAA record, the IPv6 address that you want AWS Cloud @@ -30,7 +30,7 @@ export interface IpInstanceBaseProps extends BaseInstanceProps { * * @default none */ - ipv6?: string; + readonly ipv6?: string; } /* @@ -40,7 +40,7 @@ export interface IpInstanceProps extends IpInstanceBaseProps { /** * The Cloudmap service this resource is registered to. */ - service: IService; + readonly service: IService; } /* diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/namespace.ts b/packages/@aws-cdk/aws-servicediscovery/lib/namespace.ts index cdbeecc03ecf6..f25526e6427ac 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/namespace.ts @@ -20,20 +20,25 @@ export interface INamespace extends cdk.IConstruct { * Type of Namespace */ readonly type: NamespaceType; + + /** + * Export the namespace properties + */ + export(): NamespaceImportProps; } export interface BaseNamespaceProps { /** * A name for the Namespace. */ - name: string; + readonly name: string; /** * A description of the Namespace. * * @default none */ - description?: string; + readonly description?: string; } export interface NamespaceImportProps { @@ -82,4 +87,48 @@ export abstract class NamespaceBase extends cdk.Construct implements INamespace public abstract readonly namespaceArn: string; public abstract readonly namespaceName: string; public abstract readonly type: NamespaceType; + + public export(): NamespaceImportProps { + return { + namespaceName: new cdk.CfnOutput(this, 'NamespaceName', { value: this.namespaceArn }).makeImportValue().toString(), + namespaceArn: new cdk.CfnOutput(this, 'NamespaceArn', { value: this.namespaceArn }).makeImportValue().toString(), + namespaceId: new cdk.CfnOutput(this, 'NamespaceId', { value: this.namespaceId }).makeImportValue().toString(), + type: this.type, + }; + } +} + +// The class below exists purely so that users can still type Namespace.import(). +// It does not make sense to have HttpNamespace.import({ ..., type: NamespaceType.PublicDns }), +// but at the same time ecs.Cluster wants a type-generic export()/import(). Hence, we put +// it in Namespace. + +/** + * Static Namespace class + */ +export class Namespace { + /** + * Import a namespace + */ + public static import(scope: cdk.Construct, id: string, props: NamespaceImportProps): INamespace { + return new ImportedNamespace(scope, id, props); + } + + private constructor() { + } } + +class ImportedNamespace extends NamespaceBase { + public namespaceId: string; + public namespaceArn: string; + public namespaceName: string; + public type: NamespaceType; + + constructor(scope: cdk.Construct, id: string, props: NamespaceImportProps) { + super(scope, id); + this.namespaceId = props.namespaceId; + this.namespaceArn = props.namespaceArn; + this.namespaceName = props.namespaceName; + this.type = props.type; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts index 94c3c774daa6f..1f75457df6d6e 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/non-ip-instance.ts @@ -15,7 +15,7 @@ export interface NonIpInstanceProps extends NonIpInstanceBaseProps { /** * The Cloudmap service this resource is registered to. */ - service: IService; + readonly service: IService; } /* diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts b/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts index 7fa2a662b3bf1..538afea3e67d2 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/private-dns-namespace.ts @@ -8,7 +8,7 @@ export interface PrivateDnsNamespaceProps extends BaseNamespaceProps { /** * The Amazon VPC that you want to associate the namespace with. */ - vpc: ec2.IVpcNetwork; + readonly vpc: ec2.IVpcNetwork; } /** diff --git a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts index 6a640d71c7e4b..d5bbbf048078c 100644 --- a/packages/@aws-cdk/aws-servicediscovery/lib/service.ts +++ b/packages/@aws-cdk/aws-servicediscovery/lib/service.ts @@ -49,14 +49,14 @@ export interface BaseServiceProps { * * @default CloudFormation-generated name */ - name?: string; + readonly name?: string; /** * A description of the service. * * @default none */ - description?: string; + readonly description?: string; /** * Settings for an optional health check. If you specify health check settings, AWS Cloud Map associates the health @@ -66,7 +66,7 @@ export interface BaseServiceProps { * * @default none */ - healthCheck?: HealthCheckConfig; + readonly healthCheck?: HealthCheckConfig; /** * Structure containing failure threshold for a custom health checker. @@ -75,7 +75,7 @@ export interface BaseServiceProps { * * @default none */ - customHealthCheck?: HealthCheckCustomConfig; + readonly customHealthCheck?: HealthCheckCustomConfig; } /** @@ -89,7 +89,7 @@ export interface DnsServiceProps extends BaseServiceProps { * * @default A */ - dnsRecordType?: DnsRecordType; + readonly dnsRecordType?: DnsRecordType; /** * The amount of time, in seconds, that you want DNS resolvers to cache the settings for this @@ -97,7 +97,7 @@ export interface DnsServiceProps extends BaseServiceProps { * * @default 60 */ - dnsTtlSec?: number; + readonly dnsTtlSec?: number; /** * The routing policy that you want to apply to all DNS records that AWS Cloud Map creates when you @@ -105,7 +105,7 @@ export interface DnsServiceProps extends BaseServiceProps { * * @default WEIGHTED for CNAME records and when loadBalancer is true, MULTIVALUE otherwise */ - routingPolicy?: RoutingPolicy; + readonly routingPolicy?: RoutingPolicy; /** * Whether or not this service will have an Elastic LoadBalancer registered to it as an AliasTargetInstance. @@ -115,14 +115,14 @@ export interface DnsServiceProps extends BaseServiceProps { * * @default false */ - loadBalancer?: boolean; + readonly loadBalancer?: boolean; } export interface ServiceProps extends DnsServiceProps { /** * The ID of the namespace that you want to use for DNS configuration. */ - namespace: INamespace; + readonly namespace: INamespace; } /** @@ -262,43 +262,36 @@ export class Service extends cdk.Construct implements IService { /** * Registers a resource that is accessible using values other than an IP address or a domain name (CNAME). */ - public registerNonIpInstance(props: NonIpInstanceBaseProps): IInstance { - return new NonIpInstance(this, "NonIpInstance", { + public registerNonIpInstance(id: string, props: NonIpInstanceBaseProps): IInstance { + return new NonIpInstance(this, id, { service: this, - instanceId: props.instanceId, - customAttributes: props.customAttributes + ...props }); } /** * Registers a resource that is accessible using an IP address. */ - public registerIpInstance(props: IpInstanceBaseProps): IInstance { - return new IpInstance(this, "IpInstance", { + public registerIpInstance(id: string, props: IpInstanceBaseProps): IInstance { + return new IpInstance(this, id, { service: this, - instanceId: props.instanceId, - ipv4: props.ipv4, - ipv6: props.ipv6, - port: props.port, - customAttributes: props.customAttributes + ...props }); } /** * Registers a resource that is accessible using a CNAME. */ - public registerCnameInstance(props: CnameInstanceBaseProps): IInstance { - return new CnameInstance(this, "CnameInstance", { + public registerCnameInstance(id: string, props: CnameInstanceBaseProps): IInstance { + return new CnameInstance(this, id, { service: this, - instanceId: props.instanceId, - instanceCname: props.instanceCname, - customAttributes: props.customAttributes + ...props }); } } function renderDnsRecords(dnsRecordType: DnsRecordType, dnsTtlSec?: number): CfnService.DnsRecordProperty[] { - const ttl = dnsTtlSec !== undefined ? dnsTtlSec.toString() : '60'; + const ttl = dnsTtlSec !== undefined ? dnsTtlSec : 60; if (dnsRecordType === DnsRecordType.A_AAAA) { return [{ @@ -324,14 +317,14 @@ export interface HealthCheckConfig { * * @default HTTP */ - type?: HealthCheckType; + readonly type?: HealthCheckType; /** * The path that you want Route 53 to request when performing health checks. Do not use when health check type is TCP. * * @default '/' */ - resourcePath?: string; + readonly resourcePath?: string; /** * The number of consecutive health checks that an endpoint must pass or fail for Route 53 to change the current @@ -339,7 +332,7 @@ export interface HealthCheckConfig { * * @default 1 */ - failureThreshold?: number; + readonly failureThreshold?: number; } /** @@ -352,7 +345,7 @@ export interface HealthCheckCustomConfig { * * @default 1 */ - failureThreshold?: number; + readonly failureThreshold?: number; } export enum DnsRecordType { diff --git a/packages/@aws-cdk/aws-servicediscovery/package.json b/packages/@aws-cdk/aws-servicediscovery/package.json index eb934117c9fb1..3d794f7725e37 100644 --- a/packages/@aws-cdk/aws-servicediscovery/package.json +++ b/packages/@aws-cdk/aws-servicediscovery/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-servicediscovery", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::ServiceDiscovery", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-servicediscovery", + "module": "aws_cdk.aws_servicediscovery" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-servicediscovery" }, "scripts": { "build": "cdk-build", @@ -54,24 +59,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -87,4 +92,4 @@ "resource-interface:@aws-cdk/aws-servicediscovery.IPrivateDnsNamespace" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.expected.json b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.expected.json index 35fe96074827e..dee4619e4adad 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.expected.json +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.expected.json @@ -12,7 +12,7 @@ "DnsConfig": { "DnsRecords": [ { - "TTL": "30", + "TTL": 30, "Type": "CNAME" } ], @@ -49,4 +49,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.ts b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.ts index fb90f1964324c..9a5e541ed7aff 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-cname-record.lit.ts @@ -14,7 +14,7 @@ const service = namespace.createService('Service', { dnsTtlSec: 30 }); -service.registerCnameInstance({ +service.registerCnameInstance('CnameInstance', { instanceCname: 'service.pizza', }); diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.lit.ts b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.lit.ts index adb465fee7200..0f4466f31c759 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.lit.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-http-namespace.lit.ts @@ -12,7 +12,7 @@ const service1 = namespace.createService('NonIpService', { description: 'service registering non-ip instances', }); -service1.registerNonIpInstance({ +service1.registerNonIpInstance('NonIpInstance', { customAttributes: { arn: 'arn:aws:s3:::mybucket' } }); @@ -24,7 +24,7 @@ const service2 = namespace.createService('IpService', { } }); -service2.registerIpInstance({ +service2.registerIpInstance('IpInstance', { ipv4: '54.239.25.192', }); diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.lit.expected.json b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.lit.expected.json index 73b030ceb8334..0e1e8488ea1ea 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.lit.expected.json +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-private-dns-namespace.lit.expected.json @@ -358,11 +358,11 @@ "DnsConfig": { "DnsRecords": [ { - "TTL": "30", + "TTL": 30, "Type": "A" }, { - "TTL": "30", + "TTL": 30, "Type": "AAAA" } ], @@ -450,4 +450,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.expected.json b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.expected.json index 36f9e2ada5061..cb43861fb5ede 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.expected.json +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.expected.json @@ -12,7 +12,7 @@ "DnsConfig": { "DnsRecords": [ { - "TTL": "30", + "TTL": 30, "Type": "A" } ], @@ -55,4 +55,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.ts b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.ts index 04ed6e9b19a78..a63a4358c2ee6 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/integ.service-with-public-dns-namespace.lit.ts @@ -19,7 +19,7 @@ const service = namespace.createService('Service', { } }); -service.registerIpInstance({ +service.registerIpInstance('IpInstance', { ipv4: '54.239.25.192', port: 443 }); diff --git a/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts b/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts index bc17e6fae4ba8..5f0fa3951eb68 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/test.instance.ts @@ -1,4 +1,4 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { countResources, expect, haveResource } from '@aws-cdk/assert'; import ec2 = require('@aws-cdk/aws-ec2'); import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import cdk = require('@aws-cdk/cdk'); @@ -18,7 +18,7 @@ export = { name: 'service', }); - service.registerIpInstance({ + service.registerIpInstance('IpInstance', { ipv4: '10.0.0.0', ipv6: '0:0:0:0:0:ffff:a00:0', port: 443 @@ -56,7 +56,7 @@ export = { dnsRecordType: servicediscovery.DnsRecordType.A_AAAA }); - service.registerIpInstance({ + service.registerIpInstance('IpInstance', { ipv4: '54.239.25.192', ipv6: '0:0:0:0:0:ffff:a00:0', port: 443 @@ -96,7 +96,7 @@ export = { dnsRecordType: servicediscovery.DnsRecordType.A_AAAA }); - service.registerIpInstance({ + service.registerIpInstance('IpInstance', { ipv4: '10.0.0.0', ipv6: '0:0:0:0:0:ffff:a00:0', port: 443 @@ -136,7 +136,7 @@ export = { // THEN test.throws(() => { - service.registerIpInstance({ + service.registerIpInstance('IpInstance', { instanceId: 'id', }); }, /A `port` must be specified for a service using a `SRV` record./); @@ -159,7 +159,7 @@ export = { // THEN test.throws(() => { - service.registerIpInstance({ + service.registerIpInstance('IpInstance', { port: 3306 }); }, /At least `ipv4` or `ipv6` must be specified for a service using a `SRV` record./); @@ -182,7 +182,7 @@ export = { // THEN test.throws(() => { - service.registerIpInstance({ + service.registerIpInstance('IpInstance', { port: 3306 }); }, /An `ipv4` must be specified for a service using a `A` record./); @@ -205,7 +205,7 @@ export = { // THEN test.throws(() => { - service.registerIpInstance({ + service.registerIpInstance('IpInstance', { port: 3306 }); }, /An `ipv6` must be specified for a service using a `AAAA` record./); @@ -228,7 +228,7 @@ export = { // THEN test.throws(() => { - service.registerIpInstance({ + service.registerIpInstance('IpInstance', { port: 3306 }); }, /Service must support `A`, `AAAA` or `SRV` records to register this instance type./); @@ -338,7 +338,7 @@ export = { dnsRecordType: servicediscovery.DnsRecordType.CNAME }); - service.registerCnameInstance({ + service.registerCnameInstance('CnameInstance', { instanceCname: 'foo.com', customAttributes: { dogs: 'good' } }); @@ -375,7 +375,7 @@ export = { // THEN test.throws(() => { - service.registerCnameInstance({ + service.registerCnameInstance('CnameInstance', { instanceCname: 'foo.com', }); }, /Namespace associated with Service must be a DNS Namespace/); @@ -393,7 +393,7 @@ export = { const service = namespace.createService('MyService'); - service.registerNonIpInstance({ + service.registerNonIpInstance('NonIpInstance', { customAttributes: { dogs: 'good' } }); @@ -426,7 +426,7 @@ export = { // THEN test.throws(() => { - service.registerNonIpInstance({ + service.registerNonIpInstance('NonIpInstance', { instanceId: 'nonIp', }); }, /This type of instance can only be registered for HTTP namespaces./); @@ -446,7 +446,7 @@ export = { // THEN test.throws(() => { - service.registerNonIpInstance({ + service.registerNonIpInstance('NonIpInstance', { instanceId: 'nonIp', }); }, /You must specify at least one custom attribute for this instance type./); @@ -466,7 +466,7 @@ export = { // THEN test.throws(() => { - service.registerNonIpInstance({ + service.registerNonIpInstance('NonIpInstance', { instanceId: 'nonIp', customAttributes: {} }); @@ -474,4 +474,29 @@ export = { test.done(); }, + + 'Register multiple instances on the same service'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + const namespace = new servicediscovery.PublicDnsNamespace(stack, 'MyNamespace', { + name: 'public', + }); + + const service = namespace.createService('MyService'); + + // WHEN + service.registerIpInstance('First', { + ipv4: '10.0.0.0' + }); + + service.registerIpInstance('Second', { + ipv4: '10.0.0.1' + }); + + // THEN + expect(stack).to(countResources('AWS::ServiceDiscovery::Instance', 2)); + + test.done(); + } }; diff --git a/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts b/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts index f09c336ce53fb..dad41acdabea5 100644 --- a/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts +++ b/packages/@aws-cdk/aws-servicediscovery/test/test.service.ts @@ -134,7 +134,7 @@ export = { DnsConfig: { DnsRecords: [ { - TTL: "60", + TTL: 60, Type: "A" } ], @@ -191,11 +191,11 @@ export = { DnsConfig: { DnsRecords: [ { - TTL: "60", + TTL: 60, Type: "A" }, { - TTL: "60", + TTL: 60, Type: "AAAA" } ], @@ -248,7 +248,7 @@ export = { DnsConfig: { DnsRecords: [ { - TTL: "60", + TTL: 60, Type: "CNAME" } ], @@ -427,7 +427,7 @@ export = { DnsConfig: { DnsRecords: [ { - TTL: "60", + TTL: 60, Type: "A" } ], diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-filter.ts b/packages/@aws-cdk/aws-ses/lib/receipt-filter.ts index f715454906ae1..d5b8067656c75 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-filter.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-filter.ts @@ -25,21 +25,21 @@ export interface ReceiptFilterProps { * * @default a CloudFormation generated name */ - name?: string; + readonly name?: string; /** * The ip address or range to filter. * * @default 0.0.0.0/0 */ - ip?: string; + readonly ip?: string; /** * The policy for the filter. * * @default Block */ - policy?: ReceiptFilterPolicy; + readonly policy?: ReceiptFilterPolicy; } /** @@ -69,7 +69,7 @@ export interface WhiteListReceiptFilterProps { /** * A list of ip addresses or ranges to white list. */ - ips: string[]; + readonly ips: string[]; } /** diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-rule-action.ts b/packages/@aws-cdk/aws-ses/lib/receipt-rule-action.ts index 36c8a7892e105..dc6058262c35c 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-rule-action.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-rule-action.ts @@ -13,41 +13,41 @@ export interface ReceiptRuleActionProps { /** * Adds a header to the received email. */ - addHeaderAction?: CfnReceiptRule.AddHeaderActionProperty + readonly addHeaderAction?: CfnReceiptRule.AddHeaderActionProperty /** * Rejects the received email by returning a bounce response to the sender and, * optionally, publishes a notification to Amazon SNS. */ - bounceAction?: CfnReceiptRule.BounceActionProperty; + readonly bounceAction?: CfnReceiptRule.BounceActionProperty; /** * Calls an AWS Lambda function, and optionally, publishes a notification to * Amazon SNS. */ - lambdaAction?: CfnReceiptRule.LambdaActionProperty; + readonly lambdaAction?: CfnReceiptRule.LambdaActionProperty; /** * Saves the received message to an Amazon S3 bucket and, optionally, publishes * a notification to Amazon SNS. */ - s3Action?: CfnReceiptRule.S3ActionProperty; + readonly s3Action?: CfnReceiptRule.S3ActionProperty; /** * Publishes the email content within a notification to Amazon SNS. */ - snsAction?: CfnReceiptRule.SNSActionProperty; + readonly snsAction?: CfnReceiptRule.SNSActionProperty; /** * Terminates the evaluation of the receipt rule set and optionally publishes a * notification to Amazon SNS. */ - stopAction?: CfnReceiptRule.StopActionProperty; + readonly stopAction?: CfnReceiptRule.StopActionProperty; /** * Calls Amazon WorkMail and, optionally, publishes a notification to Amazon SNS. */ - workmailAction?: CfnReceiptRule.WorkmailActionProperty; + readonly workmailAction?: CfnReceiptRule.WorkmailActionProperty; } /** @@ -69,13 +69,13 @@ export interface ReceiptRuleAddHeaderActionProps { * inclusive, and consist of alphanumeric (a-z, A-Z, 0-9) characters * and dashes only. */ - name: string; + readonly name: string; /** * The value of the header to add. Must be less than 2048 characters, * and must not contain newline characters ("\r" or "\n"). */ - value: string; + readonly value: string; } /** @@ -117,21 +117,21 @@ export interface ReceiptRuleBounceActionTemplateProps { /** * Human-readable text to include in the bounce message. */ - message: string; + readonly message: string; /** * The SMTP reply code, as defined by RFC 5321. * * @see https://tools.ietf.org/html/rfc5321 */ - smtpReplyCode: string; + readonly smtpReplyCode: string; /** * The SMTP enhanced status code, as defined by RFC 3463. * * @see https://tools.ietf.org/html/rfc3463 */ - statusCode?: string; + readonly statusCode?: string; } /** @@ -186,20 +186,20 @@ export interface ReceiptRuleBounceActionProps { /** * The template containing the message, reply code and status code. */ - template: ReceiptRuleBounceActionTemplate; + readonly template: ReceiptRuleBounceActionTemplate; /** * The email address of the sender of the bounced email. This is the address * from which the bounce message will be sent. */ - sender: string; + readonly sender: string; /** * The SNS topic to notify when the bounce action is taken. * * @default no notification */ - topic?: sns.ITopic; + readonly topic?: sns.ITopic; } /** @@ -247,21 +247,21 @@ export interface ReceiptRuleLambdaActionProps { /** * The Lambda function to invoke. */ - function: lambda.IFunction + readonly function: lambda.IFunction /** * The invocation type of the Lambda function. * * @default Event */ - invocationType?: LambdaInvocationType; + readonly invocationType?: LambdaInvocationType; /** * The SNS topic to notify when the Lambda action is taken. * * @default no notification */ - topic?: sns.ITopic; + readonly topic?: sns.ITopic; } /** @@ -280,7 +280,7 @@ export class ReceiptRuleLambdaAction implements IReceiptRuleAction { this.props.function.addPermission(permissionId, { action: 'lambda:InvokeFunction', principal: new iam.ServicePrincipal('ses.amazonaws.com'), - sourceAccount: new cdk.ScopedAws().accountId + sourceAccount: cdk.Aws.accountId }); } @@ -301,7 +301,7 @@ export interface ReceiptRuleS3ActionProps { /** * The S3 bucket that incoming email will be saved to. */ - bucket: s3.IBucket; + readonly bucket: s3.IBucket; /** * The master key that SES should use to encrypt your emails before saving @@ -309,21 +309,21 @@ export interface ReceiptRuleS3ActionProps { * * @default no encryption */ - kmsKey?: kms.IEncryptionKey; + readonly kmsKey?: kms.IEncryptionKey; /** * The key prefix of the S3 bucket. * * @default no prefix */ - objectKeyPrefix?: string; + readonly objectKeyPrefix?: string; /** * The SNS topic to notify when the S3 action is taken. * * @default no notification */ - topic?: sns.ITopic; + readonly topic?: sns.ITopic; } /** @@ -344,7 +344,7 @@ export class ReceiptRuleS3Action implements IReceiptRuleAction { .addServicePrincipal('ses.amazonaws.com') .addResource(this.props.bucket.arnForObjects(`${keyPattern}*`)) .addCondition('StringEquals', { - 'aws:Referer': new cdk.ScopedAws().accountId + 'aws:Referer': cdk.Aws.accountId }); this.props.bucket.addToResourcePolicy(s3Statement); @@ -362,7 +362,7 @@ export class ReceiptRuleS3Action implements IReceiptRuleAction { 'kms:EncryptionContext:aws:ses:message-id': 'false' }, StringEquals: { - 'kms:EncryptionContext:aws:ses:source-account': new cdk.ScopedAws().accountId + 'kms:EncryptionContext:aws:ses:source-account': cdk.Aws.accountId } }); @@ -404,12 +404,12 @@ export interface ReceiptRuleSnsActionProps { * * @default UTF-8 */ - encoding?: EmailEncoding; + readonly encoding?: EmailEncoding; /** * The SNS topic to notify. */ - topic: sns.ITopic; + readonly topic: sns.ITopic; } /** @@ -436,7 +436,7 @@ export interface ReceiptRuleStopActionProps { /** * The SNS topic to notify when the stop action is taken. */ - topic?: sns.ITopic; + readonly topic?: sns.ITopic; } /** diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts b/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts index 54122a7a2ed62..268ae3a955e4b 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-rule-set.ts @@ -32,13 +32,13 @@ export interface ReceiptRuleSetProps { * * @default a CloudFormation generated name */ - name?: string; + readonly name?: string; /** * The list of rules to add to this rule set. Rules are added in the same * order as they appear in the list. */ - rules?: ReceiptRuleOptions[] + readonly rules?: ReceiptRuleOptions[] /** * Whether to add a first rule to stop processing messages @@ -46,7 +46,7 @@ export interface ReceiptRuleSetProps { * * @default false */ - dropSpam?: boolean; + readonly dropSpam?: boolean; } /** @@ -133,7 +133,7 @@ export interface ReceiptRuleSetImportProps { /** * The receipt rule set name. */ - name: string; + readonly name: string; } /** diff --git a/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts b/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts index 05956125bcc56..482d03e1faa5e 100644 --- a/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts +++ b/packages/@aws-cdk/aws-ses/lib/receipt-rule.ts @@ -43,49 +43,49 @@ export interface ReceiptRuleOptions { * one of the recipient email addresses or domains specified in the * receipt rule. */ - actions?: IReceiptRuleAction[]; + readonly actions?: IReceiptRuleAction[]; /** * An existing rule after which the new rule will be placed. * * @default the new rule is inserted at the beginning of the rule list */ - after?: IReceiptRule; + readonly after?: IReceiptRule; /** * Whether the rule is active. * * @default true */ - enabled?: boolean; + readonly enabled?: boolean; /** * The name for the rule * * @default a CloudFormation generated name */ - name?: string; + readonly name?: string; /** * The recipient domains and email addresses that the receipt rule applies to. * * @default match all recipients under all verified domains. */ - recipients?: string[]; + readonly recipients?: string[]; /** * Wheter to scan for spam and viruses. * * @default false */ - scanEnabled?: boolean; + readonly scanEnabled?: boolean; /** * The TLS policy * * @default Optional */ - tlsPolicy?: TlsPolicy; + readonly tlsPolicy?: TlsPolicy; } /** @@ -95,7 +95,7 @@ export interface ReceiptRuleProps extends ReceiptRuleOptions { /** * The name of the rule set that the receipt rule will be added to. */ - ruleSet: IReceiptRuleSet; + readonly ruleSet: IReceiptRuleSet; } /** @@ -166,7 +166,7 @@ export interface ReceiptRuleImportProps { /** * The name of the receipt rule. */ - name: string; + readonly name: string; } /** diff --git a/packages/@aws-cdk/aws-ses/package.json b/packages/@aws-cdk/aws-ses/package.json index a03404042df45..eb91846597539 100644 --- a/packages/@aws-cdk/aws-ses/package.json +++ b/packages/@aws-cdk/aws-ses/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ses", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::SES", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-ses", + "module": "aws_cdk.aws_ses" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-ses" }, "scripts": { "build": "cdk-build", @@ -54,30 +59,30 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ses/test/integ.receipt.expected.json b/packages/@aws-cdk/aws-ses/test/integ.receipt.expected.json index e74ddbee8d0b3..24acd6873532f 100644 --- a/packages/@aws-cdk/aws-ses/test/integ.receipt.expected.json +++ b/packages/@aws-cdk/aws-ses/test/integ.receipt.expected.json @@ -68,7 +68,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "Function76856677" + "Fn::GetAtt": [ + "Function76856677", + "Arn" + ] }, "Principal": "ses.amazonaws.com", "SourceAccount": { @@ -405,7 +408,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "SingletonLambda224e77f9a32e4b4dac32983477abba164533EA15" + "Fn::GetAtt": [ + "SingletonLambda224e77f9a32e4b4dac32983477abba164533EA15", + "Arn" + ] }, "Principal": "ses.amazonaws.com", "SourceAccount": { @@ -436,4 +442,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ses/test/test.receipt-rule-action.ts b/packages/@aws-cdk/aws-ses/test/test.receipt-rule-action.ts index 4ac0e349169cb..0d92ac3a8420f 100644 --- a/packages/@aws-cdk/aws-ses/test/test.receipt-rule-action.ts +++ b/packages/@aws-cdk/aws-ses/test/test.receipt-rule-action.ts @@ -179,7 +179,10 @@ export = { expect(stack).to(haveResource('AWS::Lambda::Permission', { Action: 'lambda:InvokeFunction', FunctionName: { - Ref: 'Function76856677' + 'Fn::GetAtt': [ + 'Function76856677', + 'Arn' + ] }, Principal: 'ses.amazonaws.com', SourceAccount: { diff --git a/packages/@aws-cdk/aws-sns/lib/policy.ts b/packages/@aws-cdk/aws-sns/lib/policy.ts index 81587863d40bb..e39ad6f4d75e1 100644 --- a/packages/@aws-cdk/aws-sns/lib/policy.ts +++ b/packages/@aws-cdk/aws-sns/lib/policy.ts @@ -7,7 +7,7 @@ export interface TopicPolicyProps { /** * The set of topics this policy applies to. */ - topics: ITopic[]; + readonly topics: ITopic[]; } /** diff --git a/packages/@aws-cdk/aws-sns/lib/subscription.ts b/packages/@aws-cdk/aws-sns/lib/subscription.ts index f823d4d711347..a6b545cfd6758 100644 --- a/packages/@aws-cdk/aws-sns/lib/subscription.ts +++ b/packages/@aws-cdk/aws-sns/lib/subscription.ts @@ -9,19 +9,19 @@ export interface SubscriptionProps { /** * What type of subscription to add. */ - protocol: SubscriptionProtocol; + readonly protocol: SubscriptionProtocol; /** * The subscription endpoint. * * The meaning of this value depends on the value for 'protocol'. */ - endpoint: any; + readonly endpoint: any; /** * The topic to subscribe to. */ - topic: ITopic; + readonly topic: ITopic; /** * true if raw message delivery is enabled for the subscription. Raw messages are free of JSON formatting and can be @@ -30,7 +30,7 @@ export interface SubscriptionProps { * * @default false */ - rawMessageDelivery?: boolean; + readonly rawMessageDelivery?: boolean; } /** diff --git a/packages/@aws-cdk/aws-sns/lib/topic-base.ts b/packages/@aws-cdk/aws-sns/lib/topic-base.ts index 4e3560d87c76a..f455cb8648dcb 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic-base.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic-base.ts @@ -83,7 +83,7 @@ export interface ITopic extends /** * Grant topic publishing permissions to the given identity */ - grantPublish(identity?: iam.IPrincipal): void; + grantPublish(identity: iam.IGrantable): iam.Grant; } /** @@ -270,14 +270,13 @@ export abstract class TopicBase extends cdk.Construct implements ITopic { /** * Grant topic publishing permissions to the given identity */ - public grantPublish(identity?: iam.IPrincipal) { - if (!identity) { - return; - } - - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.topicArn) - .addActions('sns:Publish')); + public grantPublish(grantee: iam.IGrantable) { + return iam.Grant.addToPrincipalOrResource({ + grantee, + actions: ['sns:Publish'], + resourceArns: [this.topicArn], + resource: this, + }); } /** @@ -346,8 +345,8 @@ export abstract class TopicBase extends cdk.Construct implements ITopic { * Reference to an external topic. */ export interface TopicImportProps { - topicArn: string; - topicName: string; + readonly topicArn: string; + readonly topicName: string; } /** @@ -360,5 +359,5 @@ export interface EmailSubscriptionOptions { * * @default Message text (false) */ - json?: boolean; + readonly json?: boolean; } diff --git a/packages/@aws-cdk/aws-sns/lib/topic.ts b/packages/@aws-cdk/aws-sns/lib/topic.ts index de21f2e4ba433..04d5b7744215e 100644 --- a/packages/@aws-cdk/aws-sns/lib/topic.ts +++ b/packages/@aws-cdk/aws-sns/lib/topic.ts @@ -11,7 +11,7 @@ export interface TopicProps { * * @default None */ - displayName?: string; + readonly displayName?: string; /** * A name for the topic. @@ -22,7 +22,7 @@ export interface TopicProps { * * @default Generated name */ - topicName?: string; + readonly topicName?: string; } /** diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index 43c3ba77b5700..b6712107a2add 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sns", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS SNS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,7 +20,11 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-sns", + "module": "aws_cdk.aws_sns" + } }, "excludeTypescript": [ "examples" @@ -28,7 +32,8 @@ }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-sns" }, "scripts": { "build": "cdk-build", @@ -57,35 +62,35 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-s3-notifications": "^0.26.0", - "@aws-cdk/aws-sqs": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling-api": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3-notifications": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-s3-notifications": "^0.26.0", - "@aws-cdk/aws-sqs": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling-api": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-s3-notifications": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json index 7381f64431f7b..62614184e66a6 100644 --- a/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json +++ b/packages/@aws-cdk/aws-sns/test/integ.sns-lambda.expected.json @@ -83,7 +83,10 @@ "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "Echo11F3FB29" + "Fn::GetAtt": [ + "Echo11F3FB29", + "Arn" + ] }, "Principal": "sns.amazonaws.com", "SourceArn": { @@ -92,4 +95,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-sns/test/integ.sns.expected.json b/packages/@aws-cdk/aws-sns/test/integ.sns.expected.json index 1b962de49eed7..8e5512e49281d 100644 --- a/packages/@aws-cdk/aws-sns/test/integ.sns.expected.json +++ b/packages/@aws-cdk/aws-sns/test/integ.sns.expected.json @@ -8,4 +8,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-sns/test/test.sns.ts b/packages/@aws-cdk/aws-sns/test/test.sns.ts index e5af097c571b0..4a952522f73c4 100644 --- a/packages/@aws-cdk/aws-sns/test/test.sns.ts +++ b/packages/@aws-cdk/aws-sns/test/test.sns.ts @@ -355,7 +355,10 @@ export = { "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyFunc8A243A2C" + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] }, "Principal": "sns.amazonaws.com", "SourceArn": { @@ -554,7 +557,10 @@ export = { "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "MyFunc8A243A2C" + "Fn::GetAtt": [ + "MyFunc8A243A2C", + "Arn" + ] }, "Principal": "sns.amazonaws.com", "SourceArn": { diff --git a/packages/@aws-cdk/aws-sqs/lib/policy.ts b/packages/@aws-cdk/aws-sqs/lib/policy.ts index 26526764d8b86..a6419b9cfb961 100644 --- a/packages/@aws-cdk/aws-sqs/lib/policy.ts +++ b/packages/@aws-cdk/aws-sqs/lib/policy.ts @@ -7,7 +7,7 @@ export interface QueuePolicyProps { /** * The set of queues this policy applies to. */ - queues: IQueue[]; + readonly queues: IQueue[]; } /** diff --git a/packages/@aws-cdk/aws-sqs/lib/queue-base.ts b/packages/@aws-cdk/aws-sqs/lib/queue-base.ts index 1c35629933694..1a281b5691fdd 100644 --- a/packages/@aws-cdk/aws-sqs/lib/queue-base.ts +++ b/packages/@aws-cdk/aws-sqs/lib/queue-base.ts @@ -53,9 +53,9 @@ export interface IQueue extends cdk.IConstruct, s3n.IBucketNotificationDestinati * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant consume rights to + * @param grantee Principal to grant consume rights to */ - grantConsumeMessages(identity?: iam.IPrincipal): void; + grantConsumeMessages(grantee: iam.IGrantable): iam.Grant; /** * Grant access to send messages to a queue to the given identity. @@ -67,9 +67,9 @@ export interface IQueue extends cdk.IConstruct, s3n.IBucketNotificationDestinati * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant send rights to + * @param grantee Principal to grant send rights to */ - grantSendMessages(identity?: iam.IPrincipal): void; + grantSendMessages(grantee: iam.IGrantable): iam.Grant; /** * Grant an IAM principal permissions to purge all messages from the queue. @@ -80,19 +80,19 @@ export interface IQueue extends cdk.IConstruct, s3n.IBucketNotificationDestinati * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant send rights to + * @param grantee Principal to grant send rights to * @param queueActions additional queue actions to allow */ - grantPurge(identity?: iam.IPrincipal): void; + grantPurge(grantee: iam.IGrantable): iam.Grant; /** * Grant the actions defined in queueActions to the identity Principal given * on this SQS queue resource. * - * @param identity Principal to grant right to + * @param grantee Principal to grant right to * @param queueActions The actions to grant */ - grant(identity?: iam.IPrincipal, ...queueActions: string[]): void; + grant(grantee: iam.IGrantable, ...queueActions: string[]): iam.Grant; } /** @@ -214,10 +214,10 @@ export abstract class QueueBase extends cdk.Construct implements IQueue { * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant consume rights to + * @param grantee Principal to grant consume rights to */ - public grantConsumeMessages(identity?: iam.IPrincipal) { - this.grant(identity, + public grantConsumeMessages(grantee: iam.IGrantable) { + return this.grant(grantee, 'sqs:ReceiveMessage', 'sqs:ChangeMessageVisibility', 'sqs:ChangeMessageVisibilityBatch', @@ -237,10 +237,10 @@ export abstract class QueueBase extends cdk.Construct implements IQueue { * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant send rights to + * @param grantee Principal to grant send rights to */ - public grantSendMessages(identity?: iam.IPrincipal) { - this.grant(identity, + public grantSendMessages(grantee: iam.IGrantable) { + return this.grant(grantee, 'sqs:SendMessage', 'sqs:SendMessageBatch', 'sqs:GetQueueAttributes', @@ -256,11 +256,11 @@ export abstract class QueueBase extends cdk.Construct implements IQueue { * - sqs:GetQueueAttributes * - sqs:GetQueueUrl * - * @param identity Principal to grant send rights to + * @param grantee Principal to grant send rights to * @param queueActions additional queue actions to allow */ - public grantPurge(identity?: iam.IPrincipal) { - this.grant(identity, + public grantPurge(grantee: iam.IGrantable) { + return this.grant(grantee, 'sqs:PurgeQueue', 'sqs:GetQueueAttributes', 'sqs:GetQueueUrl'); @@ -270,17 +270,16 @@ export abstract class QueueBase extends cdk.Construct implements IQueue { * Grant the actions defined in queueActions to the identity Principal given * on this SQS queue resource. * - * @param identity Principal to grant right to - * @param queueActions The actions to grant + * @param grantee Principal to grant right to + * @param actions The actions to grant */ - public grant(identity?: iam.IPrincipal, ...queueActions: string[]) { - if (!identity) { - return; - } - - identity.addToPolicy(new iam.PolicyStatement() - .addResource(this.queueArn) - .addActions(...queueActions)); + public grant(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipalOrResource({ + grantee, + actions, + resourceArns: [this.queueArn], + resource: this, + }); } } @@ -291,21 +290,21 @@ export interface QueueImportProps { /** * The ARN of the queue. */ - queueArn: string; + readonly queueArn: string; /** * The URL of the queue. */ - queueUrl: string; + readonly queueUrl: string; /** * The name of the queue. * @default if queue name is not specified, the name will be derived from the queue ARN */ - queueName?: string; + readonly queueName?: string; /** * KMS encryption key, if this queue is server-side encrypted by a KMS key. */ - keyArn?: string; + readonly keyArn?: string; } diff --git a/packages/@aws-cdk/aws-sqs/lib/queue.ts b/packages/@aws-cdk/aws-sqs/lib/queue.ts index f7d0fb76946bc..b9e45d1b11071 100644 --- a/packages/@aws-cdk/aws-sqs/lib/queue.ts +++ b/packages/@aws-cdk/aws-sqs/lib/queue.ts @@ -15,7 +15,7 @@ export interface QueueProps { * * @default CloudFormation-generated name */ - queueName?: string; + readonly queueName?: string; /** * The number of seconds that Amazon SQS retains a message. @@ -25,7 +25,7 @@ export interface QueueProps { * * @default 345600 seconds (4 days) */ - retentionPeriodSec?: number; + readonly retentionPeriodSec?: number; /** * The time in seconds that the delivery of all messages in the queue is delayed. @@ -35,7 +35,7 @@ export interface QueueProps { * * @default 0 */ - deliveryDelaySec?: number; + readonly deliveryDelaySec?: number; /** * The limit of how many bytes that a message can contain before Amazon SQS rejects it. @@ -45,7 +45,7 @@ export interface QueueProps { * * @default 256KiB */ - maxMessageSizeBytes?: number; + readonly maxMessageSizeBytes?: number; /** * Default wait time for ReceiveMessage calls. @@ -57,7 +57,7 @@ export interface QueueProps { * * @default 0 */ - receiveMessageWaitTimeSec?: number; + readonly receiveMessageWaitTimeSec?: number; /** * Timeout of processing a single message. @@ -71,14 +71,14 @@ export interface QueueProps { * * @default 30 */ - visibilityTimeoutSec?: number; + readonly visibilityTimeoutSec?: number; /** * Send messages to this queue if they were unsuccessfully dequeued a number of times. * * @default no dead-letter queue */ - deadLetterQueue?: DeadLetterQueue; + readonly deadLetterQueue?: DeadLetterQueue; /** * Whether the contents of the queue are encrypted, and by what type of key. @@ -88,7 +88,7 @@ export interface QueueProps { * * @default Unencrypted */ - encryption?: QueueEncryption; + readonly encryption?: QueueEncryption; /** * External KMS master key to use for queue encryption. @@ -103,7 +103,7 @@ export interface QueueProps { * * @default If encryption is set to KMS and not specified, a key will be created. */ - encryptionMasterKey?: kms.IEncryptionKey; + readonly encryptionMasterKey?: kms.IEncryptionKey; /** * The length of time that Amazon SQS reuses a data key before calling KMS again. @@ -113,14 +113,14 @@ export interface QueueProps { * * @default 300 (5 minutes) */ - dataKeyReuseSec?: number; + readonly dataKeyReuseSec?: number; /** * Whether this a first-in-first-out (FIFO) queue. * * @default false, unless queueName ends in '.fifo' or 'contentBasedDeduplication' is true. */ - fifo?: boolean; + readonly fifo?: boolean; /** * Specifies whether to enable content-based deduplication. @@ -136,7 +136,7 @@ export interface QueueProps { * * @default false */ - contentBasedDeduplication?: boolean; + readonly contentBasedDeduplication?: boolean; } /** @@ -146,12 +146,12 @@ export interface DeadLetterQueue { /** * The dead-letter queue to which Amazon SQS moves messages after the value of maxReceiveCount is exceeded. */ - queue: IQueue; + readonly queue: IQueue; /** * The number of times a message can be unsuccesfully dequeued before being moved to the dead-letter queue. */ - maxReceiveCount: number; + readonly maxReceiveCount: number; } /** diff --git a/packages/@aws-cdk/aws-sqs/package-lock.json b/packages/@aws-cdk/aws-sqs/package-lock.json index d706a9e70d18e..3e518f04ac96f 100644 --- a/packages/@aws-cdk/aws-sqs/package-lock.json +++ b/packages/@aws-cdk/aws-sqs/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sqs", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index c07cd7e53a8be..e17607d0cb435 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-sqs", - "version": "0.26.0", + "version": "0.28.0", "description": "CDK Constructs for AWS SQS", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-sqs", + "module": "aws_cdk.aws_sqs" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-sqs" }, "scripts": { "build": "cdk-build", @@ -54,30 +59,30 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", "aws-sdk": "^2.259.1", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-s3-notifications": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling-api": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-s3-notifications": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-autoscaling-api": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-s3-notifications": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-autoscaling-api": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-s3-notifications": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -87,4 +92,4 @@ "resource-attribute:@aws-cdk/aws-sqs.IQueue.queueName" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts index 6566aa0903997..2c574c712f3c7 100644 --- a/packages/@aws-cdk/aws-sqs/test/test.sqs.ts +++ b/packages/@aws-cdk/aws-sqs/test/test.sqs.ts @@ -1,4 +1,4 @@ -import { expect, haveResource } from '@aws-cdk/assert'; +import { expect, haveResource, SynthUtils } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import s3 = require('@aws-cdk/aws-s3'); @@ -107,7 +107,7 @@ export = { test.deepEqual(stack.node.resolve(imports.queueUrl), { 'Fn::ImportValue': 'Stack:QueueQueueUrlC30FF916' }); // the exporting stack has Outputs for QueueARN and QueueURL - const outputs = stack._toCloudFormation().Outputs; + const outputs = SynthUtils.toCloudFormation(stack).Outputs; // tslint:disable-next-line:max-line-length test.deepEqual(outputs.QueueQueueArn8CF496D5, { Value: { 'Fn::GetAtt': [ 'Queue4A7E3555', 'Arn' ] }, Export: { Name: 'Stack:QueueQueueArn8CF496D5' } }); test.deepEqual(outputs.QueueQueueUrlC30FF916, { Value: { Ref: 'Queue4A7E3555' }, Export: { Name: 'Stack:QueueQueueUrlC30FF916' } }); @@ -253,7 +253,7 @@ export = { keyArn: { 'Fn::ImportValue': 'Stack:QueueWithCustomKeyKeyArn537F6E42' } }); - test.deepEqual(stack._toCloudFormation().Outputs, { + test.deepEqual(SynthUtils.toCloudFormation(stack).Outputs, { "QueueWithCustomKeyQueueArnD326BB9B": { "Value": { "Fn::GetAtt": [ @@ -301,7 +301,7 @@ export = { keyArn: { 'Fn::ImportValue': 'Stack:QueueWithManagedKeyKeyArn9C42A85D' } }); - test.deepEqual(stack._toCloudFormation().Outputs, { + test.deepEqual(SynthUtils.toCloudFormation(stack).Outputs, { "QueueWithManagedKeyQueueArn8798A14E": { "Value": { "Fn::GetAtt": [ @@ -407,7 +407,7 @@ export = { // make sure the queue policy is added as a dependency to the bucket // notifications resource so it will be created first. - test.deepEqual(stack._toCloudFormation().Resources.BucketNotifications8F2E257D.DependsOn, [ 'QueuePolicy25439813' ]); + test.deepEqual(SynthUtils.toCloudFormation(stack).Resources.BucketNotifications8F2E257D.DependsOn, [ 'QueuePolicy25439813' ]); test.done(); }, diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter-store-string.ts b/packages/@aws-cdk/aws-ssm/lib/parameter-store-string.ts index d945cef0cc2d2..b47940dfc6d86 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter-store-string.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter-store-string.ts @@ -7,14 +7,14 @@ export interface ParameterStoreStringProps { /** * The name of the parameter store value */ - parameterName: string; + readonly parameterName: string; /** * The version number of the value you wish to retrieve. * * @default The latest version will be retrieved. */ - version?: number; + readonly version?: number; } /** @@ -28,6 +28,12 @@ export class ParameterStoreString extends cdk.Construct { constructor(scope: cdk.Construct, id: string, props: ParameterStoreStringProps) { super(scope, id); + // If we don't validate this here it will lead to a very unclear + // error message in CloudFormation, so better do it. + if (!props.parameterName) { + throw new Error('ParameterStoreString: parameterName cannot be empty'); + } + // We use a different inner construct depend on whether we want the latest // or a specific version. // @@ -42,11 +48,8 @@ export class ParameterStoreString extends cdk.Construct { this.stringValue = param.stringValue; } else { // Use a dynamic reference - const dynRef = new cdk.DynamicReference(this, 'Reference', { - service: cdk.DynamicReferenceService.Ssm, - referenceKey: `${props.parameterName}:${props.version}`, - }); - this.stringValue = dynRef.stringValue; + const dynRef = new cdk.CfnDynamicReference(cdk.CfnDynamicReferenceService.Ssm, `${props.parameterName}:${props.version}`); + this.stringValue = dynRef.toString(); } } } @@ -58,12 +61,12 @@ export interface ParameterStoreSecureStringProps { /** * The name of the parameter store secure string value */ - parameterName: string; + readonly parameterName: string; /** * The version number of the value you wish to retrieve. */ - version: number; + readonly version: number; } /** @@ -74,11 +77,14 @@ export interface ParameterStoreSecureStringProps { * * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html */ -export class ParameterStoreSecureString extends cdk.DynamicReference { - constructor(scope: cdk.Construct, id: string, props: ParameterStoreSecureStringProps) { - super(scope, id, { - service: cdk.DynamicReferenceService.SsmSecure, - referenceKey: `${props.parameterName}:${props.version}`, - }); +export class ParameterStoreSecureString extends cdk.CfnDynamicReference { + constructor(props: ParameterStoreSecureStringProps) { + super(cdk.CfnDynamicReferenceService.SsmSecure, `${props.parameterName}:${props.version}`); + + // If we don't validate this here it will lead to a very unclear + // error message in CloudFormation, so better do it. + if (!props.parameterName) { + throw new Error('ParameterStoreSecureString: parameterName cannot be empty'); + } } } diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter.ts b/packages/@aws-cdk/aws-ssm/lib/parameter.ts index 2ef28b329928e..47de0226b1fbf 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter.ts @@ -26,14 +26,14 @@ export interface IParameter extends cdk.IConstruct { * * @param grantee the role to be granted read-only access to the parameter. */ - grantRead(grantee: iam.IPrincipal): void; + grantRead(grantee: iam.IGrantable): iam.Grant; /** * Grants write (PutParameter) permissions on the SSM Parameter. * * @param grantee the role to be granted write access to the parameter. */ - grantWrite(grantee: iam.IPrincipal): void; + grantWrite(grantee: iam.IGrantable): iam.Grant; } /** @@ -67,21 +67,21 @@ export interface ParameterProps { * * @default no validation is performed */ - allowedPattern?: string; + readonly allowedPattern?: string; /** * Information about the parameter that you want to add to the system. * * @default none */ - description?: string; + readonly description?: string; /** * The name of the parameter. * * @default a name will be generated by CloudFormation */ - name?: string; + readonly name?: string; } /** @@ -91,7 +91,7 @@ export interface StringParameterProps extends ParameterProps { /** * The value of the parameter. It may not reference another parameter and ``{{}}`` cannot be used in the value. */ - stringValue: string; + readonly stringValue: string; } /** @@ -101,7 +101,7 @@ export interface StringListParameterProps extends ParameterProps { /** * The values of the parameter. It may not reference another parameter and ``{{}}`` cannot be used in the value. */ - stringListValue: string[]; + readonly stringListValue: string[]; } /** @@ -124,18 +124,20 @@ export abstract class ParameterBase extends cdk.Construct implements IParameter }); } - public grantRead(grantee: iam.IPrincipal): void { - grantee.addToPolicy(new iam.PolicyStatement() - .allow() - .addActions('ssm:DescribeParameters', 'ssm:GetParameter', 'ssm:GetParameterHistory') - .addResource(this.parameterArn)); + public grantRead(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['ssm:DescribeParameters', 'ssm:GetParameter', 'ssm:GetParameterHistory'], + resourceArns: [this.parameterArn], + }); } - public grantWrite(grantee: iam.IPrincipal): void { - grantee.addToPolicy(new iam.PolicyStatement() - .allow() - .addAction('ssm:PutParameter') - .addResource(this.parameterArn)); + public grantWrite(grantee: iam.IGrantable): iam.Grant { + return iam.Grant.addToPrincipal({ + grantee, + actions: ['ssm:PutParameter'], + resourceArns: [this.parameterArn], + }); } } diff --git a/packages/@aws-cdk/aws-ssm/package.json b/packages/@aws-cdk/aws-ssm/package.json index 75c5d46f22402..bfb5b6c4e1597 100644 --- a/packages/@aws-cdk/aws-ssm/package.json +++ b/packages/@aws-cdk/aws-ssm/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-ssm", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::SSM", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-ssm", + "module": "aws_cdk.aws_ssm" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-ssm" }, "scripts": { "build": "cdk-build", @@ -54,20 +59,20 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -85,4 +90,4 @@ "*:@aws-cdk/aws-ssm.ResourceDataSync" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts b/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts index 261ec9363cf86..a00e6575511b0 100644 --- a/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts +++ b/packages/@aws-cdk/aws-ssm/test/integ.parameter-store-string.lit.ts @@ -26,10 +26,10 @@ class UsingStack extends cdk.Stack { // Retrieve a specific version of the secret (SecureString) parameter. // 'version' is always required. - const secretValue = new ssm.ParameterStoreSecureString(this, 'SecretValue', { + const secretValue = new ssm.ParameterStoreSecureString({ parameterName: '/My/Secret/Parameter', version: 5 - }).stringValue; + }); /// !hide new cdk.CfnOutput(this, 'TheValue', { value: stringValue }); diff --git a/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts b/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts index 7d2d5e1a9c9c8..1ee43b2df1f0b 100644 --- a/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts +++ b/packages/@aws-cdk/aws-ssm/test/test.parameter-store-string.ts @@ -49,13 +49,27 @@ export = { const stack = new cdk.Stack(); // WHEN - const ref = new ssm.ParameterStoreSecureString(stack, 'Ref', { + const ref = new ssm.ParameterStoreSecureString({ parameterName: '/some/key', version: 123 }); // THEN - test.equal(ref.node.resolve(ref.stringValue), '{{resolve:ssm-secure:/some/key:123}}'); + test.equal(stack.node.resolve(ref), '{{resolve:ssm-secure:/some/key:123}}'); + + test.done(); + }, + + 'empty parameterName will throw'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + test.throws(() => { + new ssm.ParameterStoreString(stack, 'Ref', { + parameterName: '', + }); + }, /parameterName cannot be empty/); test.done(); }, diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index ef0fe1b9db326..8a8c0bac3913d 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -9,7 +9,7 @@ export interface ActivityProps { * * @default If not supplied, a name is generated */ - activityName?: string; + readonly activityName?: string; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine-fragment.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine-fragment.ts index 72586781c3028..5998a53597c9e 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine-fragment.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine-fragment.ts @@ -70,12 +70,12 @@ export interface SingleStateOptions extends ParallelProps { * * @default Construct ID of the StateMachineFragment */ - stateId?: string; + readonly stateId?: string; /** * String to prefix all stateIds in the state machine with * * @default stateId */ - prefixStates?: string; + readonly prefixStates?: string; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index eec511e368d39..8cf98f1471181 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -15,26 +15,26 @@ export interface StateMachineProps { * * @default A name is automatically generated */ - stateMachineName?: string; + readonly stateMachineName?: string; /** * Definition for this state machine */ - definition: IChainable; + readonly definition: IChainable; /** * The execution role for the state machine service * * @default A role is automatically created */ - role?: iam.Role; + readonly role?: iam.Role; /** * Maximum run time for this state machine * * @default No timeout */ - timeoutSec?: number; + readonly timeoutSec?: number; } /** @@ -221,7 +221,7 @@ export interface StateMachineImportProps { /** * The ARN of the state machine */ - stateMachineArn: string; + readonly stateMachineArn: string; } class ImportedStateMachine extends cdk.Construct implements IStateMachine { diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/choice.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/choice.ts index 2352dfc4e5e6c..d62e29ca74277 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/choice.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/choice.ts @@ -13,7 +13,7 @@ export interface ChoiceProps { * * @default No comment */ - comment?: string; + readonly comment?: string; /** * JSONPath expression to select part of the state to be the input to this state. @@ -23,7 +23,7 @@ export interface ChoiceProps { * * @default $ */ - inputPath?: string; + readonly inputPath?: string; /** * JSONPath expression to select part of the state to be the output to this state. @@ -33,7 +33,7 @@ export interface ChoiceProps { * * @default $ */ - outputPath?: string; + readonly outputPath?: string; } /** @@ -109,7 +109,7 @@ export interface AfterwardsOptions { * * @default false */ - includeErrorHandlers?: boolean; + readonly includeErrorHandlers?: boolean; /** * Whether to include the default/otherwise transition for the current Choice state @@ -119,7 +119,7 @@ export interface AfterwardsOptions { * * @default false */ - includeOtherwise?: boolean; + readonly includeOtherwise?: boolean; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/fail.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/fail.ts index f033e32d7cd43..5a7ce49557e46 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/fail.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/fail.ts @@ -11,19 +11,21 @@ export interface FailProps { * * @default No comment */ - comment?: string; + readonly comment?: string; /** * Error code used to represent this failure + * + * @default No error code */ - error: string; + readonly error?: string; /** * A description for the cause of the failure * * @default No description */ - cause?: string; + readonly cause?: string; } /** @@ -34,10 +36,10 @@ export interface FailProps { export class Fail extends State { public readonly endStates: INextable[] = []; - private readonly error: string; + private readonly error?: string; private readonly cause?: string; - constructor(scope: cdk.Construct, id: string, props: FailProps) { + constructor(scope: cdk.Construct, id: string, props: FailProps = {}) { super(scope, id, props); this.error = props.error; @@ -55,4 +57,4 @@ export class Fail extends State { Cause: this.cause, }; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts index 22bd2fe03eca4..dbf54f6f6048f 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/parallel.ts @@ -13,7 +13,7 @@ export interface ParallelProps { * * @default No comment */ - comment?: string; + readonly comment?: string; /** * JSONPath expression to select part of the state to be the input to this state. @@ -23,7 +23,7 @@ export interface ParallelProps { * * @default $ */ - inputPath?: string; + readonly inputPath?: string; /** * JSONPath expression to select part of the state to be the output to this state. @@ -33,7 +33,7 @@ export interface ParallelProps { * * @default $ */ - outputPath?: string; + readonly outputPath?: string; /** * JSONPath expression to indicate where to inject the state's output @@ -43,7 +43,7 @@ export interface ParallelProps { * * @default $ */ - resultPath?: string; + readonly resultPath?: string; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts index 04954b3a2ca9b..70e4eee290f0d 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/pass.ts @@ -12,7 +12,7 @@ export interface PassProps { * * @default No comment */ - comment?: string; + readonly comment?: string; /** * JSONPath expression to select part of the state to be the input to this state. @@ -22,7 +22,7 @@ export interface PassProps { * * @default $ */ - inputPath?: string; + readonly inputPath?: string; /** * JSONPath expression to select part of the state to be the output to this state. @@ -32,7 +32,7 @@ export interface PassProps { * * @default $ */ - outputPath?: string; + readonly outputPath?: string; /** * JSONPath expression to indicate where to inject the state's output @@ -42,7 +42,7 @@ export interface PassProps { * * @default $ */ - resultPath?: string; + readonly resultPath?: string; /** * If given, treat as the result of this operation @@ -51,7 +51,7 @@ export interface PassProps { * * @default No injected result */ - result?: any; + readonly result?: any; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts index e99559edaab43..02158a9c443a0 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/state.ts @@ -12,7 +12,7 @@ export interface StateProps { * * @default No comment */ - comment?: string; + readonly comment?: string; /** * JSONPath expression to select part of the state to be the input to this state. @@ -22,7 +22,7 @@ export interface StateProps { * * @default $ */ - inputPath?: string; + readonly inputPath?: string; /** * Parameters pass a collection of key-value pairs, either static values or JSONPath expressions that select from the input. @@ -32,7 +32,7 @@ export interface StateProps { * * @default No parameters */ - parameters?: { [name: string]: any }; + readonly parameters?: { [name: string]: any }; /** * JSONPath expression to select part of the state to be the output to this state. @@ -42,7 +42,7 @@ export interface StateProps { * * @default $ */ - outputPath?: string; + readonly outputPath?: string; /** * JSONPath expression to indicate where to inject the state's output @@ -52,7 +52,7 @@ export interface StateProps { * * @default $ */ - resultPath?: string; + readonly resultPath?: string; } /** @@ -127,6 +127,10 @@ export abstract class State extends cdk.Construct implements IChainable { protected readonly resultPath?: string; protected readonly branches: StateGraph[] = []; protected defaultChoice?: State; + + /** + * @internal + */ protected _next?: State; private readonly retries: RetryProps[] = []; @@ -215,6 +219,7 @@ export abstract class State extends cdk.Construct implements IChainable { /** * Add a retrier to the retry list of this state + * @internal */ protected _addRetry(props: RetryProps = {}) { this.retries.push({ @@ -225,6 +230,7 @@ export abstract class State extends cdk.Construct implements IChainable { /** * Add an error handler to the catch list of this state + * @internal */ protected _addCatch(handler: State, props: CatchProps = {}) { this.catches.push({ @@ -382,7 +388,7 @@ export interface FindStateOptions { * * @default false */ - includeErrorHandlers?: boolean; + readonly includeErrorHandlers?: boolean; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts index f884a07d426a9..4278e281f911c 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/succeed.ts @@ -11,7 +11,7 @@ export interface SucceedProps { * * @default No comment */ - comment?: string; + readonly comment?: string; /** * JSONPath expression to select part of the state to be the input to this state. @@ -21,7 +21,7 @@ export interface SucceedProps { * * @default $ */ - inputPath?: string; + readonly inputPath?: string; /** * JSONPath expression to select part of the state to be the output to this state. @@ -31,7 +31,7 @@ export interface SucceedProps { * * @default $ */ - outputPath?: string; + readonly outputPath?: string; } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index a6ed6025de560..3aa11676d6675 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -15,14 +15,14 @@ export interface TaskProps { * * Can be either a Lambda Function or an Activity. */ - resource: IStepFunctionsTaskResource; + readonly resource: IStepFunctionsTaskResource; /** * An optional description for this state * * @default No comment */ - comment?: string; + readonly comment?: string; /** * JSONPath expression to select part of the state to be the input to this state. @@ -32,7 +32,7 @@ export interface TaskProps { * * @default $ */ - inputPath?: string; + readonly inputPath?: string; /** * Parameters pass a collection of key-value pairs, either static values or JSONPath expressions that select from the input. @@ -42,7 +42,7 @@ export interface TaskProps { * * @default No parameters */ - parameters?: { [name: string]: any }; + readonly parameters?: { [name: string]: any }; /** * JSONPath expression to select part of the state to be the output to this state. @@ -52,7 +52,7 @@ export interface TaskProps { * * @default $ */ - outputPath?: string; + readonly outputPath?: string; /** * JSONPath expression to indicate where to inject the state's output @@ -62,7 +62,7 @@ export interface TaskProps { * * @default $ */ - resultPath?: string; + readonly resultPath?: string; /** * Maximum run time of this state @@ -71,7 +71,7 @@ export interface TaskProps { * * @default 60 */ - timeoutSeconds?: number; + readonly timeoutSeconds?: number; /** * Maximum time between heart beats @@ -82,7 +82,7 @@ export interface TaskProps { * * @default No heart beat timeout */ - heartbeatSeconds?: number; + readonly heartbeatSeconds?: number; } /** @@ -281,33 +281,33 @@ export interface StepFunctionsTaskResourceProps { /** * The ARN of the resource */ - resourceArn: string; + readonly resourceArn: string; /** * Additional policy statements to add to the execution role * * @default No policy roles */ - policyStatements?: iam.PolicyStatement[]; + readonly policyStatements?: iam.PolicyStatement[]; /** * Prefix for singular metric names of activity actions * * @default No such metrics */ - metricPrefixSingular?: string; + readonly metricPrefixSingular?: string; /** * Prefix for plural metric names of activity actions * * @default No such metrics */ - metricPrefixPlural?: string; + readonly metricPrefixPlural?: string; /** * The dimensions to attach to metrics * * @default No metrics */ - metricDimensions?: cloudwatch.DimensionHash; + readonly metricDimensions?: cloudwatch.DimensionHash; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts index cc0f8ce507fdd..869763a2d2c50 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/wait.ts @@ -12,14 +12,14 @@ export interface WaitProps { * * @default No comment */ - comment?: string; + readonly comment?: string; /** * Wait a fixed number of seconds * * Exactly one of seconds, secondsPath, timestamp, timestampPath must be supplied. */ - seconds?: number; + readonly seconds?: number; /** * Wait until the given ISO8601 timestamp @@ -28,7 +28,7 @@ export interface WaitProps { * * @example 2016-03-14T01:59:00Z */ - timestamp?: string; + readonly timestamp?: string; /** * Wait for a number of seconds stored in the state object. @@ -37,7 +37,7 @@ export interface WaitProps { * * @example $.waitSeconds */ - secondsPath?: string; + readonly secondsPath?: string; /** * Wait until a timestamp found in the state object. @@ -46,7 +46,7 @@ export interface WaitProps { * * @example $.waitTimestamp */ - timestampPath?: string; + readonly timestampPath?: string; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/types.ts b/packages/@aws-cdk/aws-stepfunctions/lib/types.ts index 8af16b439bbff..1efd71f983144 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/types.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/types.ts @@ -83,14 +83,14 @@ export interface RetryProps { * * @default All errors */ - errors?: string[]; + readonly errors?: string[]; /** * How many seconds to wait initially before retrying * * @default 1 */ - intervalSeconds?: number; + readonly intervalSeconds?: number; /** * How many times to retry this particular error. @@ -100,14 +100,14 @@ export interface RetryProps { * * @default 3 */ - maxAttempts?: number; + readonly maxAttempts?: number; /** * Multiplication for how much longer the wait interval gets on every retry * * @default 2 */ - backoffRate?: number; + readonly backoffRate?: number; } /** @@ -122,7 +122,7 @@ export interface CatchProps { * * @default All errors */ - errors?: string[]; + readonly errors?: string[]; /** * JSONPath expression to indicate where to inject the error data @@ -132,7 +132,7 @@ export interface CatchProps { * * @default $ */ - resultPath?: string; + readonly resultPath?: string; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index e089cf3edfea8..0ab31030b2137 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-stepfunctions", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::StepFunctions", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-stepfunctions", + "module": "aws_cdk.aws_stepfunctions" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-stepfunctions" }, "scripts": { "build": "cdk-build", @@ -54,24 +59,24 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" @@ -81,4 +86,4 @@ "resource-attribute:@aws-cdk/aws-stepfunctions.IStateMachine.stateMachineName" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.fail.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.fail.ts new file mode 100644 index 0000000000000..4dfb061328392 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.fail.ts @@ -0,0 +1,13 @@ +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import stepfunctions = require('../lib'); + +export = { + 'Props are optional'(test: Test) { + const stack = new cdk.Stack(); + + new stepfunctions.Fail(stack, 'Fail'); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-waf/package.json b/packages/@aws-cdk/aws-waf/package.json index 8d6f230ac1623..e42bc4f131b5d 100644 --- a/packages/@aws-cdk/aws-waf/package.json +++ b/packages/@aws-cdk/aws-waf/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-waf", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::WAF", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-waf", + "module": "aws_cdk.aws_waf" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-waf" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-wafregional/package.json b/packages/@aws-cdk/aws-wafregional/package.json index f10fcf1ea7549..5910004a7e89f 100644 --- a/packages/@aws-cdk/aws-wafregional/package.json +++ b/packages/@aws-cdk/aws-wafregional/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-wafregional", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::WAFRegional", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-wafregional", + "module": "aws_cdk.aws_wafregional" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-wafregional" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-workspaces/package.json b/packages/@aws-cdk/aws-workspaces/package.json index 0585eba121b1a..80fc3dcc8c205 100644 --- a/packages/@aws-cdk/aws-workspaces/package.json +++ b/packages/@aws-cdk/aws-workspaces/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-workspaces", - "version": "0.26.0", + "version": "0.28.0", "description": "The CDK Construct Library for AWS::WorkSpaces", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-workspaces", + "module": "aws_cdk.aws_workspaces" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-workspaces" }, "scripts": { "build": "cdk-build", @@ -54,19 +59,19 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cdk/README.md b/packages/@aws-cdk/cdk/README.md index 0aef94964ed42..6c085700c021e 100644 --- a/packages/@aws-cdk/cdk/README.md +++ b/packages/@aws-cdk/cdk/README.md @@ -1,6 +1,6 @@ ## AWS Cloud Development Kit Core Library -This library includes the basic building blocks of +This library includes the basic building blocks of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) (AWS CDK). ## Aspects @@ -185,3 +185,26 @@ const vpc = new ec2.VpcNetwork(this, 'MyVpc', { ... }); vpc.node.apply(new cdk.Tag('MyKey', 'MyValue', { priority: 2 })); // ... snip ``` + +## Secrets + +To help avoid accidental storage of secrets as plain text we use the `SecretValue` type to +represent secrets. + +The best practice is to store secrets in AWS Secrets Manager and reference them using `SecretValue.secretsManager`: + +```ts +const secret = SecretValue.secretsManager('secretId', { + jsonField: 'password' // optional: key of a JSON field to retrieve (defaults to all content), + versionId: 'id' // optional: id of the version (default AWSCURRENT) + versionStage: 'stage' // optional: version stage name (default AWSCURRENT) +}); +``` + +Using AWS Secrets Manager is the recommended way to reference secrets in a CDK app. +However, `SecretValue` supports the following additional options: + + * `SecretValue.plainText(secret)`: stores the secret as plain text in your app and the resulting template (not recommended). + * `SecretValue.ssmSecure(param, version)`: refers to a secret stored as a SecureString in the SSM Parameter Store. + * `SecretValue.cfnParameter(param)`: refers to a secret passed through a CloudFormation parameter (must have `NoEcho: true`). + * `SecretValue.cfnDynamicReference(dynref)`: refers to a secret described by a CloudFormation dynamic reference (used by `ssmSecure` and `secretsManager`). diff --git a/packages/@aws-cdk/cdk/lib/app.ts b/packages/@aws-cdk/cdk/lib/app.ts index fb408123df743..46c8f6493489c 100644 --- a/packages/@aws-cdk/cdk/lib/app.ts +++ b/packages/@aws-cdk/cdk/lib/app.ts @@ -1,6 +1,6 @@ import cxapi = require('@aws-cdk/cx-api'); -import { ConstructOrder, Root } from './construct'; -import { FileSystemStore, InMemoryStore, ISynthesisSession, SynthesisSession } from './synthesis'; +import { Root } from './construct'; +import { FileSystemStore, InMemoryStore, ISynthesisSession, Synthesizer } from './synthesis'; /** * Custom construction properties for a CDK program @@ -13,14 +13,14 @@ export interface AppProps { * * @default true if running via CDK toolkit (CDK_OUTDIR is set), false otherwise */ - autoRun?: boolean; + readonly autoRun?: boolean; /** * Additional context values for the application * * @default No additional context */ - context?: { [key: string]: string }; + readonly context?: { [key: string]: string }; } /** @@ -69,35 +69,15 @@ export class App extends Root { store = new InMemoryStore(); } - const session = this._session = new SynthesisSession({ + const synth = new Synthesizer(); + + this._session = synth.synthesize(this, { store, legacyManifest: this.legacyManifest, runtimeInformation: this.runtimeInformation }); - // the three holy phases of synthesis: prepare, validate and synthesize - - // prepare - this.node.prepareTree(); - - // validate - const errors = this.node.validateTree(); - if (errors.length > 0) { - const errorList = errors.map(e => `[${e.source.node.path}] ${e.message}`).join('\n '); - throw new Error(`Validation failed with the following errors:\n ${errorList}`); - } - - // synthesize (leaves first) - for (const c of this.node.findAll(ConstructOrder.PostOrder)) { - if (SynthesisSession.isSynthesizable(c)) { - c.synthesize(session); - } - } - - // write session manifest and lock store - session.close(); - - return session; + return this._session; } /** diff --git a/packages/@aws-cdk/cdk/lib/arn.ts b/packages/@aws-cdk/cdk/lib/arn.ts index 6386155371753..8cbdbbe822f77 100644 --- a/packages/@aws-cdk/cdk/lib/arn.ts +++ b/packages/@aws-cdk/cdk/lib/arn.ts @@ -1,6 +1,7 @@ import { Fn } from './fn'; import { Stack } from './stack'; import { unresolved } from './unresolved'; +import { filterUndefined } from './util'; /** * Creates an ARN from components. @@ -130,28 +131,16 @@ export function parseArn(arn: string, sepIfToken: string = '/', hasName: boolean resourceName += rest.join(':'); } - const result: ArnComponents = { service, resource }; - if (partition) { - result.partition = partition; - } - - if (region) { - result.region = region; - } - - if (account) { - result.account = account; - } - - if (resourceName) { - result.resourceName = resourceName; - } - - if (sep) { - result.sep = sep; - } - - return result; + // "|| undefined" will cause empty strings to be treated as "undefined" + return filterUndefined({ + service: service || undefined, + resource: resource || undefined , + partition: partition || undefined, + region: region || undefined, + account: account || undefined, + resourceName, + sep + }); } /** @@ -223,13 +212,13 @@ export interface ArnComponents { * * @default The AWS partition the stack is deployed to. */ - partition?: string; + readonly partition?: string; /** * The service namespace that identifies the AWS product (for example, * 's3', 'iam', 'codepipline'). */ - service: string; + readonly service: string; /** * The region the resource resides in. Note that the ARNs for some resources @@ -237,7 +226,7 @@ export interface ArnComponents { * * @default The region the stack is deployed to. */ - region?: string; + readonly region?: string; /** * The ID of the AWS account that owns the resource, without the hyphens. @@ -246,13 +235,13 @@ export interface ArnComponents { * * @default The account the stack is deployed to. */ - account?: string; + readonly account?: string; /** * Resource type (e.g. "table", "autoScalingGroup", "certificate"). * For some resource types, e.g. S3 buckets, this field defines the bucket name. */ - resource: string; + readonly resource: string; /** * Separator between resource type and the resource. @@ -260,11 +249,11 @@ export interface ArnComponents { * Can be either '/', ':' or an empty string. Will only be used if resourceName is defined. * @default '/' */ - sep?: string; + readonly sep?: string; /** * Resource name or path within the resource (i.e. S3 bucket object key) or * a wildcard such as ``"*"``. This is service-dependent. */ - resourceName?: string; + readonly resourceName?: string; } diff --git a/packages/@aws-cdk/cdk/lib/cfn-condition.ts b/packages/@aws-cdk/cdk/lib/cfn-condition.ts index 1f28cc15047df..275db39e1207b 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-condition.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-condition.ts @@ -3,7 +3,7 @@ import { Construct } from './construct'; import { ResolveContext } from './token'; export interface CfnConditionProps { - expression?: ICfnConditionExpression; + readonly expression?: ICfnConditionExpression; } /** @@ -25,6 +25,9 @@ export class CfnCondition extends CfnRefElement implements ICfnConditionExpressi this.expression = props && props.expression; } + /** + * @internal + */ public _toCloudFormation(): object { if (!this.expression) { return { }; diff --git a/packages/@aws-cdk/cdk/lib/cfn-dynamic-reference.ts b/packages/@aws-cdk/cdk/lib/cfn-dynamic-reference.ts new file mode 100644 index 0000000000000..4f3c18495916d --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/cfn-dynamic-reference.ts @@ -0,0 +1,50 @@ +import { Token } from "./token"; + +/** + * Properties for a Dynamic Reference + */ +export interface DynamicReferenceProps { + /** + * The service to retrieve the dynamic reference from + */ + readonly service: CfnDynamicReferenceService; + + /** + * The reference key of the dynamic reference + */ + readonly referenceKey: string; +} + +/** + * References a dynamically retrieved value + * + * This is a Construct so that subclasses will (eventually) be able to attach + * metadata to themselves without having to change call signatures. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html + */ +export class CfnDynamicReference extends Token { + constructor(service: CfnDynamicReferenceService, key: string) { + super(() => '{{resolve:' + service + ':' + key + '}}'); + } +} + +/** + * The service to retrieve the dynamic reference from + */ +export enum CfnDynamicReferenceService { + /** + * Plaintext value stored in AWS Systems Manager Parameter Store + */ + Ssm = 'ssm', + + /** + * Secure string stored in AWS Systems Manager Parameter Store + */ + SsmSecure = 'ssm-secure', + + /** + * Secret stored in AWS Secrets Manager + */ + SecretsManager = 'secretsmanager', +} diff --git a/packages/@aws-cdk/cdk/lib/cfn-element.ts b/packages/@aws-cdk/cdk/lib/cfn-element.ts index ffd546b0f98cb..2d08e69a619ea 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-element.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-element.ts @@ -126,17 +126,6 @@ export abstract class CfnElement extends Construct { } } -import { Reference } from "./reference"; - -/** - * A generic, untyped reference to a Stack Element - */ -export class Ref extends Reference { - constructor(element: CfnElement) { - super({ Ref: element.logicalId }, 'Ref', element); - } -} - /** * Base class for referenceable CloudFormation constructs which are not Resources * @@ -152,8 +141,16 @@ export abstract class CfnRefElement extends CfnElement { * Returns a token to a CloudFormation { Ref } that references this entity based on it's logical ID. */ public get ref(): string { - return new Ref(this).toString(); + return this.referenceToken.toString(); + } + + /** + * Return a token that will CloudFormation { Ref } this stack element + */ + protected get referenceToken(): Token { + return new CfnReference({ Ref: this.logicalId }, 'Ref', this); } } -import { findTokens } from "./resolve"; +import { CfnReference } from "./cfn-reference"; +import { findTokens } from "./resolve"; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cfn-mapping.ts b/packages/@aws-cdk/cdk/lib/cfn-mapping.ts index 2444dff11ba5d..29565d59df80a 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-mapping.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-mapping.ts @@ -1,9 +1,10 @@ import { CfnRefElement } from './cfn-element'; import { Construct } from './construct'; import { Fn } from './fn'; +import { Token } from './token'; export interface CfnMappingProps { - mapping?: { [k1: string]: { [k2: string]: any } }; + readonly mapping?: { [k1: string]: { [k2: string]: any } }; } /** @@ -32,17 +33,22 @@ export class CfnMapping extends CfnRefElement { * @returns A reference to a value in the map based on the two keys. */ public findInMap(key1: string, key2: string): string { - if (!(key1 in this.mapping)) { + // opportunistically check that the key exists (if the key does not contain tokens) + if (!Token.unresolved(key1) && !(key1 in this.mapping)) { throw new Error(`Mapping doesn't contain top-level key '${key1}'`); } - if (!(key2 in this.mapping[key1])) { + // opportunistically check that the key exists (if the key does not contain tokens) + if (!Token.unresolved(key2) && !(key2 in this.mapping[key1])) { throw new Error(`Mapping doesn't contain second-level key '${key2}'`); } return Fn.findInMap(this.logicalId, key1, key2); } + /** + * @internal + */ public _toCloudFormation(): object { return { Mappings: { diff --git a/packages/@aws-cdk/cdk/lib/cfn-output.ts b/packages/@aws-cdk/cdk/lib/cfn-output.ts index 519c03980b297..8118ef308ec81 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-output.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-output.ts @@ -6,14 +6,14 @@ export interface CfnOutputProps { * A String type that describes the output value. * The description can be a maximum of 4 K in length. */ - description?: string; + readonly description?: string; /** * The value of the property returned by the aws cloudformation describe-stacks command. * The value of an output can include literals, parameter references, pseudo-parameters, * a mapping value, or intrinsic functions. */ - value?: any; + readonly value: any; /** * The name used to export the value of this output across stacks. @@ -24,7 +24,7 @@ export interface CfnOutputProps { * @default Automatically allocate a name when `makeImportValue()` is * called. */ - export?: string; + readonly export?: string; /** * Disables the automatic allocation of an export name for this output. @@ -34,14 +34,14 @@ export interface CfnOutputProps { * * @default false */ - disableExport?: boolean; + readonly disableExport?: boolean; /** * A condition from the "Conditions" section to associate with this output * value. If the condition evaluates to `false`, this output value will not * be included in the stack. */ - condition?: CfnCondition; + readonly condition?: CfnCondition; } export class CfnOutput extends CfnElement { @@ -75,9 +75,13 @@ export class CfnOutput extends CfnElement { * @param parent The parent construct. * @param props CfnOutput properties. */ - constructor(scope: Construct, id: string, props: CfnOutputProps = {}) { + constructor(scope: Construct, id: string, props: CfnOutputProps) { super(scope, id); + if (props.value === undefined) { + throw new Error(`Missing value for CloudFormation output at path "${this.node.path}"`); + } + this.description = props.description; this._value = props.value; this.condition = props.condition; @@ -111,6 +115,9 @@ export class CfnOutput extends CfnElement { return fn().importValue(this.obtainExportName()); } + /** + * @internal + */ public _toCloudFormation(): object { return { Outputs: { diff --git a/packages/@aws-cdk/cdk/lib/cfn-parameter.ts b/packages/@aws-cdk/cdk/lib/cfn-parameter.ts index c5006a6a8d35f..ad5f84d7a91c5 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-parameter.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-parameter.ts @@ -1,4 +1,4 @@ -import { CfnRefElement, Ref } from './cfn-element'; +import { CfnRefElement } from './cfn-element'; import { Construct } from './construct'; import { Token } from './token'; @@ -6,24 +6,24 @@ export interface CfnParameterProps { /** * The data type for the parameter (DataType). */ - type: string; + readonly type: string; /** * A value of the appropriate type for the template to use if no value is specified * when a stack is created. If you define constraints for the parameter, you must specify * a value that adheres to those constraints. */ - default?: any; + readonly default?: any; /** * A regular expression that represents the patterns to allow for String types. */ - allowedPattern?: string; + readonly allowedPattern?: string; /** * An array containing the list of values allowed for the parameter. */ - allowedValues?: string[]; + readonly allowedValues?: string[]; /** * A string that explains a constraint when the constraint is violated. @@ -31,38 +31,38 @@ export interface CfnParameterProps { * pattern of [A-Za-z0-9]+ displays the following error message when the user specifies * an invalid value: */ - constraintDescription?: string; + readonly constraintDescription?: string; /** * A string of up to 4000 characters that describes the parameter. */ - description?: string; + readonly description?: string; /** * An integer value that determines the largest number of characters you want to allow for String types. */ - maxLength?: number; + readonly maxLength?: number; /** * A numeric value that determines the largest numeric value you want to allow for Number types. */ - maxValue?: number; + readonly maxValue?: number; /** * An integer value that determines the smallest number of characters you want to allow for String types. */ - minLength?: number; + readonly minLength?: number; /** * A numeric value that determines the smallest numeric value you want to allow for Number types. */ - minValue?: number; + readonly minValue?: number; /** * Whether to mask the parameter value when anyone makes a call that describes the stack. * If you set the value to ``true``, the parameter value is masked with asterisks (``*****``). */ - noEcho?: boolean; + readonly noEcho?: boolean; } /** @@ -86,6 +86,11 @@ export class CfnParameter extends CfnRefElement { */ public stringListValue: string[]; + /** + * Indicates if this parameter has "NoEcho" set. + */ + public readonly noEcho: boolean; + private properties: CfnParameterProps; /** @@ -99,11 +104,15 @@ export class CfnParameter extends CfnRefElement { constructor(scope: Construct, id: string, props: CfnParameterProps) { super(scope, id); this.properties = props; - this.value = new Ref(this); + this.value = this.referenceToken; this.stringValue = this.value.toString(); this.stringListValue = this.value.toList(); + this.noEcho = props.noEcho || false; } + /** + * @internal + */ public _toCloudFormation(): object { return { Parameters: { diff --git a/packages/@aws-cdk/cdk/lib/cfn-reference.ts b/packages/@aws-cdk/cdk/lib/cfn-reference.ts new file mode 100644 index 0000000000000..9d533f4778d79 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/cfn-reference.ts @@ -0,0 +1,115 @@ +import { Reference } from "./reference"; + +const CFN_REFERENCE_SYMBOL = Symbol('@aws-cdk/cdk.CfnReference'); + +/** + * A Token that represents a CloudFormation reference to another resource + * + * If these references are used in a different stack from where they are + * defined, appropriate CloudFormation `Export`s and `Fn::ImportValue`s will be + * synthesized automatically instead of the regular CloudFormation references. + * + * Additionally, the dependency between the stacks will be recorded, and the toolkit + * will make sure to deploy producing stack before the consuming stack. + * + * This magic happens in the prepare() phase, where consuming stacks will call + * `consumeFromStack` on these Tokens and if they happen to be exported by a different + * Stack, we'll register the dependency. + */ +export class CfnReference extends Reference { + /** + * Check whether this is actually a Reference + */ + public static isCfnReference(x: Token): x is CfnReference { + return (x as any)[CFN_REFERENCE_SYMBOL] === true; + } + + /** + * What stack this Token is pointing to + */ + private readonly producingStack?: Stack; + + /** + * The Tokens that should be returned for each consuming stack (as decided by the producing Stack) + */ + private readonly replacementTokens: Map; + + private readonly originalDisplayName: string; + + constructor(value: any, displayName: string, target: Construct) { + if (typeof(value) === 'function') { + throw new Error('Reference can only hold CloudFormation intrinsics (not a function)'); + } + // prepend scope path to display name + super(value, `${target.node.id}.${displayName}`, target); + this.originalDisplayName = displayName; + this.replacementTokens = new Map(); + + this.producingStack = target.node.stack; + Object.defineProperty(this, CFN_REFERENCE_SYMBOL, { value: true }); + } + + public resolve(context: ResolveContext): any { + // If we have a special token for this consuming stack, resolve that. Otherwise resolve as if + // we are in the same stack. + const token = this.replacementTokens.get(context.scope.node.stack); + if (token) { + return token.resolve(context); + } else { + return super.resolve(context); + } + } + + /** + * Register a stack this references is being consumed from. + */ + public consumeFromStack(consumingStack: Stack, consumingConstruct: IConstruct) { + if (this.producingStack && this.producingStack !== consumingStack && !this.replacementTokens.has(consumingStack)) { + // We're trying to resolve a cross-stack reference + consumingStack.addDependency(this.producingStack, `${consumingConstruct.node.path} -> ${this.target.node.path}.${this.originalDisplayName}`); + this.replacementTokens.set(consumingStack, this.exportValue(this, consumingStack)); + } + } + + /** + * Export a Token value for use in another stack + * + * Works by mutating the producing stack in-place. + */ + private exportValue(tokenValue: Token, consumingStack: Stack): Token { + const producingStack = this.producingStack!; + + if (producingStack.env.account !== consumingStack.env.account || producingStack.env.region !== consumingStack.env.region) { + throw new Error('Can only reference cross stacks in the same region and account.'); + } + + // Ensure a singleton "Exports" scoping Construct + // This mostly exists to trigger LogicalID munging, which would be + // disabled if we parented constructs directly under Stack. + // Also it nicely prevents likely construct name clashes + + const exportsName = 'Exports'; + let stackExports = producingStack.node.tryFindChild(exportsName) as Construct; + if (stackExports === undefined) { + stackExports = new Construct(producingStack, exportsName); + } + + // Ensure a singleton CfnOutput for this value + const resolved = producingStack.node.resolve(tokenValue); + const id = 'Output' + JSON.stringify(resolved); + let output = stackExports.node.tryFindChild(id) as CfnOutput; + if (!output) { + output = new CfnOutput(stackExports, id, { value: tokenValue }); + } + + // We want to return an actual FnImportValue Token here, but Fn.importValue() returns a 'string', + // so construct one in-place. + return new Token({ 'Fn::ImportValue': output.obtainExportName() }); + } + +} + +import { CfnOutput } from "./cfn-output"; +import { Construct, IConstruct } from "./construct"; +import { Stack } from "./stack"; +import { ResolveContext, Token } from "./token"; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/cfn-resource.ts b/packages/@aws-cdk/cdk/lib/cfn-resource.ts index 74bc60dce7159..bcf6ba2f380e9 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-resource.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-resource.ts @@ -1,24 +1,24 @@ import cxapi = require('@aws-cdk/cx-api'); import { CfnCondition } from './cfn-condition'; import { Construct, IConstruct } from './construct'; -import { Reference } from './reference'; import { CreationPolicy, DeletionPolicy, UpdatePolicy } from './resource-policy'; import { TagManager } from './tag-manager'; import { capitalizePropertyNames, ignoreEmpty, PostResolveToken } from './util'; // import required to be here, otherwise causes a cycle when running the generated JavaScript // tslint:disable-next-line:ordered-imports import { CfnRefElement } from './cfn-element'; +import { CfnReference } from './cfn-reference'; export interface CfnResourceProps { /** * CloudFormation resource type. */ - type: string; + readonly type: string; /** * CloudFormation properties. */ - properties?: any; + readonly properties?: any; } export interface ITaggable { @@ -64,7 +64,7 @@ export class CfnResource extends CfnRefElement { /** * Options for this resource, such as condition, update policy etc. */ - public readonly options: ResourceOptions = {}; + public readonly options: IResourceOptions = {}; /** * AWS resource type. @@ -133,7 +133,7 @@ export class CfnResource extends CfnRefElement { * @param attributeName The name of the attribute. */ public getAtt(attributeName: string) { - return new Reference({ 'Fn::GetAtt': [this.logicalId, attributeName] }, attributeName, this); + return new CfnReference({ 'Fn::GetAtt': [this.logicalId, attributeName] }, attributeName, this); } /** @@ -205,6 +205,7 @@ export class CfnResource extends CfnRefElement { /** * Emits CloudFormation for this resource. + * @internal */ public _toCloudFormation(): object { try { @@ -266,7 +267,7 @@ export enum TagType { NotTaggable = 'NotTaggable', } -export interface ResourceOptions { +export interface IResourceOptions { /** * A condition to associate with this resource. This means that only if the condition evaluates to 'true' when the stack * is deployed, the resource will be included. This is provided to allow CDK projects to produce legacy templates, but noramlly diff --git a/packages/@aws-cdk/cdk/lib/cfn-rule.ts b/packages/@aws-cdk/cdk/lib/cfn-rule.ts index 207e43a5b5a1f..aeae6537e9183 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-rule.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-rule.ts @@ -29,12 +29,12 @@ export interface CfnRuleProps { * If the rule condition evaluates to false, the rule doesn't take effect. * If the function in the rule condition evaluates to true, expressions in each assert are evaluated and applied. */ - ruleCondition?: ICfnConditionExpression; + readonly ruleCondition?: ICfnConditionExpression; /** * Assertions which define the rule. */ - assertions?: RuleAssertion[]; + readonly assertions?: RuleAssertion[]; } /** @@ -92,6 +92,9 @@ export class CfnRule extends CfnRefElement { }); } + /** + * @internal + */ public _toCloudFormation(): object { return { Rules: { @@ -111,10 +114,10 @@ export interface RuleAssertion { /** * The assertion. */ - assert: ICfnConditionExpression; + readonly assert: ICfnConditionExpression; /** * The assertion description. */ - assertDescription: string; + readonly assertDescription: string; } diff --git a/packages/@aws-cdk/cdk/lib/construct.ts b/packages/@aws-cdk/cdk/lib/construct.ts index 0a2c0ea4856e6..080c97fd7b602 100644 --- a/packages/@aws-cdk/cdk/lib/construct.ts +++ b/packages/@aws-cdk/cdk/lib/construct.ts @@ -46,11 +46,11 @@ export class ConstructNode { private readonly _children: { [name: string]: IConstruct } = { }; private readonly context: { [key: string]: any } = { }; private readonly _metadata = new Array(); - private readonly references = new Set(); + private readonly references = new Set(); private readonly dependencies = new Set(); /** Will be used to cache the value of ``this.stack``. */ - private _stack?: Stack; + private _stack?: import('./stack').Stack; /** * If this is set to 'true'. addChild() calls for this construct and any child @@ -91,11 +91,13 @@ export class ConstructNode { /** * The stack the construct is a part of. */ - public get stack(): Stack { + public get stack(): import('./stack').Stack { + // Lazy import to break cyclic import + const stack: typeof import('./stack') = require('./stack'); return this._stack || (this._stack = _lookStackUp(this)); - function _lookStackUp(_this: ConstructNode) { - if (Stack.isStack(_this.host)) { + function _lookStackUp(_this: ConstructNode): import('./stack').Stack { + if (stack.Stack.isStack(_this.host)) { return _this.host; } if (!_this.scope) { @@ -471,7 +473,7 @@ export class ConstructNode { */ public recordReference(...refs: Token[]) { for (const ref of refs) { - if (ref.isReference) { + if (Reference.isReference(ref)) { this.references.add(ref); } } @@ -480,12 +482,12 @@ export class ConstructNode { /** * Return all references of the given type originating from this node or any of its children */ - public findReferences(): Token[] { - const ret = new Set(); + public findReferences(): OutgoingReference[] { + const ret = new Set(); function recurse(node: ConstructNode) { - for (const ref of node.references) { - ret.add(ref); + for (const reference of node.references) { + ret.add({ source: node.host, reference }); } for (const child of node.children) { @@ -649,17 +651,17 @@ export interface MetadataEntry { /** * The type of the metadata entry. */ - type: string; + readonly type: string; /** * The data. */ - data?: any; + readonly data?: any; /** * A stack trace for when the entry was created. */ - trace: string[]; + readonly trace: string[]; } export class ValidationError { @@ -706,12 +708,12 @@ export interface Dependency { /** * Source the dependency */ - source: IConstruct; + readonly source: IConstruct; /** * Target of the dependency */ - target: IConstruct; + readonly target: IConstruct; } /** @@ -721,13 +723,18 @@ export interface Dependency { /** * Source the dependency */ - source: IConstruct; + readonly source: IConstruct; /** * Target of the dependency */ - target: IConstruct; + readonly target: IConstruct; +} + +export interface OutgoingReference { + readonly source: IConstruct; + readonly reference: Reference; } // Import this _after_ everything else to help node work the classes out in the correct order... -import { Stack } from './stack'; +import { Reference } from './reference'; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/context.ts b/packages/@aws-cdk/cdk/lib/context.ts index 9ced8ea2a6bda..7d3debbc9375f 100644 --- a/packages/@aws-cdk/cdk/lib/context.ts +++ b/packages/@aws-cdk/cdk/lib/context.ts @@ -146,7 +146,7 @@ export interface SSMParameterProviderProps { /** * The name of the parameter to lookup */ - parameterName: string; + readonly parameterName: string; } /** * Context provider that will read values from the SSM parameter store in the indicated account and region diff --git a/packages/@aws-cdk/cdk/lib/dynamic-reference.ts b/packages/@aws-cdk/cdk/lib/dynamic-reference.ts deleted file mode 100644 index 7be363e1f2e33..0000000000000 --- a/packages/@aws-cdk/cdk/lib/dynamic-reference.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Construct } from "./construct"; -import { Token } from "./token"; - -/** - * Properties for a Dynamic Reference - */ -export interface DynamicReferenceProps { - /** - * The service to retrieve the dynamic reference from - */ - service: DynamicReferenceService; - - /** - * The reference key of the dynamic reference - */ - referenceKey: string; -} - -/** - * References a dynamically retrieved value - * - * This is a Construct so that subclasses will (eventually) be able to attach - * metadata to themselves without having to change call signatures. - * - * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html - */ -export class DynamicReference extends Construct { - private _value: string; - - constructor(scope: Construct, id: string, props: DynamicReferenceProps) { - super(scope, id); - - this._value = this.makeResolveValue(props.service, props.referenceKey); - } - - /** - * The value of this dynamic reference - */ - public get stringValue(): string { - return this._value; - } - - /** - * Make a dynamic reference Token value - * - * This is a value (similar to CDK Tokens) that will be substituted by - * CloudFormation before executing the changeset. - */ - protected makeResolveValue(service: DynamicReferenceService, referenceKey: string) { - const resolveString = '{{resolve:' + service + ':' + referenceKey + '}}'; - - // We don't strictly need to Tokenize a string here, but we do it anyway to be perfectly - // clear that DynamicReference.value is unparseable in CDK apps. - return new Token(resolveString).toString(); - } -} - -/** - * The service to retrieve the dynamic reference from - */ -export enum DynamicReferenceService { - /** - * Plaintext value stored in AWS Systems Manager Parameter Store - */ - Ssm = 'ssm', - - /** - * Secure string stored in AWS Systems Manager Parameter Store - */ - SsmSecure = 'ssm-secure', - - /** - * Secret stored in AWS Secrets Manager - */ - SecretsManager = 'secretsmanager', -} diff --git a/packages/@aws-cdk/cdk/lib/environment.ts b/packages/@aws-cdk/cdk/lib/environment.ts index e3e0cd76eca21..8ae44a4aebed2 100644 --- a/packages/@aws-cdk/cdk/lib/environment.ts +++ b/packages/@aws-cdk/cdk/lib/environment.ts @@ -6,13 +6,13 @@ export interface Environment { * The AWS account ID for this environment. * If not specified, the context parameter `default-account` is used. */ - account?: string; + readonly account?: string; /** * The AWS region for this environment. * If not specified, the context parameter `default-region` is used. */ - region?: string; + readonly region?: string; } /** diff --git a/packages/@aws-cdk/cdk/lib/include.ts b/packages/@aws-cdk/cdk/lib/include.ts index a6d9831568be6..50998841c415f 100644 --- a/packages/@aws-cdk/cdk/lib/include.ts +++ b/packages/@aws-cdk/cdk/lib/include.ts @@ -5,7 +5,7 @@ export interface IncludeProps { /** * The CloudFormation template to include in the stack (as is). */ - template: object; + readonly template: object; } /** @@ -31,6 +31,9 @@ export class Include extends CfnElement { this.template = props.template; } + /** + * @internal + */ public _toCloudFormation() { return this.template; } diff --git a/packages/@aws-cdk/cdk/lib/index.ts b/packages/@aws-cdk/cdk/lib/index.ts index c525bbed59809..dee345c7d033d 100644 --- a/packages/@aws-cdk/cdk/lib/index.ts +++ b/packages/@aws-cdk/cdk/lib/index.ts @@ -16,22 +16,22 @@ export * from './logical-id'; export * from './cfn-mapping'; export * from './cfn-output'; export * from './cfn-parameter'; +export * from './cfn-reference'; export * from './pseudo'; export * from './cfn-resource'; export * from './resource-policy'; export * from './cfn-rule'; export * from './stack'; export * from './cfn-element'; -export * from './dynamic-reference'; +export * from './cfn-dynamic-reference'; export * from './tag'; export * from './removal-policy'; export * from './arn'; -export * from './secret'; export * from './app'; export * from './context'; export * from './environment'; export * from './runtime'; - -export * from './synthesis'; +export * from './secret-value'; +export * from './synthesis'; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/pseudo.ts b/packages/@aws-cdk/cdk/lib/pseudo.ts index 407aa421eb306..93578652afd04 100644 --- a/packages/@aws-cdk/cdk/lib/pseudo.ts +++ b/packages/@aws-cdk/cdk/lib/pseudo.ts @@ -1,7 +1,16 @@ +import { CfnReference } from './cfn-reference'; import { Construct } from './construct'; -import { Reference } from './reference'; import { Token } from './token'; +const AWS_ACCOUNTID = 'AWS::AccountId'; +const AWS_URLSUFFIX = 'AWS::URLSuffix'; +const AWS_NOTIFICATIONARNS = 'AWS::NotificationARNs'; +const AWS_PARTITION = 'AWS::Partition'; +const AWS_REGION = 'AWS::Region'; +const AWS_STACKID = 'AWS::StackId'; +const AWS_STACKNAME = 'AWS::StackName'; +const AWS_NOVALUE = 'AWS::NoValue'; + /** * Accessor for pseudo parameters * @@ -14,35 +23,35 @@ export class Aws { } public static get accountId(): string { - return new AwsAccountId(undefined).toString(); + return new UnscopedPseudo(AWS_ACCOUNTID).toString(); } public static get urlSuffix(): string { - return new AwsURLSuffix(undefined).toString(); + return new UnscopedPseudo(AWS_URLSUFFIX).toString(); } public static get notificationArns(): string[] { - return new AwsNotificationARNs(undefined).toList(); + return new UnscopedPseudo(AWS_NOTIFICATIONARNS).toList(); } public static get partition(): string { - return new AwsPartition(undefined).toString(); + return new UnscopedPseudo(AWS_PARTITION).toString(); } public static get region(): string { - return new AwsRegion(undefined).toString(); + return new UnscopedPseudo(AWS_REGION).toString(); } public static get stackId(): string { - return new AwsStackId(undefined).toString(); + return new UnscopedPseudo(AWS_STACKID).toString(); } public static get stackName(): string { - return new AwsStackName(undefined).toString(); + return new UnscopedPseudo(AWS_STACKNAME).toString(); } public static get noValue(): string { - return new AwsNoValue().toString(); + return new UnscopedPseudo(AWS_NOVALUE).toString(); } } @@ -53,88 +62,46 @@ export class Aws { * tree, and their values will be exported automatically. */ export class ScopedAws { - constructor(private readonly scope?: Construct) { + constructor(private readonly scope: Construct) { } public get accountId(): string { - return new AwsAccountId(this.scope).toString(); + return new ScopedPseudo(AWS_ACCOUNTID, this.scope).toString(); } public get urlSuffix(): string { - return new AwsURLSuffix(this.scope).toString(); + return new ScopedPseudo(AWS_URLSUFFIX, this.scope).toString(); } public get notificationArns(): string[] { - return new AwsNotificationARNs(this.scope).toList(); + return new ScopedPseudo(AWS_NOTIFICATIONARNS, this.scope).toList(); } public get partition(): string { - return new AwsPartition(this.scope).toString(); + return new ScopedPseudo(AWS_PARTITION, this.scope).toString(); } public get region(): string { - return new AwsRegion(this.scope).toString(); + return new ScopedPseudo(AWS_REGION, this.scope).toString(); } public get stackId(): string { - return new AwsStackId(this.scope).toString(); + return new ScopedPseudo(AWS_STACKID, this.scope).toString(); } public get stackName(): string { - return new AwsStackName(this.scope).toString(); + return new ScopedPseudo(AWS_STACKNAME, this.scope).toString(); } } -class PseudoParameter extends Reference { - constructor(name: string, scope: Construct | undefined) { +class ScopedPseudo extends CfnReference { + constructor(name: string, scope: Construct) { super({ Ref: name }, name, scope); } } -class AwsAccountId extends PseudoParameter { - constructor(scope: Construct | undefined) { - super('AWS::AccountId', scope); - } -} - -class AwsURLSuffix extends PseudoParameter { - constructor(scope: Construct | undefined) { - super('AWS::URLSuffix', scope); - } -} - -class AwsNotificationARNs extends PseudoParameter { - constructor(scope: Construct | undefined) { - super('AWS::NotificationARNs', scope); - } -} - -export class AwsNoValue extends Token { - constructor() { - super({ Ref: 'AWS::NoValue' }); +class UnscopedPseudo extends Token { + constructor(name: string) { + super({ Ref: name }, name); } -} - -class AwsPartition extends PseudoParameter { - constructor(scope: Construct | undefined) { - super('AWS::Partition', scope); - } -} - -class AwsRegion extends PseudoParameter { - constructor(scope: Construct | undefined) { - super('AWS::Region', scope); - } -} - -class AwsStackId extends PseudoParameter { - constructor(scope: Construct | undefined) { - super('AWS::StackId', scope); - } -} - -class AwsStackName extends PseudoParameter { - constructor(scope: Construct | undefined) { - super('AWS::StackName', scope); - } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/reference.ts b/packages/@aws-cdk/cdk/lib/reference.ts index c7e897a0df26a..e9a91da8c4967 100644 --- a/packages/@aws-cdk/cdk/lib/reference.ts +++ b/packages/@aws-cdk/cdk/lib/reference.ts @@ -1,116 +1,27 @@ -import { ResolveContext, Token } from "./token"; +import { Token } from "./token"; + +const REFERENCE_SYMBOL = Symbol('@aws-cdk/cdk.Reference'); /** - * A Token that represents a CloudFormation reference to another resource - * - * If these references are used in a different stack from where they are - * defined, appropriate CloudFormation `Export`s and `Fn::ImportValue`s will be - * synthesized automatically instead of the regular CloudFormation references. + * A Token that represents a reference between two constructs * - * Additionally, the dependency between the stacks will be recorded, and the toolkit - * will make sure to deploy producing stack before the consuming stack. - * - * This magic happens in the prepare() phase, where consuming stacks will call - * `consumeFromStack` on these Tokens and if they happen to be exported by a different - * Stack, we'll register the dependency. + * References are recorded. */ export class Reference extends Token { /** * Check whether this is actually a Reference */ - public static isReferenceToken(x: Token): x is Reference { - return (x as any).consumeFromStack !== undefined; + public static isReference(x: Token): x is Reference { + return (x as any)[REFERENCE_SYMBOL] === true; } - public readonly isReference?: boolean; + public readonly target: Construct; - /** - * What stack this Token is pointing to - */ - private readonly producingStack?: Stack; - - /** - * The Tokens that should be returned for each consuming stack (as decided by the producing Stack) - */ - private readonly replacementTokens: Map; - - constructor(value: any, displayName?: string, scope?: Construct) { - if (typeof(value) === 'function') { - throw new Error('Reference can only hold CloudFormation intrinsics (not a function)'); - } - // prepend scope path to display name - if (displayName && scope) { - displayName = `${scope.node.path}.${displayName}`; - } + constructor(value: any, displayName: string, target: Construct) { super(value, displayName); - this.replacementTokens = new Map(); - this.isReference = true; - - if (scope !== undefined) { - this.producingStack = scope.node.stack; - } - } - - public resolve(context: ResolveContext): any { - // If we have a special token for this consuming stack, resolve that. Otherwise resolve as if - // we are in the same stack. - const token = this.replacementTokens.get(context.scope.node.stack); - if (token) { - return token.resolve(context); - } else { - return super.resolve(context); - } - } - - /** - * Register a stack this references is being consumed from. - */ - public consumeFromStack(consumingStack: Stack) { - if (this.producingStack && this.producingStack !== consumingStack && !this.replacementTokens.has(consumingStack)) { - // We're trying to resolve a cross-stack reference - consumingStack.addDependency(this.producingStack); - this.replacementTokens.set(consumingStack, this.exportValue(this, consumingStack)); - } + this.target = target; + Object.defineProperty(this, REFERENCE_SYMBOL, { value: true }); } - - /** - * Export a Token value for use in another stack - * - * Works by mutating the producing stack in-place. - */ - private exportValue(tokenValue: Token, consumingStack: Stack): Token { - const producingStack = this.producingStack!; - - if (producingStack.env.account !== consumingStack.env.account || producingStack.env.region !== consumingStack.env.region) { - throw new Error('Can only reference cross stacks in the same region and account.'); - } - - // Ensure a singleton "Exports" scoping Construct - // This mostly exists to trigger LogicalID munging, which would be - // disabled if we parented constructs directly under Stack. - // Also it nicely prevents likely construct name clashes - - const exportsName = 'Exports'; - let stackExports = producingStack.node.tryFindChild(exportsName) as Construct; - if (stackExports === undefined) { - stackExports = new Construct(producingStack, exportsName); - } - - // Ensure a singleton CfnOutput for this value - const resolved = producingStack.node.resolve(tokenValue); - const id = 'Output' + JSON.stringify(resolved); - let output = stackExports.node.tryFindChild(id) as CfnOutput; - if (!output) { - output = new CfnOutput(stackExports, id, { value: tokenValue }); - } - - // We want to return an actual FnImportValue Token here, but Fn.importValue() returns a 'string', - // so construct one in-place. - return new Token({ 'Fn::ImportValue': output.obtainExportName() }); - } - } -import { CfnOutput } from "./cfn-output"; -import { Construct } from "./construct"; -import { Stack } from "./stack"; +import { Construct } from "./construct"; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/resource-policy.ts b/packages/@aws-cdk/cdk/lib/resource-policy.ts index a7dcf67986cc3..a3ff6b9d84691 100644 --- a/packages/@aws-cdk/cdk/lib/resource-policy.ts +++ b/packages/@aws-cdk/cdk/lib/resource-policy.ts @@ -19,13 +19,13 @@ export interface CreationPolicy { * For an Auto Scaling group replacement update, specifies how many instances must signal success for the * update to succeed. */ - autoScalingCreationPolicy?: AutoScalingCreationPolicy; + readonly autoScalingCreationPolicy?: AutoScalingCreationPolicy; /** * When AWS CloudFormation creates the associated resource, configures the number of required success signals and * the length of time that AWS CloudFormation waits for those signals. */ - resourceSignal?: ResourceSignal; + readonly resourceSignal?: ResourceSignal; } /** @@ -40,7 +40,7 @@ export interface AutoScalingCreationPolicy { * If an instance doesn't send a signal within the time specified by the Timeout property, AWS CloudFormation assumes that the * instance wasn't created. */ - minSuccessfulInstancesPercent?: number; + readonly minSuccessfulInstancesPercent?: number; } /** @@ -54,14 +54,14 @@ export interface ResourceSignal { * If the resource receives a failure signal or doesn't receive the specified number of signals before the timeout period * expires, the resource creation fails and AWS CloudFormation rolls the stack back. */ - count?: number; + readonly count?: number; /** * The length of time that AWS CloudFormation waits for the number of signals that was specified in the Count property. * The timeout period starts after AWS CloudFormation starts creating the resource, and the timeout expires no sooner * than the time you specify but can occur shortly thereafter. The maximum time that you can specify is 12 hours. */ - timeout?: string; + readonly timeout?: string; } /** @@ -108,33 +108,33 @@ export interface UpdatePolicy { * AWS CloudFormation retains the old group until it finishes creating the new one. If the update fails, AWS CloudFormation * can roll back to the old Auto Scaling group and delete the new Auto Scaling group. */ - autoScalingReplacingUpdate?: AutoScalingReplacingUpdate; + readonly autoScalingReplacingUpdate?: AutoScalingReplacingUpdate; /** * To specify how AWS CloudFormation handles rolling updates for an Auto Scaling group, use the AutoScalingRollingUpdate * policy. Rolling updates enable you to specify whether AWS CloudFormation updates instances that are in an Auto Scaling * group in batches or all at once. */ - autoScalingRollingUpdate?: AutoScalingRollingUpdate; + readonly autoScalingRollingUpdate?: AutoScalingRollingUpdate; /** * To specify how AWS CloudFormation handles updates for the MinSize, MaxSize, and DesiredCapacity properties when * the AWS::AutoScaling::AutoScalingGroup resource has an associated scheduled action, use the AutoScalingScheduledAction * policy. */ - autoScalingScheduledAction?: AutoScalingScheduledAction; + readonly autoScalingScheduledAction?: AutoScalingScheduledAction; /** * To perform an AWS CodeDeploy deployment when the version changes on an AWS::Lambda::Alias resource, * use the CodeDeployLambdaAliasUpdate update policy. */ - codeDeployLambdaAliasUpdate?: CodeDeployLambdaAliasUpdate; + readonly codeDeployLambdaAliasUpdate?: CodeDeployLambdaAliasUpdate; /** * To modify a replication group's shards by adding or removing shards, rather than replacing the entire * AWS::ElastiCache::ReplicationGroup resource, use the UseOnlineResharding update policy. */ - useOnlineResharding?: boolean; + readonly useOnlineResharding?: boolean; } @@ -148,13 +148,13 @@ export interface AutoScalingRollingUpdate { /** * Specifies the maximum number of instances that AWS CloudFormation updates. */ - maxBatchSize?: number; + readonly maxBatchSize?: number; /** * Specifies the minimum number of instances that must be in service within the Auto Scaling group while AWS * CloudFormation updates old instances. */ - minInstancesInService?: number; + readonly minInstancesInService?: number; /** * Specifies the percentage of instances in an Auto Scaling rolling update that must signal success for an update to succeed. @@ -166,7 +166,7 @@ export interface AutoScalingRollingUpdate { * * If you specify this property, you must also enable the WaitOnResourceSignals and PauseTime properties. */ - minSuccessfulInstancesPercent?: number; + readonly minSuccessfulInstancesPercent?: number; /** * The amount of time that AWS CloudFormation pauses after making a change to a batch of instances to give those instances @@ -182,7 +182,7 @@ export interface AutoScalingRollingUpdate { * Specify PauseTime in the ISO8601 duration format (in the format PT#H#M#S, where each # is the number of hours, minutes, * and seconds, respectively). The maximum PauseTime is one hour (PT1H). */ - pauseTime?: string; + readonly pauseTime?: string; /** * Specifies the Auto Scaling processes to suspend during a stack update. Suspending processes prevents Auto Scaling from @@ -190,7 +190,7 @@ export interface AutoScalingRollingUpdate { * policies associated with an alarm. For valid values, see the ScalingProcesses.member.N parameter for the SuspendProcesses * action in the Auto Scaling API Reference. */ - suspendProcesses?: string[]; + readonly suspendProcesses?: string[]; /** * Specifies whether the Auto Scaling group waits on signals from new instances during an update. Use this property to @@ -203,7 +203,7 @@ export interface AutoScalingRollingUpdate { * verification by using the cfn-init helper script. For an example, see the verify_instance_health command in the Auto Scaling * rolling updates sample template. */ - waitOnResourceSignals?: boolean; + readonly waitOnResourceSignals?: boolean; } /** @@ -220,7 +220,7 @@ export interface AutoScalingRollingUpdate { * Auto Scaling group. */ export interface AutoScalingReplacingUpdate { - willReplace?: boolean; + readonly willReplace?: boolean; } /** @@ -240,7 +240,7 @@ export interface AutoScalingScheduledAction { * a stack update. If you modify any of the group size property values in your template, AWS CloudFormation uses the modified * values and updates your Auto Scaling group. */ - ignoreUnmodifiedGroupSizeProperties?: boolean; + readonly ignoreUnmodifiedGroupSizeProperties?: boolean; } /** @@ -251,20 +251,20 @@ export interface CodeDeployLambdaAliasUpdate { /** * The name of the AWS CodeDeploy application. */ - applicationName: string; + readonly applicationName: string; /** * The name of the AWS CodeDeploy deployment group. This is where the traffic-shifting policy is set. */ - deploymentGroupName: string; + readonly deploymentGroupName: string; /** * The name of the Lambda function to run before traffic routing starts. */ - beforeAllowTrafficHook?: string; + readonly beforeAllowTrafficHook?: string; /** * The name of the Lambda function to run after traffic routing completes. */ - afterAllowTrafficHook?: string; + readonly afterAllowTrafficHook?: string; } diff --git a/packages/@aws-cdk/cdk/lib/secret-value.ts b/packages/@aws-cdk/cdk/lib/secret-value.ts new file mode 100644 index 0000000000000..e5b12581eda2e --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/secret-value.ts @@ -0,0 +1,126 @@ +import { CfnDynamicReference, CfnDynamicReferenceService } from './cfn-dynamic-reference'; +import { CfnParameter } from './cfn-parameter'; +import { Token } from './token'; + +/** + * Work with secret values in the CDK + * + * Secret values in the CDK (such as those retrieved from SecretsManager) are + * represented as regular strings, just like other values that are only + * available at deployment time. + * + * To help you avoid accidental mistakes which would lead to you putting your + * secret values directly into a CloudFormation template, constructs that take + * secret values will not allow you to pass in a literal secret value. They do + * so by calling `Secret.assertSafeSecret()`. + * + * You can escape the check by calling `Secret.plainTex()`, but doing + * so is highly discouraged. + */ +export class SecretValue extends Token { + /** + * Construct a literal secret value for use with secret-aware constructs + * + * *Do not use this method for any secrets that you care about.* + * + * The only reasonable use case for using this method is when you are testing. + */ + public static plainText(secret: string): SecretValue { + return new SecretValue(secret); + } + + /** + * Creates a `SecretValue` with a value which is dynamically loaded from AWS Secrets Manager. + * @param secretId The ID or ARN of the secret + * @param options Options + */ + public static secretsManager(secretId: string, options: SecretsManagerSecretOptions = { }): SecretValue { + if (!secretId) { + throw new Error(`secretId cannot be empty`); + } + + const parts = [ + secretId, + 'SecretString', + options.jsonField || '', + options.versionStage || '', + options.versionId || '' + ]; + + const dyref = new CfnDynamicReference(CfnDynamicReferenceService.SecretsManager, parts.join(':')); + return this.cfnDynamicReference(dyref); + } + + /** + * Use a secret value stored from a Systems Manager (SSM) parameter. + * + * @param parameterName The name of the parameter in the Systems Manager + * Parameter Store. The parameter name is case-sensitive. + * + * @param version An integer that specifies the version of the parameter to + * use. You must specify the exact version. You cannot currently specify that + * AWS CloudFormation use the latest version of a parameter. + */ + public static ssmSecure(parameterName: string, version: string): SecretValue { + const parts = [ parameterName, version ]; + return this.cfnDynamicReference(new CfnDynamicReference(CfnDynamicReferenceService.SsmSecure, parts.join(':'))); + } + + /** + * Obtain the secret value through a CloudFormation dynamic reference. + * + * If possible, use `SecretValue.ssmSecure` or `SecretValue.secretsManager` directly. + * + * @param ref The dynamic reference to use. + */ + public static cfnDynamicReference(ref: CfnDynamicReference) { + return new SecretValue(() => ref.toString()); + } + + /** + * Obtain the secret value through a CloudFormation parameter. + * + * Generally, this is not a recommended approach. AWS Secrets Manager is the + * recommended way to reference secrets. + * + * @param param The CloudFormation parameter to use. + */ + public static cfnParameter(param: CfnParameter) { + if (!param.noEcho) { + throw new Error(`CloudFormation parameter must be configured with "NoEcho"`); + } + + return new SecretValue(param.value); + } +} + +/** + * Options for referencing a secret value from Secrets Manager. + */ +export interface SecretsManagerSecretOptions { + /** + * Specified the secret version that you want to retrieve by the staging label attached to the version. + * + * Can specify at most one of `versionId` and `versionStage`. + * + * @default AWSCURRENT + */ + readonly versionStage?: string; + + /** + * Specifies the unique identifier of the version of the secret you want to use. + * + * Can specify at most one of `versionId` and `versionStage`. + * + * @default AWSCURRENT + */ + readonly versionId?: string; + + /** + * The key of a JSON field to retrieve. This can only be used if the secret + * stores a JSON object. + * + * @default - returns all the content stored in the Secrets Manager secret. + */ + readonly jsonField?: string; +} diff --git a/packages/@aws-cdk/cdk/lib/secret.ts b/packages/@aws-cdk/cdk/lib/secret.ts deleted file mode 100644 index ba28803fe530c..0000000000000 --- a/packages/@aws-cdk/cdk/lib/secret.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { CfnParameter } from './cfn-parameter'; -import { Construct } from './construct'; -import { Token } from './token'; - -/** - * A token that represents a value that's expected to be a secret, like - * passwords and keys. - * - * It is recommended to use the `SecretParameter` construct in order to import - * secret values from the SSM Parameter Store instead of storing them in your - * code. - * - * However, you can also just pass in values, like any other token: `new Secret('bla')` - */ -export class Secret extends Token { } - -export interface SecretParameterProps { - /** - * The name of the SSM parameter where the secret value is stored. - */ - ssmParameter: string; - - /** - * A string of up to 4000 characters that describes the parameter. - * @default No description - */ - description?: string; - - /** - * A regular expression that represents the patterns to allow for String types. - */ - allowedPattern?: string; - - /** - * An array containing the list of values allowed for the parameter. - */ - allowedValues?: string[]; - - /** - * A string that explains a constraint when the constraint is violated. - * For example, without a constraint description, a parameter that has an allowed - * pattern of [A-Za-z0-9]+ displays the following error message when the user specifies - * an invalid value: - */ - constraintDescription?: string; - - /** - * An integer value that determines the largest number of characters you want to allow for String types. - */ - maxLength?: number; - - /** - * An integer value that determines the smallest number of characters you want to allow for String types. - */ - minLength?: number; -} - -/** - * Defines a secret value resolved from the Systems Manager (SSM) Parameter - * Store during deployment. This is useful for referencing values that you do - * not wish to include in your code base, such as secrets, passwords and keys. - * - * This construct will add a CloudFormation parameter to your template bound to - * an SSM parameter (of type "AWS::SSM::Parameter::Value"). Deployment - * will fail if the value doesn't exist in the target environment. - * - * Important: For values other than secrets, prefer to use the - * `SSMParameterProvider` which resolves SSM parameter in design-time, and - * ensures that stack deployments are deterministic. - */ -export class SecretParameter extends Construct { - /** - * The value of the secret parameter. - */ - public value: Secret; - - constructor(scope: Construct, id: string, props: SecretParameterProps) { - super(scope, id); - - const param = new CfnParameter(this, 'Parameter', { - type: 'AWS::SSM::Parameter::Value', - default: props.ssmParameter, - description: props.description, - allowedPattern: props.allowedPattern, - allowedValues: props.allowedValues, - constraintDescription: props.constraintDescription, - maxLength: props.maxLength, - minLength: props.minLength, - noEcho: true, - }); - - this.value = new Secret(param.ref); - } -} diff --git a/packages/@aws-cdk/cdk/lib/stack.ts b/packages/@aws-cdk/cdk/lib/stack.ts index 38ad44c04ba88..ed1023ffaee36 100644 --- a/packages/@aws-cdk/cdk/lib/stack.ts +++ b/packages/@aws-cdk/cdk/lib/stack.ts @@ -4,7 +4,6 @@ import { CfnParameter } from './cfn-parameter'; import { Construct, IConstruct, PATH_SEP } from './construct'; import { Environment } from './environment'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; -import { Reference } from './reference'; import { ISynthesisSession } from './synthesis'; import { makeUniqueId } from './uniqueid'; @@ -15,23 +14,36 @@ export interface StackProps { * If not supplied, the `default-account` and `default-region` context parameters will be * used. If they are undefined, it will not be possible to deploy the stack. */ - env?: Environment; + readonly env?: Environment; /** * Name to deploy the stack with * * @default Derived from construct path */ - stackName?: string; + readonly stackName?: string; /** * Strategy for logical ID generation * * Optional. If not supplied, the HashedNamingScheme will be used. */ - namingScheme?: IAddressingScheme; + readonly namingScheme?: IAddressingScheme; + + /** + * Should the Stack be deployed when running `cdk deploy` without arguments + * (and listed when running `cdk synth` without arguments). + * Setting this to `false` is useful when you have a Stack in your CDK app + * that you don't want to deploy using the CDK toolkit - + * for example, because you're planning on deploying it through CodePipeline. + * + * @default true + */ + readonly autoDeploy?: boolean; } +const STACK_SYMBOL = Symbol('@aws-cdk/cdk.CfnReference'); + /** * A root construct which represents a single CloudFormation stack. */ @@ -54,8 +66,8 @@ export class Stack extends Construct { * * We do attribute detection since we can't reliably use 'instanceof'. */ - public static isStack(construct: IConstruct): construct is Stack { - return (construct as any)._isStack; + public static isStack(x: any): x is Stack { + return x[STACK_SYMBOL] === true; } private static readonly VALID_STACK_NAME_REGEX = /^[A-Za-z][A-Za-z0-9-]*$/; @@ -80,7 +92,7 @@ export class Stack extends Construct { /** * Options for CloudFormation template (like version, transform, description). */ - public readonly templateOptions: TemplateOptions = {}; + public readonly templateOptions: ITemplateOptions = {}; /** * The CloudFormation stack name. @@ -90,15 +102,21 @@ export class Stack extends Construct { */ public readonly name: string; - /* - * Used to determine if this construct is a stack. + /** + * Should the Stack be deployed when running `cdk deploy` without arguments + * (and listed when running `cdk synth` without arguments). + * Setting this to `false` is useful when you have a Stack in your CDK app + * that you don't want to deploy using the CDK toolkit - + * for example, because you're planning on deploying it through CodePipeline. + * + * By default, this is `true`. */ - protected readonly _isStack = true; + public readonly autoDeploy: boolean; /** * Other stacks this stack depends on */ - private readonly stackDependencies = new Set(); + private readonly stackDependencies = new Set(); /** * Values set for parameters in cloud assembly. @@ -123,6 +141,8 @@ export class Stack extends Construct { // For unit test convenience parents are optional, so bypass the type check when calling the parent. super(scope!, name!); + Object.defineProperty(this, STACK_SYMBOL, { value: true }); + if (name && !Stack.VALID_STACK_NAME_REGEX.test(name)) { throw new Error(`Stack name must match the regular expression: ${Stack.VALID_STACK_NAME_REGEX.toString()}, got '${name}'`); } @@ -132,6 +152,7 @@ export class Stack extends Construct { this.logicalIds = new LogicalIDs(props && props.namingScheme ? props.namingScheme : new HashedAddressingScheme()); this.name = props.stackName !== undefined ? props.stackName : this.calculateStackName(); + this.autoDeploy = props && props.autoDeploy === false ? false : true; } /** @@ -259,19 +280,23 @@ export class Stack extends Construct { /** * Add a dependency between this stack and another stack */ - public addDependency(stack: Stack) { - if (stack.dependsOnStack(this)) { + public addDependency(stack: Stack, reason?: string) { + if (stack === this) { return; } // Can ignore a dependency on self + + reason = reason || 'dependency added using stack.addDependency()'; + const dep = stack.stackDependencyReasons(this); + if (dep !== undefined) { // tslint:disable-next-line:max-line-length - throw new Error(`Stack '${this.name}' already depends on stack '${stack.name}'. Adding this dependency would create a cyclic reference.`); + throw new Error(`'${stack.node.path}' depends on '${this.node.path}' (${dep.join(', ')}). Adding this dependency (${reason}) would create a cyclic reference.`); } - this.stackDependencies.add(stack); + this.stackDependencies.add({ stack, reason }); } /** * Return the stacks this stack depends on */ public dependencies(): Stack[] { - return Array.from(this.stackDependencies.values()); + return Array.from(this.stackDependencies.values()).map(d => d.stack); } /** @@ -426,6 +451,8 @@ export class Stack extends Construct { * * CloudFormation stack names can include dashes in addition to the regular identifier * character classes, and we don't allow one of the magic markers. + * + * @internal */ protected _validateId(name: string) { if (name && !Stack.VALID_STACK_NAME_REGEX.test(name)) { @@ -443,8 +470,8 @@ export class Stack extends Construct { protected prepare() { // References for (const ref of this.node.findReferences()) { - if (Reference.isReferenceToken(ref)) { - ref.consumeFromStack(this); + if (CfnReference.isCfnReference(ref.reference)) { + ref.reference.consumeFromStack(this, ref.source); } } @@ -469,65 +496,49 @@ export class Stack extends Construct { // write the CloudFormation template as a JSON file session.store.writeJson(template, this._toCloudFormation()); - const artifact: cxapi.Artifact = { - type: cxapi.ArtifactType.AwsCloudFormationStack, - environment: this.environment, - properties: { - templateFile: template, - } - }; - - if (Object.keys(this.parameterValues).length > 0) { - artifact.properties = artifact.properties || { }; - artifact.properties.parameters = this.node.resolve(this.parameterValues); - } - const deps = this.dependencies().map(s => s.name); - if (deps.length > 0) { - artifact.dependencies = deps; - } - const meta = this.collectMetadata(); - if (Object.keys(meta).length > 0) { - artifact.metadata = meta; - } - - if (this.missingContext && Object.keys(this.missingContext).length > 0) { - artifact.missing = this.missingContext; - } // add an artifact that represents this stack - session.addArtifact(this.name, artifact); + session.addArtifact(this.name, { + type: cxapi.ArtifactType.AwsCloudFormationStack, + environment: this.environment, + properties: { + templateFile: template, + parameters: Object.keys(this.parameterValues).length > 0 ? this.node.resolve(this.parameterValues) : undefined + }, + autoDeploy: this.autoDeploy ? undefined : false, + dependencies: deps.length > 0 ? deps : undefined, + metadata: Object.keys(meta).length > 0 ? meta : undefined, + missing: this.missingContext && Object.keys(this.missingContext).length > 0 ? this.missingContext : undefined + }); } /** * Applied defaults to environment attributes. */ private parseEnvironment(env: Environment = {}) { - const ret: Environment = {...env}; - - // if account is not specified, attempt to read from context. - if (!ret.account) { - ret.account = this.node.getContext(cxapi.DEFAULT_ACCOUNT_CONTEXT_KEY); - } - - // if region is not specified, attempt to read from context. - if (!ret.region) { - ret.region = this.node.getContext(cxapi.DEFAULT_REGION_CONTEXT_KEY); - } - - return ret; + return { + account: env.account ? env.account : this.node.getContext(cxapi.DEFAULT_ACCOUNT_CONTEXT_KEY), + region: env.region ? env.region : this.node.getContext(cxapi.DEFAULT_REGION_CONTEXT_KEY) + }; } /** * Check whether this stack has a (transitive) dependency on another stack + * + * Returns the list of reasons on the dependency path, or undefined + * if there is no dependency. */ - private dependsOnStack(other: Stack) { - if (this === other) { return true; } + private stackDependencyReasons(other: Stack): string[] | undefined { + if (this === other) { return []; } for (const dep of this.stackDependencies) { - if (dep.dependsOnStack(other)) { return true; } + const ret = dep.stack.stackDependencyReasons(other); + if (ret !== undefined) { + return [dep.reason].concat(ret); + } } - return false; + return undefined; } private collectMetadata() { @@ -599,7 +610,7 @@ function merge(template: any, part: any) { /** * CloudFormation template options for a stack. */ -export interface TemplateOptions { +export interface ITemplateOptions { /** * Gets or sets the description of this stack. * If provided, it will be included in the CloudFormation template's "Description" attribute. @@ -647,6 +658,7 @@ function cfnElements(node: IConstruct, into: CfnElement[] = []): CfnElement[] { // These imports have to be at the end to prevent circular imports import { ArnComponents, arnFromComponents, parseArn } from './arn'; import { CfnElement } from './cfn-element'; +import { CfnReference } from './cfn-reference'; import { CfnResource } from './cfn-resource'; import { Aws, ScopedAws } from './pseudo'; @@ -660,3 +672,8 @@ function findResources(roots: Iterable): CfnResource[] { } return ret; } + +interface StackDependency { + stack: Stack; + reason: string; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/synthesis.ts b/packages/@aws-cdk/cdk/lib/synthesis.ts index 9b5d95d3ae1ae..47d2cfdbd581e 100644 --- a/packages/@aws-cdk/cdk/lib/synthesis.ts +++ b/packages/@aws-cdk/cdk/lib/synthesis.ts @@ -2,7 +2,9 @@ import cxapi = require('@aws-cdk/cx-api'); import fs = require('fs'); import os = require('os'); import path = require('path'); +import { ConstructOrder, IConstruct } from './construct'; import { collectRuntimeInformation } from './runtime-info'; +import { filterUndefined } from './util'; export interface ISynthesizable { synthesize(session: ISynthesisSession): void; @@ -14,25 +16,54 @@ export interface ISynthesisSession { addArtifact(id: string, droplet: cxapi.Artifact): void; addBuildStep(id: string, step: cxapi.BuildStep): void; tryGetArtifact(id: string): cxapi.Artifact | undefined; + getArtifact(id: string): cxapi.Artifact; } -export interface SynthesisSessionOptions { +export interface SynthesisOptions extends ManifestOptions { /** * The file store used for this session. + * @default InMemoryStore */ - store: ISessionStore; + readonly store?: ISessionStore; /** - * Emit the legacy manifest (`cdk.out`) when the session is closed (alongside `manifest.json`). + * Whether synthesis should skip the validation phase. * @default false */ - legacyManifest?: boolean; + readonly skipValidation?: boolean; +} - /** - * Include runtime information (module versions) in manifest. - * @default true - */ - runtimeInformation?: boolean; +export class Synthesizer { + public synthesize(root: IConstruct, options: SynthesisOptions = { }): ISynthesisSession { + const session = new SynthesisSession(options); + + // the three holy phases of synthesis: prepare, validate and synthesize + + // prepare + root.node.prepareTree(); + + // validate + const validate = options.skipValidation === undefined ? true : !options.skipValidation; + if (validate) { + const errors = root.node.validateTree(); + if (errors.length > 0) { + const errorList = errors.map(e => `[${e.source.node.path}] ${e.message}`).join('\n '); + throw new Error(`Validation failed with the following errors:\n ${errorList}`); + } + } + + // synthesize (leaves first) + for (const c of root.node.findAll(ConstructOrder.PostOrder)) { + if (SynthesisSession.isSynthesizable(c)) { + c.synthesize(session); + } + } + + // write session manifest and lock store + session.close(options); + + return session; + } } export class SynthesisSession implements ISynthesisSession { @@ -48,13 +79,9 @@ export class SynthesisSession implements ISynthesisSession { private readonly artifacts: { [id: string]: cxapi.Artifact } = { }; private readonly buildSteps: { [id: string]: cxapi.BuildStep } = { }; private _manifest?: cxapi.AssemblyManifest; - private readonly legacyManifest: boolean; - private readonly runtimeInfo: boolean; - constructor(options: SynthesisSessionOptions) { - this.store = options.store; - this.legacyManifest = options.legacyManifest !== undefined ? options.legacyManifest : false; - this.runtimeInfo = options.runtimeInformation !== undefined ? options.runtimeInformation : true; + constructor(options: SynthesisOptions) { + this.store = options.store || new InMemoryStore(); } public get manifest() { @@ -67,29 +94,37 @@ export class SynthesisSession implements ISynthesisSession { public addArtifact(id: string, artifact: cxapi.Artifact): void { cxapi.validateArtifact(artifact); - this.artifacts[id] = artifact; + this.artifacts[id] = filterUndefined(artifact); } public tryGetArtifact(id: string): cxapi.Artifact | undefined { return this.artifacts[id]; } + public getArtifact(id: string): cxapi.Artifact { + const artifact = this.tryGetArtifact(id); + if (!artifact) { + throw new Error(`Cannot find artifact ${id}`); + } + return artifact; + } + public addBuildStep(id: string, step: cxapi.BuildStep) { if (id in this.buildSteps) { throw new Error(`Build step ${id} already exists`); } - this.buildSteps[id] = step; + this.buildSteps[id] = filterUndefined(step); } - public close(): cxapi.AssemblyManifest { - const manifest: cxapi.AssemblyManifest = this._manifest = { + public close(options: ManifestOptions = { }): cxapi.AssemblyManifest { + const legacyManifest = options.legacyManifest !== undefined ? options.legacyManifest : false; + const runtimeInfo = options.runtimeInformation !== undefined ? options.runtimeInformation : true; + + const manifest: cxapi.AssemblyManifest = this._manifest = filterUndefined({ version: cxapi.PROTO_RESPONSE_VERSION, artifacts: this.artifacts, - }; - - if (this.runtimeInfo) { - manifest.runtime = collectRuntimeInformation(); - } + runtime: runtimeInfo ? collectRuntimeInformation() : undefined + }); this.store.writeFile(cxapi.MANIFEST_FILE, JSON.stringify(manifest, undefined, 2)); @@ -102,20 +137,36 @@ export class SynthesisSession implements ISynthesisSession { this.store.writeFile(cxapi.BUILD_FILE, JSON.stringify(buildManifest, undefined, 2)); } - if (this.legacyManifest) { + if (legacyManifest) { const legacy: cxapi.SynthesizeResponse = { ...manifest, - stacks: renderLegacyStacks(this.artifacts, this.store) + stacks: renderLegacyStacks(manifest, this.store) }; // render the legacy manifest (cdk.out) which also contains a "stacks" attribute with all the rendered stacks. this.store.writeFile(cxapi.OUTFILE_NAME, JSON.stringify(legacy, undefined, 2)); } + this.store.lock(); + return manifest; } } +export interface ManifestOptions { + /** + * Emit the legacy manifest (`cdk.out`) when the session is closed (alongside `manifest.json`). + * @default false + */ + readonly legacyManifest?: boolean; + + /** + * Include runtime information (module versions) in manifest. + * @default true + */ + readonly runtimeInformation?: boolean; +} + export interface ISessionStore { /** * Creates a directory and returns it's full path. @@ -171,21 +222,14 @@ export interface ISessionStore { /** * Do not allow further writes into the store. */ - finalize(): void; -} - -export interface SynthesisSessionOptions { - /** - * Where to store the - */ - store: ISessionStore; + lock(): void; } export interface FileSystemStoreOptions { /** * The output directory for synthesis artifacts */ - outdir: string; + readonly outdir: string; } /** @@ -249,7 +293,7 @@ export class FileSystemStore implements ISessionStore { return fs.readdirSync(this.outdir).sort(); } - public finalize() { + public lock() { this.locked = true; } @@ -318,7 +362,7 @@ export class InMemoryStore implements ISessionStore { return [ ...Object.keys(this.files), ...Object.keys(this.dirs) ].sort(); } - public finalize() { + public lock() { this.locked = true; } @@ -332,9 +376,10 @@ export class InMemoryStore implements ISessionStore { } } -function renderLegacyStacks(artifacts: { [id: string]: cxapi.Artifact }, store: ISessionStore) { +export function renderLegacyStacks(manifest: cxapi.AssemblyManifest, store: ISessionStore) { // special case for backwards compat. build a list of stacks for the manifest const stacks = new Array(); + const artifacts = manifest.artifacts || { }; for (const [ id, artifact ] of Object.entries(artifacts)) { if (artifact.type === cxapi.ArtifactType.AwsCloudFormationStack) { @@ -349,24 +394,17 @@ function renderLegacyStacks(artifacts: { [id: string]: cxapi.Artifact }, store: throw new Error(`"environment" must match regex: ${cxapi.AWS_ENV_REGEX}`); } - const synthStack: cxapi.SynthesizedStack = { + stacks.push(filterUndefined({ name: id, environment: { name: artifact.environment.substr('aws://'.length), account: match[1], region: match[2] }, template, metadata: artifact.metadata || {}, - }; - - if (artifact.dependencies && artifact.dependencies.length > 0) { - synthStack.dependsOn = artifact.dependencies; - } - - if (artifact.missing) { - synthStack.missing = artifact.missing; - } - - stacks.push(synthStack); + autoDeploy: artifact.autoDeploy, + dependsOn: artifact.dependencies && artifact.dependencies.length > 0 ? artifact.dependencies : undefined, + missing: artifact.missing + })); } } return stacks; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cdk/lib/tag-aspect.ts b/packages/@aws-cdk/cdk/lib/tag-aspect.ts index 0e4623208170d..e6f373fffea92 100644 --- a/packages/@aws-cdk/cdk/lib/tag-aspect.ts +++ b/packages/@aws-cdk/cdk/lib/tag-aspect.ts @@ -11,7 +11,7 @@ export interface TagProps { * * @default true */ - applyToLaunchedInstances?: boolean; + readonly applyToLaunchedInstances?: boolean; /** * An array of Resource Types that will not receive this tag @@ -21,7 +21,7 @@ export interface TagProps { * this array. * @default [] */ - excludeResourceTypes?: string[]; + readonly excludeResourceTypes?: string[]; /** * An array of Resource Types that will receive this tag @@ -30,7 +30,7 @@ export interface TagProps { * tag only to Resource types that are included in this array. * @default [] */ - includeResourceTypes?: string[]; + readonly includeResourceTypes?: string[]; /** * Priority of the tag operation @@ -50,7 +50,7 @@ export interface TagProps { * - 50 for tags added directly to CloudFormation resources * */ - priority?: number; + readonly priority?: number; } /** diff --git a/packages/@aws-cdk/cdk/lib/tag.ts b/packages/@aws-cdk/cdk/lib/tag.ts index a05aa282335b9..5e3f2446018a5 100644 --- a/packages/@aws-cdk/cdk/lib/tag.ts +++ b/packages/@aws-cdk/cdk/lib/tag.ts @@ -5,10 +5,10 @@ export interface CfnTag { /** * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html#cfn-resource-tags-key */ - key: string; + readonly key: string; /** * @link http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html#cfn-resource-tags-value */ - value: string; + readonly value: string; } diff --git a/packages/@aws-cdk/cdk/lib/token.ts b/packages/@aws-cdk/cdk/lib/token.ts index 232bd92f1ec97..4ce9792079931 100644 --- a/packages/@aws-cdk/cdk/lib/token.ts +++ b/packages/@aws-cdk/cdk/lib/token.ts @@ -1,5 +1,6 @@ import { IConstruct } from "./construct"; import { TOKEN_MAP } from "./encoding"; +import { unresolved } from './unresolved'; /** * If objects has a function property by this name, they will be considered tokens, and this @@ -19,12 +20,14 @@ export const RESOLVE_METHOD = 'resolve'; */ export class Token { /** - * Indicate whether this Token represent a "reference" + * Returns true if obj is a token (i.e. has the resolve() method or is a string + * that includes token markers), or it's a listifictaion of a Token string. * - * The Construct tree can be queried for the Reference Tokens that - * are used in it. + * @param obj The object to test. */ - public readonly isReference?: boolean; + public static unresolved(obj: any): boolean { + return unresolved(obj); + } private tokenStringification?: string; private tokenListification?: string[]; @@ -128,8 +131,8 @@ export class Token { * Current resolution context for tokens */ export interface ResolveContext { - scope: IConstruct; - prefix: string[]; + readonly scope: IConstruct; + readonly prefix: string[]; } /** @@ -147,4 +150,4 @@ export interface IResolvedValuePostProcessor { */ export function isResolvedValuePostProcessor(x: any): x is IResolvedValuePostProcessor { return x.postProcess !== undefined; -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/unresolved.ts b/packages/@aws-cdk/cdk/lib/unresolved.ts index 1262f6f86c0fd..06cb599149351 100644 --- a/packages/@aws-cdk/cdk/lib/unresolved.ts +++ b/packages/@aws-cdk/cdk/lib/unresolved.ts @@ -6,6 +6,7 @@ import { RESOLVE_METHOD } from "./token"; * that includes token markers), or it's a listifictaion of a Token string. * * @param obj The object to test. + * @deprecated use `Token.unresolved` */ export function unresolved(obj: any): boolean { if (typeof(obj) === 'string') { diff --git a/packages/@aws-cdk/cdk/lib/util.ts b/packages/@aws-cdk/cdk/lib/util.ts index 941b14d8c1e8a..ba83bfc00f81b 100644 --- a/packages/@aws-cdk/cdk/lib/util.ts +++ b/packages/@aws-cdk/cdk/lib/util.ts @@ -50,6 +50,28 @@ export function ignoreEmpty(obj: any): any { }); } +/** + * Returns a copy of `obj` without undefined values in maps or arrays. + */ +export function filterUndefined(obj: any): any { + if (Array.isArray(obj)) { + return obj.filter(x => x !== undefined).map(x => filterUndefined(x)); + } + + if (typeof(obj) === 'object') { + const ret: any = { }; + for (const [key, value] of Object.entries(obj)) { + if (value === undefined) { + continue; + } + ret[key] = filterUndefined(value); + } + return ret; + } + + return obj; +} + /** * A Token that applies a function AFTER resolve resolution */ diff --git a/packages/@aws-cdk/cdk/package-lock.json b/packages/@aws-cdk/cdk/package-lock.json index e6f9c7b90b2c6..b6f124bfcc11a 100644 --- a/packages/@aws-cdk/cdk/package-lock.json +++ b/packages/@aws-cdk/cdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cdk", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cdk/package.json b/packages/@aws-cdk/cdk/package.json index 3328616464ef4..23554ccc5fae6 100644 --- a/packages/@aws-cdk/cdk/package.json +++ b/packages/@aws-cdk/cdk/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cdk", - "version": "0.26.0", + "version": "0.28.0", "description": "AWS Cloud Development Kit Core Library", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.cdk", + "module": "aws_cdk.cdk" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/cdk" }, "awslint": { "exclude": [ @@ -62,20 +67,20 @@ "license": "Apache-2.0", "devDependencies": { "@types/lodash": "^4.14.118", - "cdk-build-tools": "^0.26.0", - "cfn2ts": "^0.26.0", + "cdk-build-tools": "^0.28.0", + "cfn2ts": "^0.28.0", "fast-check": "^1.7.0", "lodash": "^4.17.11", - "pkglint": "^0.26.0" + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/cx-api": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/cx-api": "^0.26.0" + "@aws-cdk/cx-api": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cdk/test/test.app.ts b/packages/@aws-cdk/cdk/test/test.app.ts index 8897e98f2d29e..04c8995d0932b 100644 --- a/packages/@aws-cdk/cdk/test/test.app.ts +++ b/packages/@aws-cdk/cdk/test/test.app.ts @@ -41,7 +41,7 @@ function synthStack(name: string, includeMetadata: boolean = false, context?: an } if (!includeMetadata) { - delete stack.metadata; + delete (stack as any).metadata; } return stack; @@ -52,9 +52,9 @@ export = { const response = synth(); // clean up metadata so assertion will be sane - response.stacks.forEach(s => delete s.metadata); - delete response.runtime; - delete response.artifacts; + response.stacks.forEach(s => delete (s as any).metadata); + delete (response as any).runtime; + delete (response as any).artifacts; test.deepEqual(response, { version: '0.19.0', diff --git a/packages/@aws-cdk/cdk/test/test.dynamic-reference.ts b/packages/@aws-cdk/cdk/test/test.dynamic-reference.ts index fa82494a33abe..b16b999f9527e 100644 --- a/packages/@aws-cdk/cdk/test/test.dynamic-reference.ts +++ b/packages/@aws-cdk/cdk/test/test.dynamic-reference.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { DynamicReference, DynamicReferenceService, Stack } from '../lib'; +import { CfnDynamicReference, CfnDynamicReferenceService, Stack } from '../lib'; export = { 'can create dynamic references with service and key with colons'(test: Test) { @@ -7,13 +7,10 @@ export = { const stack = new Stack(); // WHEN - const ref = new DynamicReference(stack, 'Ref', { - service: DynamicReferenceService.Ssm, - referenceKey: 'a:b:c', - }); + const ref = new CfnDynamicReference(CfnDynamicReferenceService.Ssm, 'a:b:c'); // THEN - test.equal(stack.node.resolve(ref.stringValue), '{{resolve:ssm:a:b:c}}'); + test.equal(stack.node.resolve(ref), '{{resolve:ssm:a:b:c}}'); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/test.include.ts b/packages/@aws-cdk/cdk/test/test.include.ts index d8761503c4f22..0f4b400f21dc9 100644 --- a/packages/@aws-cdk/cdk/test/test.include.ts +++ b/packages/@aws-cdk/cdk/test/test.include.ts @@ -21,7 +21,7 @@ export = { new Include(stack, 'T1', { template: clone(template) }); new CfnResource(stack, 'MyResource3', { type: 'ResourceType3', properties: { P3: 'Hello' } }); - new CfnOutput(stack, 'MyOutput', { description: 'Out!', disableExport: true }); + new CfnOutput(stack, 'MyOutput', { description: 'Out!', disableExport: true, value: 'hey' }); new CfnParameter(stack, 'MyParam2', { type: 'Integer' }); test.deepEqual(stack._toCloudFormation(), { @@ -33,7 +33,7 @@ export = { MyResource2: { Type: 'ResourceType2' }, MyResource3: { Type: 'ResourceType3', Properties: { P3: 'Hello' } } }, Outputs: { - MyOutput: { Description: 'Out!' } } }); + MyOutput: { Description: 'Out!', Value: 'hey' } } }); test.done(); }, @@ -43,7 +43,7 @@ export = { new Include(stack, 'T1', { template }); new CfnResource(stack, 'MyResource3', { type: 'ResourceType3', properties: { P3: 'Hello' } }); - new CfnOutput(stack, 'MyOutput', { description: 'Out!' }); + new CfnOutput(stack, 'MyOutput', { description: 'Out!', value: 'in' }); new CfnParameter(stack, 'MyParam', { type: 'Integer' }); // duplicate! test.throws(() => stack._toCloudFormation()); diff --git a/packages/@aws-cdk/cdk/test/test.logical-id.ts b/packages/@aws-cdk/cdk/test/test.logical-id.ts index a2037722587e3..5b7466cd2cc77 100644 --- a/packages/@aws-cdk/cdk/test/test.logical-id.ts +++ b/packages/@aws-cdk/cdk/test/test.logical-id.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { CfnResource, Construct, HashedAddressingScheme, IAddressingScheme, Ref, Stack } from '../lib'; +import { CfnResource, Construct, HashedAddressingScheme, IAddressingScheme, Stack } from '../lib'; /** * These tests are executed once (for specific ID schemes) @@ -204,7 +204,7 @@ const allSchemesTests: {[name: string]: (scheme: IAddressingScheme, test: Test) // WHEN const c1 = new CfnResource(stack, 'OriginalName', { type: 'R1' }); - const ref = new Ref(c1); + const ref = c1.ref; const c2 = new CfnResource(stack, 'Construct2', { type: 'R2', properties: { ReferenceToR1: ref } }); c2.node.addDependency(c1); diff --git a/packages/@aws-cdk/cdk/test/test.mappings.ts b/packages/@aws-cdk/cdk/test/test.mappings.ts index 82cae2a2ab406..f9407e1c1b86d 100644 --- a/packages/@aws-cdk/cdk/test/test.mappings.ts +++ b/packages/@aws-cdk/cdk/test/test.mappings.ts @@ -1,5 +1,5 @@ import { Test } from 'nodeunit'; -import { CfnMapping, CfnResource, Stack } from '../lib'; +import { Aws, CfnMapping, CfnResource, Fn, Stack } from '../lib'; export = { 'mappings can be added as another type of entity, and mapping.findInMap can be used to get a token'(test: Test) { @@ -43,4 +43,24 @@ export = { test.done(); }, + + 'allow using unresolved tokens in find-in-map'(test: Test) { + const stack = new Stack(); + + const mapping = new CfnMapping(stack, 'mapping', { + mapping: { + instanceCount: { + 'us-east-1': 12 + } + } + }); + + const v1 = mapping.findInMap('instanceCount', Aws.region); + const v2 = Fn.findInMap(mapping.logicalId, 'instanceCount', Aws.region); + + const expected = { 'Fn::FindInMap': [ 'mapping', 'instanceCount', { Ref: 'AWS::Region' } ] }; + test.deepEqual(stack.node.resolve(v1), expected); + test.deepEqual(stack.node.resolve(v2), expected); + test.done(); + } }; diff --git a/packages/@aws-cdk/cdk/test/test.output.ts b/packages/@aws-cdk/cdk/test/test.output.ts index 639152ad6cbd4..43b239c33583d 100644 --- a/packages/@aws-cdk/cdk/test/test.output.ts +++ b/packages/@aws-cdk/cdk/test/test.output.ts @@ -1,11 +1,11 @@ import { Test } from 'nodeunit'; -import { CfnOutput, CfnResource, Ref, Stack } from '../lib'; +import { CfnOutput, CfnResource, Stack } from '../lib'; export = { 'outputs can be added to the stack'(test: Test) { const stack = new Stack(); const res = new CfnResource(stack, 'MyResource', { type: 'R' }); - const ref = new Ref(res); + const ref = res.ref; new CfnOutput(stack, 'MyOutput', { export: 'ExportName', @@ -23,21 +23,22 @@ export = { 'outputs cannot be referenced'(test: Test) { const stack = new Stack(); - const output = new CfnOutput(stack, 'MyOutput', { description: 'My CfnOutput' }); + const output = new CfnOutput(stack, 'MyOutput', { description: 'My CfnOutput', value: 'boom' }); test.throws(() => output.ref); test.done(); }, 'disableExport can be used to disable the auto-export behavior'(test: Test) { const stack = new Stack(); - const output = new CfnOutput(stack, 'MyOutput', { disableExport: true }); + const output = new CfnOutput(stack, 'MyOutput', { disableExport: true, value: 'boom' }); test.equal(output.export, null); // cannot specify `export` and `disableExport` at the same time. test.throws(() => new CfnOutput(stack, 'YourOutput', { disableExport: true, - export: 'bla' + export: 'bla', + value: 'boom' }), /Cannot set `disableExport` and specify an export name/); test.done(); @@ -45,19 +46,20 @@ export = { 'if stack name is undefined, we will only use the logical ID for the export name'(test: Test) { const stack = new Stack(); - const output = new CfnOutput(stack, 'MyOutput'); + const output = new CfnOutput(stack, 'MyOutput', { value: 'boom' }); test.deepEqual(stack.node.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'Stack:MyOutput' }); test.done(); }, 'makeImportValue can be used to create an Fn::ImportValue from an output'(test: Test) { const stack = new Stack(undefined, 'MyStack'); - const output = new CfnOutput(stack, 'MyOutput'); + const output = new CfnOutput(stack, 'MyOutput', { value: 'boom' }); test.deepEqual(stack.node.resolve(output.makeImportValue()), { 'Fn::ImportValue': 'MyStack:MyOutput' }); test.deepEqual(stack._toCloudFormation(), { Outputs: { MyOutput: { + Value: 'boom', Export: { Name: 'MyStack:MyOutput' } } } diff --git a/packages/@aws-cdk/cdk/test/test.secret-value.ts b/packages/@aws-cdk/cdk/test/test.secret-value.ts new file mode 100644 index 0000000000000..5d3e54f013f30 --- /dev/null +++ b/packages/@aws-cdk/cdk/test/test.secret-value.ts @@ -0,0 +1,97 @@ +import { Test } from 'nodeunit'; +import { CfnDynamicReference, CfnDynamicReferenceService, CfnParameter, SecretValue, Stack } from '../lib'; + +export = { + 'plainText'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const v = SecretValue.plainText('this just resolves to a string'); + + // THEN + test.deepEqual(stack.node.resolve(v), 'this just resolves to a string'); + test.done(); + }, + + 'secretsManager'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const v = SecretValue.secretsManager('secret-id', { + jsonField: 'json-key', + versionId: 'version-id', + versionStage: 'version-stage' + }); + + // THEN + test.deepEqual(stack.node.resolve(v), '{{resolve:secretsmanager:secret-id:SecretString:json-key:version-stage:version-id}}'); + test.done(); + }, + + 'secretsManager with defaults'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const v = SecretValue.secretsManager('secret-id'); + + // THEN + test.deepEqual(stack.node.resolve(v), '{{resolve:secretsmanager:secret-id:SecretString:::}}'); + test.done(); + }, + + 'secretsManager with an empty ID'(test: Test) { + test.throws(() => SecretValue.secretsManager(''), /secretId cannot be empty/); + test.done(); + }, + + 'ssmSecure'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const v = SecretValue.ssmSecure('param-name', 'param-version'); + + // THEN + test.deepEqual(stack.node.resolve(v), '{{resolve:ssm-secure:param-name:param-version}}'); + test.done(); + }, + + 'cfnDynamicReference'(test: Test) { + // GIVEN + const stack = new Stack(); + + // WHEN + const v = SecretValue.cfnDynamicReference(new CfnDynamicReference(CfnDynamicReferenceService.Ssm, 'foo:bar')); + + // THEN + test.deepEqual(stack.node.resolve(v), '{{resolve:ssm:foo:bar}}'); + test.done(); + }, + + 'cfnParameter (with NoEcho)'(test: Test) { + // GIVEN + const stack = new Stack(); + const p = new CfnParameter(stack, 'MyParam', { type: 'String', noEcho: true }); + + // WHEN + const v = SecretValue.cfnParameter(p); + + // THEN + test.deepEqual(stack.node.resolve(v), { Ref: 'MyParam' }); + test.done(); + }, + + 'fails if cfnParameter does not have NoEcho'(test: Test) { + // GIVEN + const stack = new Stack(); + const p = new CfnParameter(stack, 'MyParam', { type: 'String' }); + + // THEN + test.throws(() => SecretValue.cfnParameter(p), /CloudFormation parameter must be configured with "NoEcho"/); + test.done(); + } + +}; \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/test.secret.ts b/packages/@aws-cdk/cdk/test/test.secret.ts deleted file mode 100644 index f240df0413c60..0000000000000 --- a/packages/@aws-cdk/cdk/test/test.secret.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Test } from 'nodeunit'; -import { Secret, SecretParameter, Stack } from '../lib'; - -export = { - 'Secret is merely a token'(test: Test) { - const stack = new Stack(); - const foo = new Secret('Foo'); - const bar = new Secret(() => 'Bar'); - - test.deepEqual(stack.node.resolve(foo), 'Foo'); - test.deepEqual(stack.node.resolve(bar), 'Bar'); - test.done(); - }, - - 'SecretParameter can be used to define values resolved from SSM parameter store during deployment'(test: Test) { - const stack = new Stack(); - - const mySecret = new SecretParameter(stack, 'MySecret', { ssmParameter: '/my/secret/param' }); - - new SecretParameter(stack, 'Boom', { - ssmParameter: 'Boom', - description: 'description', - constraintDescription: 'constraintDescription', - minLength: -100, - maxLength: 2000, - allowedPattern: 'allowed-pattern', - allowedValues: [ 'allowed', 'values' ], - }); - - test.deepEqual(stack._toCloudFormation(), { Parameters: - { MySecretParameterBB81DE58: - { Type: 'AWS::SSM::Parameter::Value', - Default: '/my/secret/param', - NoEcho: true }, - BoomParameterB3EB3942: - { Type: 'AWS::SSM::Parameter::Value', - Default: 'Boom', - AllowedPattern: 'allowed-pattern', - AllowedValues: [ 'allowed', 'values' ], - ConstraintDescription: 'constraintDescription', - Description: 'description', - MaxLength: 2000, - MinLength: -100, - NoEcho: true } } }); - - // value resolves to a "Ref" - test.deepEqual(stack.node.resolve(mySecret.value), { Ref: 'MySecretParameterBB81DE58' }); - - test.done(); - } -}; diff --git a/packages/@aws-cdk/cdk/test/test.stack.ts b/packages/@aws-cdk/cdk/test/test.stack.ts index 8fd5099d7c750..4a7791a0778a4 100644 --- a/packages/@aws-cdk/cdk/test/test.stack.ts +++ b/packages/@aws-cdk/cdk/test/test.stack.ts @@ -118,7 +118,7 @@ export = { const stack = new Stack(); const p = new CfnParameter(stack, 'MyParam', { type: 'String' }); - const o = new CfnOutput(stack, 'MyOutput'); + const o = new CfnOutput(stack, 'MyOutput', { value: 'boom' }); const c = new CfnCondition(stack, 'MyCondition'); test.equal(stack.node.findChild(p.node.path), p); @@ -286,7 +286,8 @@ export = { test.throws(() => { app.node.prepareTree(); - }, /Adding this dependency would create a cyclic reference/); + // tslint:disable-next-line:max-line-length + }, "'Stack2' depends on 'Stack1' (Stack2/SomeParameter -> Stack1.AWS::AccountId). Adding this dependency (Stack1/SomeParameter -> Stack2.AWS::AccountId) would create a cyclic reference."); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/test.synthesis.ts b/packages/@aws-cdk/cdk/test/test.synthesis.ts index 3398759363eb6..c360054a24546 100644 --- a/packages/@aws-cdk/cdk/test/test.synthesis.ts +++ b/packages/@aws-cdk/cdk/test/test.synthesis.ts @@ -4,7 +4,7 @@ import { Test } from 'nodeunit'; import os = require('os'); import path = require('path'); import cdk = require('../lib'); -import { FileSystemStore, InMemoryStore, SynthesisSession } from '../lib'; +import { Construct, FileSystemStore, InMemoryStore, ISynthesisSession, SynthesisSession, Synthesizer } from '../lib'; const storeTestMatrix: any = {}; @@ -89,7 +89,7 @@ export = { 'one-stack': { type: 'aws:cloudformation:stack', environment: 'aws://unknown-account/unknown-region', - properties: { templateFile: 'one-stack.template.json' } + properties: { templateFile: 'one-stack.template.json' }, } }, }); @@ -127,6 +127,49 @@ export = { test.done(); }, + 'it should be possible to synthesize without an app'(test: Test) { + const calls = new Array(); + + class SynthesizeMe extends Construct { + constructor() { + super(undefined as any, 'id'); + } + + protected validate(): string[] { + calls.push('validate'); + return []; + } + + protected prepare(): void { + calls.push('prepare'); + } + + protected synthesize(session: ISynthesisSession) { + calls.push('synthesize'); + + session.addArtifact('art', { + type: cxapi.ArtifactType.AwsEcrDockerImage, + environment: 'aws://unknown-account/us-east-1' + }); + + session.store.writeJson('hey.json', { hello: 123 }); + } + } + + const root = new SynthesizeMe(); + + const synth = new Synthesizer(); + const result = synth.synthesize(root); + + test.deepEqual(calls, [ 'prepare', 'validate', 'synthesize' ]); + test.deepEqual(result.store.readJson('hey.json'), { hello: 123 }); + test.deepEqual(result.manifest.artifacts!.art, { + type: 'aws:ecr:image', + environment: 'aws://unknown-account/us-east-1' + }); + test.done(); + }, + 'store': storeTestMatrix }; @@ -145,7 +188,7 @@ const storeTests = { test.throws(() => store.writeFile('bla.txt', 'override is forbidden')); // WHEN - store.finalize(); + store.lock(); // THEN test.throws(() => store.writeFile('another.txt', 'locked!')); @@ -173,7 +216,7 @@ const storeTests = { test.throws(() => store.mkdir('dir1')); // WHEN - store.finalize(); + store.lock(); test.throws(() => store.mkdir('dir3')); test.done(); }, diff --git a/packages/@aws-cdk/cfnspec/.gitignore b/packages/@aws-cdk/cfnspec/.gitignore index 24df59c3eb5fb..ea8c6f0a9fe02 100644 --- a/packages/@aws-cdk/cfnspec/.gitignore +++ b/packages/@aws-cdk/cfnspec/.gitignore @@ -2,6 +2,7 @@ *.js node_modules spec +CHANGELOG.md.new .LAST_BUILD .nyc_output diff --git a/packages/@aws-cdk/cfnspec/CHANGELOG.md b/packages/@aws-cdk/cfnspec/CHANGELOG.md index 99b2bce2586d6..886903e147257 100644 --- a/packages/@aws-cdk/cfnspec/CHANGELOG.md +++ b/packages/@aws-cdk/cfnspec/CHANGELOG.md @@ -1,3 +1,97 @@ +# CloudFormation Resource Specification v2.30.0 + +## New Resource Types + + +## Attribute Changes + + +## Property Changes + + +## Property Type Changes + +* AWS::Batch::JobDefinition.ResourceRequirement (__added__) +* AWS::Batch::JobDefinition.ContainerProperties ResourceRequirements (__added__) +* Tag Key.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-tag.html#cfn-iotanalytics-datastore-tag-key + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-tag.html#cfn-dms-endpoint-tag-key +* Tag Key.Required (__changed__) + * Old: true + * New: false +* Tag Value.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-tag.html#cfn-iotanalytics-datastore-tag-value + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-tag.html#cfn-dms-endpoint-tag-value +* Tag Value.Required (__changed__) + * Old: true + * New: false + + +# CloudFormation Resource Specification v2.29.0 + +## New Resource Types + +* AWS::AppMesh::Mesh +* AWS::AppMesh::Route +* AWS::AppMesh::VirtualNode +* AWS::AppMesh::VirtualRouter +* AWS::AppMesh::VirtualService + +## Attribute Changes + + +## Property Changes + +* AWS::EKS::Cluster Version.UpdateType (__changed__) + * Old: Immutable + * New: Mutable + +## Property Type Changes + +* AWS::ServiceDiscovery::Service.DnsRecord TTL.PrimitiveType (__changed__) + * Old: String + * New: Double +* Tag Key.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-tag.html#cfn-dms-endpoint-tag-key + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-tag.html#cfn-iotanalytics-datastore-tag-key +* Tag Key.Required (__changed__) + * Old: false + * New: true +* Tag Value.Documentation (__changed__) + * Old: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dms-endpoint-tag.html#cfn-dms-endpoint-tag-value + * New: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iotanalytics-datastore-tag.html#cfn-iotanalytics-datastore-tag-value +* Tag Value.Required (__changed__) + * Old: false + * New: true + + + + + + +# Serverless Application Model (SAM) Resource Specification v2016-10-31 + +## New Resource Types + + +## Attribute Changes + + +## Property Changes + +* AWS::Serverless::Api TracingEnabled (__added__) +* AWS::Serverless::Function PermissionsBoundary (__added__) + +## Property Type Changes + +* AWS::Serverless::Function.DynamoDBEvent Enabled (__added__) +* AWS::Serverless::Function.DynamoDBEvent BatchSize.Required (__changed__) + * Old: true + * New: false +* AWS::Serverless::Function.KinesisEvent Enabled (__added__) +* AWS::Serverless::Function.SQSEvent Enabled (__added__) + + diff --git a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts index 5b6cda41097f3..b6afb59d3464c 100644 --- a/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts +++ b/packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts @@ -24,7 +24,7 @@ async function main() { // iterate over all cloudformation namespaces for (const namespace of cfnspec.namespaces()) { - const [ moduleFamily, moduleBaseName ] = namespace.split('::'); + const [moduleFamily, moduleBaseName] = (namespace === 'AWS::Serverless' ? 'AWS::SAM' : namespace).split('::'); const moduleName = `${moduleFamily}-${moduleBaseName.replace(/V\d+$/, '')}`.toLocaleLowerCase(); const packagePath = path.join(root, moduleName); @@ -73,6 +73,13 @@ async function main() { ? lowcaseModuleName : `${moduleFamily.toLocaleLowerCase()}-${lowcaseModuleName}`; + // python names + const pythonDistSubName = moduleFamily === 'AWS' + ? lowcaseModuleName + : `${moduleFamily.toLocaleLowerCase()}.${lowcaseModuleName}`; + const pythonDistName = `aws-cdk.${pythonDistSubName}`; + const pythonModuleName = pythonDistName.replace(/-/g, "_"); + async function write(relativePath: string, contents: string[] | string | object) { const fullPath = path.join(packagePath, relativePath); const dir = path.dirname(fullPath); @@ -116,6 +123,10 @@ async function main() { artifactId: javaArtifactId } }, + python: { + distName: pythonDistName, + module: pythonModuleName + }, sphinx: {} } }, diff --git a/packages/@aws-cdk/cfnspec/build-tools/update.sh b/packages/@aws-cdk/cfnspec/build-tools/update.sh index e2e2a34477433..21403ff0e7559 100755 --- a/packages/@aws-cdk/cfnspec/build-tools/update.sh +++ b/packages/@aws-cdk/cfnspec/build-tools/update.sh @@ -8,6 +8,8 @@ set -euo pipefail scriptdir=$(cd $(dirname $0) && pwd) +rm -f CHANGELOG.md.new + function update-spec() { local title=$1 local url=$2 @@ -16,6 +18,12 @@ function update-spec() { local intermediate="$(mktemp -d)/new.json" + # fail if the spec has changes, otherwise we won't be able to determine the diff + if [ -n "$(git status --porcelain ${target})" ]; then + echo "The file ${target} has changes, revert them before cfn-update" + exit 1 + fi + echo >&2 "Downloading from ${url}..." if ${gunzip}; then curl -sL "${url}" | gunzip - > ${intermediate} @@ -26,14 +34,9 @@ function update-spec() { echo >&2 "Sorting..." sort-json ${intermediate} - echo >&2 "Updaging CHANGELOG.md..." - touch CHANGELOG.md - mv CHANGELOG.md CHANGELOG.md.bak - rm -f CHANGELOG.md - node build-tools/spec-diff.js "${title}" "${target}" "${intermediate}" > CHANGELOG.md - echo "" >> CHANGELOG.md - cat CHANGELOG.md.bak >> CHANGELOG.md - rm CHANGELOG.md.bak + echo >&2 "Updating CHANGELOG.md..." + node build-tools/spec-diff.js "${title}" "${target}" "${intermediate}" >> CHANGELOG.md.new + echo "" >> CHANGELOG.md.new echo >&2 "Updarting source spec..." rm -f ${target} @@ -55,5 +58,18 @@ update-spec \ npm run build echo >&2 "Creating missing AWS construct libraries for new resource types..." -node ${scriptdir}/create-missing-libraries.js +node ${scriptdir}/create-missing-libraries.js || { + echo "------------------------------------------------------------------------------------" + echo "cfn-spec update script failed when trying to create modules for new services" + echo "Fix the error (you will likely need to add RefKind patches), and then run 'npm run update' again" + exit 1 +} + +# update decdk dep list +(cd ${scriptdir}/../../../decdk && node ./deps.js || true) + +# append old changelog after new and replace as the last step because otherwise we will not be idempotent +cat CHANGELOG.md >> CHANGELOG.md.new +cp CHANGELOG.md.new CHANGELOG.md + diff --git a/packages/@aws-cdk/cfnspec/package-lock.json b/packages/@aws-cdk/cfnspec/package-lock.json index 55f1be9fac84a..27e9eab019c5e 100644 --- a/packages/@aws-cdk/cfnspec/package-lock.json +++ b/packages/@aws-cdk/cfnspec/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cfnspec", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cfnspec/package.json b/packages/@aws-cdk/cfnspec/package.json index 9f10fdd20d0b7..df9815a21622c 100644 --- a/packages/@aws-cdk/cfnspec/package.json +++ b/packages/@aws-cdk/cfnspec/package.json @@ -1,7 +1,7 @@ { "name": "@aws-cdk/cfnspec", "description": "The CloudFormation resource specification used by @aws-cdk packages", - "version": "0.26.0", + "version": "0.28.0", "scripts": { "update": "cdk-build && /bin/bash build-tools/update.sh", "build": "cdk-build && node build-tools/build", @@ -23,11 +23,11 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/md5": "^2.1.32", - "cdk-build-tools": "^0.26.0", + "cdk-build-tools": "^0.28.0", "fast-json-patch": "^2.0.6", "fs-extra": "^7.0.0", "json-diff": "^0.3.1", - "pkglint": "^0.26.0", + "pkglint": "^0.28.0", "sort-json": "^2.0.0" }, "dependencies": { diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index 811f7c7d0dd7b..97e891618a32d 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -917,6 +917,489 @@ } } }, + "AWS::AppMesh::Mesh.EgressFilter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-mesh-egressfilter.html", + "Properties": { + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-mesh-egressfilter.html#cfn-appmesh-mesh-egressfilter-type", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Mesh.MeshSpec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-mesh-meshspec.html", + "Properties": { + "EgressFilter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-mesh-meshspec.html#cfn-appmesh-mesh-meshspec-egressfilter", + "Required": false, + "Type": "EgressFilter", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Mesh.TagRef": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-mesh-tagref.html", + "Properties": { + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-mesh-tagref.html#cfn-appmesh-mesh-tagref-key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-mesh-tagref.html#cfn-appmesh-mesh-tagref-value", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Route.HttpRoute": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-httproute.html", + "Properties": { + "Action": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-httproute.html#cfn-appmesh-route-httproute-action", + "Required": true, + "Type": "HttpRouteAction", + "UpdateType": "Mutable" + }, + "Match": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-httproute.html#cfn-appmesh-route-httproute-match", + "Required": true, + "Type": "HttpRouteMatch", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Route.HttpRouteAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-httprouteaction.html", + "Properties": { + "WeightedTargets": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-httprouteaction.html#cfn-appmesh-route-httprouteaction-weightedtargets", + "ItemType": "WeightedTarget", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Route.HttpRouteMatch": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-httproutematch.html", + "Properties": { + "Prefix": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-httproutematch.html#cfn-appmesh-route-httproutematch-prefix", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Route.RouteSpec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-routespec.html", + "Properties": { + "HttpRoute": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-routespec.html#cfn-appmesh-route-routespec-httproute", + "Required": false, + "Type": "HttpRoute", + "UpdateType": "Mutable" + }, + "TcpRoute": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-routespec.html#cfn-appmesh-route-routespec-tcproute", + "Required": false, + "Type": "TcpRoute", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Route.TagRef": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-tagref.html", + "Properties": { + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-tagref.html#cfn-appmesh-route-tagref-key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-tagref.html#cfn-appmesh-route-tagref-value", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Route.TcpRoute": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-tcproute.html", + "Properties": { + "Action": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-tcproute.html#cfn-appmesh-route-tcproute-action", + "Required": true, + "Type": "TcpRouteAction", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Route.TcpRouteAction": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-tcprouteaction.html", + "Properties": { + "WeightedTargets": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-tcprouteaction.html#cfn-appmesh-route-tcprouteaction-weightedtargets", + "ItemType": "WeightedTarget", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Route.WeightedTarget": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-weightedtarget.html", + "Properties": { + "VirtualNode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-weightedtarget.html#cfn-appmesh-route-weightedtarget-virtualnode", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Weight": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-route-weightedtarget.html#cfn-appmesh-route-weightedtarget-weight", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.AccessLog": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-accesslog.html", + "Properties": { + "File": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-accesslog.html#cfn-appmesh-virtualnode-accesslog-file", + "Required": false, + "Type": "FileAccessLog", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.Backend": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-backend.html", + "Properties": { + "VirtualService": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-backend.html#cfn-appmesh-virtualnode-backend-virtualservice", + "Required": false, + "Type": "VirtualServiceBackend", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.DnsServiceDiscovery": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-dnsservicediscovery.html", + "Properties": { + "Hostname": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-dnsservicediscovery.html#cfn-appmesh-virtualnode-dnsservicediscovery-hostname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.FileAccessLog": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-fileaccesslog.html", + "Properties": { + "Path": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-fileaccesslog.html#cfn-appmesh-virtualnode-fileaccesslog-path", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.HealthCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-healthcheck.html", + "Properties": { + "HealthyThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-healthcheck.html#cfn-appmesh-virtualnode-healthcheck-healthythreshold", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "IntervalMillis": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-healthcheck.html#cfn-appmesh-virtualnode-healthcheck-intervalmillis", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "Path": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-healthcheck.html#cfn-appmesh-virtualnode-healthcheck-path", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Port": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-healthcheck.html#cfn-appmesh-virtualnode-healthcheck-port", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Mutable" + }, + "Protocol": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-healthcheck.html#cfn-appmesh-virtualnode-healthcheck-protocol", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "TimeoutMillis": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-healthcheck.html#cfn-appmesh-virtualnode-healthcheck-timeoutmillis", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "UnhealthyThreshold": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-healthcheck.html#cfn-appmesh-virtualnode-healthcheck-unhealthythreshold", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.Listener": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-listener.html", + "Properties": { + "HealthCheck": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-listener.html#cfn-appmesh-virtualnode-listener-healthcheck", + "Required": false, + "Type": "HealthCheck", + "UpdateType": "Mutable" + }, + "PortMapping": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-listener.html#cfn-appmesh-virtualnode-listener-portmapping", + "Required": true, + "Type": "PortMapping", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.Logging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-logging.html", + "Properties": { + "AccessLog": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-logging.html#cfn-appmesh-virtualnode-logging-accesslog", + "Required": false, + "Type": "AccessLog", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.PortMapping": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-portmapping.html", + "Properties": { + "Port": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-portmapping.html#cfn-appmesh-virtualnode-portmapping-port", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "Protocol": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-portmapping.html#cfn-appmesh-virtualnode-portmapping-protocol", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.ServiceDiscovery": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-servicediscovery.html", + "Properties": { + "DNS": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-servicediscovery.html#cfn-appmesh-virtualnode-servicediscovery-dns", + "Required": true, + "Type": "DnsServiceDiscovery", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.TagRef": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-tagref.html", + "Properties": { + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-tagref.html#cfn-appmesh-virtualnode-tagref-key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-tagref.html#cfn-appmesh-virtualnode-tagref-value", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.VirtualNodeSpec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-virtualnodespec.html", + "Properties": { + "Backends": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-virtualnodespec.html#cfn-appmesh-virtualnode-virtualnodespec-backends", + "ItemType": "Backend", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Listeners": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-virtualnodespec.html#cfn-appmesh-virtualnode-virtualnodespec-listeners", + "ItemType": "Listener", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "Logging": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-virtualnodespec.html#cfn-appmesh-virtualnode-virtualnodespec-logging", + "Required": false, + "Type": "Logging", + "UpdateType": "Mutable" + }, + "ServiceDiscovery": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-virtualnodespec.html#cfn-appmesh-virtualnode-virtualnodespec-servicediscovery", + "Required": false, + "Type": "ServiceDiscovery", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualNode.VirtualServiceBackend": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-virtualservicebackend.html", + "Properties": { + "VirtualServiceName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualnode-virtualservicebackend.html#cfn-appmesh-virtualnode-virtualservicebackend-virtualservicename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualRouter.PortMapping": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-portmapping.html", + "Properties": { + "Port": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-portmapping.html#cfn-appmesh-virtualrouter-portmapping-port", + "PrimitiveType": "Integer", + "Required": true, + "UpdateType": "Mutable" + }, + "Protocol": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-portmapping.html#cfn-appmesh-virtualrouter-portmapping-protocol", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualRouter.TagRef": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-tagref.html", + "Properties": { + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-tagref.html#cfn-appmesh-virtualrouter-tagref-key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-tagref.html#cfn-appmesh-virtualrouter-tagref-value", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualRouter.VirtualRouterListener": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-virtualrouterlistener.html", + "Properties": { + "PortMapping": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-virtualrouterlistener.html#cfn-appmesh-virtualrouter-virtualrouterlistener-portmapping", + "Required": true, + "Type": "PortMapping", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualRouter.VirtualRouterSpec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-virtualrouterspec.html", + "Properties": { + "Listeners": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualrouter-virtualrouterspec.html#cfn-appmesh-virtualrouter-virtualrouterspec-listeners", + "ItemType": "VirtualRouterListener", + "Required": true, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualService.TagRef": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-tagref.html", + "Properties": { + "Key": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-tagref.html#cfn-appmesh-virtualservice-tagref-key", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-tagref.html#cfn-appmesh-virtualservice-tagref-value", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualService.VirtualNodeServiceProvider": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-virtualnodeserviceprovider.html", + "Properties": { + "VirtualNodeName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-virtualnodeserviceprovider.html#cfn-appmesh-virtualservice-virtualnodeserviceprovider-virtualnodename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualService.VirtualRouterServiceProvider": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-virtualrouterserviceprovider.html", + "Properties": { + "VirtualRouterName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-virtualrouterserviceprovider.html#cfn-appmesh-virtualservice-virtualrouterserviceprovider-virtualroutername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualService.VirtualServiceProvider": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-virtualserviceprovider.html", + "Properties": { + "VirtualNode": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-virtualserviceprovider.html#cfn-appmesh-virtualservice-virtualserviceprovider-virtualnode", + "Required": false, + "Type": "VirtualNodeServiceProvider", + "UpdateType": "Mutable" + }, + "VirtualRouter": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-virtualserviceprovider.html#cfn-appmesh-virtualservice-virtualserviceprovider-virtualrouter", + "Required": false, + "Type": "VirtualRouterServiceProvider", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::VirtualService.VirtualServiceSpec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-virtualservicespec.html", + "Properties": { + "Provider": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appmesh-virtualservice-virtualservicespec.html#cfn-appmesh-virtualservice-virtualservicespec-provider", + "Required": false, + "Type": "VirtualServiceProvider", + "UpdateType": "Mutable" + } + } + }, "AWS::AppStream::DirectoryConfig.ServiceAccountCredentials": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-appstream-directoryconfig-serviceaccountcredentials.html", "Properties": { @@ -2429,6 +2912,13 @@ "Required": false, "UpdateType": "Mutable" }, + "ResourceRequirements": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-containerproperties.html#cfn-batch-jobdefinition-containerproperties-resourcerequirements", + "ItemType": "ResourceRequirement", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, "Ulimits": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-containerproperties.html#cfn-batch-jobdefinition-containerproperties-ulimits", "ItemType": "Ulimit", @@ -2538,6 +3028,23 @@ } } }, + "AWS::Batch::JobDefinition.ResourceRequirement": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-resourcerequirement.html", + "Properties": { + "Type": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-resourcerequirement.html#cfn-batch-jobdefinition-resourcerequirement-type", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + }, + "Value": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-resourcerequirement.html#cfn-batch-jobdefinition-resourcerequirement-value", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Mutable" + } + } + }, "AWS::Batch::JobDefinition.RetryStrategy": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-batch-jobdefinition-retrystrategy.html", "Properties": { @@ -19428,7 +19935,7 @@ "Properties": { "TTL": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-servicediscovery-service-dnsrecord.html#cfn-servicediscovery-service-dnsrecord-ttl", - "PrimitiveType": "String", + "PrimitiveType": "Double", "Required": true, "UpdateType": "Mutable" }, @@ -20110,7 +20617,7 @@ } } }, - "ResourceSpecificationVersion": "2.28.0", + "ResourceSpecificationVersion": "2.30.0", "ResourceTypes": { "AWS::AmazonMQ::Broker": { "Attributes": { @@ -21550,6 +22057,226 @@ } } }, + "AWS::AppMesh::Mesh": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "MeshName": { + "PrimitiveType": "String" + }, + "Uid": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-mesh.html", + "Properties": { + "MeshName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-mesh.html#cfn-appmesh-mesh-meshname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Spec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-mesh.html#cfn-appmesh-mesh-spec", + "Required": false, + "Type": "MeshSpec", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-mesh.html#cfn-appmesh-mesh-tags", + "ItemType": "TagRef", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + } + } + }, + "AWS::AppMesh::Route": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "MeshName": { + "PrimitiveType": "String" + }, + "RouteName": { + "PrimitiveType": "String" + }, + "Uid": { + "PrimitiveType": "String" + }, + "VirtualRouterName": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-route.html", + "Properties": { + "MeshName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-route.html#cfn-appmesh-route-meshname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "RouteName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-route.html#cfn-appmesh-route-routename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Spec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-route.html#cfn-appmesh-route-spec", + "Required": true, + "Type": "RouteSpec", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-route.html#cfn-appmesh-route-tags", + "ItemType": "TagRef", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "VirtualRouterName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-route.html#cfn-appmesh-route-virtualroutername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::AppMesh::VirtualNode": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "MeshName": { + "PrimitiveType": "String" + }, + "Uid": { + "PrimitiveType": "String" + }, + "VirtualNodeName": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualnode.html", + "Properties": { + "MeshName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualnode.html#cfn-appmesh-virtualnode-meshname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Spec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualnode.html#cfn-appmesh-virtualnode-spec", + "Required": true, + "Type": "VirtualNodeSpec", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualnode.html#cfn-appmesh-virtualnode-tags", + "ItemType": "TagRef", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "VirtualNodeName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualnode.html#cfn-appmesh-virtualnode-virtualnodename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::AppMesh::VirtualRouter": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "MeshName": { + "PrimitiveType": "String" + }, + "Uid": { + "PrimitiveType": "String" + }, + "VirtualRouterName": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualrouter.html", + "Properties": { + "MeshName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualrouter.html#cfn-appmesh-virtualrouter-meshname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Spec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualrouter.html#cfn-appmesh-virtualrouter-spec", + "Required": true, + "Type": "VirtualRouterSpec", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualrouter.html#cfn-appmesh-virtualrouter-tags", + "ItemType": "TagRef", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "VirtualRouterName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualrouter.html#cfn-appmesh-virtualrouter-virtualroutername", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, + "AWS::AppMesh::VirtualService": { + "Attributes": { + "Arn": { + "PrimitiveType": "String" + }, + "MeshName": { + "PrimitiveType": "String" + }, + "Uid": { + "PrimitiveType": "String" + }, + "VirtualServiceName": { + "PrimitiveType": "String" + } + }, + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualservice.html", + "Properties": { + "MeshName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualservice.html#cfn-appmesh-virtualservice-meshname", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "Spec": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualservice.html#cfn-appmesh-virtualservice-spec", + "Required": true, + "Type": "VirtualServiceSpec", + "UpdateType": "Mutable" + }, + "Tags": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualservice.html#cfn-appmesh-virtualservice-tags", + "ItemType": "TagRef", + "Required": false, + "Type": "List", + "UpdateType": "Mutable" + }, + "VirtualServiceName": { + "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appmesh-virtualservice.html#cfn-appmesh-virtualservice-virtualservicename", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + } + } + }, "AWS::AppStream::DirectoryConfig": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-directoryconfig.html", "Properties": { @@ -27773,7 +28500,7 @@ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-version", "PrimitiveType": "String", "Required": false, - "UpdateType": "Immutable" + "UpdateType": "Mutable" } } }, diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_sam.spec.json b/packages/@aws-cdk/cfnspec/spec-source/000_sam.spec.json index f102409bbec7f..ea5ccfff6a627 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_sam.spec.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_sam.spec.json @@ -175,7 +175,13 @@ "BatchSize": { "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb", "PrimitiveType": "Integer", - "Required": true, + "Required": false, + "UpdateType": "Immutable" + }, + "Enabled": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#dynamodb", + "PrimitiveType": "Boolean", + "Required": false, "UpdateType": "Immutable" }, "StartingPosition": { @@ -269,6 +275,12 @@ "Required": false, "UpdateType": "Immutable" }, + "Enabled": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, "StartingPosition": { "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#kinesis", "PrimitiveType": "String", @@ -364,6 +376,12 @@ "Required": false, "UpdateType": "Immutable" }, + "Enabled": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sqs", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, "Queue": { "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#sqs", "PrimitiveType": "String", @@ -532,6 +550,12 @@ "Required": true, "UpdateType": "Immutable" }, + "TracingEnabled": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi", + "PrimitiveType": "Boolean", + "Required": false, + "UpdateType": "Immutable" + }, "Variables": { "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessapi", "PrimitiveItemType": "String", @@ -666,6 +690,12 @@ "Required": false, "UpdateType": "Immutable" }, + "PermissionsBoundary": { + "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, "Policies": { "Documentation": "https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction", "ItemTypes": [ diff --git a/packages/@aws-cdk/cfnspec/spec-source/600_RefKinds_patch.json b/packages/@aws-cdk/cfnspec/spec-source/600_RefKinds_patch.json index fc81dcdd6c852..3d9fbad1fb95a 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/600_RefKinds_patch.json +++ b/packages/@aws-cdk/cfnspec/spec-source/600_RefKinds_patch.json @@ -4607,6 +4607,66 @@ ], "description": "Set RefKind of AWS::Greengrass::SubscriptionDefinitionVersion to Arn" } + }, + "AWS::AppMesh::Mesh": { + "patch": { + "operations": [ + { + "op": "add", + "path": "/RefKind", + "value": "Arn" + } + ], + "description": "Set RefKind of AWS::AppMesh::Mesh to Arn" + } + }, + "AWS::AppMesh::Route": { + "patch": { + "operations": [ + { + "op": "add", + "path": "/RefKind", + "value": "Arn" + } + ], + "description": "Set RefKind of AWS::AppMesh::Route to Arn" + } + }, + "AWS::AppMesh::VirtualNode": { + "patch": { + "operations": [ + { + "op": "add", + "path": "/RefKind", + "value": "Arn" + } + ], + "description": "Set RefKind of AWS::AppMesh::VirtualNode to Arn" + } + }, + "AWS::AppMesh::VirtualRouter": { + "patch": { + "operations": [ + { + "op": "add", + "path": "/RefKind", + "value": "Arn" + } + ], + "description": "Set RefKind of AWS::AppMesh::VirtualRouter to Arn" + } + }, + "AWS::AppMesh::VirtualService": { + "patch": { + "operations": [ + { + "op": "add", + "path": "/RefKind", + "value": "Arn" + } + ], + "description": "Set RefKind of AWS::AppMesh::VirtualService to Arn" + } } } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloudformation-diff/package-lock.json b/packages/@aws-cdk/cloudformation-diff/package-lock.json index 862d0c5f19609..faf6305d408f9 100644 --- a/packages/@aws-cdk/cloudformation-diff/package-lock.json +++ b/packages/@aws-cdk/cloudformation-diff/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cloudformation-diff", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/cloudformation-diff/package.json b/packages/@aws-cdk/cloudformation-diff/package.json index ef5194a3cb9d0..0b7c98a7676b1 100644 --- a/packages/@aws-cdk/cloudformation-diff/package.json +++ b/packages/@aws-cdk/cloudformation-diff/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cloudformation-diff", - "version": "0.26.0", + "version": "0.28.0", "description": "Utilities to diff CDK stacks against CloudFormation templates", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -24,8 +24,8 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-cdk/cfnspec": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0", + "@aws-cdk/cfnspec": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0", "colors": "^1.2.1", "diff": "^4.0.1", "fast-deep-equal": "^2.0.1", @@ -36,13 +36,14 @@ "devDependencies": { "@types/string-width": "^2.0.0", "@types/table": "^4.0.5", - "cdk-build-tools": "^0.26.0", + "cdk-build-tools": "^0.28.0", "fast-check": "^1.8.0", - "pkglint": "^0.26.0" + "pkglint": "^0.28.0" }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", - "type": "git" + "type": "git", + "directory": "packages/@aws-cdk/cloudformation-diff" }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/cx-api/lib/artifacts.ts b/packages/@aws-cdk/cx-api/lib/artifacts.ts index 133027dfe04b1..ce59da7d077db 100644 --- a/packages/@aws-cdk/cx-api/lib/artifacts.ts +++ b/packages/@aws-cdk/cx-api/lib/artifacts.ts @@ -7,16 +7,17 @@ export enum ArtifactType { } export interface Artifact { - type: ArtifactType; - environment: string; // format: aws://account/region - metadata?: { [path: string]: any }; - dependencies?: string[]; - missing?: { [key: string]: any }; - properties?: { [name: string]: any }; + readonly type: ArtifactType; + readonly environment: string; // format: aws://account/region + readonly metadata?: { [path: string]: any }; + readonly dependencies?: string[]; + readonly missing?: { [key: string]: any }; + readonly properties?: { [name: string]: any }; + readonly autoDeploy?: boolean; } export function validateArtifact(artifcat: Artifact) { if (!AWS_ENV_REGEX.test(artifcat.environment)) { throw new Error(`Artifact "environment" must conform to ${AWS_ENV_REGEX}: ${artifcat.environment}`); } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cx-api/lib/build.ts b/packages/@aws-cdk/cx-api/lib/build.ts index c4363f67d5f50..4b5ec14188ed5 100644 --- a/packages/@aws-cdk/cx-api/lib/build.ts +++ b/packages/@aws-cdk/cx-api/lib/build.ts @@ -1,13 +1,13 @@ export interface BuildStep { - type: string; - depends?: string[]; - parameters: { + readonly type: string; + readonly depends?: string[]; + readonly parameters: { [key: string]: any }; } export interface BuildManifest { - steps: { [id: string]: BuildStep }; + readonly steps: { [id: string]: BuildStep }; } export enum BuildStepType { diff --git a/packages/@aws-cdk/cx-api/lib/context/availability-zones.ts b/packages/@aws-cdk/cx-api/lib/context/availability-zones.ts index 6850c2e593fc1..fd66c220b9909 100644 --- a/packages/@aws-cdk/cx-api/lib/context/availability-zones.ts +++ b/packages/@aws-cdk/cx-api/lib/context/availability-zones.ts @@ -7,13 +7,12 @@ export interface AvailabilityZonesContextQuery { /** * Query account */ - account?: string; + readonly account?: string; /** * Query region */ - region?: string; - + readonly region?: string; } /** diff --git a/packages/@aws-cdk/cx-api/lib/context/hosted-zone.ts b/packages/@aws-cdk/cx-api/lib/context/hosted-zone.ts index 7862b0affac82..c2dad65d924f7 100644 --- a/packages/@aws-cdk/cx-api/lib/context/hosted-zone.ts +++ b/packages/@aws-cdk/cx-api/lib/context/hosted-zone.ts @@ -7,22 +7,22 @@ export interface HostedZoneContextQuery { /** * Query account */ - account?: string; + readonly account?: string; /** * Query region */ - region?: string; + readonly region?: string; /** * The domain name e.g. example.com to lookup */ - domainName: string; + readonly domainName: string; /** * True if the zone you want to find is a private hosted zone */ - privateZone?: boolean; + readonly privateZone?: boolean; /** * The VPC ID to that the private zone must be associated with @@ -30,7 +30,7 @@ export interface HostedZoneContextQuery { * If you provide VPC ID and privateZone is false, this will return no results * and raise an error. */ - vpcId?: string; + readonly vpcId?: string; } /** diff --git a/packages/@aws-cdk/cx-api/lib/context/ssm-parameter.ts b/packages/@aws-cdk/cx-api/lib/context/ssm-parameter.ts index caf807aca1ceb..c0b27a46db796 100644 --- a/packages/@aws-cdk/cx-api/lib/context/ssm-parameter.ts +++ b/packages/@aws-cdk/cx-api/lib/context/ssm-parameter.ts @@ -7,17 +7,17 @@ export interface SSMParameterContextQuery { /** * Query account */ - account?: string; + readonly account?: string; /** * Query region */ - region?: string; + readonly region?: string; /** * Parameter name to query */ - parameterName?: string; + readonly parameterName?: string; } // Response is a string \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/lib/context/vpc.ts b/packages/@aws-cdk/cx-api/lib/context/vpc.ts index 8a4490cfa3fe8..ac112df1fdbee 100644 --- a/packages/@aws-cdk/cx-api/lib/context/vpc.ts +++ b/packages/@aws-cdk/cx-api/lib/context/vpc.ts @@ -7,12 +7,12 @@ export interface VpcContextQuery { /** * Query account */ - account?: string; + readonly account?: string; /** * Query region */ - region?: string; + readonly region?: string; /** * Filters to apply to the VPC @@ -21,7 +21,7 @@ export interface VpcContextQuery { * * @see https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html */ - filter: {[key: string]: string}; + readonly filter: {[key: string]: string}; } /** @@ -32,57 +32,57 @@ export interface VpcContextResponse { /** * VPC id */ - vpcId: string; + readonly vpcId: string; /** * AZs */ - availabilityZones: string[]; + readonly availabilityZones: string[]; /** * IDs of all public subnets * * Element count: #(availabilityZones) · #(publicGroups) */ - publicSubnetIds?: string[]; + readonly publicSubnetIds?: string[]; /** * Name of public subnet groups * * Element count: #(publicGroups) */ - publicSubnetNames?: string[]; + readonly publicSubnetNames?: string[]; /** * IDs of all private subnets * * Element count: #(availabilityZones) · #(privateGroups) */ - privateSubnetIds?: string[]; + readonly privateSubnetIds?: string[]; /** * Name of private subnet groups * * Element count: #(privateGroups) */ - privateSubnetNames?: string[]; + readonly privateSubnetNames?: string[]; /** * IDs of all isolated subnets * * Element count: #(availabilityZones) · #(isolatedGroups) */ - isolatedSubnetIds?: string[]; + readonly isolatedSubnetIds?: string[]; /** * Name of isolated subnet groups * * Element count: #(isolatedGroups) */ - isolatedSubnetNames?: string[]; + readonly isolatedSubnetNames?: string[]; /** * The VPN gateway ID */ - vpnGatewayId?: string; + readonly vpnGatewayId?: string; } diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index 8bbbdcf9340bb..459e3bee879e1 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -40,8 +40,8 @@ export const CONTEXT_ENV = 'CDK_CONTEXT_JSON'; * Represents a missing piece of context. */ export interface MissingContext { - provider: string; - props: { + readonly provider: string; + readonly props: { account?: string; region?: string; [key: string]: any; @@ -52,40 +52,41 @@ export interface AssemblyManifest { /** * Protocol version */ - version: string; + readonly version: string; /** * The set of artifacts in this assembly. */ - artifacts?: { [id: string]: Artifact }; + readonly artifacts?: { [id: string]: Artifact }; /** * Runtime information. */ - runtime?: AppRuntime; + readonly runtime?: AppRuntime; } /** * @deprecated use `AssemblyManifest` */ export interface SynthesizeResponse extends AssemblyManifest { - stacks: SynthesizedStack[]; + readonly stacks: SynthesizedStack[]; } /** * A complete synthesized stack */ export interface SynthesizedStack { - name: string; - environment: Environment; - missing?: { [key: string]: MissingContext }; - metadata: StackMetadata; - template: any; + readonly name: string; + readonly environment: Environment; + readonly missing?: { [key: string]: MissingContext }; + readonly metadata: StackMetadata; + readonly template: any; + readonly autoDeploy?: boolean; /** * Other stacks this stack depends on */ - dependsOn?: string[]; + readonly dependsOn?: string[]; } /** @@ -95,17 +96,17 @@ export interface MetadataEntry { /** * The type of the metadata entry. */ - type: string; + readonly type: string; /** * The data. */ - data?: any; + readonly data?: any; /** * A stack trace for when the entry was created. */ - trace: string[]; + readonly trace: string[]; } /** @@ -120,7 +121,7 @@ export interface AppRuntime { /** * The list of libraries loaded in the application, associated with their versions. */ - libraries: { [name: string]: string }; + readonly libraries: { [name: string]: string }; } /** @@ -176,3 +177,9 @@ export const OUTFILE_NAME = 'cdk.out'; * Disable the collection and reporting of version information. */ export const DISABLE_VERSION_REPORTING = 'aws:cdk:disable-version-reporting'; + +/** + * If this context key is set, the CDK will stage assets under the specified + * directory. Otherwise, assets will not be staged. + */ +export const ASSET_STAGING_DIR_CONTEXT = 'aws:cdk:asset-staging-dir'; diff --git a/packages/@aws-cdk/cx-api/lib/environment.ts b/packages/@aws-cdk/cx-api/lib/environment.ts index 078379aed1c9c..6dac39a141f27 100644 --- a/packages/@aws-cdk/cx-api/lib/environment.ts +++ b/packages/@aws-cdk/cx-api/lib/environment.ts @@ -3,11 +3,11 @@ */ export interface Environment { /** The arbitrary name of this environment (user-set, or at least user-meaningful) */ - name: string; + readonly name: string; /** The 12-digit AWS account ID for the account this environment deploys into */ - account: string; + readonly account: string; /** The AWS region name where this environment deploys into */ - region: string; + readonly region: string; } diff --git a/packages/@aws-cdk/cx-api/lib/metadata/assets.ts b/packages/@aws-cdk/cx-api/lib/metadata/assets.ts index 3bcb79b6f0723..ef446fc583cac 100644 --- a/packages/@aws-cdk/cx-api/lib/metadata/assets.ts +++ b/packages/@aws-cdk/cx-api/lib/metadata/assets.ts @@ -30,49 +30,49 @@ export interface FileAssetMetadataEntry { /** * Requested packaging style */ - packaging: 'zip' | 'file'; + readonly packaging: 'zip' | 'file'; /** * Path on disk to the asset */ - path: string; + readonly path: string; /** * Logical identifier for the asset */ - id: string; + readonly id: string; /** * Name of parameter where S3 bucket should be passed in */ - s3BucketParameter: string; + readonly s3BucketParameter: string; /** * Name of parameter where S3 key should be passed in */ - s3KeyParameter: string; + readonly s3KeyParameter: string; } export interface ContainerImageAssetMetadataEntry { /** * Type of asset */ - packaging: 'container-image'; + readonly packaging: 'container-image'; /** * Path on disk to the asset */ - path: string; + readonly path: string; /** * Logical identifier for the asset */ - id: string; + readonly id: string; /** * ECR Repository name and tag (separated by ":") where this asset is stored. */ - imageNameParameter: string; + readonly imageNameParameter: string; /** * ECR repository name, if omitted a default name based on the asset's @@ -83,7 +83,7 @@ export interface ContainerImageAssetMetadataEntry { * * * @default automatically derived from the asset's ID. */ - repositoryName?: string; + readonly repositoryName?: string; } export type AssetMetadataEntry = FileAssetMetadataEntry | ContainerImageAssetMetadataEntry; diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index dca21e9a7d61b..1a0720fd704f9 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/cx-api", - "version": "0.26.0", + "version": "0.28.0", "description": "Cloud executable protocol", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,7 +20,11 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.cx-api", + "module": "aws_cdk.cx_api" + } } }, "scripts": { @@ -39,12 +43,13 @@ }, "license": "Apache-2.0", "devDependencies": { - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", - "type": "git" + "type": "git", + "directory": "packages/@aws-cdk/cx-api" }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/region-info/.npmignore b/packages/@aws-cdk/region-info/.npmignore index 2def7f128ae25..b2f96da0e58a1 100644 --- a/packages/@aws-cdk/region-info/.npmignore +++ b/packages/@aws-cdk/region-info/.npmignore @@ -8,4 +8,7 @@ dist !*.js coverage .nyc_output -*.tgz \ No newline at end of file +*.tgz + +# Include .jsii +!.jsii diff --git a/packages/@aws-cdk/region-info/package-lock.json b/packages/@aws-cdk/region-info/package-lock.json index b6dfc9aa49e07..274a1ffd6aa89 100644 --- a/packages/@aws-cdk/region-info/package-lock.json +++ b/packages/@aws-cdk/region-info/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/region-info", - "version": "0.25.3", + "version": "0.27.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/@aws-cdk/region-info/package.json b/packages/@aws-cdk/region-info/package.json index 95755685df0bf..320a212ded48f 100644 --- a/packages/@aws-cdk/region-info/package.json +++ b/packages/@aws-cdk/region-info/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/region-info", - "version": "0.26.0", + "version": "0.28.0", "description": "AWS region information, such as service principal names", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,7 +20,11 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.region-info", + "module": "aws_cdk.region_info" + } } }, "cdk-build": { @@ -45,9 +49,9 @@ "license": "Apache-2.0", "devDependencies": { "@types/fs-extra": "^5.0.5", - "cdk-build-tools": "^0.26.0", + "cdk-build-tools": "^0.28.0", "fs-extra": "^7.0.1", - "pkglint": "^0.26.0" + "pkglint": "^0.28.0" }, "jest": { "coverageReporters": [ @@ -69,7 +73,8 @@ }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", - "type": "git" + "type": "git", + "directory": "packages/@aws-cdk/region-info" }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/runtime-values/lib/rtv.ts b/packages/@aws-cdk/runtime-values/lib/rtv.ts index 8407430b8698b..ac60a15dc3a81 100644 --- a/packages/@aws-cdk/runtime-values/lib/rtv.ts +++ b/packages/@aws-cdk/runtime-values/lib/rtv.ts @@ -7,12 +7,12 @@ export interface RuntimeValueProps { * A namespace for the runtime value. * It is recommended to use the name of the library/package that advertises this value. */ - package: string; + readonly package: string; /** * The value to advertise. Can be either a primitive value or a token. */ - value: any; + readonly value: any; } /** @@ -74,15 +74,12 @@ export class RuntimeValue extends cdk.Construct { * Grants a principal read permissions on this runtime value. * @param principal The principal (e.g. Role, User, Group) */ - public grantRead(principal?: iam.IPrincipal) { + public grantRead(grantee: iam.IGrantable) { + return iam.Grant.addToPrincipal({ + grantee, + resourceArns: [this.parameterArn], + actions: RuntimeValue.SSM_READ_ACTIONS - // sometimes "role" is optional, so we want `rtv.grantRead(role)` to be a no-op - if (!principal) { - return; - } - - principal.addToPolicy(new iam.PolicyStatement() - .addResource(this.parameterArn) - .addActions(...RuntimeValue.SSM_READ_ACTIONS)); + }); } } diff --git a/packages/@aws-cdk/runtime-values/package.json b/packages/@aws-cdk/runtime-values/package.json index a654902086c32..4c0e235b176be 100644 --- a/packages/@aws-cdk/runtime-values/package.json +++ b/packages/@aws-cdk/runtime-values/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/runtime-values", - "version": "0.26.0", + "version": "0.28.0", "description": "Runtime values support for the AWS CDK", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,12 +20,17 @@ "signAssembly": true, "assemblyOriginatorKeyFile": "../../key.snk" }, - "sphinx": {} + "sphinx": {}, + "python": { + "distName": "aws-cdk.runtime-values", + "module": "aws_cdk.runtime_values" + } } }, "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/runtime-values" }, "scripts": { "build": "cdk-build", @@ -49,25 +54,25 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-sqs": "^0.26.0", - "cdk-build-tools": "^0.26.0", - "cdk-integ-tools": "^0.26.0", - "pkglint": "^0.26.0" + "@aws-cdk/assert": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-ssm": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-ssm": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0" + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0" }, "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts index 81e8b7d4d48f6..0c45cf9b4eb33 100644 --- a/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts +++ b/packages/@aws-cdk/runtime-values/test/integ.rtv.lambda.ts @@ -27,7 +27,7 @@ class TestStack extends cdk.Stack { // this line adds read permissions for this SSM parameter to the policies associated with // the IAM roles of the Lambda function and the EC2 fleet - queueUrlRtv.grantRead(fn.role); + queueUrlRtv.grantRead(fn); // adds the `RTV_STACK_NAME` to the environment of the lambda function // and the fleet (via user-data) diff --git a/packages/@aws-cdk/runtime-values/test/test.rtv.ts b/packages/@aws-cdk/runtime-values/test/test.rtv.ts index ce965f9bf7436..05eb8b94625b1 100644 --- a/packages/@aws-cdk/runtime-values/test/test.rtv.ts +++ b/packages/@aws-cdk/runtime-values/test/test.rtv.ts @@ -13,7 +13,6 @@ export = { new RuntimeValueTest(stack, 'RuntimeValue'); - console.log(JSON.stringify(stack._toCloudFormation(), undefined, 2)); test.done(); } }; diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index b629bb0ce9a27..02e7a78681950 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -33,7 +33,7 @@ async function parseCommandLineArguments() { return yargs .env('CDK') .usage('Usage: cdk -a COMMAND') - .option('app', { type: 'string', alias: 'a', desc: 'REQUIRED: Command-line for executing your CDK app (e.g. "node bin/my-app.js")' }) + .option('app', { type: 'string', alias: 'a', desc: 'REQUIRED: Command-line for executing your CDK app (e.g. "node bin/my-app.js")', requiresArg: true }) .option('context', { type: 'array', alias: 'c', desc: 'Add contextual string parameter.', nargs: 1, requiresArg: 'KEY=VALUE' }) .option('plugin', { type: 'array', alias: 'p', desc: 'Name or path of a node package that extend the CDK features. Can be specified multiple times', nargs: 1 }) .option('rename', { type: 'string', desc: 'Rename stack name if different from the one defined in the cloud executable', requiresArg: '[ORIGINAL:]RENAMED' }) @@ -42,20 +42,21 @@ async function parseCommandLineArguments() { .option('ignore-errors', { type: 'boolean', default: false, desc: 'Ignores synthesis errors, which will likely produce an invalid output' }) .option('json', { type: 'boolean', alias: 'j', desc: 'Use JSON output instead of YAML' }) .option('verbose', { type: 'boolean', alias: 'v', desc: 'Show debug logs' }) - .option('profile', { type: 'string', desc: 'Use the indicated AWS profile as the default environment' }) - .option('proxy', { type: 'string', desc: 'Use the indicated proxy. Will read from HTTPS_PROXY environment variable if not specified.' }) + .option('profile', { type: 'string', desc: 'Use the indicated AWS profile as the default environment', requiresArg: true }) + .option('proxy', { type: 'string', desc: 'Use the indicated proxy. Will read from HTTPS_PROXY environment variable if not specified.', requiresArg: true }) .option('ec2creds', { type: 'boolean', alias: 'i', default: undefined, desc: 'Force trying to fetch EC2 instance credentials. Default: guess EC2 instance status.' }) .option('version-reporting', { type: 'boolean', desc: 'Include the "AWS::CDK::Metadata" resource in synthesized templates (enabled by default)', default: undefined }) .option('path-metadata', { type: 'boolean', desc: 'Include "aws:cdk:path" CloudFormation metadata for each resource (enabled by default)', default: true }) .option('asset-metadata', { type: 'boolean', desc: 'Include "aws:asset:*" CloudFormation metadata for resources that user assets (enabled by default)', default: true }) - .option('role-arn', { type: 'string', alias: 'r', desc: 'ARN of Role to use when invoking CloudFormation', default: undefined }) - .option('toolkit-stack-name', { type: 'string', desc: 'The name of the CDK toolkit stack' }) + .option('role-arn', { type: 'string', alias: 'r', desc: 'ARN of Role to use when invoking CloudFormation', default: undefined, requiresArg: true }) + .option('toolkit-stack-name', { type: 'string', desc: 'The name of the CDK toolkit stack', requiresArg: true }) + .option('staging', { type: 'string', desc: 'directory name for staging assets (use --no-asset-staging to disable)', default: '.cdk.staging' }) .command([ 'list', 'ls' ], 'Lists all stacks in the app', yargs => yargs .option('long', { type: 'boolean', default: false, alias: 'l', desc: 'display environment information for each stack' })) .command([ 'synthesize [STACKS..]', 'synth [STACKS..]' ], 'Synthesizes and prints the CloudFormation template for this stack', yargs => yargs .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' }) .option('interactive', { type: 'boolean', alias: 'i', desc: 'interactively watch and show template updates' }) - .option('output', { type: 'string', alias: 'o', desc: 'write CloudFormation template for requested stacks to the given directory' }) + .option('output', { type: 'string', alias: 'o', desc: 'write CloudFormation template for requested stacks to the given directory', requiresArg: true }) .option('numbered', { type: 'boolean', alias: 'n', desc: 'prefix filenames with numbers to indicate deployment ordering' })) .command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment') .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs @@ -68,8 +69,8 @@ async function parseCommandLineArguments() { .option('force', { type: 'boolean', alias: 'f', desc: 'Do not ask for confirmation before destroying the stacks' })) .command('diff [STACKS..]', 'Compares the specified stack with the deployed stack or a local template file, and returns with status 1 if any difference is found', yargs => yargs .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only diff requested stacks, don\'t include dependencies' }) - .option('context-lines', { type: 'number', desc: 'number of context lines to include in arbitrary JSON diff rendering', default: 3 }) - .option('template', { type: 'string', desc: 'the path to the CloudFormation template to compare with' }) + .option('context-lines', { type: 'number', desc: 'number of context lines to include in arbitrary JSON diff rendering', default: 3, requiresArg: true }) + .option('template', { type: 'string', desc: 'the path to the CloudFormation template to compare with', requiresArg: true }) .option('strict', { type: 'boolean', desc: 'do not filter out AWS::CDK::Metadata resources', default: false })) .command('metadata [STACK]', 'Returns all metadata associated with this stack') .command('init [TEMPLATE]', 'Create a new, empty CDK project from a template. Invoked without TEMPLATE, the app template will be used.', yargs => yargs diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index 5d830301dfd1d..72d1412c367c6 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -42,6 +42,9 @@ export async function execProgram(aws: SDK, config: Configuration): Promise s.autoDeploy !== false); + debug('Stack name not specified, so defaulting to all available stacks: ' + listStackNames(autoDeployedStacks)); + return this.applyRenames(autoDeployedStacks); } const allStacks = new Map(); @@ -191,15 +193,17 @@ export class AppStacks { } const resourcePresent = stack.environment.region === 'default-region' || regionInfo.Fact.find(stack.environment.region, regionInfo.FactName.cdkMetadataResourceAvailable) === 'YES'; - if (!stack.template.Resources.CDKMetadata && resourcePresent) { - stack.template.Resources.CDKMetadata = { - Type: 'AWS::CDK::Metadata', - Properties: { - Modules: modules - } - }; - } else { - warning(`The stack ${stack.name} already includes a CDKMetadata resource`); + if (resourcePresent) { + if (!stack.template.Resources.CDKMetadata) { + stack.template.Resources.CDKMetadata = { + Type: 'AWS::CDK::Metadata', + Properties: { + Modules: modules + } + }; + } else { + warning(`The stack ${stack.name} already includes a CDKMetadata resource`); + } } } } diff --git a/packages/aws-cdk/lib/init-templates/app/csharp/.template.gitignore b/packages/aws-cdk/lib/init-templates/app/csharp/.template.gitignore index 8f83e36d4919a..cdc2138dcea2d 100644 --- a/packages/aws-cdk/lib/init-templates/app/csharp/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/app/csharp/.template.gitignore @@ -1,3 +1,5 @@ +# CDK asset staging directory +.cdk.staging # Created by https://www.gitignore.io/api/csharp diff --git a/packages/aws-cdk/lib/init-templates/app/fsharp/.template.gitignore b/packages/aws-cdk/lib/init-templates/app/fsharp/.template.gitignore index 8f83e36d4919a..cdc2138dcea2d 100644 --- a/packages/aws-cdk/lib/init-templates/app/fsharp/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/app/fsharp/.template.gitignore @@ -1,3 +1,5 @@ +# CDK asset staging directory +.cdk.staging # Created by https://www.gitignore.io/api/csharp diff --git a/packages/aws-cdk/lib/init-templates/app/java/.template.gitignore b/packages/aws-cdk/lib/init-templates/app/java/.template.gitignore index 977763f5766de..486800f4b4226 100644 --- a/packages/aws-cdk/lib/init-templates/app/java/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/app/java/.template.gitignore @@ -6,3 +6,7 @@ target .settings .vscode *.iml + +# CDK asset staging directory +.cdk.staging + diff --git a/packages/aws-cdk/lib/init-templates/app/java/src/main/java/com/myorg/HelloConstruct.java b/packages/aws-cdk/lib/init-templates/app/java/src/main/java/com/myorg/HelloConstruct.java index 7a2b629a6135b..3b57fa5725f4a 100644 --- a/packages/aws-cdk/lib/init-templates/app/java/src/main/java/com/myorg/HelloConstruct.java +++ b/packages/aws-cdk/lib/init-templates/app/java/src/main/java/com/myorg/HelloConstruct.java @@ -1,7 +1,7 @@ package com.myorg; import software.amazon.awscdk.Construct; -import software.amazon.awscdk.services.iam.IPrincipal; +import software.amazon.awscdk.services.iam.IGrantable; import software.amazon.awscdk.services.s3.Bucket; import software.amazon.awscdk.services.s3.BucketProps; @@ -24,9 +24,9 @@ public HelloConstruct(final Construct parent, final String name, final HelloCons /** * Given an principal, grants it READ access on all buckets. - * @param principal The principal (User, Group, Role) + * @param grantee The principal (User, Group, Role) */ - public void grantRead(final IPrincipal principal) { - buckets.forEach(b -> b.grantRead(principal, "*")); + public void grantRead(final IGrantable grantee) { + buckets.forEach(b -> b.grantRead(grantee, "*")); } } diff --git a/packages/aws-cdk/lib/init-templates/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py b/packages/aws-cdk/lib/init-templates/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py new file mode 100644 index 0000000000000..46f1808e14701 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/app/python/%name.PythonModule%/%name.PythonModule%_stack.template.py @@ -0,0 +1,9 @@ +from aws_cdk import cdk + + +class %name.PascalCased%Stack(cdk.Stack): + + def __init__(self, app: cdk.App, id: str, **kwargs) -> None: + super().__init__(app, id) + + # The code that defines your stack goes here diff --git a/packages/aws-cdk/lib/init-templates/app/python/%name.PythonModule%/__init__.py b/packages/aws-cdk/lib/init-templates/app/python/%name.PythonModule%/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/aws-cdk/lib/init-templates/app/python/.gitignore b/packages/aws-cdk/lib/init-templates/app/python/.gitignore new file mode 100644 index 0000000000000..098bee3a753ab --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/app/python/.gitignore @@ -0,0 +1,9 @@ +*.swp +package-lock.json +__pycache__ +.pytest_cache +.env +*.egg-info + +# CDK asset staging directory +.cdk.staging diff --git a/packages/aws-cdk/lib/init-templates/app/python/README.template.md b/packages/aws-cdk/lib/init-templates/app/python/README.template.md new file mode 100644 index 0000000000000..5bca5316fc006 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/app/python/README.template.md @@ -0,0 +1,58 @@ + +# Welcome to your CDK Python project! + +This is a blank project for Python development with CDK. + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +This project is set up like a standard Python project. The initialization +process also creates a virtualenv within this project, stored under the .env +directory. To create the virtualenv it assumes that there is a `python3` +(or `python` for Windows) executable in your path with access to the `venv` +package. If for any reason the automatic creation of the virtualenv fails, +you can create the virtualenv manually. + +To manually create a virtualenv on MacOS and Linux: + +``` +$ %python-executable% -m venv .env +``` + +After the init process completes and the virtualenv is created, you can use the following +step to activate your virtualenv. + +``` +$ source .env/bin/activate +``` + +If you are a Windows platform, you would activate the virtualenv like this: + +``` +% .env\Scripts\activate.bat +``` + +Once the virtualenv is activated, you can install the required dependencies. + +``` +$ pip install -r requirements.txt +``` + +At this point you can now synthesize the CloudFormation template for this code. + +``` +$ cdk synth +``` + +To add additional dependencies, for example other CDK libraries, just add +them to your `setup.py` file and rerun the `pip install -r requirements.txt` +command. + +# Useful commands + + * `cdk ls` list all stacks in the app + * `cdk synth` emits the synthesized CloudFormation template + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk docs` open CDK documentation + +Enjoy! diff --git a/packages/aws-cdk/lib/init-templates/app/python/app.template.py b/packages/aws-cdk/lib/init-templates/app/python/app.template.py new file mode 100644 index 0000000000000..809e0f20b9208 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/app/python/app.template.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 + +from aws_cdk import cdk + +from %name.PythonModule%.%name.PythonModule%_stack import %name.PascalCased%Stack + + +app = cdk.App() +%name.PascalCased%Stack(app, "%name%-cdk-1") + +app.run() diff --git a/packages/aws-cdk/lib/init-templates/app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/app/python/cdk.template.json new file mode 100644 index 0000000000000..d7293493c4415 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/app/python/cdk.template.json @@ -0,0 +1,3 @@ +{ + "app": "%python-executable% app.py" +} diff --git a/packages/aws-cdk/lib/init-templates/app/python/requirements.txt b/packages/aws-cdk/lib/init-templates/app/python/requirements.txt new file mode 100644 index 0000000000000..d6e1198b1ab1f --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/app/python/requirements.txt @@ -0,0 +1 @@ +-e . diff --git a/packages/aws-cdk/lib/init-templates/app/python/setup.template.py b/packages/aws-cdk/lib/init-templates/app/python/setup.template.py new file mode 100644 index 0000000000000..d43176bdf3b08 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/app/python/setup.template.py @@ -0,0 +1,45 @@ +import setuptools + + +with open("README.md") as fp: + long_description = fp.read() + + +setuptools.setup( + name="%name.PythonModule%", + version="0.0.1", + + description="An empty CDK Python app", + long_description=long_description, + long_description_content_type="text/markdown", + + author="author", + + package_dir={"": "%name.PythonModule%"}, + packages=setuptools.find_packages(where="%name.PythonModule%"), + + install_requires=[ + "aws-cdk.cdk", + ], + + python_requires=">=3.6", + + classifiers=[ + "Development Status :: 4 - Beta", + + "Intended Audience :: Developers", + + "License :: OSI Approved :: Apache Software License", + + "Programming Language :: JavaScript", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + + "Topic :: Software Development :: Code Generators", + "Topic :: Utilities", + + "Typing :: Typed", + ], +) diff --git a/packages/aws-cdk/lib/init-templates/app/typescript/.template.gitignore b/packages/aws-cdk/lib/init-templates/app/typescript/.template.gitignore index 64d60cacf04a2..18d423e69d5b6 100644 --- a/packages/aws-cdk/lib/init-templates/app/typescript/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/app/typescript/.template.gitignore @@ -1,3 +1,6 @@ *.js *.d.ts node_modules + +# CDK asset staging directory +.cdk.staging diff --git a/packages/aws-cdk/lib/init-templates/app/typescript/.template.npmignore b/packages/aws-cdk/lib/init-templates/app/typescript/.template.npmignore index f0e06b3b5ec1d..95251b90d3d7e 100644 --- a/packages/aws-cdk/lib/init-templates/app/typescript/.template.npmignore +++ b/packages/aws-cdk/lib/init-templates/app/typescript/.template.npmignore @@ -1,2 +1,5 @@ *.ts !*.d.ts + +# CDK asset staging directory +.cdk.staging diff --git a/packages/aws-cdk/lib/init-templates/lib/typescript/.template.gitignore b/packages/aws-cdk/lib/init-templates/lib/typescript/.template.gitignore index 64d60cacf04a2..18d423e69d5b6 100644 --- a/packages/aws-cdk/lib/init-templates/lib/typescript/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/lib/typescript/.template.gitignore @@ -1,3 +1,6 @@ *.js *.d.ts node_modules + +# CDK asset staging directory +.cdk.staging diff --git a/packages/aws-cdk/lib/init-templates/lib/typescript/.template.npmignore b/packages/aws-cdk/lib/init-templates/lib/typescript/.template.npmignore index f0e06b3b5ec1d..95251b90d3d7e 100644 --- a/packages/aws-cdk/lib/init-templates/lib/typescript/.template.npmignore +++ b/packages/aws-cdk/lib/init-templates/lib/typescript/.template.npmignore @@ -1,2 +1,5 @@ *.ts !*.d.ts + +# CDK asset staging directory +.cdk.staging diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/.gitignore b/packages/aws-cdk/lib/init-templates/sample-app/python/.gitignore new file mode 100644 index 0000000000000..098bee3a753ab --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/.gitignore @@ -0,0 +1,9 @@ +*.swp +package-lock.json +__pycache__ +.pytest_cache +.env +*.egg-info + +# CDK asset staging directory +.cdk.staging diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/README.template.md b/packages/aws-cdk/lib/init-templates/sample-app/python/README.template.md new file mode 100644 index 0000000000000..92caad837d81f --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/README.template.md @@ -0,0 +1,65 @@ + +# Welcome to your CDK Python project! + +You should explore the contents of this template. It demonstrates a CDK app with two instances of +a stack (`HelloStack`) which also uses a user-defined construct (`HelloConstruct`). + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +This project is set up like a standard Python project. The initialization process also creates +a virtualenv within this project, stored under the .env directory. To create the virtualenv +it assumes that there is a `python3` executable in your path with access to the `venv` package. +If for any reason the automatic creation of the virtualenv fails, you can create the virtualenv +manually once the init process completes. + +To manually create a virtualenv on MacOS and Linux: + +``` +$ %python-executable% -m venv .env +``` + +After the init process completes and the virtualenv is created, you can use the following +step to activate your virtualenv. + +``` +$ source .env/bin/activate +``` + +If you are a Windows platform, you would activate the virtualenv like this: + +``` +% .env\Scripts\activate.bat +``` + +Once the virtualenv is activated, you can install the required dependencies. + +``` +$ pip install -r requirements.txt +``` + +At this point you can now synthesize the CloudFormation template for this code. + +``` +$ cdk synth +``` + +You can now begin exploring the source code, contained in the hello directory. +There is also a very trivial test included that can be run like this: + +``` +$ pytest +``` + +To add additional dependencies, for example other CDK libraries, just add to +your requirements.txt file and rerun the `pip install -r requirements.txt` +command. + +# Useful commands + + * `cdk ls` list all stacks in the app + * `cdk synth` emits the synthesized CloudFormation template + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk docs` open CDK documentation + +Enjoy! diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/app.py b/packages/aws-cdk/lib/init-templates/sample-app/python/app.py new file mode 100644 index 0000000000000..1754ff4ab6f1b --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/app.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + +from aws_cdk import cdk + +from hello.hello_stack import MyStack + + +app = cdk.App() +MyStack(app, "hello-cdk-1", env={'region': 'us-east-2'}) +MyStack(app, "hello-cdk-2", env={'region': 'us-west-2'}) + +app.run() diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/cdk.template.json b/packages/aws-cdk/lib/init-templates/sample-app/python/cdk.template.json new file mode 100644 index 0000000000000..d7293493c4415 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/cdk.template.json @@ -0,0 +1,3 @@ +{ + "app": "%python-executable% app.py" +} diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/hello/__init__.py b/packages/aws-cdk/lib/init-templates/sample-app/python/hello/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/hello/hello_construct.py b/packages/aws-cdk/lib/init-templates/sample-app/python/hello/hello_construct.py new file mode 100644 index 0000000000000..d9637d43d82a5 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/hello/hello_construct.py @@ -0,0 +1,22 @@ +from aws_cdk import ( + aws_iam as iam, + aws_s3 as s3, + cdk, +) + + +class HelloConstruct(cdk.Construct): + + @property + def buckets(self): + return tuple(self._buckets) + + def __init__(self, scope: cdk.Construct, id: str, num_buckets: int) -> None: + super().__init__(scope, id) + self._buckets = [] + for i in range(0, num_buckets): + self._buckets.append(s3.Bucket(self, f"Bucket-{i}")) + + def grant_read(self, principal: iam.IPrincipal): + for b in self.buckets: + b.grant_read(principal, "*") diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/hello/hello_stack.py b/packages/aws-cdk/lib/init-templates/sample-app/python/hello/hello_stack.py new file mode 100644 index 0000000000000..0efa1aa145d04 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/hello/hello_stack.py @@ -0,0 +1,30 @@ +from aws_cdk import ( + aws_iam as iam, + aws_sqs as sqs, + aws_sns as sns, + cdk +) + +from hello_construct import HelloConstruct + + +class MyStack(cdk.Stack): + + def __init__(self, app: cdk.App, id: str, **kwargs) -> None: + super().__init__(app, id, **kwargs) + + queue = sqs.Queue( + self, "MyFirstQueue", + visibility_timeout_sec=300, + ) + + topic = sns.Topic( + self, "MyFirstTopic", + display_name="My First Topic" + ) + + topic.subscribe_queue(queue) + + hello = HelloConstruct(self, "MyHelloConstruct", num_buckets=4) + user = iam.User(self, "MyUser") + hello.grant_read(user) diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/requirements.txt b/packages/aws-cdk/lib/init-templates/sample-app/python/requirements.txt new file mode 100644 index 0000000000000..ae60ed5f14ca8 --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/requirements.txt @@ -0,0 +1,2 @@ +-e . +pytest diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/setup.py b/packages/aws-cdk/lib/init-templates/sample-app/python/setup.py new file mode 100644 index 0000000000000..c44c585d2d90c --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/setup.py @@ -0,0 +1,49 @@ +import setuptools + + +with open("README.md") as fp: + long_description = fp.read() + + +setuptools.setup( + name="hello", + version="0.0.1", + + description="A sample CDK Python app", + long_description=long_description, + long_description_content_type="text/markdown", + + author="author", + + package_dir={"": "hello"}, + packages=setuptools.find_packages(where="hello"), + + install_requires=[ + "aws-cdk.cdk", + "aws-cdk.aws_iam", + "aws-cdk.aws_sqs", + "aws-cdk.aws_sns", + "aws-cdk.aws_s3", + ], + + python_requires=">=3.6", + + classifiers=[ + "Development Status :: 4 - Beta", + + "Intended Audience :: Developers", + + "License :: OSI Approved :: Apache Software License", + + "Programming Language :: JavaScript", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + + "Topic :: Software Development :: Code Generators", + "Topic :: Utilities", + + "Typing :: Typed", + ], +) diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/tests/__init__.py b/packages/aws-cdk/lib/init-templates/sample-app/python/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/tests/unit/__init__.py b/packages/aws-cdk/lib/init-templates/sample-app/python/tests/unit/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/aws-cdk/lib/init-templates/sample-app/python/tests/unit/test_hello_construct.py b/packages/aws-cdk/lib/init-templates/sample-app/python/tests/unit/test_hello_construct.py new file mode 100644 index 0000000000000..4af1107c5e83b --- /dev/null +++ b/packages/aws-cdk/lib/init-templates/sample-app/python/tests/unit/test_hello_construct.py @@ -0,0 +1,16 @@ +import unittest + +from aws_cdk import cdk + +from hello.hello_construct import HelloConstruct + +class TestHelloConstruct(unittest.TestCase): + + def setUp(self): + self.app = cdk.App() + self.stack = cdk.Stack(self.app, "TestStack") + + def test_num_buckets(self): + num_buckets = 10 + hello = HelloConstruct(self.stack, "Test1", num_buckets) + assert len(hello.buckets) == num_buckets \ No newline at end of file diff --git a/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.gitignore b/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.gitignore index 64d60cacf04a2..18d423e69d5b6 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.gitignore +++ b/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.gitignore @@ -1,3 +1,6 @@ *.js *.d.ts node_modules + +# CDK asset staging directory +.cdk.staging diff --git a/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.npmignore b/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.npmignore index f0e06b3b5ec1d..95251b90d3d7e 100644 --- a/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.npmignore +++ b/packages/aws-cdk/lib/init-templates/sample-app/typescript/.template.npmignore @@ -1,2 +1,5 @@ *.ts !*.d.ts + +# CDK asset staging directory +.cdk.staging diff --git a/packages/aws-cdk/lib/init.ts b/packages/aws-cdk/lib/init.ts index f0475873c3691..a36a047a9000c 100644 --- a/packages/aws-cdk/lib/init.ts +++ b/packages/aws-cdk/lib/init.ts @@ -42,6 +42,16 @@ export async function cliInit(type?: string, language?: string, canUseNetwork?: await initializeProject(template, language, canUseNetwork !== undefined ? canUseNetwork : true); } +/** + * Returns the name of the Python executable for this OS + */ +function pythonExecutable() { + let python = 'python3'; + if (process.platform === 'win32') { + python = 'python'; + } + return python; +} const INFO_DOT_JSON = 'info.json'; export class InitTemplate { @@ -142,7 +152,9 @@ export class InitTemplate { .replace(/%name\.camelCased%/g, camelCase(project.name)) .replace(/%name\.PascalCased%/g, camelCase(project.name, { pascalCase: true })) .replace(/%cdk-version%/g, cdkVersion) - .replace(/%cdk-home%/g, CDK_HOME); + .replace(/%cdk-home%/g, CDK_HOME) + .replace(/%name\.PythonModule%/g, project.name.replace(/-/g, '_')) + .replace(/%python-executable%/g, pythonExecutable()); } } @@ -231,6 +243,8 @@ async function postInstall(language: string, canUseNetwork: boolean) { return await postInstallTypescript(canUseNetwork); case 'java': return await postInstallJava(canUseNetwork); + case 'python': + return await postInstallPython(); } } @@ -260,6 +274,17 @@ async function postInstallJava(canUseNetwork: boolean) { await execute('mvn', 'package'); } +async function postInstallPython() { + const python = pythonExecutable(); + print(`Executing ${colors.green('Creating virtualenv...')}`); + try { + await execute(python, '-m venv', '.env'); + } catch (e) { + print('Unable to create virtualenv automatically'); + print(`Please run ${colors.green(python + ' -m venv .env')}!`); + } +} + /** * @param dir a directory to be checked * @returns true if ``dir`` is within a git repository. diff --git a/packages/aws-cdk/lib/settings.ts b/packages/aws-cdk/lib/settings.ts index 0ba8383750e5b..5f984f6e43fa7 100644 --- a/packages/aws-cdk/lib/settings.ts +++ b/packages/aws-cdk/lib/settings.ts @@ -210,6 +210,7 @@ export class Settings { requireApproval: argv.requireApproval, toolkitStackName: argv.toolkitStackName, versionReporting: argv.versionReporting, + staging: argv.staging }); } diff --git a/packages/aws-cdk/package-lock.json b/packages/aws-cdk/package-lock.json index e9500355caadb..dac5833770557 100644 --- a/packages/aws-cdk/package-lock.json +++ b/packages/aws-cdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "aws-cdk", - "version": "0.25.3", + "version": "0.27.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 5a8266c2f70ce..b070ab282e2b2 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -1,7 +1,7 @@ { "name": "aws-cdk", "description": "CDK Toolkit, the command line tool for CDK apps", - "version": "0.26.0", + "version": "0.28.0", "main": "lib/index.js", "types": "lib/index.d.ts", "bin": { @@ -43,16 +43,16 @@ "@types/uuid": "^3.4.3", "@types/yaml": "^1.0.0", "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.26.0", + "cdk-build-tools": "^0.28.0", "mockery": "^2.1.0", - "pkglint": "^0.26.0", + "pkglint": "^0.28.0", "sinon": "^7.2.7" }, "dependencies": { - "@aws-cdk/applet-js": "^0.26.0", - "@aws-cdk/cloudformation-diff": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0", - "@aws-cdk/region-info": "^0.26.0", + "@aws-cdk/applet-js": "^0.28.0", + "@aws-cdk/cloudformation-diff": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0", + "@aws-cdk/region-info": "^0.28.0", "archiver": "^2.1.1", "aws-sdk": "^2.259.1", "camelcase": "^5.0.0", @@ -72,7 +72,8 @@ }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", - "type": "git" + "type": "git", + "directory": "packages/aws-cdk" }, "keywords": [ "aws", diff --git a/packages/aws-cdk/test/api/test.stacks.ts b/packages/aws-cdk/test/api/test.stacks.ts index 55aefd18d7a4d..2322668d3ad67 100644 --- a/packages/aws-cdk/test/api/test.stacks.ts +++ b/packages/aws-cdk/test/api/test.stacks.ts @@ -86,4 +86,86 @@ export = { test.done(); }, -}; \ No newline at end of file + + async 'does not return non-autoDeployed Stacks when called without any selectors'(test: Test) { + // GIVEN + const stacks = appStacksWith([ + { + name: 'NotAutoDeployedStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + autoDeploy: false, + }, + ]); + + // WHEN + const synthed = await stacks.selectStacks([], ExtendedStackSelection.None); + + // THEN + test.equal(synthed.length, 0); + + test.done(); + }, + + async 'does return non-autoDeployed Stacks when called with selectors matching it'(test: Test) { + // GIVEN + const stacks = appStacksWith([ + { + name: 'NotAutoDeployedStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + autoDeploy: false, + }, + ]); + + // WHEN + const synthed = await stacks.selectStacks(['NotAutoDeployedStack'], ExtendedStackSelection.None); + + // THEN + test.equal(synthed.length, 1); + + test.done(); + }, + + async "does return an non-autoDeployed Stack when it's a dependency of a selected Stack"(test: Test) { + // GIVEN + const stacks = appStacksWith([ + { + name: 'NotAutoDeployedStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + autoDeploy: false, + }, + { + name: 'AutoDeployedStack', + template: { resource: 'Resource' }, + environment: { name: 'dev', account: '12345', region: 'here' }, + metadata: {}, + dependsOn: ['NotAutoDeployedStack'], + }, + ]); + + // WHEN + const synthed = await stacks.selectStacks(['AutoDeployedStack'], ExtendedStackSelection.Upstream); + + // THEN + test.equal(synthed.length, 2); + + test.done(); + }, +}; + +function appStacksWith(stacks: cxapi.SynthesizedStack[]): AppStacks { + const response: cxapi.SynthesizeResponse = { + version: '1', + stacks, + }; + return new AppStacks({ + configuration: new Configuration(), + aws: new SDK(), + synthesizer: async () => response, + }); +} diff --git a/packages/cdk-dasm/.gitignore b/packages/cdk-dasm/.gitignore new file mode 100644 index 0000000000000..88e6bb7a9196f --- /dev/null +++ b/packages/cdk-dasm/.gitignore @@ -0,0 +1,5 @@ +*.js +*.d.ts +!deps.js +test/fixture/.jsii +cdk.schema.json diff --git a/packages/cdk-dasm/README.md b/packages/cdk-dasm/README.md new file mode 100644 index 0000000000000..6b295025c32e2 --- /dev/null +++ b/packages/cdk-dasm/README.md @@ -0,0 +1,114 @@ +# CDK CloudFormation Disassembler + +[![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges) + +---- + +## __WIP__ - this module is still not fully functional: + +- [ ] Does not handle intrinsic functions +- [ ] Only handles the "Resources" section (parameters, outputs, mappings, + conditions, ...) +- [ ] Keys in JSON blobs (such as IAM policies) are converted to camel case + (instead of remain as pascal case). +- [ ] Only TypeScript is supported + +----- + +Converts an AWS CloudFormation template into AWS CDK code which synthesizes the +same exact template. + +## Why you should not use this tool? + +Generally, this is not a recommended approach when using the AWS CDK, but some +people may find this useful as a means to get started or migrate an existing +template. + +Using this method means that you will have to use the low-level resources (e.g. +`s3.CfnBucket` instead of `s3.Bucket`). This means that you lose a substantial +portion of the value of the CDK, which abstracts away much of the boilerplate +and glue logic required to work with AWS resources. + +For example, this is how you would define an S3 bucket encrypted with a KMS key +with high-level resources: + +```ts +new s3.Bucket(this, 'MyBucket', { + encryption: s3.BucketEncryption.Kms +}); +``` + +And this is how the same exact configuration will be defined using low-level +resources: + +```ts +new kms.CfnKey(this, 'MyBucketKeyC17130CF', { + keyPolicy: { + "statement": [ + { + "action": [ "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*", "kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion" ], + "effect": "Allow", + "principal": { + "aws": { "Fn::Join": [ "", [ "arn:", { "Ref": "AWS::Partition" }, ":iam::", { "Ref": "AWS::AccountId" }, ":root" ] ] } + }, + "resource": "*" + } + ], + "version": "2012-10-17" + } +}); + +new s3.CfnBucket(this, 'MyBucketF68F3FF0', { + bucketEncryption: { + "serverSideEncryptionConfiguration": [ + { + "serverSideEncryptionByDefault": { + "kmsMasterKeyId": Fn.getAtt('MyBucketKeyC17130CF', 'Arn').toString(), + "sseAlgorithm": "aws:kms" + } + } + ] + }, +}); +``` +As you can see, there are a lot of details here that you really don't want to +care about (like the value to put under `sseAlgorithm` or which actions are +required in the key policy so the key can be managed by administrators. Also, +this is actually one of the more simple examples we have in the CDK. + +The AWS Construct Library includes a very large amount of "undifferentiated +heavy lifting" that you can only enjoy if you use the high level resources which +encapsulate all this goodness for you behind a nice clean object-oriented API. + +Therefore, we encourage you to use the high-level constructs in the AWS +Construct Library as much as possible. If you encounter a gap or missing +capability or resource, take a look at the [Escape +Hatches](https://docs.aws.amazon.com/CDK/latest/userguide/cfn_layer.html) +section of the User Guide. + +## Usage + +```console +$ cdk-dasm < my-stack-template.json > my-stack.ts +``` + +For example, given: + +```json +{ + "Resources": { + "MyTopic": { + "Type": "AWS::SNS::Topic", + "Properties": { + "DisplayName": "YoTopic" + } + } + } +} +``` + +The output will be: + +```ts + +``` \ No newline at end of file diff --git a/packages/cdk-dasm/bin/cdk-dasm b/packages/cdk-dasm/bin/cdk-dasm new file mode 100755 index 0000000000000..01f5d76dbdc6d --- /dev/null +++ b/packages/cdk-dasm/bin/cdk-dasm @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('./cdk-dasm.js'); \ No newline at end of file diff --git a/packages/cdk-dasm/bin/cdk-dasm.ts b/packages/cdk-dasm/bin/cdk-dasm.ts new file mode 100644 index 0000000000000..60bb573a0ab9f --- /dev/null +++ b/packages/cdk-dasm/bin/cdk-dasm.ts @@ -0,0 +1,14 @@ +import YAML = require('yaml'); +import { dasmTypeScript } from '../lib'; + +let s = ''; +process.stdin.resume(); +process.stdin.on('data', data => { + s += data.toString('utf-8'); +}); + +process.stdin.on('end', () => { + dasmTypeScript(YAML.parse(s)).then(out => { + process.stdout.write(out); + }); +}); diff --git a/packages/cdk-dasm/lib/dasm.ts b/packages/cdk-dasm/lib/dasm.ts new file mode 100644 index 0000000000000..52f759c561746 --- /dev/null +++ b/packages/cdk-dasm/lib/dasm.ts @@ -0,0 +1,153 @@ +import { CodeMaker, toCamelCase } from 'codemaker'; +import fs = require('fs'); +import os = require('os'); +import path = require('path'); +import { promisify } from 'util'; + +const mkdtemp = promisify(fs.mkdtemp); +const readFile = promisify(fs.readFile); + +interface ConstructDefinition { + namespace: string; + className: string; + id: string; + props: { [key: string]: any }; +} + +export interface DisassemblerOptions { + /** + * Include a timestamp in the generated output. + * + * @default true + */ + readonly timestamp?: boolean; +} + +export async function dasmTypeScript(template: Template, options: DisassemblerOptions = {}) { + const definitions = new Array(); + + for (const [ id, resource ] of Object.entries(template.Resources || {})) { + const type = resource.Type; + const props = resource.Properties || {}; + + definitions.push({ + id, + ...toCfnClassName(type), + props: capitalizeKeys(props) + }); + } + + const code = new CodeMaker(); + + const outFile = 'out.ts'; + + code.openFile(outFile); + + const timestamp = options.timestamp !== undefined ? options.timestamp : true; + const suffix = timestamp ? `at ${new Date().toISOString()}` : ''; + + code.line(`// generated by cdk-dasm${suffix}`); + code.line(); + + // + // imports + // + + code.line(`import { Stack, StackProps, Construct, Fn } from '@aws-cdk/cdk';`); + + for (const def of definitions) { + const importName = `@aws-cdk/aws-${def.namespace}`; + code.line(`import ${def.namespace} = require('${importName}');`); + } + + code.line(); + + // + // stack + // + + code.openBlock(`export class MyStack extends Stack`); + code.openBlock(`constructor(scope: Construct, id: string, props: StackProps = {})`); + code.line(`super(scope, id, props);`); + + for (const def of definitions) { + + // no props + if (Object.keys(def.props).length === 0) { + code.line(`new ${def.className}(this, '${def.id}');`); + continue; + } + + code.indent(`new ${def.className}(this, '${def.id}', {`); + + for (const [key, value] of Object.entries(def.props)) { + const json = JSON.stringify(value, undefined, 2); + const [ first, ...rest ] = json.split('\n'); + + if (rest.length === 0) { + code.line(`${key}: ${first},`); // single line + } else { + code.line(`${key}: ${first}`); + rest.forEach((r, i) => { + code.line(r + ((i === rest.length - 1) ? ',' : '')); + }); + } + } + + code.unindent('});'); + } + + code.closeBlock(); + + code.closeBlock(' // MyStack'); + + code.closeFile(outFile); + + const workdir = await mkdtemp(path.join(os.tmpdir(), 'cdk-dasm-typescript')); + await code.save(workdir); + + return (await readFile(path.join(workdir, outFile))).toString(); +} + +function capitalizeKeys(x: any): any { + if (typeof(x) === 'function') { + throw new Error(`function?`); + } + + if (Array.isArray(x)) { + return x.map(i => capitalizeKeys(i)); + } + + if (typeof(x) === 'object') { + const ret: { [key: string]: any } = {}; + for (const [ key, value ] of Object.entries(x)) { + let newKey; + if (key === 'Ref' || key.startsWith('Fn::')) { + newKey = key; + } else { + newKey = toCamelCase(key); + } + + ret[newKey] = capitalizeKeys(value); + } + return ret; + } + + // primitive + return x; +} + +function toCfnClassName(resourceType: string) { + const [ , namespace, type ] = resourceType.split('::'); + const className = `${namespace.toLocaleLowerCase()}.Cfn${type}`; + return { namespace: namespace.toLocaleLowerCase(), className }; +} + +interface Template { + Resources: { [id: string]: Resource }; +} + +interface Resource { + Type: string; + Properties?: { [prop: string]: any } +} diff --git a/packages/cdk-dasm/lib/index.ts b/packages/cdk-dasm/lib/index.ts new file mode 100644 index 0000000000000..c0fffbca00353 --- /dev/null +++ b/packages/cdk-dasm/lib/index.ts @@ -0,0 +1 @@ +export * from './dasm'; \ No newline at end of file diff --git a/packages/cdk-dasm/package-lock.json b/packages/cdk-dasm/package-lock.json new file mode 100644 index 0000000000000..46dc619f37616 --- /dev/null +++ b/packages/cdk-dasm/package-lock.json @@ -0,0 +1,5149 @@ +{ + "name": "cdk-dasm", + "version": "0.28.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.3.tgz", + "integrity": "sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helpers": "^7.4.3", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0" + } + }, + "@babel/helpers": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.3.tgz", + "integrity": "sha512-BMh7X0oZqb36CfyhvtbSmcWc3GXocfxv3yNsAEuM0l+fAqSO22rQrUpijr3oE/10jCTrB6/0b9kzmG4VetCj8Q==", + "dev": true, + "requires": { + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz", + "integrity": "sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==", + "dev": true + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", + "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/traverse": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz", + "integrity": "sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/types": "^7.4.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "@cnakazawa/watch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", + "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@jest/console": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", + "integrity": "sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==", + "dev": true, + "requires": { + "@jest/source-map": "^24.3.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + } + }, + "@jest/core": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.7.1.tgz", + "integrity": "sha512-ivlZ8HX/FOASfHcb5DJpSPFps8ydfUYzLZfgFFqjkLijYysnIEOieg72YRhO4ZUB32xu40hsSMmaw+IGYeKONA==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/reporters": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-changed-files": "^24.7.0", + "jest-config": "^24.7.1", + "jest-haste-map": "^24.7.1", + "jest-message-util": "^24.7.1", + "jest-regex-util": "^24.3.0", + "jest-resolve-dependencies": "^24.7.1", + "jest-runner": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-snapshot": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "jest-watcher": "^24.7.1", + "micromatch": "^3.1.10", + "p-each-series": "^1.0.0", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "rimraf": "^2.5.4", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "@jest/environment": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.7.1.tgz", + "integrity": "sha512-wmcTTYc4/KqA+U5h1zQd5FXXynfa7VGP2NfF+c6QeGJ7c+2nStgh65RQWNX62SC716dTtqheTRrZl0j+54oGHw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^24.7.1", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "jest-mock": "^24.7.0" + } + }, + "@jest/fake-timers": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.7.1.tgz", + "integrity": "sha512-4vSQJDKfR2jScOe12L9282uiwuwQv9Lk7mgrCSZHA9evB9efB/qx8i0KJxsAKtp8fgJYBJdYY7ZU6u3F4/pyjA==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-mock": "^24.7.0" + } + }, + "@jest/reporters": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.7.1.tgz", + "integrity": "sha512-bO+WYNwHLNhrjB9EbPL4kX/mCCG4ZhhfWmO3m4FSpbgr7N83MFejayz30kKjgqr7smLyeaRFCBQMbXpUgnhAJw==", + "dev": true, + "requires": { + "@jest/environment": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-api": "^2.1.1", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-source-maps": "^3.0.1", + "jest-haste-map": "^24.7.1", + "jest-resolve": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-util": "^24.7.1", + "jest-worker": "^24.6.0", + "node-notifier": "^5.2.1", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + } + }, + "@jest/source-map": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz", + "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.7.1.tgz", + "integrity": "sha512-3U7wITxstdEc2HMfBX7Yx3JZgiNBubwDqQMh+BXmZXHa3G13YWF3p6cK+5g0hGkN3iufg/vGPl3hLxQXD74Npg==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/test-sequencer": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.7.1.tgz", + "integrity": "sha512-84HQkCpVZI/G1zq53gHJvSmhUer4aMYp9tTaffW28Ih5OxfCg8hGr3nTSbL1OhVDRrFZwvF+/R9gY6JRkDUpUA==", + "dev": true, + "requires": { + "@jest/test-result": "^24.7.1", + "jest-haste-map": "^24.7.1", + "jest-runner": "^24.7.1", + "jest-runtime": "^24.7.1" + } + }, + "@jest/transform": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.7.1.tgz", + "integrity": "sha512-EsOUqP9ULuJ66IkZQhI5LufCHlTbi7hrcllRMUEV/tOgqBVQi93+9qEvkX0n8mYpVXQ8VjwmICeRgg58mrtIEw==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.7.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.7.1", + "jest-regex-util": "^24.3.0", + "jest-util": "^24.7.1", + "micromatch": "^3.1.10", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + } + }, + "@jest/types": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.7.0.tgz", + "integrity": "sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/yargs": "^12.0.9" + } + }, + "@types/babel__core": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.1.tgz", + "integrity": "sha512-+hjBtgcFPYyCTo0A15+nxrCVJL7aC6Acg87TXd5OW3QhHswdrOLoles+ldL2Uk8q++7yIfl4tURtztccdeeyOw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz", + "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.6.tgz", + "integrity": "sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz", + "integrity": "sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg==", + "dev": true + }, + "@types/jest": { + "version": "24.0.11", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.11.tgz", + "integrity": "sha512-2kLuPC5FDnWIDvaJBzsGTBQaBbnDweznicvK7UGYzlIJP4RJR2a4A/ByLUXEyEgag6jz8eHdlWExGDtH3EYUXQ==", + "dev": true, + "requires": { + "@types/jest-diff": "*" + } + }, + "@types/jest-diff": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz", + "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==", + "dev": true + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "@types/yaml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/yaml/-/yaml-1.0.1.tgz", + "integrity": "sha512-Bq8/v5jtk4TsPY4qSmlEM2hucw5vgA7jWaDV6NFNv6ZjSoqxicRB5RH8Cc1eMEr0VcbgnysGIujg6hR3nZ14sw==", + "dev": true + }, + "@types/yargs": { + "version": "12.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", + "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==", + "dev": true + }, + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-globals": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", + "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-jest": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.7.1.tgz", + "integrity": "sha512-GPnLqfk8Mtt0i4OemjWkChi73A3ALs4w2/QbG64uAj8b5mmwzxc7jbJVRZt8NJkxi6FopVHog9S3xX6UJKb2qg==", + "dev": true, + "requires": { + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.6.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz", + "integrity": "sha512-RNNVv2lsHAXJQsEJ5jonQwrJVWK8AcZpG1oxhnjCUaAjL7xahYLANhPUZbzEQHjKy1NMYUwn+0NPKQc8iSY4xQ==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.0.0", + "test-exclude": "^5.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz", + "integrity": "sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==", + "dev": true, + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", + "integrity": "sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.6.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "codemaker": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/codemaker/-/codemaker-0.9.0.tgz", + "integrity": "sha512-ZFaGAyhg7+jss1vrnFaDRNLA2zDxEn9GVmMextVINg9fZZ+URnUNrnepHSOfNpAFlds/FqhcNsGytpCHfcYCDA==", + "requires": { + "camelcase": "^5.2.0", + "decamelize": "^1.2.0", + "fs-extra": "^7.0.1" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true, + "optional": true + }, + "compare-versions": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", + "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", + "dev": true + }, + "cssstyle": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", + "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "diff-sequences": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz", + "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==", + "dev": true + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "exec-sh": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", + "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expect": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.7.1.tgz", + "integrity": "sha512-mGfvMTPduksV3xoI0xur56pQsg2vJjNf5+a+bXOjqCkiCBbmCayrBbHS/75y9K430cfqyocPr2ZjiNiRx4SRKw==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.3.0", + "jest-matcher-utils": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-regex-util": "^24.3.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "dev": true, + "requires": { + "bser": "^2.0.0" + } + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "handlebars": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.1.tgz", + "integrity": "sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.1.tgz", + "integrity": "sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==", + "dev": true, + "requires": { + "async": "^2.6.1", + "compare-versions": "^3.2.1", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.3", + "istanbul-lib-hook": "^2.0.3", + "istanbul-lib-instrument": "^3.1.0", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.2", + "istanbul-reports": "^2.1.1", + "js-yaml": "^3.12.0", + "make-dir": "^1.3.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.3.tgz", + "integrity": "sha512-CLmEqwEhuCYtGcpNVJjLV1DQyVnIqavMLFHV/DP+np/g3qvdxu3gsPqYoJMXm15sN84xOlckFB3VNvRbf5yEgA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz", + "integrity": "sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.3", + "semver": "^5.5.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz", + "integrity": "sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz", + "integrity": "sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.1.1.tgz", + "integrity": "sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw==", + "dev": true, + "requires": { + "handlebars": "^4.1.0" + } + }, + "jest": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.7.1.tgz", + "integrity": "sha512-AbvRar5r++izmqo5gdbAjTeA6uNRGoNRuj5vHB0OnDXo2DXWZJVuaObiGgtlvhKb+cWy2oYbQSfxv7Q7GjnAtA==", + "dev": true, + "requires": { + "import-local": "^2.0.0", + "jest-cli": "^24.7.1" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "jest-cli": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.7.1.tgz", + "integrity": "sha512-32OBoSCVPzcTslGFl6yVCMzB2SqX3IrWwZCY5mZYkb0D2WsogmU3eV2o8z7+gRQa4o4sZPX/k7GU+II7CxM6WQ==", + "dev": true, + "requires": { + "@jest/core": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "jest-config": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "prompts": "^2.0.1", + "realpath-native": "^1.1.0", + "yargs": "^12.0.2" + } + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "jest-changed-files": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.7.0.tgz", + "integrity": "sha512-33BgewurnwSfJrW7T5/ZAXGE44o7swLslwh8aUckzq2e17/2Os1V0QU506ZNik3hjs8MgnEMKNkcud442NCDTw==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "execa": "^1.0.0", + "throat": "^4.0.0" + } + }, + "jest-config": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.7.1.tgz", + "integrity": "sha512-8FlJNLI+X+MU37j7j8RE4DnJkvAghXmBWdArVzypW6WxfGuxiL/CCkzBg0gHtXhD2rxla3IMOSUAHylSKYJ83g==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^24.7.1", + "@jest/types": "^24.7.0", + "babel-jest": "^24.7.1", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.7.1", + "jest-environment-node": "^24.7.1", + "jest-get-type": "^24.3.0", + "jest-jasmine2": "^24.7.1", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.7.0", + "realpath-native": "^1.1.0" + } + }, + "jest-diff": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.7.0.tgz", + "integrity": "sha512-ULQZ5B1lWpH70O4xsANC4tf4Ko6RrpwhE3PtG6ERjMg1TiYTC2Wp4IntJVGro6a8HG9luYHhhmF4grF0Pltckg==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.3.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.7.0" + } + }, + "jest-docblock": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz", + "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==", + "dev": true, + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.7.1.tgz", + "integrity": "sha512-4fsS8fEfLa3lfnI1Jw6NxjhyRTgfpuOVTeUZZFyVYqeTa4hPhr2YkToUhouuLTrL2eMGOfpbdMyRx0GQ/VooKA==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.3.0", + "jest-util": "^24.7.1", + "pretty-format": "^24.7.0" + } + }, + "jest-environment-jsdom": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.7.1.tgz", + "integrity": "sha512-Gnhb+RqE2JuQGb3kJsLF8vfqjt3PHKSstq4Xc8ic+ax7QKo4Z0RWGucU3YV+DwKR3T9SYc+3YCUQEJs8r7+Jxg==", + "dev": true, + "requires": { + "@jest/environment": "^24.7.1", + "@jest/fake-timers": "^24.7.1", + "@jest/types": "^24.7.0", + "jest-mock": "^24.7.0", + "jest-util": "^24.7.1", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.7.1.tgz", + "integrity": "sha512-GJJQt1p9/C6aj6yNZMvovZuxTUd+BEJprETdvTKSb4kHcw4mFj8777USQV0FJoJ4V3djpOwA5eWyPwfq//PFBA==", + "dev": true, + "requires": { + "@jest/environment": "^24.7.1", + "@jest/fake-timers": "^24.7.1", + "@jest/types": "^24.7.0", + "jest-mock": "^24.7.0", + "jest-util": "^24.7.1" + } + }, + "jest-get-type": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", + "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", + "dev": true + }, + "jest-haste-map": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.7.1.tgz", + "integrity": "sha512-g0tWkzjpHD2qa03mTKhlydbmmYiA2KdcJe762SbfFo/7NIMgBWAA0XqQlApPwkWOF7Cxoi/gUqL0i6DIoLpMBw==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.4.0", + "jest-util": "^24.7.1", + "jest-worker": "^24.6.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.7.1.tgz", + "integrity": "sha512-Y/9AOJDV1XS44wNwCaThq4Pw3gBPiOv/s6NcbOAkVRRUEPu+36L2xoPsqQXsDrxoBerqeyslpn2TpCI8Zr6J2w==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.7.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.7.1", + "jest-matcher-utils": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-snapshot": "^24.7.1", + "jest-util": "^24.7.1", + "pretty-format": "^24.7.0", + "throat": "^4.0.0" + } + }, + "jest-leak-detector": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.7.0.tgz", + "integrity": "sha512-zV0qHKZGXtmPVVzT99CVEcHE9XDf+8LwiE0Ob7jjezERiGVljmqKFWpV2IkG+rkFIEUHFEkMiICu7wnoPM/RoQ==", + "dev": true, + "requires": { + "pretty-format": "^24.7.0" + } + }, + "jest-matcher-utils": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.7.0.tgz", + "integrity": "sha512-158ieSgk3LNXeUhbVJYRXyTPSCqNgVXOp/GT7O94mYd3pk/8+odKTyR1JLtNOQSPzNi8NFYVONtvSWA/e1RDXg==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.7.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.7.0" + } + }, + "jest-message-util": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.7.1.tgz", + "integrity": "sha512-dk0gqVtyqezCHbcbk60CdIf+8UHgD+lmRHifeH3JRcnAqh4nEyPytSc9/L1+cQyxC+ceaeP696N4ATe7L+omcg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.7.0.tgz", + "integrity": "sha512-6taW4B4WUcEiT2V9BbOmwyGuwuAFT2G8yghF7nyNW1/2gq5+6aTqSPcS9lS6ArvEkX55vbPAS/Jarx5LSm4Fng==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "dev": true + }, + "jest-regex-util": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", + "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", + "dev": true + }, + "jest-resolve": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.7.1.tgz", + "integrity": "sha512-Bgrc+/UUZpGJ4323sQyj85hV9d+ANyPNu6XfRDUcyFNX1QrZpSoM0kE4Mb2vZMAYTJZsBFzYe8X1UaOkOELSbw==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "jest-pnp-resolver": "^1.2.1", + "realpath-native": "^1.1.0" + } + }, + "jest-resolve-dependencies": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.7.1.tgz", + "integrity": "sha512-2Eyh5LJB2liNzfk4eo7bD1ZyBbqEJIyyrFtZG555cSWW9xVHxII2NuOkSl1yUYTAYCAmM2f2aIT5A7HzNmubyg==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "jest-regex-util": "^24.3.0", + "jest-snapshot": "^24.7.1" + } + }, + "jest-runner": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.7.1.tgz", + "integrity": "sha512-aNFc9liWU/xt+G9pobdKZ4qTeG/wnJrJna3VqunziDNsWT3EBpmxXZRBMKCsNMyfy+A/XHiV+tsMLufdsNdgCw==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.4.2", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-config": "^24.7.1", + "jest-docblock": "^24.3.0", + "jest-haste-map": "^24.7.1", + "jest-jasmine2": "^24.7.1", + "jest-leak-detector": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-resolve": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-util": "^24.7.1", + "jest-worker": "^24.6.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + } + }, + "jest-runtime": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.7.1.tgz", + "integrity": "sha512-0VAbyBy7tll3R+82IPJpf6QZkokzXPIS71aDeqh+WzPRXRCNz6StQ45otFariPdJ4FmXpDiArdhZrzNAC3sj6A==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.7.1", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/yargs": "^12.0.2", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.7.1", + "jest-haste-map": "^24.7.1", + "jest-message-util": "^24.7.1", + "jest-mock": "^24.7.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.7.1", + "jest-snapshot": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "yargs": "^12.0.2" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "jest-serializer": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz", + "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==", + "dev": true + }, + "jest-snapshot": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.7.1.tgz", + "integrity": "sha512-8Xk5O4p+JsZZn4RCNUS3pxA+ORKpEKepE+a5ejIKrId9CwrVN0NY+vkqEkXqlstA5NMBkNahXkR/4qEBy0t5yA==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "expect": "^24.7.1", + "jest-diff": "^24.7.0", + "jest-matcher-utils": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-resolve": "^24.7.1", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^24.7.0", + "semver": "^5.5.0" + } + }, + "jest-util": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.7.1.tgz", + "integrity": "sha512-/KilOue2n2rZ5AnEBYoxOXkeTu6vi7cjgQ8MXEkih0oeAXT6JkS3fr7/j8+engCjciOU1Nq5loMSKe0A1oeX0A==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/fake-timers": "^24.7.1", + "@jest/source-map": "^24.3.0", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + } + }, + "jest-validate": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.7.0.tgz", + "integrity": "sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "camelcase": "^5.0.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.3.0", + "leven": "^2.1.0", + "pretty-format": "^24.7.0" + } + }, + "jest-watcher": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.7.1.tgz", + "integrity": "sha512-Wd6TepHLRHVKLNPacEsBwlp9raeBIO+01xrN24Dek4ggTS8HHnOzYSFnvp+6MtkkJ3KfMzy220KTi95e2rRkrw==", + "dev": true, + "requires": { + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/yargs": "^12.0.9", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.7.1", + "string-length": "^2.0.0" + } + }, + "jest-worker": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.6.0.tgz", + "integrity": "sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==", + "dev": true, + "requires": { + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime-db": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", + "dev": true + }, + "mime-types": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", + "dev": true, + "requires": { + "mime-db": "~1.38.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwsapi": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.3.tgz", + "integrity": "sha512-RowAaJGEgYXEZfQ7tvvdtAQUKPyTR6T6wNu0fwlNsGQYr/h3yQc6oI8WnVZh3Y/Sylwc+dtAlvPqfFZjhTyk3A==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "requires": { + "p-reduce": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "pretty-format": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", + "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "prompts": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.4.tgz", + "integrity": "sha512-HTzM3UWp/99A0gk51gAegwo1QRYA7xjcZufMNe33rCclFszUYAuHe1fIN/3ZmiHeGPkUsNaRyQm1hHOfM0PKxA==", + "dev": true, + "requires": { + "kleur": "^3.0.2", + "sisteransi": "^1.0.0" + } + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "dev": true, + "requires": { + "util.promisify": "^1.0.0" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "dev": true, + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rsvp": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.4.tgz", + "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sisteransi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", + "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, + "test-exclude": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.1.0.tgz", + "integrity": "sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" + }, + "dependencies": { + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + } + } + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "dev": true + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uglify-js": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.4.tgz", + "integrity": "sha512-GpKo28q/7Bm5BcX9vOu4S46FwisbPbAmkkqPnGIpKvKTM96I85N6XHQV+k4I6FA2wxgLhcsSyHoNhzucwCflvA==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yaml": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.3.2.tgz", + "integrity": "sha512-ZZZIdcApMRcAez37EVrtCim+8JUESX0zRcsv+HMfatIX79cX22CAnVkxDrZhAmzsnka2nb/mvaTybzDYcnrIew==" + } + } +} diff --git a/packages/cdk-dasm/package.json b/packages/cdk-dasm/package.json new file mode 100644 index 0000000000000..3ff297c74b40a --- /dev/null +++ b/packages/cdk-dasm/package.json @@ -0,0 +1,43 @@ +{ + "name": "cdk-dasm", + "version": "0.28.0", + "description": "AWS CDK disassembler: convert CloudFormation to code", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git" + }, + "bin": { + "decdk": "bin/cdk-dasm" + }, + "scripts": { + "build": "tsc && chmod +x bin/cdk-dasm", + "watch": "tsc -w", + "test": "jest", + "package": "mkdir -p dist/js && cd dist/js && npm pack ../../" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "dependencies": { + "yaml": "1.3.2", + "codemaker": "^0.9.0" + }, + "devDependencies": { + "@types/jest": "^24.0.1", + "@types/yaml": "1.0.1", + "jest": "^24.4.0" + }, + "keywords": [ + "aws", + "cdk" + ], + "homepage": "https://github.com/awslabs/aws-cdk", + "engines": { + "node": ">= 8.10.0" + } +} \ No newline at end of file diff --git a/packages/cdk-dasm/test/__snapshots__/dasm.test.js.snap b/packages/cdk-dasm/test/__snapshots__/dasm.test.js.snap new file mode 100644 index 0000000000000..faaaa0dcc7f12 --- /dev/null +++ b/packages/cdk-dasm/test/__snapshots__/dasm.test.js.snap @@ -0,0 +1,113 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`basic test 1`] = ` +"// generated by cdk-dasm + +import { Stack, StackProps, Construct, Fn } from '@aws-cdk/cdk'; +import sns = require('@aws-cdk/aws-sns'); + +export class MyStack extends Stack { + constructor(scope: Construct, id: string, props: StackProps = {}) { + super(scope, id, props); + new sns.CfnTopic(this, 'MyTopic', { + displayName: \\"hello hello\\", + }); + } +} +" +`; + +exports[`bucket-and-key 1`] = ` +"// generated by cdk-dasm + +import { Stack, StackProps, Construct, Fn } from '@aws-cdk/cdk'; +import kms = require('@aws-cdk/aws-kms'); +import s3 = require('@aws-cdk/aws-s3'); +import cdk = require('@aws-cdk/aws-cdk'); + +export class MyStack extends Stack { + constructor(scope: Construct, id: string, props: StackProps = {}) { + super(scope, id, props); + new kms.CfnKey(this, 'MyBucketKeyC17130CF', { + keyPolicy: { + \\"statement\\": [ + { + \\"action\\": [ + \\"kms:Create*\\", + \\"kms:Describe*\\", + \\"kms:Enable*\\", + \\"kms:List*\\", + \\"kms:Put*\\", + \\"kms:Update*\\", + \\"kms:Revoke*\\", + \\"kms:Disable*\\", + \\"kms:Get*\\", + \\"kms:Delete*\\", + \\"kms:ScheduleKeyDeletion\\", + \\"kms:CancelKeyDeletion\\" + ], + \\"effect\\": \\"Allow\\", + \\"principal\\": { + \\"aws\\": { + \\"Fn::Join\\": [ + \\"\\", + [ + \\"arn:\\", + { + \\"Ref\\": \\"AWS::Partition\\" + }, + \\":iam::\\", + { + \\"Ref\\": \\"AWS::AccountId\\" + }, + \\":root\\" + ] + ] + } + }, + \\"resource\\": \\"*\\" + } + ], + \\"version\\": \\"2012-10-17\\" + }, + description: \\"Created by Pg12321Stack/MyBucket\\", + }); + new s3.CfnBucket(this, 'MyBucketF68F3FF0', { + bucketEncryption: { + \\"serverSideEncryptionConfiguration\\": [ + { + \\"serverSideEncryptionByDefault\\": { + \\"kmsMasterKeyId\\": { + \\"Fn::GetAtt\\": [ + \\"MyBucketKeyC17130CF\\", + \\"Arn\\" + ] + }, + \\"sseAlgorithm\\": \\"aws:kms\\" + } + } + ] + }, + }); + new cdk.CfnMetadata(this, 'CDKMetadata', { + modules: \\"aws-cdk=0.28.0,@aws-cdk/aws-events=0.28.0,@aws-cdk/aws-iam=0.28.0,@aws-cdk/aws-kms=0.28.0,@aws-cdk/aws-s3=0.28.0,@aws-cdk/aws-s3-notifications=0.28.0,@aws-cdk/cdk=0.28.0,@aws-cdk/cx-api=0.28.0,@aws-cdk/region-info=0.28.0,jsii-runtime=node.js/v10.13.0\\", + }); + } +} +" +`; + +exports[`no props 1`] = ` +"// generated by cdk-dasm + +import { Stack, StackProps, Construct, Fn } from '@aws-cdk/cdk'; +import s3 = require('@aws-cdk/aws-s3'); + +export class MyStack extends Stack { + constructor(scope: Construct, id: string, props: StackProps = {}) { + super(scope, id, props); + new s3.CfnBucket(this, 'Boom'); + } +} +" +`; diff --git a/packages/cdk-dasm/test/bucket-key.json b/packages/cdk-dasm/test/bucket-key.json new file mode 100644 index 0000000000000..7247403220870 --- /dev/null +++ b/packages/cdk-dasm/test/bucket-key.json @@ -0,0 +1,85 @@ +{ + "Resources": { + "MyBucketKeyC17130CF": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": [ + "kms:Create*", + "kms:Describe*", + "kms:Enable*", + "kms:List*", + "kms:Put*", + "kms:Update*", + "kms:Revoke*", + "kms:Disable*", + "kms:Get*", + "kms:Delete*", + "kms:ScheduleKeyDeletion", + "kms:CancelKeyDeletion" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "Description": "Created by Pg12321Stack/MyBucket" + }, + "DeletionPolicy": "Retain", + "Metadata": { + "aws:cdk:path": "Pg12321Stack/MyBucket/Key/Resource" + } + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "MyBucketKeyC17130CF", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + } + }, + "DeletionPolicy": "Retain", + "Metadata": { + "aws:cdk:path": "Pg12321Stack/MyBucket/Resource" + } + }, + "CDKMetadata": { + "Type": "AWS::CDK::Metadata", + "Properties": { + "Modules": "aws-cdk=0.28.0,@aws-cdk/aws-events=0.28.0,@aws-cdk/aws-iam=0.28.0,@aws-cdk/aws-kms=0.28.0,@aws-cdk/aws-s3=0.28.0,@aws-cdk/aws-s3-notifications=0.28.0,@aws-cdk/cdk=0.28.0,@aws-cdk/cx-api=0.28.0,@aws-cdk/region-info=0.28.0,jsii-runtime=node.js/v10.13.0" + } + } + } +} \ No newline at end of file diff --git a/packages/cdk-dasm/test/dasm.test.ts b/packages/cdk-dasm/test/dasm.test.ts new file mode 100644 index 0000000000000..2255740651399 --- /dev/null +++ b/packages/cdk-dasm/test/dasm.test.ts @@ -0,0 +1,32 @@ +import { dasmTypeScript } from '../lib'; + +test('basic test', async () => { + expect(await dasm({ + Resources: { + MyTopic: { + Type: 'AWS::SNS::Topic', + Properties: { + DisplayName: 'hello hello' + } + } + } + })).toMatchSnapshot(); +}); + +test('no props', async () => { + expect(await dasm({ + Resources: { + Boom: { + Type: 'AWS::S3::Bucket' + } + } + })).toMatchSnapshot(); +}); + +test('bucket-and-key', async () => { + expect(await dasm(require('./bucket-key.json'))).toMatchSnapshot(); +}); + +async function dasm(template: any) { + return dasmTypeScript(template, { timestamp: false }); +} \ No newline at end of file diff --git a/packages/cdk-dasm/tsconfig.json b/packages/cdk-dasm/tsconfig.json new file mode 100644 index 0000000000000..8319ad9617cd6 --- /dev/null +++ b/packages/cdk-dasm/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2018", + "lib": ["es2016", "es2017.object", "es2017.string"], + "module": "commonjs", + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "inlineSourceMap": true, + "inlineSources": true, + "noEmitOnError": false + }, + "exclude": [ + "test/enrichments/**" + ] +} diff --git a/packages/cdk/.gitignore b/packages/cdk/.gitignore new file mode 100644 index 0000000000000..46cc09051f6b6 --- /dev/null +++ b/packages/cdk/.gitignore @@ -0,0 +1,5 @@ + +dist +.LAST_PACKAGE +.LAST_BUILD +*.snk diff --git a/packages/cdk/.npmignore b/packages/cdk/.npmignore new file mode 100644 index 0000000000000..bc9fd0e49f9a1 --- /dev/null +++ b/packages/cdk/.npmignore @@ -0,0 +1,5 @@ + +dist +.LAST_PACKAGE +.LAST_BUILD +*.snk \ No newline at end of file diff --git a/packages/cdk/LICENSE b/packages/cdk/LICENSE new file mode 100644 index 0000000000000..46c185646b439 --- /dev/null +++ b/packages/cdk/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-2019 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/cdk/NOTICE b/packages/cdk/NOTICE new file mode 100644 index 0000000000000..8585168af8b7d --- /dev/null +++ b/packages/cdk/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/cdk/README.md b/packages/cdk/README.md new file mode 100644 index 0000000000000..660d903102aa2 --- /dev/null +++ b/packages/cdk/README.md @@ -0,0 +1,2 @@ +## AWS CDK Toolkit +This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. \ No newline at end of file diff --git a/packages/cdk/bin/cdk b/packages/cdk/bin/cdk new file mode 100644 index 0000000000000..8d88043cbe7ac --- /dev/null +++ b/packages/cdk/bin/cdk @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('aws-cdk/bin/cdk'); diff --git a/packages/cdk/package.json b/packages/cdk/package.json new file mode 100644 index 0000000000000..23ac2f393f39a --- /dev/null +++ b/packages/cdk/package.json @@ -0,0 +1,38 @@ +{ + "name": "cdk", + "version": "0.28.0", + "description": "AWS CDK Toolkit", + "main": " bin/cdk", + "bin": { + "cdk": "bin/cdk" + }, + "dependencies": { + "aws-cdk": "^0.28.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git" + }, + "keywords": [ + "aws", + "cdk", + "toolkit" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/awslabs/aws-cdk/issues" + }, + "homepage": "https://github.com/awslabs/aws-cdk", + "scripts": { + "build": "echo Nothing to build", + "package": "mkdir -p dist/js && cd dist/js && npm pack ../../" + }, + "engines": { + "node": ">= 8.10.0" + } +} diff --git a/packages/decdk/deps.js b/packages/decdk/deps.js index f1265264abe82..07d94a72971d2 100644 --- a/packages/decdk/deps.js +++ b/packages/decdk/deps.js @@ -6,7 +6,7 @@ const fs = require('fs'); const path = require('path'); const pkg = require('./package.json'); -const deps = pkg.dependencies || {}; +const deps = pkg.dependencies || (pkg.dependencies = {}); const root = path.resolve('..', '..', 'packages', '@aws-cdk'); const modules = fs.readdirSync(root); @@ -18,8 +18,17 @@ for (const dir of modules) { continue; } const meta = require(path.join(module, 'package.json')); - const exists = deps[meta.name]; + + if (meta.deprecated) { + if (exists) { + console.error(`spurious dependency on deprecated: ${meta.name}`); + errors = true; + } + delete deps[meta.name]; + continue; + } + if (!exists) { console.error(`missing dependency: ${meta.name}`); errors = true; @@ -40,4 +49,4 @@ fs.writeFileSync(path.join(__dirname, 'package.json'), JSON.stringify(pkg, undef if (errors) { console.error('errors found. updated package.json'); process.exit(1); -} \ No newline at end of file +} diff --git a/packages/decdk/examples/pipeline.json b/packages/decdk/examples/pipeline.json index 02c348df3a56e..2d7dfce94d0c6 100644 --- a/packages/decdk/examples/pipeline.json +++ b/packages/decdk/examples/pipeline.json @@ -24,7 +24,7 @@ "name": "Source", "actions": [ { - "@aws-cdk/aws-codecommit.PipelineSourceAction": { + "@aws-cdk/aws-codepipeline-actions.CodeCommitSourceAction": { "repository": { "Ref": "Repo" }, "actionName": "Source" } @@ -35,11 +35,11 @@ "name": "Build", "actions": [ { - "@aws-cdk/aws-codebuild.PipelineBuildAction": { + "@aws-cdk/aws-codepipeline-actions.CodeBuildBuildAction": { "actionName": "Build", "project": { "Ref": "BuildProject" }, "inputArtifact": { - "@aws-cdk/aws-codepipeline-api.Artifact": { + "@aws-cdk/aws-codepipeline.Artifact": { "artifactName": "Source" } } @@ -51,4 +51,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/decdk/package-lock.json b/packages/decdk/package-lock.json index 683752c1ff76b..cdb7328c8fbb4 100644 --- a/packages/decdk/package-lock.json +++ b/packages/decdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "decdk", - "version": "0.25.3", + "version": "0.27.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2797,21 +2797,29 @@ } }, "jsii-reflect": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/jsii-reflect/-/jsii-reflect-0.7.15.tgz", - "integrity": "sha512-ZY/1utHln83ly1UscerO93O+O4b5ZFaMlZDO1S+4wvhYs2yLxjLMhgzToHXdLiy1ja9hXqFpSBVDpUKB2sC9rA==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/jsii-reflect/-/jsii-reflect-0.8.0.tgz", + "integrity": "sha512-rK6syj+Usm+rFzVSBM3FCT7ThzYUWck1YWyI+eDV1ffOjnh7rSG4NvUK3VcA+/IZ+DmJ/sPGnnTJ9w9XjZ7/2g==", "requires": { "colors": "^1.3.3", "fs-extra": "^7.0.1", - "jsii-spec": "^0.7.15", - "oo-ascii-tree": "^0.7.15", + "jsii-spec": "^0.8.0", + "oo-ascii-tree": "^0.8.0", "yargs": "^13.2.1" }, "dependencies": { "get-caller-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.1.tgz", - "integrity": "sha512-SpOZHfz845AH0wJYVuZk2jWDqFmu7Xubsx+ldIpwzy5pDUpu7OJHK7QYNSA2NPlDSKQwM1GFaAkciOWjjW92Sg==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "jsii-spec": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/jsii-spec/-/jsii-spec-0.8.0.tgz", + "integrity": "sha512-vkPtZKE36nwKsJ9MfVmmy4tjRJ5rb/dVemCYGkmJ5+xX88BzxZCfHfhdbB3rEAMQPt5KKXc9aV/OrF1m6pc5HQ==", + "requires": { + "jsonschema": "^1.2.4" + } }, "require-main-filename": { "version": "2.0.0", @@ -2819,9 +2827,9 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" }, "yargs": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.1.tgz", - "integrity": "sha512-HgY0xHGmPPakg6kEDufqxZuXVtvPZcipORC8O7S44iEnwsUmP+qnhReHc6d1dyeIZkrPmYFblh45Z2oeDn++fQ==", + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", "requires": { "cliui": "^4.0.0", "find-up": "^3.0.0", @@ -2851,6 +2859,7 @@ "version": "0.7.15", "resolved": "https://registry.npmjs.org/jsii-spec/-/jsii-spec-0.7.15.tgz", "integrity": "sha512-OytA5XH4EvhJXTEac9h8ugqEosg9GZcqsIYQEzIi6zD44gjjIIPSbfyL8X70Z1dR3hlVNz7NdbTrxI88+X+h/w==", + "dev": true, "requires": { "jsonschema": "^1.2.4" } @@ -3356,9 +3365,9 @@ } }, "oo-ascii-tree": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-0.7.15.tgz", - "integrity": "sha512-fMGG/Ns+kMO9def/XmzhH2M7X4Z0/BFJBTjo36puDjG6tieLIdgu+38yvTS3/PCV/JUr0v37ZrXOh/Hu/yX2Jg==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/oo-ascii-tree/-/oo-ascii-tree-0.8.0.tgz", + "integrity": "sha512-NAE24C9mESzpHJsT92kA9+cdBD0FvPO2XNKxPonn2iZ2kPlcQ0V0N+Vytrfv5RNg3iVgV0ep7Ac0RSy26w++Ag==" }, "optimist": { "version": "0.6.1", @@ -4256,26 +4265,26 @@ } }, "string-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.0.0.tgz", - "integrity": "sha512-rr8CUxBbvOZDUvc5lNIJ+OC1nPVpz+Siw9VBtUjB9b6jZehZLFt0JMCZzShFHIsI8cbhm0EsNIfWJMFV3cu3Ew==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.0.0" + "strip-ansi": "^5.1.0" }, "dependencies": { "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, "strip-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", - "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { - "ansi-regex": "^4.0.0" + "ansi-regex": "^4.1.0" } } } diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 786fafc7abe61..9a039da126ca4 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -1,6 +1,6 @@ { "name": "decdk", - "version": "0.26.0", + "version": "0.28.0", "description": "Declarative CDK: a CloudFormation-like syntax for defining CDK stacks", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -25,106 +25,109 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-cdk/alexa-ask": "^0.26.0", - "@aws-cdk/app-delivery": "^0.26.0", - "@aws-cdk/assets": "^0.26.0", - "@aws-cdk/assets-docker": "^0.26.0", - "@aws-cdk/aws-amazonmq": "^0.26.0", - "@aws-cdk/aws-apigateway": "^0.26.0", - "@aws-cdk/aws-applicationautoscaling": "^0.26.0", - "@aws-cdk/aws-appstream": "^0.26.0", - "@aws-cdk/aws-appsync": "^0.26.0", - "@aws-cdk/aws-athena": "^0.26.0", - "@aws-cdk/aws-autoscaling": "^0.26.0", - "@aws-cdk/aws-autoscaling-api": "^0.26.0", - "@aws-cdk/aws-autoscaling-common": "^0.26.0", - "@aws-cdk/aws-autoscalingplans": "^0.26.0", - "@aws-cdk/aws-batch": "^0.26.0", - "@aws-cdk/aws-budgets": "^0.26.0", - "@aws-cdk/aws-certificatemanager": "^0.26.0", - "@aws-cdk/aws-cloud9": "^0.26.0", - "@aws-cdk/aws-cloudformation": "^0.26.0", - "@aws-cdk/aws-cloudfront": "^0.26.0", - "@aws-cdk/aws-cloudtrail": "^0.26.0", - "@aws-cdk/aws-cloudwatch": "^0.26.0", - "@aws-cdk/aws-codebuild": "^0.26.0", - "@aws-cdk/aws-codecommit": "^0.26.0", - "@aws-cdk/aws-codedeploy": "^0.26.0", - "@aws-cdk/aws-codedeploy-api": "^0.26.0", - "@aws-cdk/aws-codepipeline": "^0.26.0", - "@aws-cdk/aws-codepipeline-api": "^0.26.0", - "@aws-cdk/aws-cognito": "^0.26.0", - "@aws-cdk/aws-config": "^0.26.0", - "@aws-cdk/aws-datapipeline": "^0.26.0", - "@aws-cdk/aws-dax": "^0.26.0", - "@aws-cdk/aws-directoryservice": "^0.26.0", - "@aws-cdk/aws-dlm": "^0.26.0", - "@aws-cdk/aws-dms": "^0.26.0", - "@aws-cdk/aws-docdb": "^0.26.0", - "@aws-cdk/aws-dynamodb": "^0.26.0", - "@aws-cdk/aws-ec2": "^0.26.0", - "@aws-cdk/aws-ecr": "^0.26.0", - "@aws-cdk/aws-ecs": "^0.26.0", - "@aws-cdk/aws-efs": "^0.26.0", - "@aws-cdk/aws-eks": "^0.26.0", - "@aws-cdk/aws-elasticache": "^0.26.0", - "@aws-cdk/aws-elasticbeanstalk": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancing": "^0.26.0", - "@aws-cdk/aws-elasticloadbalancingv2": "^0.26.0", - "@aws-cdk/aws-elasticsearch": "^0.26.0", - "@aws-cdk/aws-emr": "^0.26.0", - "@aws-cdk/aws-events": "^0.26.0", - "@aws-cdk/aws-fsx": "^0.26.0", - "@aws-cdk/aws-gamelift": "^0.26.0", - "@aws-cdk/aws-glue": "^0.26.0", - "@aws-cdk/aws-greengrass": "^0.26.0", - "@aws-cdk/aws-guardduty": "^0.26.0", - "@aws-cdk/aws-iam": "^0.26.0", - "@aws-cdk/aws-inspector": "^0.26.0", - "@aws-cdk/aws-iot": "^0.26.0", - "@aws-cdk/aws-iot1click": "^0.26.0", - "@aws-cdk/aws-iotanalytics": "^0.26.0", - "@aws-cdk/aws-kinesis": "^0.26.0", - "@aws-cdk/aws-kinesisanalytics": "^0.26.0", - "@aws-cdk/aws-kinesisfirehose": "^0.26.0", - "@aws-cdk/aws-kms": "^0.26.0", - "@aws-cdk/aws-lambda": "^0.26.0", - "@aws-cdk/aws-lambda-event-sources": "^0.26.0", - "@aws-cdk/aws-logs": "^0.26.0", - "@aws-cdk/aws-neptune": "^0.26.0", - "@aws-cdk/aws-opsworks": "^0.26.0", - "@aws-cdk/aws-opsworkscm": "^0.26.0", - "@aws-cdk/aws-quickstarts": "^0.26.0", - "@aws-cdk/aws-ram": "^0.26.0", - "@aws-cdk/aws-rds": "^0.26.0", - "@aws-cdk/aws-redshift": "^0.26.0", - "@aws-cdk/aws-robomaker": "^0.26.0", - "@aws-cdk/aws-route53": "^0.26.0", - "@aws-cdk/aws-route53resolver": "^0.26.0", - "@aws-cdk/aws-s3": "^0.26.0", - "@aws-cdk/aws-s3-deployment": "^0.26.0", - "@aws-cdk/aws-s3-notifications": "^0.26.0", - "@aws-cdk/aws-sagemaker": "^0.26.0", - "@aws-cdk/aws-sdb": "^0.26.0", - "@aws-cdk/aws-secretsmanager": "^0.26.0", - "@aws-cdk/aws-serverless": "^0.26.0", - "@aws-cdk/aws-servicecatalog": "^0.26.0", - "@aws-cdk/aws-servicediscovery": "^0.26.0", - "@aws-cdk/aws-ses": "^0.26.0", - "@aws-cdk/aws-sns": "^0.26.0", - "@aws-cdk/aws-sqs": "^0.26.0", - "@aws-cdk/aws-ssm": "^0.26.0", - "@aws-cdk/aws-stepfunctions": "^0.26.0", - "@aws-cdk/aws-waf": "^0.26.0", - "@aws-cdk/aws-wafregional": "^0.26.0", - "@aws-cdk/aws-workspaces": "^0.26.0", - "@aws-cdk/cdk": "^0.26.0", - "@aws-cdk/cfnspec": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0", - "@aws-cdk/region-info": "^0.26.0", - "@aws-cdk/runtime-values": "^0.26.0", + "@aws-cdk/alexa-ask": "^0.28.0", + "@aws-cdk/app-delivery": "^0.28.0", + "@aws-cdk/assets": "^0.28.0", + "@aws-cdk/assets-docker": "^0.28.0", + "@aws-cdk/aws-amazonmq": "^0.28.0", + "@aws-cdk/aws-apigateway": "^0.28.0", + "@aws-cdk/aws-applicationautoscaling": "^0.28.0", + "@aws-cdk/aws-appmesh": "^0.28.0", + "@aws-cdk/aws-appstream": "^0.28.0", + "@aws-cdk/aws-appsync": "^0.28.0", + "@aws-cdk/aws-athena": "^0.28.0", + "@aws-cdk/aws-autoscaling": "^0.28.0", + "@aws-cdk/aws-autoscaling-api": "^0.28.0", + "@aws-cdk/aws-autoscaling-common": "^0.28.0", + "@aws-cdk/aws-autoscalingplans": "^0.28.0", + "@aws-cdk/aws-batch": "^0.28.0", + "@aws-cdk/aws-budgets": "^0.28.0", + "@aws-cdk/aws-certificatemanager": "^0.28.0", + "@aws-cdk/aws-cloud9": "^0.28.0", + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-cloudfront": "^0.28.0", + "@aws-cdk/aws-cloudtrail": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-codebuild": "^0.28.0", + "@aws-cdk/aws-codecommit": "^0.28.0", + "@aws-cdk/aws-codedeploy": "^0.28.0", + "@aws-cdk/aws-codedeploy-api": "^0.28.0", + "@aws-cdk/aws-codepipeline": "^0.28.0", + "@aws-cdk/aws-codepipeline-actions": "^0.28.0", + "@aws-cdk/aws-codepipeline-api": "^0.28.0", + "@aws-cdk/aws-cognito": "^0.28.0", + "@aws-cdk/aws-config": "^0.28.0", + "@aws-cdk/aws-datapipeline": "^0.28.0", + "@aws-cdk/aws-dax": "^0.28.0", + "@aws-cdk/aws-directoryservice": "^0.28.0", + "@aws-cdk/aws-dlm": "^0.28.0", + "@aws-cdk/aws-dms": "^0.28.0", + "@aws-cdk/aws-docdb": "^0.28.0", + "@aws-cdk/aws-dynamodb": "^0.28.0", + "@aws-cdk/aws-dynamodb-global": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-ecr": "^0.28.0", + "@aws-cdk/aws-ecs": "^0.28.0", + "@aws-cdk/aws-efs": "^0.28.0", + "@aws-cdk/aws-eks": "^0.28.0", + "@aws-cdk/aws-elasticache": "^0.28.0", + "@aws-cdk/aws-elasticbeanstalk": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancing": "^0.28.0", + "@aws-cdk/aws-elasticloadbalancingv2": "^0.28.0", + "@aws-cdk/aws-elasticsearch": "^0.28.0", + "@aws-cdk/aws-emr": "^0.28.0", + "@aws-cdk/aws-events": "^0.28.0", + "@aws-cdk/aws-fsx": "^0.28.0", + "@aws-cdk/aws-gamelift": "^0.28.0", + "@aws-cdk/aws-glue": "^0.28.0", + "@aws-cdk/aws-greengrass": "^0.28.0", + "@aws-cdk/aws-guardduty": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-inspector": "^0.28.0", + "@aws-cdk/aws-iot": "^0.28.0", + "@aws-cdk/aws-iot1click": "^0.28.0", + "@aws-cdk/aws-iotanalytics": "^0.28.0", + "@aws-cdk/aws-kinesis": "^0.28.0", + "@aws-cdk/aws-kinesisanalytics": "^0.28.0", + "@aws-cdk/aws-kinesisfirehose": "^0.28.0", + "@aws-cdk/aws-kms": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-lambda-event-sources": "^0.28.0", + "@aws-cdk/aws-logs": "^0.28.0", + "@aws-cdk/aws-neptune": "^0.28.0", + "@aws-cdk/aws-opsworks": "^0.28.0", + "@aws-cdk/aws-opsworkscm": "^0.28.0", + "@aws-cdk/aws-quickstarts": "^0.28.0", + "@aws-cdk/aws-ram": "^0.28.0", + "@aws-cdk/aws-rds": "^0.28.0", + "@aws-cdk/aws-redshift": "^0.28.0", + "@aws-cdk/aws-robomaker": "^0.28.0", + "@aws-cdk/aws-route53": "^0.28.0", + "@aws-cdk/aws-route53resolver": "^0.28.0", + "@aws-cdk/aws-s3": "^0.28.0", + "@aws-cdk/aws-s3-deployment": "^0.28.0", + "@aws-cdk/aws-s3-notifications": "^0.28.0", + "@aws-cdk/aws-sagemaker": "^0.28.0", + "@aws-cdk/aws-sam": "^0.28.0", + "@aws-cdk/aws-sdb": "^0.28.0", + "@aws-cdk/aws-secretsmanager": "^0.28.0", + "@aws-cdk/aws-servicecatalog": "^0.28.0", + "@aws-cdk/aws-servicediscovery": "^0.28.0", + "@aws-cdk/aws-ses": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0", + "@aws-cdk/aws-ssm": "^0.28.0", + "@aws-cdk/aws-stepfunctions": "^0.28.0", + "@aws-cdk/aws-waf": "^0.28.0", + "@aws-cdk/aws-wafregional": "^0.28.0", + "@aws-cdk/aws-workspaces": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/cfnspec": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0", + "@aws-cdk/region-info": "^0.28.0", + "@aws-cdk/runtime-values": "^0.28.0", "fs-extra": "^7.0.1", - "jsii-reflect": "^0.7.15", + "jsii-reflect": "^0.8.0", "jsonschema": "^1.2.4", "yaml": "1.3.2", "yargs": "^13.1.0" diff --git a/packages/decdk/test/__snapshots__/synth.test.js.snap b/packages/decdk/test/__snapshots__/synth.test.js.snap index e333c1430fc61..ec8cec0b9419f 100644 --- a/packages/decdk/test/__snapshots__/synth.test.js.snap +++ b/packages/decdk/test/__snapshots__/synth.test.js.snap @@ -142,7 +142,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloLambda3D9C82D6", + "Fn::GetAtt": Array [ + "HelloLambda3D9C82D6", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -180,7 +183,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloLambda3D9C82D6", + "Fn::GetAtt": Array [ + "HelloLambda3D9C82D6", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -218,7 +224,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloLambda3D9C82D6", + "Fn::GetAtt": Array [ + "HelloLambda3D9C82D6", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -256,7 +265,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloLambda3D9C82D6", + "Fn::GetAtt": Array [ + "HelloLambda3D9C82D6", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -290,7 +302,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloLambda3D9C82D6", + "Fn::GetAtt": Array [ + "HelloLambda3D9C82D6", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -324,7 +339,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloLambda3D9C82D6", + "Fn::GetAtt": Array [ + "HelloLambda3D9C82D6", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -688,6 +706,7 @@ Object { ], }, }, + "ServiceRegistries": Array [], "TaskDefinition": Object { "Ref": "MyTaskDef01F0D39B", }, @@ -1132,7 +1151,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloWorldFunctionB2AB6E79", + "Fn::GetAtt": Array [ + "HelloWorldFunctionB2AB6E79", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -1170,7 +1192,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloWorldFunctionB2AB6E79", + "Fn::GetAtt": Array [ + "HelloWorldFunctionB2AB6E79", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -1208,7 +1233,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloWorldFunctionB2AB6E79", + "Fn::GetAtt": Array [ + "HelloWorldFunctionB2AB6E79", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -1242,7 +1270,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloWorldFunctionB2AB6E79", + "Fn::GetAtt": Array [ + "HelloWorldFunctionB2AB6E79", + "Arn", + ], }, "Principal": "apigateway.amazonaws.com", "SourceArn": Object { @@ -1352,7 +1383,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "HelloWorldFunctionB2AB6E79", + "Fn::GetAtt": Array [ + "HelloWorldFunctionB2AB6E79", + "Arn", + ], }, "Principal": "sns.amazonaws.com", "SourceArn": Object { @@ -1595,7 +1629,10 @@ Object { "Properties": Object { "Action": "lambda:InvokeFunction", "FunctionName": Object { - "Ref": "LambdaD247545B", + "Fn::GetAtt": Array [ + "LambdaD247545B", + "Arn", + ], }, "Principal": "sns.amazonaws.com", "SourceArn": Object { diff --git a/packages/decdk/test/schema.test.ts b/packages/decdk/test/schema.test.ts index 3ac79cdeb4252..50eefb6a40f26 100644 --- a/packages/decdk/test/schema.test.ts +++ b/packages/decdk/test/schema.test.ts @@ -9,6 +9,9 @@ const fixturedir = path.join(__dirname, 'fixture'); // tslint:disable:no-console +// JSII often does not complete in the default 5 second Jest timeout +jest.setTimeout(10_000); + let typesys: reflect.TypeSystem; beforeAll(async () => { @@ -21,7 +24,7 @@ beforeAll(async () => { await typesys.load(path.dirname(require.resolve('@aws-cdk/cdk/.jsii'))); }); -test('schemaForInterface: interface with primitives', () => { +test('schemaForInterface: interface with primitives', async () => { // GIVEN const defs = { }; const ctx = SchemaContext.root(defs); diff --git a/packages/simple-resource-bundler/package-lock.json b/packages/simple-resource-bundler/package-lock.json index 7167892a01c0d..045577385fa86 100644 --- a/packages/simple-resource-bundler/package-lock.json +++ b/packages/simple-resource-bundler/package-lock.json @@ -1,6 +1,6 @@ { "name": "simple-resource-bundler", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/simple-resource-bundler/package.json b/packages/simple-resource-bundler/package.json index e1eb3934a1b22..12b3444554273 100644 --- a/packages/simple-resource-bundler/package.json +++ b/packages/simple-resource-bundler/package.json @@ -1,6 +1,6 @@ { "name": "simple-resource-bundler", - "version": "0.26.0", + "version": "0.28.0", "description": "Command-line tool to embed resources into JS libraries", "main": "bundler.js", "types": "bundler.d.ts", @@ -24,8 +24,8 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { "fs-extra": "^7.0.0", @@ -34,7 +34,8 @@ }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", - "type": "git" + "type": "git", + "directory": "packages/simple-resource-bundler" }, "keywords": [ "aws", diff --git a/scripts/foreach.sh b/scripts/foreach.sh index e8e0cede9ca24..47072ad0b3edf 100755 --- a/scripts/foreach.sh +++ b/scripts/foreach.sh @@ -19,9 +19,11 @@ # # -------------------------------------------------------------------------------------------------- set -euo pipefail +scriptdir=$(cd $(dirname $0) && pwd) statefile="${HOME}/.foreach.state" commandfile="${HOME}/.foreach.command" command_arg="${@:-}" +base=$PWD function heading { printf "\e[38;5;81m$@\e[0m\n" @@ -35,11 +37,18 @@ function success { printf "\e[32;5;81m$@\e[0m\n" } +if [[ "${1:-}" == "--reset" ]]; then + rm -f ~/.foreach.* + success "state cleared. you are free to start a new command." + exit 0 +fi + + if [ -f "${statefile}" ] && [ -f "${commandfile}" ]; then command="$(cat ${commandfile})" if [ ! -z "${command_arg}" ] && [ "${command}" != "${command_arg}" ]; then error "error: there is still an active session for: \"${command}\". to reset:" - error " rm -f ~/.foreach.*" + error " $0 --reset" exit 1 fi fi @@ -59,7 +68,7 @@ fi next="$(head -n1 ${statefile})" if [ -z "${next}" ]; then success "done (queue is empty). to reset:" - success " rm -f ~/.foreach.*" + success " $0 --reset" exit 0 fi @@ -70,9 +79,11 @@ heading "${next}: ${command} (${remaining} remaining)" ( cd ${next} - ${command} || { - error "error: last command failed. fix problem and resume by executing:" - error " $0" + ${command} &> /tmp/foreach.stdio || { + cd ${base} + cat /tmp/foreach.stdio | ${scriptdir}/path-prefix ${next} + error "error: last command failed. fix problem and resume by executing: $0" + error "directory: ${next}" exit 1 } ) diff --git a/scripts/path-prefix b/scripts/path-prefix new file mode 100755 index 0000000000000..42749114f8b01 --- /dev/null +++ b/scripts/path-prefix @@ -0,0 +1,38 @@ +#!/usr/bin/env node +// converts relative file paths at the beginning of each input line to absolute file paths +const path = require('path'); +const fs = require('fs'); +const rl = require('readline'); + +const REMOVE_COLORS = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; + +const dir = process.argv[2]; +if (!dir) { + throw new Error(`usage: path-prefix DIR`); +} + +const reldir = path.relative(process.cwd(), dir); + +const ifc = rl.createInterface(process.stdin); +ifc.on('line', line => { + line = line.toString(); + const [ relative, ...rest ] = line.split(':'); + const rel = relative.replace(REMOVE_COLORS, ''); + const absolute = path.join(dir, rel); + if (relative && fs.existsSync(absolute)) { + process.stdout.write(path.join(reldir, rel) + ':' + rest.join(':') + '\n'); + } else { + process.stdout.write(line + '\n'); + } +}); + +process.stdin.resume(); + +function exists(p) { + try { + fs.readFileSync(p); + return true; + } catch (e) { + return false; + } +} \ No newline at end of file diff --git a/tools/awslint/lib/linter.ts b/tools/awslint/lib/linter.ts index 7c5628c0bbed8..0e80795e3e1d2 100644 --- a/tools/awslint/lib/linter.ts +++ b/tools/awslint/lib/linter.ts @@ -1,4 +1,5 @@ import reflect = require('jsii-reflect'); +import { PrimitiveType } from 'jsii-spec'; export interface LinterOptions { /** @@ -115,10 +116,16 @@ export class Evaluation { return this.assert(actual === expected, scope, ` (expected="${expected}",actual="${actual}")`); } + public assertTypesEqual(ts: reflect.TypeSystem, actual: TypeSpecifier, expected: TypeSpecifier, scope: string) { + const a = typeReferenceFrom(ts, actual); + const e = typeReferenceFrom(ts, expected); + return this.assert(a.toString() === e.toString(), scope, ` (expected="${e}",actual="${a}")`); + } + public assertSignature(method: reflect.Method, expectations: MethodSignatureExpectations) { const scope = method.parentType.fqn + '.' + method.name; if (expectations.returns) { - this.assertEquals(expectations.returns.toString(), expectations.returns, scope); + this.assertTypesEqual(method.system, method.returns, expectations.returns, scope); } if (expectations.parameters) { @@ -135,18 +142,7 @@ export class Evaluation { this.assertEquals(actualName, expectedName, pscope); } if (expect.type) { - const expectedType = expect.type; - const actualType = (() => { - if (actual.type.fqn) { - return actual.type.fqn.fqn; - } - if (actual.type.primitive) { - return actual.type.primitive; - } - return actual.type.toString(); - })(); - - this.assertEquals(actualType, expectedType, pscope); + this.assertTypesEqual(method.system, actual.type, expect.type, pscope); } } } @@ -205,9 +201,19 @@ export interface Rule { eval(linter: Evaluation): void; } +/** + * A type constraint + * + * Be super flexible about how types can be represented. Ultimately, we will + * compare what you give to a TypeReference, because that's what's in the JSII + * Reflect model. However, if you already have a real Type, or just a string to + * a user-defined type, that's fine too. We'll Do The Right Thing. + */ +export type TypeSpecifier = reflect.TypeReference | reflect.Type | string; + export interface MethodSignatureParameterExpectation { name?: string; - type?: string; + type?: TypeSpecifier; /** should this param be optional? */ optional?: boolean; @@ -215,7 +221,7 @@ export interface MethodSignatureParameterExpectation { export interface MethodSignatureExpectations { parameters?: MethodSignatureParameterExpectation[]; - returns?: string; + returns?: TypeSpecifier; } export enum DiagnosticLevel { @@ -232,3 +238,24 @@ export interface Diagnostic { scope: string; message: string; } + +/** + * Convert a type specifier to a TypeReference + */ +function typeReferenceFrom(ts: reflect.TypeSystem, x: TypeSpecifier): reflect.TypeReference { + if (isTypeReference(x)) { return x; } + + if (typeof x === 'string') { + if (x.indexOf('.') === -1) { + return new reflect.TypeReference(ts, { primitive: x as PrimitiveType }); + } else { + return new reflect.TypeReference(ts, { fqn: x }); + } + } + + return new reflect.TypeReference(ts, x); +} + +function isTypeReference(x: any): x is reflect.TypeReference { + return x instanceof reflect.TypeReference; +} \ No newline at end of file diff --git a/tools/awslint/lib/rules/module.ts b/tools/awslint/lib/rules/module.ts index ae4ac8db9d315..c733e8749c4f7 100644 --- a/tools/awslint/lib/rules/module.ts +++ b/tools/awslint/lib/rules/module.ts @@ -25,7 +25,17 @@ moduleLinter.add( { eval: e => { if (!e.ctx.namespace) { return; } if (!e.ctx.assembly) { return; } - const namespace = e.ctx.namespace.toLocaleLowerCase().replace('::', '-'); + const namespace = overrideNamespace(e.ctx.namespace.toLocaleLowerCase().replace('::', '-')); e.assertEquals(e.ctx.assembly.name, `@aws-cdk/${namespace}`, e.ctx.assembly.name); } }); + +/** + * Overrides special-case namespaces like aws-serverless=>aws-sam + */ +function overrideNamespace(namespace: string) { + if (namespace === 'aws-serverless') { + return 'aws-sam'; + } + return namespace; +} diff --git a/tools/awslint/lib/rules/resource.ts b/tools/awslint/lib/rules/resource.ts index 8c1f391fd2ffb..bc59f7efaad3c 100644 --- a/tools/awslint/lib/rules/resource.ts +++ b/tools/awslint/lib/rules/resource.ts @@ -11,6 +11,8 @@ export const resourceLinter = new Linter(assembly => { })); }); +const GRANT_RESULT_FQN = '@aws-cdk/aws-iam.Grant'; + interface ResourceLinterContext { readonly ts: reflect.TypeSystem; readonly resource: CfnResourceSpec; @@ -97,10 +99,10 @@ resourceLinter.add({ resourceLinter.add({ code: 'resource-interface-extends-construct', - message: 'resource interface must extends cdk.IConstruct', + message: 'resource interface must extend cdk.IConstruct', eval: e => { if (!e.ctx.resourceInterface) { return; } - e.assert(e.ctx.resourceInterface.interfaces.some(i => i.fqn === CONSTRUCT_INTERFACE_FQN), e.ctx.resourceInterface.fqn); + e.assert(e.ctx.resourceInterface.getInterfaces(true).some(i => i.fqn === CONSTRUCT_INTERFACE_FQN), e.ctx.resourceInterface.fqn); } }); @@ -149,7 +151,7 @@ resourceLinter.add({ } e.assertSignature(importMethod, { - returns: e.ctx.resourceInterface.fqn, + returns: e.ctx.resourceInterface, parameters: [ { name: 'scope', type: CONSTRUCT_FQN }, { name: 'id', type: 'string' }, @@ -173,8 +175,25 @@ resourceLinter.add({ if (!e.ctx.importPropsInterface) { return; } e.assertSignature(exportMethod, { - returns: e.ctx.importPropsInterface.fqn, + returns: e.ctx.importPropsInterface, parameters: [] }); } }); + +resourceLinter.add({ + code: 'grant-result', + message: `"grant" method must return ${GRANT_RESULT_FQN}`, + eval: e => { + if (!e.ctx.resourceClass) { return; } + + const grantResultType = e.ctx.ts.findFqn(GRANT_RESULT_FQN); + const grantMethods = e.ctx.resourceClass.getMethods(true).filter(m => m.name.startsWith('grant')); + + for (const grantMethod of grantMethods) { + e.assertSignature(grantMethod, { + returns: grantResultType + }); + } + } +}); \ No newline at end of file diff --git a/tools/awslint/lib/util.ts b/tools/awslint/lib/util.ts index 6302a19001770..01057d14e0644 100644 --- a/tools/awslint/lib/util.ts +++ b/tools/awslint/lib/util.ts @@ -12,4 +12,4 @@ export function isConstruct(c: reflect.ClassType) { const bases = c.getAncestors(); const root = bases[bases.length - 1]; return root === constructClass; -} +} \ No newline at end of file diff --git a/tools/awslint/package-lock.json b/tools/awslint/package-lock.json index 642379f50f68a..8f012af8ca82a 100644 --- a/tools/awslint/package-lock.json +++ b/tools/awslint/package-lock.json @@ -1,6 +1,6 @@ { "name": "awslint", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/awslint/package.json b/tools/awslint/package.json index 55ebfbbe8f29b..3fdbd6482e69a 100644 --- a/tools/awslint/package.json +++ b/tools/awslint/package.json @@ -1,7 +1,7 @@ { "name": "awslint", "private": true, - "version": "0.26.0", + "version": "0.28.0", "description": "Enforces the AWS Construct Library guidelines", "main": "index.js", "scripts": { diff --git a/tools/cdk-build-tools/lib/compile.ts b/tools/cdk-build-tools/lib/compile.ts index c58e7e75c579d..ef8a1bbed0ab6 100644 --- a/tools/cdk-build-tools/lib/compile.ts +++ b/tools/cdk-build-tools/lib/compile.ts @@ -7,7 +7,13 @@ import { Timers } from "./timer"; * Run the compiler on the current package */ export async function compileCurrentPackage(timers: Timers, compilers: CompilerOverrides = {}): Promise { - await shell(packageCompiler(compilers), timers); + const stdout = await shell(packageCompiler(compilers), timers); + + // WORKAROUND: jsii 0.8.2 does not exit with non-zero on compilation errors + // until this is released: https://github.com/awslabs/jsii/pull/442 + if (stdout.trim()) { + throw new Error(`Compilation failed`); + } // Find files in bin/ that look like they should be executable, and make them so. const scripts = currentPackageJson().bin || {}; diff --git a/tools/cdk-build-tools/package-lock.json b/tools/cdk-build-tools/package-lock.json index 389e899f292bf..e716f752a5427 100644 --- a/tools/cdk-build-tools/package-lock.json +++ b/tools/cdk-build-tools/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-build-tools", - "version": "0.25.3", + "version": "0.27.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -13,17 +13,17 @@ } }, "@babel/core": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.3.4.tgz", - "integrity": "sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.0.tgz", + "integrity": "sha512-Dzl7U0/T69DFOTwqz/FJdnOSWS57NpjNfCwMKHABr589Lg8uX1RrlBIJ7L5Dubt/xkLsx0xH5EBFzlBVes1ayA==", "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.3.4", - "@babel/helpers": "^7.2.0", - "@babel/parser": "^7.3.4", - "@babel/template": "^7.2.2", - "@babel/traverse": "^7.3.4", - "@babel/types": "^7.3.4", + "@babel/generator": "^7.4.0", + "@babel/helpers": "^7.4.0", + "@babel/parser": "^7.4.0", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.0", + "@babel/types": "^7.4.0", "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", @@ -54,11 +54,11 @@ } }, "@babel/generator": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.4.tgz", - "integrity": "sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", "requires": { - "@babel/types": "^7.3.4", + "@babel/types": "^7.4.0", "jsesc": "^2.5.1", "lodash": "^4.17.11", "source-map": "^0.5.0", @@ -96,21 +96,21 @@ "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" }, "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.4.0" } }, "@babel/helpers": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.3.1.tgz", - "integrity": "sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.2.tgz", + "integrity": "sha512-gQR1eQeroDzFBikhrCccm5Gs2xBjZ57DNjGbqTaHo911IpmSxflOQWMAHPw/TXk8L3isv7s9lYzUkexOeTQUYg==", "requires": { - "@babel/template": "^7.1.2", - "@babel/traverse": "^7.1.5", - "@babel/types": "^7.3.0" + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.0", + "@babel/types": "^7.4.0" } }, "@babel/highlight": { @@ -124,9 +124,9 @@ } }, "@babel/parser": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.4.tgz", - "integrity": "sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ==" + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.2.tgz", + "integrity": "sha512-9fJTDipQFvlfSVdD/JBtkiY0br9BtfvW2R8wo6CX/Ej2eMuV0gWPk1M67Mt3eggQvBqYW1FCEk8BN7WvGm/g5g==" }, "@babel/plugin-syntax-object-rest-spread": { "version": "7.2.0", @@ -137,26 +137,26 @@ } }, "@babel/template": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.2.2.tgz", - "integrity": "sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", + "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.2.2", - "@babel/types": "^7.2.2" + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0" } }, "@babel/traverse": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.3.4.tgz", - "integrity": "sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.0.tgz", + "integrity": "sha512-/DtIHKfyg2bBKnIN+BItaIlEg5pjAnzHOIQe5w+rHAw/rg9g0V7T4rqPX8BJPfW11kt3koyjAnTNwCzb28Y1PA==", "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.3.4", + "@babel/generator": "^7.4.0", "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.3.4", - "@babel/types": "^7.3.4", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.11" @@ -178,9 +178,9 @@ } }, "@babel/types": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.4.tgz", - "integrity": "sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", "requires": { "esutils": "^2.0.2", "lodash": "^4.17.11", @@ -409,9 +409,9 @@ "dev": true }, "@types/node": { - "version": "11.11.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.3.tgz", - "integrity": "sha512-wp6IOGu1lxsfnrD+5mX6qwSwWuqsdkKKxTN4aQc4wByHAKZJf9/D4KXPQ1POUjEbnCP5LMggB0OEFNY9OTsMqg==" + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.12.0.tgz", + "integrity": "sha512-Lg00egj78gM+4aE0Erw05cuDbvX9sLJbaaPwwRtdCdAMnIudqrQZ0oZX98Ek0yiSK/A2nubHgJfvII/rTT2Dwg==" }, "@types/stack-utils": { "version": "1.0.1", @@ -419,9 +419,9 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" }, "@types/yargs": { - "version": "12.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.9.tgz", - "integrity": "sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==" + "version": "12.0.10", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.10.tgz", + "integrity": "sha512-WsVzTPshvCSbHThUduGGxbmnwcpkgSctHGHTqzWyFg4lYAuV5qXlyFPOsP3OWqCINfmg/8VXP+zJaa4OxEsBQQ==" }, "abab": { "version": "2.0.0", @@ -973,23 +973,13 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "codemaker": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/codemaker/-/codemaker-0.7.15.tgz", - "integrity": "sha512-eIbUqMweJnXZWTn0pzPBEZRDX+Y91c8P6d//YXhcHAw2WLVwUHs/xRZLGZJwdLc/NpAkRKns5F2SgJWc4ghWCw==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/codemaker/-/codemaker-0.8.2.tgz", + "integrity": "sha512-agUY157vcCXFnJ9gDM8/iKbq62PWHkccnztbeuS1NZ8Pm9Z13Z9FHMuXWrh/zOQjggXpr5y3pPBpCHOlDYnHLw==", "requires": { - "camelcase": "^5.0.0", - "decamelize": "^2.0.0", + "camelcase": "^5.2.0", + "decamelize": "^1.2.0", "fs-extra": "^7.0.1" - }, - "dependencies": { - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", - "requires": { - "xregexp": "4.0.0" - } - } } }, "collection-visit": { @@ -1101,9 +1091,9 @@ "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==" }, "cssstyle": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.1.tgz", - "integrity": "sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", + "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", "requires": { "cssom": "0.3.x" } @@ -1345,9 +1335,9 @@ } }, "esm": { - "version": "3.2.18", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.18.tgz", - "integrity": "sha512-1UENjnnI37UDp7KuOqKYjfqdaMim06eBWnDv37smaxTIzDl0ZWnlgoXwsVwD9+Lidw+q/f1gUf2diVMDCycoVw==" + "version": "3.2.21", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.21.tgz", + "integrity": "sha512-DkNi6jYtrT/FdO8pPEH+GFtBE321T3nup8FWdgaYpzaf4tK6aVJiCfgxNotZjEbnZ32t2VEtqIfbjp5GBN65Nw==" }, "esprima": { "version": "4.0.1", @@ -1666,9 +1656,9 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "function-loop": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-1.0.1.tgz", - "integrity": "sha1-gHa7MF6OajzO7ikgdl8zDRkPNAw=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-1.0.2.tgz", + "integrity": "sha512-Iw4MzMfS3udk/rqxTiDDCllhGwlOrsr50zViTOO/W6lS/9y6B1J0BD2VZzrnWUYBJsl3aeqjgR5v7bWWhZSYbA==" }, "get-caller-file": { "version": "1.0.3", @@ -1730,11 +1720,11 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" }, "handlebars": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.0.tgz", - "integrity": "sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.1.tgz", + "integrity": "sha512-3Zhi6C0euYZL5sM0Zcy7lInLXKQ+YLcF/olbN010mzGQ4XVm50JeyBnMqofHh696GrciGruC7kCcApPDJvVgwA==", "requires": { - "async": "^2.5.0", + "neo-async": "^2.6.0", "optimist": "^0.6.1", "source-map": "^0.6.1", "uglify-js": "^3.1.4" @@ -2600,9 +2590,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.2.tgz", - "integrity": "sha512-QHn/Lh/7HhZ/Twc7vJYQTkjuCa0kaCcDcjK5Zlk2rvnUpy7DxMJ23+Jc2dcyvltwQVg1nygAVlB2oRDFHoRS5Q==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", + "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -2652,62 +2642,42 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "jsii": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/jsii/-/jsii-0.7.15.tgz", - "integrity": "sha512-J49txVIwIo+Re0HqP5v6LPnR5VSamg8ygjzUhDAMkpiGM5wk61+bexvndH+e1Q5o/EYkgQlLju3wJdI7bw7cmw==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/jsii/-/jsii-0.8.2.tgz", + "integrity": "sha512-VbRwBFWlmWDoiNoNreX96ii37LEwviFvfSyTY37tMZFXs8QSG5NIrR66JmNaF/ejbQpubqAKxW/Nb/YWCqhRbg==", "requires": { "case": "^1.6.1", "colors": "^1.3.3", "deep-equal": "^1.0.1", "fs-extra": "^7.0.1", - "jsii-spec": "^0.7.15", + "jsii-spec": "^0.8.2", "log4js": "^4.0.2", "semver": "^5.6.0", "sort-json": "^2.0.0", "spdx-license-list": "^5.0.0", "typescript": "^3.3.3333", - "yargs": "^12.0.5" - }, - "dependencies": { - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - } + "yargs": "^13.2.2" } }, "jsii-pacmak": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/jsii-pacmak/-/jsii-pacmak-0.7.15.tgz", - "integrity": "sha512-F8XDh3p7j+vUxiHvx2uJOr4D/gGCilpNwpu/70tdgdZTL3A2gAtL+R4+g/BqbXT73J+BAUrsBdTpawyoWvBluA==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/jsii-pacmak/-/jsii-pacmak-0.8.2.tgz", + "integrity": "sha512-guqjntPr9uVkGGR+4fAralIoHpPsdCLsQsDf7Kh3GCRjgLn20JeHBgv4vT6Wym0nqjqL/5fY4nsXI/9w/OVZ0A==", "requires": { "clone": "^2.1.2", - "codemaker": "^0.7.15", + "codemaker": "^0.8.2", + "escape-string-regexp": "^1.0.5", "fs-extra": "^7.0.1", - "jsii-spec": "^0.7.15", + "jsii-spec": "^0.8.2", "spdx-license-list": "^5.0.0", "xmlbuilder": "^11.0.0", - "yargs": "^13.2.1" + "yargs": "^13.2.2" } }, "jsii-spec": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/jsii-spec/-/jsii-spec-0.7.15.tgz", - "integrity": "sha512-OytA5XH4EvhJXTEac9h8ugqEosg9GZcqsIYQEzIi6zD44gjjIIPSbfyL8X70Z1dR3hlVNz7NdbTrxI88+X+h/w==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/jsii-spec/-/jsii-spec-0.8.2.tgz", + "integrity": "sha512-eDL96xDOMwk5tzSBqbOi1Hh17Y42eLyovoo4CEcwaHpBRR0mSccPyjXyjAEWJVMg1yyA2UEX0DrPgJa2yY7oeQ==", "requires": { "jsonschema": "^1.2.4" } @@ -2842,21 +2812,21 @@ "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==" }, "log4js": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.0.2.tgz", - "integrity": "sha512-KE7HjiieVDPPdveA3bJZSuu0n8chMkFl8mIoisBFxwEJ9FmXe4YzNuiqSwYUiR1K8q8/5/8Yd6AClENY1RA9ww==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.1.0.tgz", + "integrity": "sha512-eDa+zZPeVEeK6QGJAePyXM6pg4P3n3TO5rX9iZMVY48JshsTyLJZLIL5HipI1kQ2qLsSyOpUqNND/C5H4WhhiA==", "requires": { "date-format": "^2.0.0", - "debug": "^3.1.0", + "debug": "^4.1.1", "flatted": "^2.0.0", "rfdc": "^1.1.2", - "streamroller": "^1.0.1" + "streamroller": "^1.0.4" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "requires": { "ms": "^2.1.1" } @@ -3074,6 +3044,11 @@ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==" + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -4228,9 +4203,9 @@ "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" }, "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.1.0.tgz", + "integrity": "sha512-H2RyIJ7+A3rjkwKC2l5GGtU4H1vkxKCAGsWasNVd0Set+6i4znxbWy6/j16YDPJDWxhsgZiKAstMEP8wCdSpjA==" }, "parse-json": { "version": "4.0.0", @@ -4337,9 +4312,9 @@ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "prompts": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.3.tgz", - "integrity": "sha512-H8oWEoRZpybm6NV4to9/1limhttEo13xK62pNvn2JzY0MA03p7s0OjtmhXyon3uJmxiJJVSuUwEJFFssI3eBiQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.4.tgz", + "integrity": "sha512-HTzM3UWp/99A0gk51gAegwo1QRYA7xjcZufMNe33rCclFszUYAuHe1fIN/3ZmiHeGPkUsNaRyQm1hHOfM0PKxA==", "requires": { "kleur": "^3.0.2", "sisteransi": "^1.0.0" @@ -4375,9 +4350,9 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "react-is": { - "version": "16.8.4", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.4.tgz", - "integrity": "sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==" + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" }, "read-pkg": { "version": "3.0.0", @@ -4604,9 +4579,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" }, "set-blocking": { "version": "2.0.0", @@ -4902,9 +4877,9 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, "streamroller": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.3.tgz", - "integrity": "sha512-P7z9NwP51EltdZ81otaGAN3ob+/F88USJE546joNq7bqRNTe6jc74fTBDyynxP4qpIfKlt/CesEYicuMzI0yJg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.4.tgz", + "integrity": "sha512-Wc2Gm5ygjSX8ZpW9J7Y9FwiSzTlKSvcl0FTTMd3rn7RoxDXpBW+xD9TY5sWL2n0UR61COB0LG1BQvN6nTUQbLQ==", "requires": { "async": "^2.6.1", "date-format": "^2.0.0", @@ -4985,9 +4960,9 @@ } }, "strip-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.1.0.tgz", - "integrity": "sha512-TjxrkPONqO2Z8QDCpeE2j6n0M6EwxzyDgzEeGp+FbdvaJAt//ClYi6W5my+3ROlC/hZX2KACUwDfK49Ka5eDvg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "requires": { "ansi-regex": "^4.1.0" } @@ -5016,9 +4991,9 @@ "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" }, "tap": { - "version": "12.6.0", - "resolved": "https://registry.npmjs.org/tap/-/tap-12.6.0.tgz", - "integrity": "sha512-HsU8Djx7WhkP8SZbtdtb1P/g74QdMYgLtge9/MiNZ2uKXa1KV36nHgWIFI0BlrhnzcS9n3WfqmLY2tIBTjl+ew==", + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/tap/-/tap-12.6.1.tgz", + "integrity": "sha512-av4rQscF4IspCJ16BM+/G6LKcKwkB6HBtixf0x0PTZQCW3KlicBy9F4SwkazbMSGMrecVWvFQFeabiy5YPhAhw==", "requires": { "bind-obj-methods": "^2.0.0", "browser-process-hrtime": "^1.0.0", @@ -5033,7 +5008,7 @@ "function-loop": "^1.0.1", "glob": "^7.1.3", "isexe": "^2.0.0", - "js-yaml": "^3.12.1", + "js-yaml": "^3.13.0", "minipass": "^2.3.5", "mkdirp": "^0.5.1", "nyc": "^13.3.0", @@ -5324,14 +5299,14 @@ } }, "typescript": { - "version": "3.3.3333", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.3333.tgz", - "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==" + "version": "3.3.4000", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.4000.tgz", + "integrity": "sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==" }, "uglify-js": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", - "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.2.tgz", + "integrity": "sha512-imog1WIsi9Yb56yRt5TfYVxGmnWs3WSGU73ieSOlMVFwhJCA9W8fqFFMMj4kgDqiS/80LGdsYnWL7O9UcjEBlg==", "optional": true, "requires": { "commander": "~2.19.0", @@ -5629,14 +5604,9 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, "xmlbuilder": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.0.tgz", - "integrity": "sha512-LzeAc96zUlknAk0F+xOXC8hO1D4ISG1ivov9UBjFkPcbSk6jVGhm9J8pTQp1ksZp9YbOws8pae8tVs+hwQl12w==" - }, - "xregexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", - "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==" + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, "y18n": { "version": "4.0.0", diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 8bb0d2c59eaee..ff178dabcffca 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -1,12 +1,13 @@ { "name": "cdk-build-tools", "private": true, - "version": "0.26.0", + "version": "0.28.0", "description": "Tools package with shared build scripts for CDK packages", "main": "lib/index.js", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "tools/cdk-build-tools" }, "bin": { "cdk-build": "bin/cdk-build", @@ -30,14 +31,14 @@ "@types/fs-extra": "^5.0.5", "@types/jest": "^24.0.11", "@types/yargs": "^12.0.9", - "pkglint": "^0.26.0" + "pkglint": "^0.28.0" }, "dependencies": { - "awslint": "^0.26.0", + "awslint": "^0.28.0", "fs-extra": "^7.0.1", "jest": "^24.4.0", - "jsii": "^0.7.15", - "jsii-pacmak": "^0.7.15", + "jsii": "^0.8.2", + "jsii-pacmak": "^0.8.2", "nodeunit": "^0.11.3", "nyc": "^13.3.0", "ts-jest": "^24.0.0", diff --git a/tools/cdk-integ-tools/bin/cdk-integ-assert.ts b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts index e3adbaf38c18a..df68a688598e1 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ-assert.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ-assert.ts @@ -17,7 +17,13 @@ async function main() { } const expected = await test.readExpected(); - const actual = await test.invoke(['--json', '--no-path-metadata', '--no-asset-metadata', 'synth'], { json: true, context: STATIC_TEST_CONTEXT }); + + const args = new Array(); + args.push('--no-path-metadata'); + args.push('--no-asset-metadata'); + args.push('--no-staging'); + + const actual = await test.invoke(['--json', ...args, 'synth'], { json: true, context: STATIC_TEST_CONTEXT }); const diff = diffTemplate(expected, actual); diff --git a/tools/cdk-integ-tools/bin/cdk-integ.ts b/tools/cdk-integ-tools/bin/cdk-integ.ts index 96c39794d5c0f..8eed72579a672 100644 --- a/tools/cdk-integ-tools/bin/cdk-integ.ts +++ b/tools/cdk-integ-tools/bin/cdk-integ.ts @@ -28,6 +28,7 @@ async function main() { // don't inject cloudformation metadata into template args.push('--no-path-metadata'); args.push('--no-asset-metadata'); + args.push('--no-staging'); // inject "--verbose" to the command line of "cdk" if we are in verbose mode if (argv.verbose) { diff --git a/tools/cdk-integ-tools/package-lock.json b/tools/cdk-integ-tools/package-lock.json index 0e9a7c871cd7a..6efc3446c73ff 100644 --- a/tools/cdk-integ-tools/package-lock.json +++ b/tools/cdk-integ-tools/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-integ-tools", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/cdk-integ-tools/package.json b/tools/cdk-integ-tools/package.json index 8b45493c0565a..f3099a2d3387a 100644 --- a/tools/cdk-integ-tools/package.json +++ b/tools/cdk-integ-tools/package.json @@ -1,12 +1,13 @@ { "name": "cdk-integ-tools", "private": true, - "version": "0.26.0", + "version": "0.28.0", "description": "Package with integration test scripts for CDK packages", "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "tools/cdk-integ-tools" }, "bin": { "cdk-integ": "bin/cdk-integ", @@ -27,13 +28,13 @@ "license": "Apache-2.0", "devDependencies": { "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { - "@aws-cdk/cloudformation-diff": "^0.26.0", - "@aws-cdk/cx-api": "^0.26.0", - "aws-cdk": "^0.26.0", + "@aws-cdk/cloudformation-diff": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0", + "aws-cdk": "^0.28.0", "yargs": "^9.0.1" }, "keywords": [ diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index 289e09c2a7bc3..80c8089fd5a5b 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -519,7 +519,7 @@ export default class CodeGenerator { const javascriptPropertyName = genspec.cloudFormationToScriptName(propName); this.docLink(spec.Documentation, additionalDocs); - this.code.line(`${javascriptPropertyName}${question}: ${this.findNativeType(context, spec, propName)};`); + this.code.line(`readonly ${javascriptPropertyName}${question}: ${this.findNativeType(context, spec, propName)};`); return javascriptPropertyName; } diff --git a/tools/cfn2ts/lib/genspec.ts b/tools/cfn2ts/lib/genspec.ts index ca5ec4ffaf613..4603e4b9b092f 100644 --- a/tools/cfn2ts/lib/genspec.ts +++ b/tools/cfn2ts/lib/genspec.ts @@ -119,7 +119,17 @@ export function packageName(module: SpecName | string): string { throw new Error(`Module component name must be "AWS::Xxx" or "Alexa::Xxx" (module: ${module})`); } - return parts[parts.length - 1].toLowerCase(); + return overridePackageName(parts[parts.length - 1].toLowerCase()); +} + +/** + * Overrides special-case namespaces like serverless=>sam + */ +function overridePackageName(name: string) { + if (name === 'serverless') { + return 'sam'; + } + return name; } /** diff --git a/tools/cfn2ts/package-lock.json b/tools/cfn2ts/package-lock.json index c7850342cd8fd..40b48c88c01d6 100644 --- a/tools/cfn2ts/package-lock.json +++ b/tools/cfn2ts/package-lock.json @@ -1,6 +1,6 @@ { "name": "cfn2ts", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/cfn2ts/package.json b/tools/cfn2ts/package.json index e2fef4f16fe99..e5f0045c698fa 100644 --- a/tools/cfn2ts/package.json +++ b/tools/cfn2ts/package.json @@ -1,13 +1,14 @@ { "name": "cfn2ts", "private": true, - "version": "0.26.0", + "version": "0.28.0", "description": "Generates typescript types from CloudFormation spec, with support for enrichments", "main": "lib/index.js", "types": "lib/index.d.ts", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "tools/cfn2ts" }, "bin": { "cfn2ts": "bin/cfn2ts" @@ -31,7 +32,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@aws-cdk/cfnspec": "^0.26.0", + "@aws-cdk/cfnspec": "^0.28.0", "codemaker": "^0.6.4", "fast-json-patch": "^2.0.6", "fs-extra": "^7.0.0", @@ -40,8 +41,8 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "keywords": [ "aws", diff --git a/tools/pkglint/lib/packagejson.ts b/tools/pkglint/lib/packagejson.ts index 98cdb3da06a10..8dfa4561a9b5d 100644 --- a/tools/pkglint/lib/packagejson.ts +++ b/tools/pkglint/lib/packagejson.ts @@ -49,6 +49,10 @@ export interface Report { * Class representing a package.json file and the issues we found with it */ export class PackageJson { + public static fromDirectory(dir: string) { + return new PackageJson(path.join(dir, 'package.json')); + } + public readonly json: { [key: string]: any }; public readonly packageRoot: string; public readonly packageName: string; diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index c159231db8ef3..2477fac9dc021 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -4,7 +4,13 @@ import path = require('path'); import semver = require('semver'); import { LICENSE, NOTICE } from './licensing'; import { PackageJson, ValidationRule } from './packagejson'; -import { deepGet, deepSet, expectDevDependency, expectJSON, fileShouldBe, fileShouldContain, monoRepoVersion } from './util'; +import { + deepGet, deepSet, + expectDevDependency, expectJSON, + fileShouldBe, fileShouldContain, + findInnerPackages, + monoRepoRoot, monoRepoVersion +} from './util'; /** * Verify that the package name matches the directory name @@ -45,6 +51,8 @@ export class RepositoryCorrect extends ValidationRule { public validate(pkg: PackageJson): void { expectJSON(this.name, pkg, 'repository.type', 'git'); expectJSON(this.name, pkg, 'repository.url', 'https://github.com/awslabs/aws-cdk.git'); + const pkgDir = path.relative(monoRepoRoot(), pkg.packageRoot); + expectJSON(this.name, pkg, 'repository.directory', pkgDir); } } @@ -191,6 +199,19 @@ export class JSIIJavaPackageIsRequired extends ValidationRule { } } +export class JSIIPythonTarget extends ValidationRule { + public readonly name = 'jsii/python'; + + public validate(pkg: PackageJson): void { + if (!isJSII(pkg)) { return; } + + const moduleName = cdkModuleName(pkg.json.name); + + expectJSON(this.name, pkg, 'jsii.targets.python.distName', moduleName.python.distName); + expectJSON(this.name, pkg, 'jsii.targets.python.module', moduleName.python.module); + } +} + export class JSIISphinxTarget extends ValidationRule { public readonly name = 'jsii/sphinx'; @@ -297,13 +318,19 @@ function cdkModuleName(name: string) { .map(s => s === 'aws' ? 'AWS' : caseUtils.pascal(s)) .join('.'); + const pythonName = name.replace(/^@/g, "").replace(/\//g, ".").split(".").map(caseUtils.kebab).join("."); + return { javaPackage: `software.amazon.awscdk${isCdkPkg ? '' : `.${name.replace(/^aws-/, 'services-').replace(/-/g, '.')}`}`, mavenArtifactId: isCdkPkg ? 'cdk' : name.startsWith('aws-') || name.startsWith('alexa-') ? name.replace(/^aws-/, '') : `cdk-${name}`, - dotnetNamespace: `Amazon.CDK${isCdkPkg ? '' : `.${dotnetSuffix}`}` + dotnetNamespace: `Amazon.CDK${isCdkPkg ? '' : `.${dotnetSuffix}`}`, + python: { + distName: `aws-cdk.${pythonName}`, + module: `aws_cdk.${pythonName.replace(/-/g, "_")}`, + }, }; } @@ -731,6 +758,36 @@ export class JestCoverageTarget extends ValidationRule { } } +/** + * Packages inside JSII packages (typically used for embedding Lambda handles) + * must only have dev dependencies and their node_modules must have been + * blacklisted for publishing + * + * We might loosen this at some point but we'll have to bundle all runtime dependencies + * and we don't have good transitive license checks. + */ +export class PackageInJsiiPackageNoRuntimeDeps extends ValidationRule { + public name = 'lambda-packages-no-runtime-deps'; + + public validate(pkg: PackageJson) { + if (!isJSII(pkg)) { return; } + + for (const inner of findInnerPackages(pkg.packageRoot)) { + const innerPkg = PackageJson.fromDirectory(inner); + + if (Object.keys(innerPkg.dependencies).length > 0) { + pkg.report({ + ruleName: `${this.name}:1`, + message: `NPM Package '${innerPkg.packageName}' inside jsii package can only have devDepencencies` + }); + } + + const nodeModulesRelPath = path.relative(pkg.packageRoot, innerPkg.packageRoot) + '/node_modules'; + fileShouldContain(`${this.name}:2`, pkg, '.npmignore', nodeModulesRelPath); + } + } +} + /** * Determine whether this is a JSII package * diff --git a/tools/pkglint/lib/util.ts b/tools/pkglint/lib/util.ts index b2cd1715c3464..a31a09feb0f21 100644 --- a/tools/pkglint/lib/util.ts +++ b/tools/pkglint/lib/util.ts @@ -133,19 +133,41 @@ export function monoRepoVersion() { return lernaJson.version; } -function findLernaJSON() { - let dir = process.cwd(); +export function findUpward(dir: string, pred: (x: string) => boolean): string | undefined { while (true) { - const fullPath = path.join(dir, 'lerna.json'); - if (fs.existsSync(fullPath)) { - return fullPath; - } + if (pred(dir)) { return dir; } const parent = path.dirname(dir); if (parent === dir) { - throw new Error('Could not find lerna.json'); + return undefined; } dir = parent; } } + +export function monoRepoRoot() { + const ret = findUpward(process.cwd(), d => fs.existsSync(path.join(d, 'lerna.json'))); + if (!ret) { + throw new Error('Could not find lerna.json'); + } + return ret; +} + +function findLernaJSON() { + return path.join(monoRepoRoot(), 'lerna.json'); +} + +export function* findInnerPackages(dir: string): IterableIterator { + for (const fname of fs.readdirSync(dir, { encoding: 'utf8' })) { + const stat = fs.statSync(path.join(dir, fname)); + if (!stat.isDirectory()) { continue; } + if (fname === 'node_modules') { continue; } + + if (fs.existsSync(path.join(dir, fname, 'package.json'))) { + yield path.join(dir, fname); + } + + yield* findInnerPackages(path.join(dir, fname)); + } +} diff --git a/tools/pkglint/package-lock.json b/tools/pkglint/package-lock.json index fd6259711e79b..f5745c72cd588 100644 --- a/tools/pkglint/package-lock.json +++ b/tools/pkglint/package-lock.json @@ -1,6 +1,6 @@ { "name": "pkglint", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index 9cea1194c9a0c..0cca3d09ade07 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -1,6 +1,6 @@ { "name": "pkglint", - "version": "0.26.0", + "version": "0.28.0", "private": true, "description": "Validate and fix package.json files", "main": "lib/index.js", diff --git a/tools/pkgtools/package-lock.json b/tools/pkgtools/package-lock.json index 02974d4715eac..6d6923ab2fbd9 100644 --- a/tools/pkgtools/package-lock.json +++ b/tools/pkgtools/package-lock.json @@ -1,6 +1,6 @@ { "name": "pkgtools", - "version": "0.25.3", + "version": "0.26.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/pkgtools/package.json b/tools/pkgtools/package.json index a2962f1805a76..5437323bcf595 100644 --- a/tools/pkgtools/package.json +++ b/tools/pkgtools/package.json @@ -1,12 +1,13 @@ { "name": "pkgtools", "private": true, - "version": "0.26.0", + "version": "0.28.0", "description": "Tools for generating cross-package artifacts", "main": "index.js", "repository": { "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "tools/pkgtools" }, "bin": { "cdk-version": "bin/cdk-version", @@ -28,8 +29,8 @@ "devDependencies": { "@types/fs-extra": "^5.0.4", "@types/yargs": "^8.0.3", - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" + "cdk-build-tools": "^0.28.0", + "pkglint": "^0.28.0" }, "dependencies": { "fs-extra": "^7.0.0", diff --git a/tools/y-npm/.gitignore b/tools/y-npm/.gitignore deleted file mode 100644 index 0941d495e5df1..0000000000000 --- a/tools/y-npm/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*.js -*.d.ts - -.LAST_BUILD -.nyc_output -coverage -.nycrc -*.snk \ No newline at end of file diff --git a/tools/y-npm/.npmignore b/tools/y-npm/.npmignore deleted file mode 100644 index 95f93d57521a4..0000000000000 --- a/tools/y-npm/.npmignore +++ /dev/null @@ -1,5 +0,0 @@ -*.ts -!*.d.ts - -.LAST_BUILD -*.snk \ No newline at end of file diff --git a/tools/y-npm/README.md b/tools/y-npm/README.md deleted file mode 100644 index 2db2fb794eade..0000000000000 --- a/tools/y-npm/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# `Y-NPM` -`y-npm` (pronounce *"Why, npm?"*) is a drop-in replacement for `npm` that uses local files instead of the standard `npm` -registry for the packages it has been set up with. If you're wondering why you would want to do such things, refer to -the [use-cases](#Use-cases) section of this document. - -# Usage -## Adding a package to the `y-npm` overlay -Simply publish your packages using the `y-npm` command instead of the `npm` command (this cannot work for packages that -have `publishConfig` or `private: true` in their `package.json` file). No authentication is required. -* For a package you're developing: - ```shell - y-npm publish path/to/package - ``` -* For a package from your `npm` registry of choice: - ```shell - TGZ=$(npm pack package[@version]) # Download the package locally - y-npm publish ${TGZ} # Publish the downloaded package - rm ${TGZ} # Clean-up after yourself - ``` - -## Determine which packages you have in the `y-npm` overlay -The `y-ls` subcommand will list all packages present in the local overlay and output their name and (latest) -version number to `STDOUT`. -```shell -y-npm y-ls -``` - -## Install packages using the `y-npm` overlay -Simply replace invokations of `npm` with `y-npm`, using the exact same arguments: -```shell -y-npm install -y-npm install --save-dev @types/node -y-npm run build -``` - -## Overlay location -The overlay location is determined using the following procedure: -1. The environment variable `$Y_NPM_REPOSITORY` -2. Walk up the tree from `$PWD` (the process' working directory) until: - 1. A `y/npm` directory is found - 2. The root of the filesystem is reached -3. Walk up the tree from where `y-npm` is installed in until: - 1. A `y/npm` directory is found - 2. The root of the filesystem is reached -4. Bail out in error. - -## Debugging -Verbose logging can be enabled by setting the `Y_NPM_VERBOSE` environment variable to a truthy value: -```shell -Y_NPM_VERBOSE=1 y-npm install -``` - -# Use-cases -* Distributing private packages without having to set-up a full-fledged `npm` registry for private use. - > Simply prepare an overlay locally, then distribute that overlay (for example, in a ZIP file) to interested users. -* Enable full offline usage of `npm` - > Prepare an overlay with all the package versions that need to be available offline. No internet connectivity will - > be required for using those. -* Replacing a dependency with customized code (for quick experimentations, ...) - > Publish your custom version under the exact same name as the package you want to replace to your overlay, and - > `y-npm` will use that instead of the version found in the public `npm` registry. diff --git a/tools/y-npm/bin/y-npm b/tools/y-npm/bin/y-npm deleted file mode 100755 index 53f4161d75b9d..0000000000000 --- a/tools/y-npm/bin/y-npm +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -require('./y-npm.js'); diff --git a/tools/y-npm/bin/y-npm.template.cmd b/tools/y-npm/bin/y-npm.template.cmd deleted file mode 100644 index bfc4734e5f836..0000000000000 --- a/tools/y-npm/bin/y-npm.template.cmd +++ /dev/null @@ -1,7 +0,0 @@ -@IF EXIST "%~dp0\node.exe" ( - "%~dp0\node.exe" "%~dp0\..\y-npm\bin\y-npm" %* -) ELSE ( - @SETLOCAL - @SET PATHEXT=%PATHEXT:;.JS;=;% - node "%~dp0\..\y-npm\bin\y-npm" %* -) diff --git a/tools/y-npm/bin/y-npm.ts b/tools/y-npm/bin/y-npm.ts deleted file mode 100644 index f71165da942f3..0000000000000 --- a/tools/y-npm/bin/y-npm.ts +++ /dev/null @@ -1,58 +0,0 @@ -import 'source-map-support/register'; - -import colors = require('colors/safe'); -import fs = require('fs-extra'); -import { determineStorageDirectory } from '../lib/determine-storage-directory'; -import { findAllPackages } from '../lib/find-all-packages'; -import { debug, setVerbose } from '../lib/logging'; -import { runNpmCommand } from '../lib/run-npm-command'; -import { withVerdaccio } from '../lib/start-verdaccio'; -import { withTemporaryFile } from '../lib/with-temporary-file'; - -async function main(argv: string[]): Promise { - // Disable colors if $Y_NPM_NO_COLOR is set to a truthy value - if (process.env.Y_NPM_NO_COLOR) { - colors.disable(); - } - // Make verbose if $Y_NPM_VERBOSE is set to a truthy value - if (process.env.Y_NPM_VERBOSE) { - setVerbose(true); - debug(`Verbose logging enabled by $Y_NPM_VERBOSE=${process.env.Y_NPM_VERBOSE}`); - } - debug(`Arguments: ${JSON.stringify(argv)}`); - - const storage = await determineStorageDirectory(); - if (argv.length === 1) { - switch (argv[0]) { - case 'y-ls': - debug(`Invoking y-npm command: y-ls`); - await listLocalPackages(); - return 0; - } - } - - return (await withTemporaryFile('npmrc', async npmrc => { - return await withVerdaccio(storage, argv.find(arg => arg === 'publish') != null, async verdaccio => { - const userConfig = `registry=http:${verdaccio.endpoint}\n${verdaccio.endpoint}:_authToken=none\n`; - debug(`Writing user config to ${colors.green(npmrc)}:\n${colors.blue(userConfig)}`); - await fs.writeFile(npmrc, userConfig); - return await(await runNpmCommand(argv, { npm_config_registry: undefined, npm_config_userconfig: npmrc })); - }); - })).exitCode; - - // ### Helper functions ### - - async function listLocalPackages(dir: string = storage) { - for (const pkg of await findAllPackages(dir)) { - process.stdout.write(`${colors.green(pkg.name)}@${colors.blue(pkg.maxVersion || '*')}\n`); - } - } -} - -main(process.argv.slice(2)) - .then(exitCode => process.exit(exitCode)) - .catch(err => { - // tslint:disable-next-line:no-console - console.error(err.stack); - process.exit(-1); - }); diff --git a/tools/y-npm/lib/determine-storage-directory.ts b/tools/y-npm/lib/determine-storage-directory.ts deleted file mode 100644 index 4b90c2019e036..0000000000000 --- a/tools/y-npm/lib/determine-storage-directory.ts +++ /dev/null @@ -1,52 +0,0 @@ -import fs = require('fs-extra'); -import path = require('path'); -import { debug } from './logging'; - -/** The parent directory of this module's location */ -const installRoot = path.resolve(path.join(__dirname, '..', '..')); -/** The standard name of y-npm overlay directories */ -const overlayName = path.join('y', 'npm'); - -/** - * Determines the directory where the overlay is located, using the following workflow: - * 1. ${Y_NPM_REPOSITORY} - * 2. Walk up the tree from ${cwd} until: - * * A y/npm directory is found - * * The root of the filesystem is reached - * 3. Walk up the tree from the module's install location until: - * * A y/npm directory is found - * * The root of the filesystem is reached - * 4. Bail out in error. - * @returns the directory where the overlay directory is located. - */ -export async function determineStorageDirectory() { - let result: string | undefined = process.env.Y_NPM_REPOSITORY; - if (!result) { result = await findOverlayDirectory(process.cwd()); } - if (!result) { result = await findOverlayDirectory(installRoot); } - if (!result) { - throw new Error(`Unable to locate the ${overlayName} overlay directory, maybe you should set the Y_NPM_REPOSITORY environment variable?`); - } - debug(`Storage directory: ${result} (override by setting the Y_NPM_REPOSITORY environment variable)`); - if (!await fs.pathExists(result)) { - throw new Error(`Storage directory does not exist: ${result}`); - } - return result; -} - -/** - * Search for an overlay directory in a tree structure, walking up from the provided directory. - * @param dir the directory where the search starts - * @returns the full path to the overlay directory, or ``undefined`` if the root - * of the filesystem is reached without finding one. - */ -async function findOverlayDirectory(dir: string): Promise { - const candidate = path.join(dir, overlayName); - if (await fs.pathExists(candidate)) { - return path.normalize(candidate); - } - const parent = path.normalize(path.resolve(path.join(dir, '..'))); - if (parent !== dir) { - return await findOverlayDirectory(parent); - } - return undefined; -} diff --git a/tools/y-npm/lib/find-all-packages.ts b/tools/y-npm/lib/find-all-packages.ts deleted file mode 100644 index d931e333f94b2..0000000000000 --- a/tools/y-npm/lib/find-all-packages.ts +++ /dev/null @@ -1,29 +0,0 @@ -import fs = require('fs-extra'); -import path = require('path'); - -export class PackageVersion { - constructor(public readonly name: string, - public readonly maxVersion: string, - public readonly allVersions: string[]) {} - - public toString() { - return `${this.name}@${this.maxVersion}`; - } -} - -export async function findAllPackages(dir: string): Promise { - const packages = new Array(); - for (const file of await fs.readdir(dir)) { - const fullPath = path.join(dir, file); - if (file === 'package.json') { - const pkgInfo = require(fullPath); - const versions = Object.keys(pkgInfo.versions); - packages.push(new PackageVersion(pkgInfo.name, versions.sort()[0], versions)); - } else if ((await fs.stat(fullPath)).isDirectory()) { - for (const pkg of await findAllPackages(fullPath)) { - packages.push(pkg); - } - } - } - return [...packages]; -} diff --git a/tools/y-npm/lib/logging.ts b/tools/y-npm/lib/logging.ts deleted file mode 100644 index 61794a5e32c7e..0000000000000 --- a/tools/y-npm/lib/logging.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { blue, green, red, yellow } from 'colors/safe'; - -const prefix = `[${green('y-npm')}|${blue(process.pid.toString())}]`; -let verbose: boolean = false; - -export function setVerbose(newVerbose: boolean) { - verbose = newVerbose; -} - -export function debug(message: string) { - if (!verbose) { return; } - print(message); -} - -export function error(message: string) { - return print(red(message)); -} - -export function warning(message: string) { - return print(yellow(message)); -} - -function print(message: string) { - process.stderr.write(`${prefix} ${message}\n`); -} diff --git a/tools/y-npm/lib/run-npm-command.ts b/tools/y-npm/lib/run-npm-command.ts deleted file mode 100644 index 07d22f4cc8118..0000000000000 --- a/tools/y-npm/lib/run-npm-command.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { spawn } from 'child_process'; -import { debug, error } from '../lib/logging'; - -export const NO_ADDITIONAL_ENV = undefined; -export const QUIET = true; - -export type ExitStatus = { code: number, signal?: undefined } - | { code?: undefined, signal: string }; - -export class CommandResult { - constructor(public readonly exitStatus: ExitStatus, public readonly stdout: Buffer, public readonly stderr: Buffer) {} - - public assertSuccess(showStderrOnFailure: boolean = true) { - if (!this.success && showStderrOnFailure) { - process.stderr.write(this.stderr); - } - if (this.signaled) { - throw new Error(`The process received signal: ${this.exitStatus.signal}`); - } - if (!this.success) { - throw new Error(`The process exited with status: ${this.exitCode}`); - } - } - - public get success(): boolean { - return this.exitStatus.code === 0; - } - - public get signaled(): boolean { - return this.exitStatus.signal != null; - } - - public get exitCode(): number { - return this.exitStatus.code != null ? this.exitStatus.code : 128; - } -} - -/** - * Executes ``npm`` with the provided arguments and inheriting the current - * process' environment, with optional additional environment variables. - * The command's ``stdin``, ``stdout`` and ``stderr`` will be inherited from - * the current process. - * - * @param args the CLI arguments to be passed to ``npm`` - * @param additionalEnv the extra environment variables to provide to the - * ``npm`` command. - * @param silent whether output should not be forwarded to stdout. - * - * @returns the exit status of the ``npm`` command, and it's standard output. - */ -export async function runNpmCommand(args: string[], additionalEnv?: NodeJS.ProcessEnv, silent: boolean = false): Promise { - return await runCommand('npm', args, additionalEnv, silent); -} - -/** - * Executes a command with the provided arguments and inheriting the current - * process' environment, with optional additional environment variables. - * The command's ``stdin``, ``stdout`` and ``stderr`` will be inherited from - * the current process. - * - * @param command the command to be invoked - * @param args the CLI arguments to be passed to the ``command`` - * @param additionalEnv the extra environment variables to provide to the - * ``command``. - * @param silent whether output should not be forwarded to stdout. - * - * @returns the exit status of the ``command``, and it's standard output. - */ -export function runCommand(command: string, args: string[], additionalEnv?: NodeJS.ProcessEnv, silent: boolean = false): Promise { - return new Promise((resolve, reject) => { - try { - debug(`Additional environment variables: ${JSON.stringify(additionalEnv)}`); - debug(`Command: ${command} ${args.join(' ')}`); - const env = { ...process.env }; - for (const key of Object.keys(additionalEnv || {})) { - const value = additionalEnv![key]; - if (value == null) { - delete env[key]; - } else { - env[key] = value; - } - } - // `shell: true` is required because on Windows, batch files must be run from a shell, and y-npm - // is invoked using the batch file y-npm.cmd (to work around symlink issues on Windows). - const child = spawn(command, args, { detached: false, env, shell: true, stdio: ['inherit', 'pipe', 'pipe'] }); - debug(`Command PID: ${child.pid}`); - const stdout = new Array(); - const stderr = new Array(); - child.stdout.on('data', chunk => { - if (!silent) { process.stdout.write(chunk); } - stdout.push(chunk as Buffer); - }); - child.stderr.on('data', chunk => { - if (!silent) { process.stderr.write(chunk); } - stderr.push(chunk as Buffer); - }); - child.on('close', (code, signal) => { - if (code != null) { - (code === 0 ? debug : error)(`Child exited with status ${code}`); - resolve(new CommandResult({ code }, Buffer.concat(stdout), Buffer.concat(stderr))); - } else { - error(`Child received signal ${signal}`); - resolve(new CommandResult({ signal }, Buffer.concat(stdout), Buffer.concat(stderr))); - } - }); - } catch (err) { - error(err.stack); - reject(error); - } - }); -} diff --git a/tools/y-npm/lib/start-verdaccio.ts b/tools/y-npm/lib/start-verdaccio.ts deleted file mode 100644 index 77756ac96b8c6..0000000000000 --- a/tools/y-npm/lib/start-verdaccio.ts +++ /dev/null @@ -1,94 +0,0 @@ -import http = require('http'); -import path = require('path'); -import { findAllPackages } from './find-all-packages'; -import { debug, error } from './logging'; - -export interface VerdaccioHandle { - server: http.Server; - endpoint: string; -} - -/** - * Starts a ``verdaccio`` server using a specific storage directory. - * - * When created with ``forPublishing = false``, all packages that are - * available in ``storage`` will be served directly from there, and anything - * else will be served proxied from ``https://registry.npmjs.org``. - * - * When created with ``forPublishing = true``, all packages will be published - * into the ``storage`` directory, and will never be proxied to any remote - * registry. - * - * @param storage the directory where ``verdaccio`` storage is located. - * @param forPublishing whether this server will be used to publish. - * @return a promise with a ``Server`` (that one should call ``#close()`` on - * when done working with ``verdaccio``), and a string containing the - * endpoint (something like ``//localhost:port/``). - */ -export async function startVerdaccio(storage: string, forPublishing: boolean): Promise { - const packages = forPublishing ? { '**': { access: '$all', publish: '$all' } } : await buildPackageConfig(storage); - return await new Promise((resolve, reject) => { - function verdaccioHandler(webServer: http.Server, addr: any, _pkgName: string, _pkgVersion: string) { - webServer.listen(addr.port || addr.path, addr.host, () => { - debug('Verdaccio startup completed.'); - resolve({ server: webServer, endpoint: `//${addr.host}:${addr.port || addr.path}/` }); - }); - } - - const config = { - storage, - uplinks: { npmjs: { url: 'https://registry.npmjs.org', cache: false } }, - packages, - max_body_size: '100mb', - logs: [{ - type: 'file', - path: path.join(storage, 'verdaccio.log'), - format: 'pretty-timestamped', - level: 'info' - }], - web: { enabled: false }, - publish: { allow_offline: true } - }; - debug(`Verdaccio config (publishing mode: ${forPublishing}): ${JSON.stringify(config, null, 2)}`); - - try { - // tslint:disable-next-line:no-var-requires - require('verdaccio').default(config, 6000, config.storage, '1.0.0', 'verdaccio', verdaccioHandler); - } catch (e) { - error(`Failed to start verdaccio: ${e.stack}`); - reject(e); - } - }); -} - -export async function withVerdaccio(storage: string, forPublishing: boolean, callback: (handle: VerdaccioHandle) => (T | Promise)): Promise { - const verdaccio = await startVerdaccio(storage, forPublishing); - try { - return await callback(verdaccio); - } finally { - verdaccio.server.close(); - } -} - -interface PackageConfig { - access: '$all'; - proxy?: 'npmjs'; - storage?: false; -} - -/** - * Computes the ``packages`` portion of the Verdaccio config to specifically - * disable proxying to ``npmjs`` for packages that are locally overridden. - * - * @param storage is the directory where the Verdaccio storage is located - * @returns a valid ``packages`` directive for Verdaccio configuration. - */ -async function buildPackageConfig(storage: string) { - const result: { [name: string]: PackageConfig } = {}; - for (const pkg of await findAllPackages(storage)) { - result[pkg.name] = { access: '$all' }; - } - // Order of entries in the map is important, so this must be the last entry - result['**'] = { proxy: 'npmjs', access: '$all', storage: false }; - return result; -} diff --git a/tools/y-npm/lib/with-temporary-file.ts b/tools/y-npm/lib/with-temporary-file.ts deleted file mode 100644 index 9b5eea91fd302..0000000000000 --- a/tools/y-npm/lib/with-temporary-file.ts +++ /dev/null @@ -1,21 +0,0 @@ -import fs = require('fs-extra'); -import os = require('os'); -import path = require('path'); - -/** - * Executes a block of code with a temporary file, that will be deleted once the - * execution is completed. - * - * @param name the name of the file to be created. - * @param callback the function to be invoked with the temporary file's path. - * @returns the result of calling ``callback`` with the temporary file. - */ -export async function withTemporaryFile(name: string, callback: (file: string) => (T | Promise)): Promise { - const dir = await fs.mkdtemp(os.tmpdir() + path.sep); - try { - const file = path.join(dir, name); - return await callback(file); - } finally { - await fs.remove(dir); - } -} diff --git a/tools/y-npm/package-lock.json b/tools/y-npm/package-lock.json deleted file mode 100644 index fe5ab164a17fc..0000000000000 --- a/tools/y-npm/package-lock.json +++ /dev/null @@ -1,1461 +0,0 @@ -{ - "name": "y-npm", - "version": "0.25.3", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/colors": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/colors/-/colors-1.2.1.tgz", - "integrity": "sha512-7jNkpfN2lVO07nJ1RWzyMnNhH/I5N9iWuMPx9pedptxJ4MODf8rRV0lbJi6RakQ4sKQk231Fw4e2W9n3D7gZ3w==", - "dev": true, - "requires": { - "colors": "*" - } - }, - "@types/fs-extra": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.4.tgz", - "integrity": "sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "10.12.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.21.tgz", - "integrity": "sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ==", - "dev": true - }, - "@types/semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", - "dev": true - }, - "@verdaccio/file-locking": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@verdaccio/file-locking/-/file-locking-0.0.8.tgz", - "integrity": "sha512-kK7siED1Yc/t8+G3Iyb0vdQ6mM+TKNW2wM8LO0D6bXg3rBWlf863JG7JIedSGUeMzwFOKjX75jreiE+xVeAb3w==", - "requires": { - "lockfile": "1.0.4", - "lodash": "4.17.11" - } - }, - "@verdaccio/local-storage": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@verdaccio/local-storage/-/local-storage-1.1.4.tgz", - "integrity": "sha512-ocmot986URUER2DYXFM2iMqRTlO1so7tY2uxPF86+T9qOpvBS+TT2Q+ZwMyDJxe6f5GMAjpB19WFFFBq8k6LSA==", - "requires": { - "@verdaccio/file-locking": "0.0.7", - "@verdaccio/streams": "1.0.0", - "async": "2.6.1", - "http-errors": "1.7.1", - "lodash": "4.17.11", - "mkdirp": "0.5.1" - }, - "dependencies": { - "@verdaccio/file-locking": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@verdaccio/file-locking/-/file-locking-0.0.7.tgz", - "integrity": "sha512-yTecDiJrYJMNnsPA4Nminygc/bPRf2HB6Pj/o/IS8scljYvV++LRXuglefM1I7p18qs3mcgX/bofCZM4x2SOJA==", - "requires": { - "lockfile": "1.0.3", - "lodash": "4.17.10" - }, - "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - } - } - }, - "lockfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.3.tgz", - "integrity": "sha1-Jjj8OaAzHpysGgS3F5mTHJxQ33k=" - } - } - }, - "@verdaccio/streams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@verdaccio/streams/-/streams-1.0.0.tgz", - "integrity": "sha512-AjEo5LXk4Yf0SaXSc3y4i1t+wxY552O7WrVJPtnC6H7nUsSrygg/ODCG1RSKelskOq6b5p/LyXnsTkmCFXyjDQ==" - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "ajv": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.8.1.tgz", - "integrity": "sha512-eqxCp82P+JfqL683wwsL73XmFs1eG6qjw+RD3YHx+Jll1r0jNd4dh8QG9NYAeNGA/hnZjeEDgtTskgJULbxpWQ==", - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "apache-md5": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/apache-md5/-/apache-md5-1.1.2.tgz", - "integrity": "sha1-7klza2ObTxCLbp5ibG2pkwa0FpI=" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "requires": { - "lodash": "^4.17.10" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" - }, - "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" - }, - "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "bunyan": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", - "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", - "requires": { - "dtrace-provider": "~0.8", - "moment": "^2.10.6", - "mv": "~2", - "safe-json-stringify": "~1" - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "colors": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", - "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==" - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" - }, - "compressible": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", - "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", - "requires": { - "mime-db": ">= 1.36.0 < 2" - } - }, - "compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.14", - "debug": "2.6.9", - "on-headers": "~1.0.1", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cookies": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz", - "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==", - "requires": { - "depd": "~1.1.2", - "keygrip": "~1.0.3" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "date-fns": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz", - "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "dom-walk": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", - "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" - }, - "dtrace-provider": { - "version": "0.8.7", - "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz", - "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", - "optional": true, - "requires": { - "nan": "^2.10.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ecdsa-sig-formatter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", - "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "express": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", - "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", - "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.3", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", - "qs": "6.5.2", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.2", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "global": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", - "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", - "requires": { - "min-document": "^2.19.0", - "process": "~0.5.1" - } - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" - }, - "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", - "requires": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "http-errors": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz", - "integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ipaddr.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "js-base64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", - "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==" - }, - "js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" - }, - "js-yaml": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.1.tgz", - "integrity": "sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, - "jsonwebtoken": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz", - "integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==", - "requires": { - "jws": "^3.1.5", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "jwa": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.2.0.tgz", - "integrity": "sha512-Grku9ZST5NNQ3hqNUodSkDfEBqAmGA1R8yiyPHOnLzEKI0GaCQC/XhFmsheXYuXzFQJdILbh+lYBiliqG5R/Vg==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.10", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.1.tgz", - "integrity": "sha512-bGA2omSrFUkd72dhh05bIAN832znP4wOU3lfuXtRBuGTbsmNmDXMQg28f0Vsxaxgk4myF5YkKQpz6qeRpMgX9g==", - "requires": { - "jwa": "^1.2.0", - "safe-buffer": "^5.0.1" - } - }, - "keygrip": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", - "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==" - }, - "lockfile": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", - "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", - "requires": { - "signal-exit": "^3.0.2" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "lunr": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-0.7.0.tgz", - "integrity": "sha1-59J5onPEq0L/WEthzjKjUTotOFk=" - }, - "marked": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.0.tgz", - "integrity": "sha512-HduzIW2xApSXKXJSpCipSxKyvMbwRRa/TwMbepmlZziKdH8548WSoDP4SxzulEKjlo8BE39l+2fwJZuRKOln6g==" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==" - }, - "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" - }, - "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", - "requires": { - "mime-db": "~1.37.0" - } - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", - "requires": { - "dom-walk": "^0.1.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", - "optional": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "mv": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", - "requires": { - "mkdirp": "~0.5.1", - "ncp": "~2.0.0", - "rimraf": "~2.4.0" - } - }, - "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", - "optional": true - }, - "ncp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=" - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pkginfo": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", - "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=" - }, - "process": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" - }, - "proxy-addr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", - "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.8.0" - } - }, - "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" - }, - "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - } - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "rimraf": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", - "requires": { - "glob": "^6.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-json-stringify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", - "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - }, - "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" - } - } - }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - } - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-support": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", - "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } - }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "optional": true - } - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "unix-crypt-td-js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.0.0.tgz", - "integrity": "sha1-HAgkFQSBvHoB1J6Y8exmjYJBLzs=" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "verdaccio": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/verdaccio/-/verdaccio-3.11.2.tgz", - "integrity": "sha512-w68s7fRJcN0+6BRxVqbh+ftZraMKGfH92F5Y1QQp3PTnvLp3z6vL4EPBdBaaWkLT9HV/FrL2mEO0DlSeX3uKfg==", - "requires": { - "@verdaccio/file-locking": "0.0.8", - "@verdaccio/local-storage": "1.1.4", - "@verdaccio/streams": "1.0.0", - "JSONStream": "1.3.5", - "async": "2.6.1", - "body-parser": "1.18.3", - "bunyan": "1.8.12", - "chalk": "2.4.2", - "commander": "2.19.0", - "compression": "1.7.3", - "cookies": "0.7.3", - "cors": "2.8.5", - "date-fns": "1.29.0", - "express": "4.16.4", - "global": "4.3.2", - "handlebars": "4.0.12", - "http-errors": "1.7.1", - "js-base64": "2.5.1", - "js-string-escape": "1.0.1", - "js-yaml": "3.12.1", - "jsonwebtoken": "8.4.0", - "lockfile": "1.0.4", - "lodash": "4.17.11", - "lunr": "0.7.0", - "marked": "0.6.0", - "mime": "2.4.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "mv": "2.1.1", - "pkginfo": "0.4.1", - "request": "2.88.0", - "semver": "5.6.0", - "verdaccio-audit": "1.1.0", - "verdaccio-htpasswd": "0.2.3" - } - }, - "verdaccio-audit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/verdaccio-audit/-/verdaccio-audit-1.1.0.tgz", - "integrity": "sha512-5bCiTWWBauq49vF1Ndt0Jl0GbIBDOzjiRNO33gWBPfgjoZfPn8wFpR/woFcMfS3SPzC06rH9LBcUn9KN3uWjEg==", - "requires": { - "express": "4.16.4", - "request": "2.88.0" - } - }, - "verdaccio-htpasswd": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/verdaccio-htpasswd/-/verdaccio-htpasswd-0.2.3.tgz", - "integrity": "sha512-NgtqhsTukvdnAiyD0dlJjC7cH9gdUhKp3Ohtgr56AZW+i4qJ5X0IYr7MVV+JboHFRd7FGJ9iaSvLiW7nZ1TvIg==", - "requires": { - "@verdaccio/file-locking": "0.0.8", - "apache-md5": "1.1.2", - "bcryptjs": "2.4.3", - "unix-crypt-td-js": "1.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - } - } -} diff --git a/tools/y-npm/package.json b/tools/y-npm/package.json deleted file mode 100644 index 91594c7016f9f..0000000000000 --- a/tools/y-npm/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "y-npm", - "version": "0.26.0", - "description": "Run npm commands using a local registry overlay", - "private": true, - "author": { - "name": "Amazon Web Services", - "url": "https://aws.amazon.com", - "organization": true - }, - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/awslabs/aws-cdk.git" - }, - "bin": { - "y-npm": "bin/y-npm" - }, - "scripts": { - "build": "cdk-build", - "watch": "cdk-watch", - "lint": "tslint -p .", - "test": "cdk-test", - "pkglint": "pkglint -f", - "package": "cdk-package" - }, - "dependencies": { - "colors": "^1.2.1", - "fs-extra": "^7.0.0", - "semver": "^5.5.0", - "source-map-support": "^0.5.6", - "verdaccio": "^3.2.0" - }, - "devDependencies": { - "@types/colors": "^1.2.1", - "@types/fs-extra": "^5.0.4", - "@types/semver": "^5.5.0", - "cdk-build-tools": "^0.26.0", - "pkglint": "^0.26.0" - }, - "keywords": [ - "aws", - "cdk" - ], - "homepage": "https://github.com/awslabs/aws-cdk", - "engines": { - "node": ">= 8.10.0" - } -} diff --git a/tools/y-npm/test/test.sh b/tools/y-npm/test/test.sh deleted file mode 100644 index e4542e269624a..0000000000000 --- a/tools/y-npm/test/test.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/bin/bash -set -euo pipefail - -export ROOT=$PWD # So it's available in sub-shells - -############## -### SET-UP ### -############## - -dir=$(mktemp -d) -cleanup() { - cd ${ROOT} - rm -rf ${dir} -} -trap cleanup EXIT - -# Now we'll create a hierarchy, so we can test overlay determination is correct: -# -# ${dir} -# ├─ install -# │ ├─ usr We'll "npm install" y-npm here -# │ └─ y/npm This is the install-relative repository -# ├─ cwd -# │ ├─ y/npm This is the $PWD-relative repository -# │ └─ sub PWD to test the tree crawling of PWD-relative detection -# └─ env -# └─ repository This will be the environment-configured repository - -install_prefix=${dir}/install/usr -( - mkdir -p ${install_prefix} - cd ${install_prefix} - # Hopping thorugh `npm pack` to avoid `npm`'s local symlinking, that would interfere with install-relative discover - tarball=$(npm pack --ignore-scripts ${ROOT}) - npm install --global-style --no-save ${tarball} - ln -s node_modules/.bin bin -) -Y_NPM=${install_prefix}/bin/y-npm - -mkdir -p ${dir}/install/y/npm/install-relative -echo '{"name": "install-relative", "versions": { "1.0.0": {} }}' \ - > ${dir}/install/y/npm/install-relative/package.json - -mkdir -p ${dir}/cwd/y/npm/cwd-relative -echo '{"name": "cwd-relative", "versions": { "1.0.0": {} }}' \ - > ${dir}/cwd/y/npm/cwd-relative/package.json -mkdir -p ${dir}/cwd/sub - -mkdir -p ${dir}/env/repository/env -echo '{"name": "env", "versions": { "1.0.0": {} }}' \ - > ${dir}/env/repository/env/package.json - -unset Y_NPM_REPOSITORY # in case the environment had it - -################## -### TEST CASES ### -################## -exit_code=0 -export Y_NPM_NO_COLOR=1 # Disable colored output - -( - title="Correctly uses install-relative repository: " - cd ${dir} - expected="install-relative@1.0.0" - actual=$(${Y_NPM} y-ls) - if [ ${expected} == ${actual} ]; then - echo "✅ ${title}" - else - echo "❌ ${title}" - echo "├─ Expected: ${expected}" - echo "└─ Actual: ${actual}" - exit_code=1 - fi -) - -( - title="Correctly uses \$PWD-relative repository (current directory)" - cd ${dir}/cwd - expected="cwd-relative@1.0.0" - actual=$(${Y_NPM} y-ls) - if [ ${expected} == ${actual} ]; then - echo "✅ ${title}" - else - echo "❌ ${title}" - echo "├─ Expected: ${expected}" - echo "└─ Actual: ${actual}" - exit_code=1 - fi -) - -( - title="Correctly uses \$PWD-relative repository (parent directory)" - cd ${dir}/cwd/sub - expected="cwd-relative@1.0.0" - actual=$(${Y_NPM} y-ls) - if [ ${expected} == ${actual} ]; then - echo "✅ ${title}" - else - echo "❌ ${title}" - echo "├─ Expected: ${expected}" - echo "└─ Actual: ${actual}" - exit_code=1 - fi -) - -( - title="Correctly uses environment-configured repository" - cd ${dir} - expected="env@1.0.0" - actual=$(Y_NPM_REPOSITORY=${dir}/env/repository ${Y_NPM} y-ls) - if [ ${expected} == ${actual} ]; then - echo "✅ ${title}" - else - echo "❌ ${title}" - echo "├─ Expected: ${expected}" - echo "└─ Actual: ${actual}" - exit_code=1 - fi -) - -( - title="Correctly adds published packages to overlay" - cd ${dir} - version=$(node -e "console.log(require('${ROOT}/package.json').version);") - NL=$'\n' - expected="install-relative@1.0.0${NL}y-npm@${version}" - ${Y_NPM} publish ${install_prefix}/y-npm-*.tgz - actual=$(${Y_NPM} y-ls) - if [ "${expected}" == "${actual}" ]; then - echo "✅ ${title}" - else - echo "❌ ${title}" - echo "├─ Expected: ${expected}" - echo "└─ Actual: ${actual}" - exit_code=1 - fi -) - -exit ${exit_code} diff --git a/tools/y-npm/tsconfig.json b/tools/y-npm/tsconfig.json deleted file mode 100644 index 86197fd793270..0000000000000 --- a/tools/y-npm/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "target":"ES2018", - "module": "commonjs", - "lib": ["es2016", "es2017.object", "es2017.string"], - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, - "inlineSourceMap": true, - "inlineSources": true, - "experimentalDecorators": true, - "strictPropertyInitialization":false - } -} -