diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 99aa3f60ce033..4f82171ed77ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,7 @@ and let us know if it's not up-to-date (even better, submit a PR with your corr - [Finding dependency cycles between packages](#finding-dependency-cycles-between-packages) - [Updating all Dependencies](#updating-all-dependencies) - [Running CLI integration tests](#running-cli-integration-tests) + - [Changing the Cloud Assembly Schema](#changing-cloud-assembly-schema) - [API Compatibility Checks](#api-compatibility-checks) - [Examples](#examples) - [Feature Flags](#feature-flags) @@ -54,7 +55,7 @@ For day-to-day development and normal contributions, the following SDKs and tool - [.NET Core SDK 3.0](https://www.microsoft.com/net/download) - [Python 3.6.5](https://www.python.org/downloads/release/python-365/) - [Ruby 2.5.1](https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-5-1-released/) - + The basic commands to get the repository cloned and built locally follow: ```console @@ -143,7 +144,7 @@ Integration tests perform a few functions in the CDK code base - 3. (Optionally) Acts as a way to validate that constructs set up the CloudFormation resources as expected. A successful CloudFormation deployment does not mean that the resources are set up correctly. -If you are working on a new feature that is using previously unused CloudFormation resource types, or involves +If you are working on a new feature that is using previously unused CloudFormation resource types, or involves configuring resource types across services, you need to write integration tests that use these resource types or features. @@ -556,6 +557,11 @@ run as part of the regular build, since they have some particular requirements. See the [CLI CONTRIBUTING.md file](packages/aws-cdk/CONTRIBUTING.md) for more information on running those tests. +### Changing Cloud Assembly Schema + +If you plan on making changes to the `cloud-assembly-schema` package, make sure you familiarize yourself with +its own [contribution guide](./packages/@aws-cdk/cloud-assembly-schema/CONTRIBUTING.md) + ### API Compatibility Checks All stable APIs in the CDK go through a compatibility check during build using diff --git a/design/cloud-assembly.md b/design/cloud-assembly.md index a1f2854abc833..437216eb2139d 100644 --- a/design/cloud-assembly.md +++ b/design/cloud-assembly.md @@ -1,473 +1,3 @@ -# Cloud Assembly Specification, Version 1.0 +# Cloud Assembly Specification, Version 2.0 -The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, -**RECOMMENDED**, **MAY**, and **OPTIONAL** in this document are to be interpreted as described in [RFC 2119] when they -are spelled out in bold, capital letters (as they are shown here). - -## Introduction -A *Cloud Assembly* is a self-contained document container designed to hold the components of *cloud applications*, -including all the parts that are needed in order to deploy those to a *cloud* provider. This document is the -specification of the *Cloud Assembly* format as well as requirements imposed on *Cloud Assemblers* and *Cloud Runtimes*. - -### Design Goals -The design goals for the *Cloud Assembly Specification* are the following: -* The *Cloud Assembly Specification* is extensible. -* The *Cloud Assembly Specification* is cloud-agnostic. -* The *Cloud Assembly Specification* is easy to implement and use. -* The *Cloud Assembly Specification* supports authenticity and integrity guarantees. -* A *Cloud Assembly* is self-contained, making deployments reproductible. - -## Specification -A *Cloud Assembly* is a ZIP archive that **SHOULD** conform to the [ISO/IEC 21320-1:2015] *Document Container File* -standard. *Cloud Assembly* files **SHOULD** use the `.cloud` extension in order to make them easier to recognize by -users. - -Documents in the archive can be stored with any name and directory structure, however the following entries at the root -of the archive are reserved for special use: -* `manifest.json` **MUST** be present and contains the [manifest document](#manifest-document) for the *Cloud Assembly*. -* `signature.asc`, when present, **MUST** contain the [digital signature](#digital-signature) of the *Cloud Assembly*. - -### Manifest Document -The `manifest.json` file is the entry point of the *Cloud Assembly*. It **MUST** be a valid [JSON] document composed of -a single `object` that conforms to the following schema: - -Key |Type |Required|Description ---------------|---------------------|:------:|----------- -`schema` |`string` |Required|The schema for the document. **MUST** be `cloud-assembly/1.0`. -`droplets` |`Map` |Required|A mapping of [*Logical ID*](#logical-id) to [Droplet](#droplet). -`missing` |`Map`| |A mapping of context keys to [missing information](#missing). - -The [JSON] specification allows for keys to be specified multiple times in a given `object`. However, *Cloud Assembly* -consumers **MAY** assume keys are unique, and *Cloud Assemblers* **SHOULD** avoid generating duplicate keys. If -duplicate keys are present and the manifest parser permits it, the latest specified value **SHOULD** be preferred. - -### Logical ID -*Logical IDs* are `string`s that uniquely identify [Droplet](#droplet)s in the context of a *Cloud Assembly*. -* A *Logical ID* **MUST NOT** be empty. -* A *Logical ID* **SHOULD NOT** exceed `256` characters. -* A *Logical ID* **MUST** be composed of only the following ASCII printable characters: - + Upper-case letters: `A` (`0x41`) through `Z` (`0x5A`) - + Lower-case letters: `a` (`0x61`) through `z` (`0x7A`) - + Numeric characters: `0` (`0x30`) through `9` (`0x39`) - + Plus: `+` (`0x2B`) - + Minus: `-` (`0x2D`) - + Forward-slash: `/` (`0x2F`) - + Underscore: `_` (`0x5F`) -* A *Logical ID* **MUST NOT** contain the `.` (`0x2E`) character as it is used in the string substitution pattern for - cross-droplet references to separate the *Logical ID* from the *attribute* name. - -In other words, *Logical IDs* are expected to match the following regular expression: -```js -/^[A-Za-z0-9+\/_-]{1,256}$/ -``` - -### Droplet -Clouds are made of Droplets. They are the building blocks of *Cloud Assemblies*. They model a part of the -*cloud application* that can be deployed independently, provided its dependencies are fulfilled. Droplets are specified -using [JSON] objects that **MUST** conform to the following schema: - -Key |Type |Required|Description --------------|----------------------|:------:|----------- -`type` |`string` |Required|The [*Droplet Type*](#droplet-type) specifier of this Droplet. -`environment`|`string` |required|The target [environment](#environment) in which Droplet is deployed. -`dependsOn` |`string[]` | |*Logical IDs* of other Droplets that must be deployed before this one. -`metadata` |`Map`| |Arbitrary named [metadata](#metadata) associated with this Droplet. -`properties` |`Map` | |The properties of this Droplet as documented by its maintainers. - -Each [Droplet Type](#droplet-type) can produce output strings that allow Droplets to provide informations that other -[Droplets](#droplet) can use when composing the *cloud application*. Each Droplet implementer is responsible to document -the output attributes it supports. References to these outputs are modeled using special `string` tokens within entries -of the `properties` section of Droplets: - -``` -${LogicalId.attributeName} - ╰───┬───╯ ╰─────┬─────╯ - │ └─ The name of the output attribute - └───────────── The Logical ID of the Droplet -``` - -The following escape sequences are valid: -* `\\` encodes the `\` literal -* `\${` encodes the `${` literal - -Deployment systems **SHOULD** return an error upon encountering an occurrence of the `\` literal that is not part of a -valid escape sequence. - -Droplets **MUST NOT** cause circular dependencies. Deployment systems **SHOULD** detect cycles and fail upon discovering -one. - -#### Droplet Type -Every Droplet has a type specifier, which allows *Cloud Assembly* consumers to know how to deploy it. The type -specifiers are `string`s that use an URI-like syntax (`protocol://path`), providing the coordinates to a reference -implementation for the Droplet behavior. - -Deployment systems **MUST** support at least one protocol, and **SHOULD** support all the protocols specified in -the following sub-sections. - -##### The `npm` protocol -Type specifiers using the `npm` protocol have the following format: -``` -npm://[@namespace/]package/ClassName[@version] -╰┬╯ ╰────┬────╯ ╰──┬──╯ ╰───┬───╯ ╰──┬──╯ - │ │ │ │ └─ Optional version specifier - │ │ │ └─────────── Fully qualified name of the Handler class - │ │ └──────────────────── Name of the NPM package - │ └────────────────────────────── Optional NPM namespace - └───────────────────────────────────────── NPM protocol specifier -``` - -#### Environment -Environments help Deployment systems determine where to deploy a particular Droplet. They are referenced by `string`s -that use an URI-like syntax (`protocol://path`). - -Deployment systems **MUST** support at least one protocol, and **SHOULD** support all the protocols specified in the -following sub-sections. - -##### The `aws` protocol -Environments using the `aws` protocol have the following format: -``` -aws://account/region -╰┬╯ ╰──┬──╯ ╰──┬─╯ - │ │ └─ The name of an AWS region (e.g: eu-west-1) - │ └───────── An AWS account ID (e.g: 123456789012) - └───────────────── AWS protocol specifier -``` - -### Metadata -Metadata can be attached to [Droplets](#droplet) to allow tools that work with *Cloud Assemblies* to share additional -information about the *cloud application*. Metadata **SHOULD NOT** be used to convey data that is necessary for -correctly process the *Cloud Assembly*, since any tool that consumes a *Cloud Assembly* **MAY** choose to ignore any or -all Metadata. - -Key |Type |Required|Description --------|--------|:------:|----------- -`kind` |`string`|Required|A token identifying the kind of metadata. -`value`|`any` |Required|The value associated with this metadata. - -A common use-case for Metadata is reporting warning or error messages that were emitted during the creation of the -*Cloud Assembly*, so that deployment systems can present this information to users or logs. Warning and error messages -**SHOULD** set the `kind` field to `warning` and `error` respectively, and the `value` field **SHOULD** contain a single -`string`. Deployment systems **MAY** reject *Cloud Assemblies* that include [Droplets](#droplet) that carry one or more -`error` Metadata entries, and they **SHOULD** surface `warning` messages, either directly through their user interface, -or in the execution log. - -### Missing -[Droplets](#droplet) may require contextual information to be available in order to correctly participate in a -*Cloud Assembly*. When information is missing, *Cloud Assembly* producers report the missing information by adding -entries to the `missing` section of the [manifest document](#manifest-document). The values are [JSON] `object`s that -**MUST** conform to the following schema: - -Key |Type |Required|Description ----------------|-----------------|:------:|----------- -`provider` |`string` |Required|A tag that identifies the entity that should provide the information. -`props` |`Map`|Required|Properties that are required in order to obtain the missing information. - -### Digital Signature -#### Signing -*Cloud Assemblers* **SHOULD** support digital signature of *Cloud Assemblies*. When support for digital signature is -present, *Cloud Assemblers*: -* **MUST** require configuration of the [PGP][RFC 4880] key that will be used for signing. - -##### Signing Algorithm -The digital signature of *Cloud Assemblies* starts by establishing an attestation document that provides cryptographic -summary information about the contents of the signed assembly. It is a [JSON] document composed of a single `object` -with the following fields: - -Field |Type |Description ------------|----------------------|----------- -`timestamp`|`string` |The [ISO 8601] timestamp of the attestation document creation time -`algorithm`|`string` |The hashing algorithm used to derive the `FileData` hashes. -`nonce` |`string` |The nonce used when deriving the `FileData` hashes. -`items` |`Map`|Summary information about the attested files. - -The `algorithm` field **MUST** be set to the standard identifier of a standard hashing algorithm, such as `SHA256`. -Algorithms that are vulnerable to known collision attacks **SHOULD** not be used. - -The `nonce` field **MUST** be set to a byte array generated using a cryptographically secure random number generator. A -`nonce` **MUST NOT** be re-used. It **MUST** be composed of at least `32` bytes, and **SHOULD** be the same length or -larger than the size of the output of the chosen `algorithm`. - -The `items` field **MUST** contain one entry for each file in the *Cloud Assembly*, keyed on the relative path to the -file within the container document, with a value that contains the following keys: -Key |Type |Description -------|--------|----------- -`size`|`string`|The decimal representation of the file size in bytes. -`hash`|`string`|The base-64 encoded result of hashing the file's content appended with the `nonce` using the `algorithm`. - -Here is a schematic example: -```js -{ - // When this attestation document was created - "timestamp": "2018-11-15T11:08:52", - // The hashing algorithm for the attestation is SHA256 - "algorithm": "SHA256", - // 32 bytes of cryptographically-secure randomness - "nonce": "2tDLdIoy1VtzLFjfzXVqzsNJHa9862y/WQgqKzC9+xs=", - "items": { - "data/data.bin": { - // The file is really 1024 times the character 'a' - "size": "1024", - // SHA256( + ) - "hash": "HIKJYDnT92EKILbFt2SOzA8dWF0YMEBHS72xLSw4lok=" - }, - /* ...other files of the assembly... */ - } -} -``` - -Once the attestation is ready, it is digitally *signed* using the configured [PGP][RFC 4880] key. The key **MUST** be -valid as of the `timestamp` field included in the attestation. The signature **MUST** not be detached, and is -**RECOMMENDED** to use the *cleartext signature framework* described in section 7 of [RFC 4880] so the attestation can -be read by a human. - -#### Verifying -Deployment systems **SHOULD** support verifying signed *Cloud Assemblies*. If support for signature verification is not -present, a warning **MUST** be emitted when processing a *Cloud Assembly* that contains the `signature.asc` file. - -Deployment systems that support verifying signed *Cloud Assemblies*: -* **SHOULD** be configurable to *require* that an assembly is signed. When this requirement is active, an error **MUST** - be returned when attempting to deploy an un-signed *Cloud Assembly*. -* **MUST** verify the integrity and authenticity of signed *Cloud Assemblies* prior to attempting to load any file - included in it, except for `signature.asc`. - * An error **MUST** be raised if the *Cloud Assembly*'s integrity is not verified by the signature. - * An error **MUST** be raised if the [PGP][RFC 4880] key has expired according to the signature timestamp. - * An error **MUST** be raised if the [PGP][RFC 4880] key is known to have been revoked. Deployment systems **MAY** - trust locally available information pertaining to the key's validity. -* **SHOULD** allow configuration of a list of trusted [PGP][RFC 4880] keys. - -## Annex -### Examples of Droplets for the AWS Cloud -The Droplet specifications provided in this section are for illustration purpose only. - -#### `@aws-cdk/aws-cloudformation.StackDroplet` -A [*CloudFormation* stack][CFN Stack]. - -##### Properties -Property |Type |Required|Description --------------|--------------------|:------:|----------- -`stackName` |`string` |Required|The name of the *CloudFormation* stack once deployed. -`template` |`string` |Required|The assembly-relative path to the *CloudFormation* template document. -`parameters` |`Map`| |Parameters to be passed to the [stack][CFN Stack] upon deployment. -`stackPolicy`|`string` | |The assembly-relative path to the [Stack Policy][CFN Stack Policy]. - -##### Output Attributes -Attribute |Type |Description ----------------|--------------------|----------- -`output.`|`string`|Data returned by the [*CloudFormation* Outputs][CFN Output] named `` of the stack. -`stackArn` |`string`|The ARN of the [stack][CFN Stack]. - -##### Example -```json -{ - "type": "npm://@aws-cdk/aws-cloudformation.StackDroplet", - "environment": "aws://000000000000/bermuda-triangle-1", - "properties": { - "template": "my-stack/template.yml", - "parameters": { - "bucketName": "${helperStack.output.bucketName}", - "objectKey": "${helperStack.output.objectKey}" - }, - "stackPolicy": "my-stack/policy.json" - } -} -``` - -[CFN Stack]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacks.html -[CFN Stack Policy]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html -[CFN Outputs]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html - -#### `@aws-cdk/assets.FileDroplet` -A file that needs to be uploaded to an *S3* bucket. - -##### Properties -Property |Type |Required|Description -------------|--------|:------:|----------- -`file` |`string`|Required|The assembly-relative path to the file that will be uploaded. -`bucketName`|`string`|Required|The name of the bucket where this file will be uploaded. -`objectKey` |`string`|Required|The key at which to place the object in the bucket. - -##### Output Attributes -Attribute |Type |Description -------------|--------|----------- -`bucketName`|`string`|The name of the bucket where the file was uploaded. -`objectKey` |`string`|The key at which the file was uploaded in the bucket. - -##### Example -```json -{ - "type": "npm://@aws-cdk/assets.FileDroplet", - "environment": "aws://000000000000/bermuda-triangle-1", - "properties": { - "file": "assets/file.bin", - "bucket": "${helperStack.outputs.bucketName}", - "objectKey": "assets/da39a3ee5e6b4b0d3255bfef95601890afd80709/nifty-asset.png" - } -} -``` - -#### `@aws-cdk/aws-ecr.DockerImageDroplet` -A Docker image to be published to an *ECR* registry. - -##### Properties -Property |Type |Required|Description -------------|--------|:------:|----------- -`savedImage`|`string`|Required|The assembly-relative path to the tar archive obtained from `docker image save`. -`pushTarget`|`string`|Required|Where the image should be pushed to (e.g: `.dkr.ecr..amazon.com/`). -`tagName` |`string`| |The name of the tag to use when pushing the image (default: `latest`). - -##### Output Attributes -Attribute |Type |Description ---------------|--------|----------- -`exactImageId`|`string`|An absolute reference to the published image version (`imageName@DIGEST`). -`imageName` |`string`|The full tagged image name (`imageName:tagName`). - -##### Example -```json -{ - "type": "npm://@aws-cdk/aws-ecr.DockerImageDroplet", - "environment": "aws://000000000000/bermuda-triangle-1", - "properties": { - "savedImage": "docker/37e6de0b24fa.tar", - "imageName": "${helperStack.output.ecrImageName}", - "tagName": "latest" - } -} -``` - -### Example -Here is an example the contents of a complete *Cloud Assembly* that deploys AWS resources: -``` -☁️ my-assembly.cloud -├─ manifest.json Cloud Assembly manifest -├─ stacks -│ ├─ PipelineStack.yml CloudFormation template -│ ├─ ServiceStack-beta.yml CloudFormation template -│ ├─ ServiceStack-beta.stack-policy.json CloudFormation stack policy -│ ├─ ServiceStack-prod.yml CloudFormation template -│ └─ ServiceStack-prod.stack-policy.json CloudFormation stack policy -├─ docker -│ └─ docker-image.tar Saved Docker image (docker image save) -├─ assets -│ └─ static-website Files for a static website -│ ├─ index.html -│ └─ style.css -└─ signature.asc Cloud Assembly digital signature -``` - -#### `manifest.json` -```json -{ - "schema": "cloud-assembly/1.0", - "droplets": { - "PipelineStack": { - "type": "npm://@aws-cdk/aws-cloudformation.StackDroplet", - "environment": "aws://123456789012/eu-west-1", - "properties": { - "template": "stacks/PipelineStack.yml" - } - }, - "ServiceStack-beta": { - "type": "npm://@aws-cdk/aws-cloudformation.StackDroplet", - "environment": "aws://123456789012/eu-west-1", - "properties": { - "template": "stacks/ServiceStack-beta.yml", - "stackPolicy": "stacks/ServiceStack-beta.stack-policy.json", - "parameters": { - "image": "${DockerImage.exactImageId}", - "websiteFilesBucket": "${StaticFiles.bucketName}", - "websiteFilesKeyPrefix": "${StaticFiles.keyPrefix}", - } - } - }, - "ServiceStack-prod": { - "type": "npm://@aws-cdk/aws-cloudformation.StackDroplet", - "environment": "aws://123456789012/eu-west-1", - "properties": { - "template": "stacks/ServiceStack-prod.yml", - "stackPolicy": "stacks/ServiceStack-prod.stack-policy.json", - "parameters": { - "image": "${DockerImage.exactImageId}", - "websiteFilesBucket": "${StaticFiles.bucketName}", - "websiteFilesKeyPrefix": "${StaticFiles.keyPrefix}", - } - } - }, - "DockerImage": { - "type": "npm://@aws-cdk/aws-ecr.DockerImageDroplet", - "environment": "aws://123456789012/eu-west-1", - "properties": { - "savedImage": "docker/docker-image.tar", - "imageName": "${PipelineStack.output.ecrImageName}" - } - }, - "StaticFiles": { - "type": "npm://@aws-cdk/assets.DirectoryDroplet", - "environment": "aws://123456789012/eu-west-1", - "properties": { - "directory": "assets/static-website", - "bucketName": "${PipelineStack.output.stagingBucket}" - } - } - } -} -``` - -#### `signature.asc` -```pgp ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA256 - -{ - "algorithm": "SHA-256", - "items": { - "assets/static-website/index.html": { - "size": ..., - "hash": "..." - }, - "assets/static-website/style.css": { - "size": ..., - "hash": "..." - }, - "docker/docker-image.tar": { - "size": ..., - "hash": "..." - }, - "manifest.json": { - "size": ..., - "hash": "..." - }, - "stacks/PipelineStack.yml": { - "size": ..., - "hash": "..." - }, - "stacks/ServiceStack-beta.stack-policy.json": { - "size": ..., - "hash": "..." - }, - "stacks/ServiceStack-beta.yml": { - "size": ..., - "hash": "..." - }, - "stacks/ServiceStack-prod.stack-policy.json": { - "size": ..., - "hash": "..." - }, - "stacks/ServiceStack-prod.yml": { - "size": ..., - "hash": "..." - }, - }, - "nonce": "mUz0aYEhMlVmhJLNr5sizPKlJx1Kv38ApBc12NW6wPE=", - "timestamp": "2018-11-06T14:56:23Z" -} ------BEGIN PGP SIGNATURE----- -[...] ------END PGP SIGNATURE----- -``` - - -[RFC 2119]: https://tools.ietf.org/html/rfc2119 -[ISO/IEC 21320-1:2015]: https://www.iso.org/standard/60101.html -[JSON]: https://www.json.org -[RFC 4880]: https://tools.ietf.org/html/rfc4880 -[ISO 8601]: https://www.iso.org/standard/40874.html +See [cloud-assembly-schema](../packages/@aws-cdk/cloud-assembly-schema/README.md) \ No newline at end of file diff --git a/package.json b/package.json index 5009750f03cba..8f5ee541a2c86 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,10 @@ "@aws-cdk/aws-ecr-assets/minimatch/**", "@aws-cdk/aws-lambda-nodejs/parcel-bundler", "@aws-cdk/aws-lambda-nodejs/parcel-bundler/**", + "@aws-cdk/cloud-assembly-schema/jsonschema", + "@aws-cdk/cloud-assembly-schema/jsonschema/**", + "@aws-cdk/cloud-assembly-schema/semver", + "@aws-cdk/cloud-assembly-schema/semver/**", "@aws-cdk/cx-api/semver", "@aws-cdk/cx-api/semver/**", "@aws-cdk/cx-api/semver/**" 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 b2176cb6619dd..713b662808ad4 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 @@ -3,8 +3,8 @@ import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as cpactions from '@aws-cdk/aws-codepipeline-actions'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; export interface PipelineDeployStackActionProps { /** @@ -101,7 +101,7 @@ export class PipelineDeployStackAction implements codepipeline.IAction { constructor(props: PipelineDeployStackActionProps) { this.stack = props.stack; - const assets = this.stack.node.metadata.filter(md => md.type === cxapi.ASSET_METADATA); + const assets = this.stack.node.metadata.filter(md => md.type === cxschema.ArtifactMetadataEntryType.ASSET); if (assets.length > 0) { // FIXME: Implement the necessary actions to publish assets throw new Error(`Cannot deploy the stack ${this.stack.stackName} because it references ${assets.length} asset(s)`); diff --git a/packages/@aws-cdk/app-delivery/package.json b/packages/@aws-cdk/app-delivery/package.json index 1680e7a4bbe1a..156f4572f54cf 100644 --- a/packages/@aws-cdk/app-delivery/package.json +++ b/packages/@aws-cdk/app-delivery/package.json @@ -48,6 +48,7 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "constructs": "^2.0.0" }, "devDependencies": { @@ -85,6 +86,7 @@ "@aws-cdk/aws-iam": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "constructs": "^2.0.0" }, "engines": { 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 5146eae9b58cd..28abbdad6936c 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 @@ -6,8 +6,8 @@ import * as cpactions from '@aws-cdk/aws-codepipeline-actions'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; import * as fc from 'fast-check'; import * as nodeunit from 'nodeunit'; import { PipelineDeployStackAction } from '../lib/pipeline-deploy-stack-action'; @@ -390,7 +390,7 @@ export = nodeunit.testCase({ const deployedStack = new cdk.Stack(app, 'DeployedStack'); for (let i = 0 ; i < assetCount ; i++) { - deployedStack.node.addMetadata(cxapi.ASSET_METADATA, {}); + deployedStack.node.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, {}); } test.throws(() => { diff --git a/packages/@aws-cdk/assert/lib/inspector.ts b/packages/@aws-cdk/assert/lib/inspector.ts index a1567657460e9..f633de428f4f2 100644 --- a/packages/@aws-cdk/assert/lib/inspector.ts +++ b/packages/@aws-cdk/assert/lib/inspector.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as api from '@aws-cdk/cx-api'; import { Assertion, not } from './assertion'; import { MatchStyle, matchTemplate } from './assertions/match-template'; @@ -65,9 +66,9 @@ export class StackPathInspector extends Inspector { const metadata = this.stack.manifest.metadata || {}; const md = metadata[this.path] || metadata[`/${this.stack.id}${this.path}`]; if (md === undefined) { return undefined; } - const resourceMd = md.find(entry => entry.type === api.LOGICAL_ID_METADATA_KEY); + const resourceMd = md.find(entry => entry.type === cxschema.ArtifactMetadataEntryType.LOGICAL_ID); if (resourceMd === undefined) { return undefined; } - const logicalId = resourceMd.data; + const logicalId = resourceMd.data as cxschema.LogMessageMetadataEntry; return this.stack.template.Resources[logicalId]; } } diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index cf86bae2aff2c..4149ff2f8c597 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -39,6 +39,7 @@ "@aws-cdk/cloudformation-diff": "0.0.0", "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "constructs": "^2.0.0" }, "peerDependencies": { diff --git a/packages/@aws-cdk/assert/test/have-output.test.ts b/packages/@aws-cdk/assert/test/have-output.test.ts index 75494cd2f68cd..d7d3cfa300eb7 100644 --- a/packages/@aws-cdk/assert/test/have-output.test.ts +++ b/packages/@aws-cdk/assert/test/have-output.test.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { unlink, writeFileSync } from 'fs'; import { join } from 'path'; @@ -187,7 +188,7 @@ function mkStack(template: any): cxapi.CloudFormationStackArtifact { const assembly = new cxapi.CloudAssemblyBuilder(); assembly.addArtifact(stackName, { - type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, environment: cxapi.EnvironmentUtils.format('123456789012', 'bermuda-triangle-1'), properties: { templateFile: templateFileName diff --git a/packages/@aws-cdk/assert/test/have-resource.test.ts b/packages/@aws-cdk/assert/test/have-resource.test.ts index 00b131b26f28f..e272701be7f18 100644 --- a/packages/@aws-cdk/assert/test/have-resource.test.ts +++ b/packages/@aws-cdk/assert/test/have-resource.test.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { writeFileSync } from 'fs'; import { join } from 'path'; @@ -142,7 +143,7 @@ describe('property absence', () => { function mkStack(template: any): cxapi.CloudFormationStackArtifact { const assembly = new cxapi.CloudAssemblyBuilder(); assembly.addArtifact('test', { - type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, environment: cxapi.EnvironmentUtils.format('123456789', 'us-west-2'), properties: { templateFile: 'template.json' diff --git a/packages/@aws-cdk/aws-autoscaling/package.json b/packages/@aws-cdk/aws-autoscaling/package.json index 863ce0dc470d6..f850f70c28ead 100644 --- a/packages/@aws-cdk/aws-autoscaling/package.json +++ b/packages/@aws-cdk/aws-autoscaling/package.json @@ -69,7 +69,8 @@ "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "nodeunit": "^0.11.3", - "pkglint": "0.0.0" + "pkglint": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0" }, "dependencies": { "@aws-cdk/aws-autoscaling-common": "0.0.0", 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 041052f1e4b9c..8bfe5b624056f 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 @@ -2,8 +2,8 @@ import { ABSENT, expect, haveResource, haveResourceLike, InspectionFailure, Reso import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; import { Test } from 'nodeunit'; import * as autoscaling from '../lib'; @@ -827,7 +827,7 @@ export = { }); // THEN - test.deepEqual(asg.node.metadata[0].type, cxapi.WARNING_METADATA_KEY); + test.deepEqual(asg.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); test.deepEqual(asg.node.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); test.done(); @@ -854,7 +854,7 @@ export = { }); // THEN - test.deepEqual(asg.node.metadata[0].type, cxapi.WARNING_METADATA_KEY); + test.deepEqual(asg.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); test.deepEqual(asg.node.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); test.done(); diff --git a/packages/@aws-cdk/aws-ec2/package.json b/packages/@aws-cdk/aws-ec2/package.json index 34b1a881fc0bc..6ad5a8a9e7e5e 100644 --- a/packages/@aws-cdk/aws-ec2/package.json +++ b/packages/@aws-cdk/aws-ec2/package.json @@ -68,7 +68,8 @@ "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", "nodeunit": "^0.11.3", - "pkglint": "0.0.0" + "pkglint": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0" }, "dependencies": { "@aws-cdk/aws-cloudwatch": "0.0.0", diff --git a/packages/@aws-cdk/aws-ec2/test/test.instance.ts b/packages/@aws-cdk/aws-ec2/test/test.instance.ts index e72ed39d3dcd3..b8fe78b6974b7 100644 --- a/packages/@aws-cdk/aws-ec2/test/test.instance.ts +++ b/packages/@aws-cdk/aws-ec2/test/test.instance.ts @@ -1,7 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import { StringParameter } from '@aws-cdk/aws-ssm'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Stack } from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; import { Test } from 'nodeunit'; import { AmazonLinuxImage, BlockDeviceVolume, EbsDeviceVolumeType, Instance, InstanceClass, InstanceSize, InstanceType, Vpc } from '../lib'; @@ -237,7 +237,7 @@ export = { }); // THEN - test.deepEqual(instance.node.metadata[0].type, cxapi.WARNING_METADATA_KEY); + test.deepEqual(instance.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); test.deepEqual(instance.node.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); test.done(); @@ -264,7 +264,7 @@ export = { }); // THEN - test.deepEqual(instance.node.metadata[0].type, cxapi.WARNING_METADATA_KEY); + test.deepEqual(instance.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); test.deepEqual(instance.node.metadata[0].data, 'iops will be ignored without volumeType: EbsDeviceVolumeType.IO1'); test.done(); diff --git a/packages/@aws-cdk/aws-ecr-assets/package.json b/packages/@aws-cdk/aws-ecr-assets/package.json index 0332e1aa36bd8..4d8169f5b36af 100644 --- a/packages/@aws-cdk/aws-ecr-assets/package.json +++ b/packages/@aws-cdk/aws-ecr-assets/package.json @@ -67,7 +67,8 @@ "cdk-integ-tools": "0.0.0", "nodeunit": "^0.11.3", "pkglint": "0.0.0", - "proxyquire": "^2.1.3" + "proxyquire": "^2.1.3", + "@aws-cdk/cloud-assembly-schema": "0.0.0" }, "dependencies": { "@aws-cdk/assets": "0.0.0", diff --git a/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts b/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts index 05b216ebda9a8..730ce0e93a89b 100644 --- a/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts +++ b/packages/@aws-cdk/aws-ecr-assets/test/test.image-asset.ts @@ -1,7 +1,7 @@ import { expect, haveResource } from '@aws-cdk/assert'; import * as iam from '@aws-cdk/aws-iam'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { App, Lazy, Stack } from '@aws-cdk/core'; -import { ASSET_METADATA } from '@aws-cdk/cx-api'; import * as fs from 'fs'; import { Test } from 'nodeunit'; import * as path from 'path'; @@ -50,8 +50,8 @@ export = { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === ASSET_METADATA); - test.deepEqual(assetMetadata && assetMetadata.data.buildArgs, { a: 'b' }); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + test.deepEqual(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).buildArgs, { a: 'b' }); test.done(); }, @@ -69,8 +69,8 @@ export = { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === ASSET_METADATA); - test.deepEqual(assetMetadata && assetMetadata.data.target, 'a-target'); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + test.deepEqual(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).target, 'a-target'); test.done(); }, @@ -86,8 +86,8 @@ export = { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === ASSET_METADATA); - test.deepEqual(assetMetadata && assetMetadata.data.file, 'Dockerfile.Custom'); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + test.deepEqual(assetMetadata && (assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry).file, 'Dockerfile.Custom'); test.done(); }, diff --git a/packages/@aws-cdk/aws-efs/package.json b/packages/@aws-cdk/aws-efs/package.json index 408bd2c3566e6..faebde4028261 100644 --- a/packages/@aws-cdk/aws-efs/package.json +++ b/packages/@aws-cdk/aws-efs/package.json @@ -89,6 +89,7 @@ "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "constructs": "^2.0.0" }, "homepage": "https://github.com/aws/aws-cdk", @@ -97,6 +98,7 @@ "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-kms": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "constructs": "^2.0.0" }, "engines": { diff --git a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts index d89d38d3798c0..e42469da4b3ff 100644 --- a/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts +++ b/packages/@aws-cdk/aws-efs/test/efs-file-system.test.ts @@ -1,8 +1,8 @@ import {expect as expectCDK, haveResource} from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import {Stack} from '@aws-cdk/core'; -import {WARNING_METADATA_KEY} from '@aws-cdk/cx-api'; import {EfsFileSystem, EfsLifecyclePolicyProperty, EfsPerformanceMode, EfsThroughputMode} from '../lib/efs-file-system'; let stack = new Stack(); @@ -127,7 +127,7 @@ test('Warning when provisioned throughput is less than the valid range', () => { provisionedThroughputInMibps: 0 }); - expect(fileSystem.node.metadata[0].type).toMatch(WARNING_METADATA_KEY); + expect(fileSystem.node.metadata[0].type).toMatch(cxschema.ArtifactMetadataEntryType.WARN); expect(fileSystem.node.metadata[0].data).toContain('Valid values for throughput are 1-1024 MiB/s'); expect(fileSystem.node.metadata[0].data).toContain('You can get this limit increased by contacting AWS Support'); @@ -141,7 +141,7 @@ test('Warning when provisioned throughput is above than the valid range', () => provisionedThroughputInMibps: 1025 }); - expect(fileSystem.node.metadata[0].type).toMatch(WARNING_METADATA_KEY); + expect(fileSystem.node.metadata[0].type).toMatch(cxschema.ArtifactMetadataEntryType.WARN); expect(fileSystem.node.metadata[0].data).toContain('Valid values for throughput are 1-1024 MiB/s'); expect(fileSystem.node.metadata[0].data).toContain('You can get this limit increased by contacting AWS Support'); diff --git a/packages/@aws-cdk/aws-s3-assets/package.json b/packages/@aws-cdk/aws-s3-assets/package.json index 9a4d6c39586e0..9cf786fa4215a 100644 --- a/packages/@aws-cdk/aws-s3-assets/package.json +++ b/packages/@aws-cdk/aws-s3-assets/package.json @@ -68,6 +68,7 @@ "nodeunit": "^0.11.3", "pkglint": "0.0.0", "sinon": "^9.0.2", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "ts-mock-imports": "^1.3.0" }, "dependencies": { diff --git a/packages/@aws-cdk/aws-s3-assets/test/test.asset.ts b/packages/@aws-cdk/aws-s3-assets/test/test.asset.ts index b7d6b71d5ee2c..873b9849fe29a 100644 --- a/packages/@aws-cdk/aws-s3-assets/test/test.asset.ts +++ b/packages/@aws-cdk/aws-s3-assets/test/test.asset.ts @@ -1,5 +1,6 @@ import { expect, haveResource, ResourcePart, SynthUtils } from '@aws-cdk/assert'; import * as iam from '@aws-cdk/aws-iam'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cdk from '@aws-cdk/core'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; @@ -341,7 +342,7 @@ export = { const session = app.synth(); const artifact = session.getStackByName(stack.stackName); const metadata = artifact.manifest.metadata || {}; - const md = Object.values(metadata)[0]![0]!.data; + const md = Object.values(metadata)[0]![0]!.data as cxschema.AssetMetadataEntry; test.deepEqual(md.path, 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2'); test.done(); } diff --git a/packages/@aws-cdk/cloud-assembly-schema/.gitignore b/packages/@aws-cdk/cloud-assembly-schema/.gitignore new file mode 100644 index 0000000000000..954f38c905f96 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/.gitignore @@ -0,0 +1,17 @@ +lib/*.js +test/*.js +scripts/*.js +*.js.map +*.d.ts +node_modules +dist +tsconfig.json +tslint.json +.jsii + +.LAST_BUILD +.LAST_PACKAGE +*.snk +.nyc_output +coverage +nyc.config.js \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/.npmignore b/packages/@aws-cdk/cloud-assembly-schema/.npmignore new file mode 100644 index 0000000000000..5f2fbb23042e4 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/.npmignore @@ -0,0 +1,20 @@ +# 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 + +*.tsbuildinfo + +tsconfig.json \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/CONTRIBUTING.md b/packages/@aws-cdk/cloud-assembly-schema/CONTRIBUTING.md new file mode 100644 index 0000000000000..0938fdb9e465f --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/CONTRIBUTING.md @@ -0,0 +1,67 @@ +## Cloud Assembly Schema + +Making changes to this module should only happen when you introduce new cloud assembly capabilities. + +> For example: supporting the `--target` option when building docker containers. + +If you decided these changes are necessary, simply go ahead and make the necessary modifications to +the interfaces that describe the schema. Our tests and validation mechanisms will ensure you make those +changes correctly. + +### Module Structure + +There are two main things to understand about the files in this module: + +- [`lib/manifest.ts`](./lib/manifest.ts) + + This is the typescript code that defines our schema. It is solely comprised of structs (property only interfaces). + It directly maps to the way we want manifest files to be stored on disk. When you want to make changes to the schema, + this is the file you should be editing. + +- [`lib/schema`](./schema/) + + This directory contains the generated json [schema](./schema/cloud-assembly.schema.json) from the aformentioned + typescript code. It also contains a [version](./schema/cloud-assembly.version.json) file that holds the current version + of the schema. These files are **not** intended for manual editing. Keep reading to understand how they change and when. + +### Schema Generation + +The schema can be generated by running `yarn update-schema`. It reads the [`manifest.ts`](./lib/manifest.ts) file and writes +an updated json schema to [`cloud-assembly.schema.json`](./schema/cloud-assembly.schema.json). +In addition, this command also performs a `major` version bump on the [version](./schema/cloud-assembly.version.json) file. + +Note that it is not generated as part of the build, this is to ensure developers will be intentional when making +changes to the schema. If changes to the code are perfomed, without generating a new schema, the tests will fail: + +```console +$ yarn test +FAIL test/schema.test.js (5.902s) + ✓ manifest save (7ms) + ✕ cloud-assembly.json.schema is correct (5304ms) + ✓ manifest load (4ms) + ✓ manifest load fails for invalid nested property (5ms) + ✓ manifest load fails for invalid artifact type (1ms) + ✓ stack-tags are deserialized properly (1ms) + ✓ can access random metadata (1ms) + + ● cloud-assembly.json.schema is correct + + Whoops, Looks like the schema has changed. Did you forget to run 'yarn update-schema'? +``` + +### Schema Validation + +Being a **stable** `jsii` module, it undergoes strict API compatibility checks with the help +of [`jsii-diff`](https://github.com/aws/jsii/tree/master/packages/jsii-diff). +This means that breaking changes will be rejected. These include: + +- Adding a required property. (same as changing from *optional* to *required*) +- Changing the type of the property. + +In addition, the interfaces defined here are programatically exposed to users, via the `manifest` +property of the [`CloudAssembly`]((../cx-api/lib/cloud-assembly.ts)) class. This means that the following are +also considered breaking changes: + +- Changing a property from *required* to *optional*. +- Removing an optional property. +- Removing a required property. diff --git a/packages/@aws-cdk/cloud-assembly-schema/LICENSE b/packages/@aws-cdk/cloud-assembly-schema/LICENSE new file mode 100644 index 0000000000000..b71ec1688783a --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/cloud-assembly-schema/NOTICE b/packages/@aws-cdk/cloud-assembly-schema/NOTICE new file mode 100644 index 0000000000000..bfccac9a7f69c --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/cloud-assembly-schema/README.md b/packages/@aws-cdk/cloud-assembly-schema/README.md new file mode 100644 index 0000000000000..74f693b450d4f --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/README.md @@ -0,0 +1,62 @@ +## Cloud Assembly Schema + + +--- + +![Stability: Stable](https://img.shields.io/badge/stability-Stable-success.svg?style=for-the-badge) + + +--- + + +This module is part of the [AWS Cloud Development Kit](https://github.com/aws/aws-cdk) project. + +## Cloud Assembly + +The *Cloud Assembly* is the output of the synthesis operation. It is produced as part of the +[`cdk synth`](https://github.com/aws/aws-cdk/tree/master/packages/aws-cdk#cdk-synthesize) +command, or the [`app.synth()`](https://github.com/aws/aws-cdk/blob/master/packages/@aws-cdk/core/lib/app.ts#L135) method invocation. + +Its essentially a set of files and directories, one of which is the `manifest.json` file. It defines the set of instructions that are +needed in order to deploy the assembly directory. + +> For example, when `cdk deploy` is executed, the CLI reads this file and performs its instructions: +> - Build container images. +> - Upload assets. +> - Deploy CloudFormation templates. + +Therefore, the assembly is how the CDK class library and CDK CLI (or any other consumer) communicate. To ensure compatibility +between the assembly and its consumers, we treat the manifest file as a well defined, versioned schema. + +## Schema + +This module contains the typescript structs that comprise the `manifest.json` file, as well as the +generated [*json-schema*](./schema/cloud-assembly.schema.json). + +## Versioning + +The schema version is specified in the [`cloud-assembly.version.json`](./schema/cloud-assembly.schema.json) file, under the `version` property. +It follows semantic versioning, but with a small twist. + +When we add instructions to the assembly, they are reflected in the manifest file and the *json-schema* accordingly. +Every such instruction, is crucial for ensuring the correct deployment behavior. This means that to properly deploy a cloud assembly, +consumers must be aware of every such instruction modification. + +For this reason, every change to the schema, even though it might not strictly break validation of the *json-schema* format, +is considered `major` version bump. + +## How to consume + +If you'd like to consume the [schema file](./schema/cloud-assembly.schema.json) in order to do validations on `manifest.json` files, +simply download it from this repo and run it against standard *json-schema* validators, such as [jsonschema](https://www.npmjs.com/package/jsonschema). + +Consumers must take into account the `major` version of the schema they are consuming. They should reject cloud assemblies +with a `major` version that is higher than what they expect. While schema validation might pass on such assemblies, the deployment integrity +cannot be guaranteed because some instructions will be ignored. + +> For example, if your consumer was built when the schema version was 2.0.0, you should reject deploying cloud assemblies with a +> manifest version of 3.0.0. + +## Contributing + +See [Contribution Guide](./CONTRIBUTING.md) \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/index.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/index.ts new file mode 100644 index 0000000000000..f12754d9eecd4 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/index.ts @@ -0,0 +1,2 @@ +export * from './manifest'; +export * from './schema'; diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts new file mode 100644 index 0000000000000..17b4c86cc9a68 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts @@ -0,0 +1,119 @@ +import * as fs from 'fs'; +import * as jsonschema from 'jsonschema'; +import * as semver from 'semver'; +import * as assembly from './schema'; + +// this prefix is used by the CLI to identify this specific error. +// in which case we want to instruct the user to upgrade his CLI. +// see exec.ts#createAssembly +export const VERSION_MISMATCH: string = 'Cloud assembly schema version mismatch'; + +/** + * Protocol utility class. + */ +export class Manifest { + /** + * Save manifest to file. + * + * @param manifest - manifest. + */ + public static save(manifest: assembly.AssemblyManifest, filePath: string) { + fs.writeFileSync(filePath, JSON.stringify(manifest, undefined, 2)); + } + + /** + * Load manifest from file. + * + * @param filePath - path to the manifest file. + */ + public static load(filePath: string): assembly.AssemblyManifest { + const raw: assembly.AssemblyManifest = JSON.parse(fs.readFileSync(filePath, 'UTF-8')); + Manifest.patchStackTags(raw); + Manifest.validate(raw); + return raw; + } + + /** + * Fetch the current schema version number. + */ + public static version(): string { + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require('../schema/cloud-assembly.version.json').version; + } + + // eslint-disable-next-line @typescript-eslint/no-require-imports + private static schema: jsonschema.Schema = require('../schema/cloud-assembly.schema.json'); + + private static validate(manifest: assembly.AssemblyManifest) { + + function parseVersion(version: string) { + const ver = semver.valid(version); + if (!ver) { + throw new Error(`Invalid semver string: "${version}"`); + } + return ver; + } + + const maxSupported = parseVersion(Manifest.version()); + const actual = parseVersion(manifest.version); + + // first validate the version should be accepted. + if (semver.gt(actual, maxSupported)) { + // we use a well known error prefix so that the CLI can identify this specific error + // and print some more context to the user. + throw new Error(`${VERSION_MISMATCH}: Maximum schema version supported is ${maxSupported}, but found ${actual}`); + } + + // now validate the format is good. + const validator = new jsonschema.Validator(); + const result = validator.validate(manifest, Manifest.schema, { + + // does exist but is not in the TypeScript definitions + nestedErrors: true, + + allowUnknownAttributes: false + + } as any); + if (!result.valid) { + throw new Error(`Invalid assembly manifest:\n${result}`); + } + + } + + /** + * This requires some explaining... + * + * We previously used `{ Key, Value }` for the object that represents a stack tag. (Notice the casing) + * @link https://github.com/aws/aws-cdk/blob/v1.27.0/packages/aws-cdk/lib/api/cxapp/stacks.ts#L427. + * + * When that object moved to this package, it had to be JSII compliant, which meant the property + * names must be `camelCased`, and not `PascalCased`. This meant it no longer matches the structure in the `manifest.json` file. + * In order to support current manifest files, we have to translate the `PascalCased` representation to the new `camelCased` one. + * + * Note that the serialization itself still writes `PascalCased` because it relates to how CloudFormation expects it. + * + * Ideally, we would start writing the `camelCased` and translate to how CloudFormation expects it when needed. But this requires nasty + * backwards-compatibility code and it just doesn't seem to be worth the effort. + */ + private static patchStackTags(manifest: assembly.AssemblyManifest) { + for (const artifact of Object.values(manifest.artifacts || [])) { + if (artifact.type === assembly.ArtifactType.AWS_CLOUDFORMATION_STACK) { + for (const metadataEntries of Object.values(artifact.metadata || [])) { + for (const metadataEntry of metadataEntries) { + if (metadataEntry.type === assembly.ArtifactMetadataEntryType.STACK_TAGS && metadataEntry.data) { + + const metadataAny = metadataEntry as any; + + metadataAny.data = metadataAny.data.map((t: any) => { + return { key: t.Key, value: t.Value }; + }); + } + } + } + } + } + } + + private constructor() {} + +} diff --git a/packages/@aws-cdk/cloud-assembly-schema/lib/schema.ts b/packages/@aws-cdk/cloud-assembly-schema/lib/schema.ts new file mode 100644 index 0000000000000..7b27ed49e8a6f --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/lib/schema.ts @@ -0,0 +1,339 @@ +/** + * Common properties for asset metadata. + */ +interface BaseAssetMetadataEntry { + /** + * Requested packaging style + */ + readonly packaging: string; + + /** + * Logical identifier for the asset + */ + readonly id: string; + + /** + * The hash of the asset source. + */ + readonly sourceHash: string; + + /** + * Path on disk to the asset + */ + readonly path: string; +} + +/** + * Metadata Entry spec for files. + */ +export interface FileAssetMetadataEntry extends BaseAssetMetadataEntry { + /** + * Requested packaging style + */ + readonly packaging: 'zip' | 'file'; + + /** + * Name of parameter where S3 bucket should be passed in + */ + readonly s3BucketParameter: string; + + /** + * Name of parameter where S3 key should be passed in + */ + readonly s3KeyParameter: string; + + /** + * The name of the parameter where the hash of the bundled asset should be passed in. + */ + readonly artifactHashParameter: string; +} + +/** + * Metadata Entry spec for container images. + */ +export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry { + /** + * Type of asset + */ + readonly packaging: 'container-image'; + + /** + * ECR Repository name and repo digest (separated by "@sha256:") where this + * image is stored. + * + * @default undefined If not specified, `repositoryName` and `imageTag` are + * required because otherwise how will the stack know where to find the asset, + * ha? + * @deprecated specify `repositoryName` and `imageTag` instead, and then you + * know where the image will go. + */ + readonly imageNameParameter?: string; + + /** + * ECR repository name, if omitted a default name based on the asset's ID is + * used instead. Specify this property if you need to statically address the + * image, e.g. from a Kubernetes Pod. Note, this is only the repository name, + * without the registry and the tag parts. + * + * @default - this parameter is REQUIRED after 1.21.0 + */ + readonly repositoryName?: string; + + /** + * The docker image tag to use for tagging pushed images. This field is + * required if `imageParameterName` is ommited (otherwise, the app won't be + * able to find the image). + * + * @default - this parameter is REQUIRED after 1.21.0 + */ + readonly imageTag?: string; + + /** + * Build args to pass to the `docker build` command + * + * @default no build args are passed + */ + readonly buildArgs?: { [key: string]: string }; + + /** + * Docker target to build to + * + * @default no build target + */ + readonly target?: string; + + /** + * Path to the Dockerfile (relative to the directory). + * + * @default - no file is passed + */ + readonly file?: string; +} + +/** + * Metadata Entry spec for stack tag. + */ +export interface Tag { + /** + * Tag key. + */ + readonly key: string + + /** + * Tag value. + */ + readonly value: string +} + +/** + * @see ArtifactMetadataEntryType.ASSET + */ +export type AssetMetadataEntry = FileAssetMetadataEntry | ContainerImageAssetMetadataEntry; + +// Type aliases for metadata entries. +// Used simply to assign names to data types for more clearity. + +/** + * @see ArtifactMetadataEntryType.INFO + * @see ArtifactMetadataEntryType.WARN + * @see ArtifactMetadataEntryType.ERROR + */ +export type LogMessageMetadataEntry = string; + +/** + * @see ArtifactMetadataEntryType.LOGICAL_ID + */ +export type LogicalIdMetadataEntry = string; + +/** + * @see ArtifactMetadataEntryType.STACK_TAGS + */ +export type StackTagsMetadataEntry = Tag[]; + +/** + * Union type for all metadata entries that might exist in the manifest. + */ +export type MetadataEntryData = AssetMetadataEntry | LogMessageMetadataEntry | LogicalIdMetadataEntry | StackTagsMetadataEntry; + +/** + * Type of artifact metadata entry. + */ +export enum ArtifactMetadataEntryType { + /** + * Asset in metadata. + */ + ASSET = 'aws:cdk:asset', + + /** + * Metadata key used to print INFO-level messages by the toolkit when an app is syntheized. + */ + INFO = 'aws:cdk:info', + + /** + * Metadata key used to print WARNING-level messages by the toolkit when an app is syntheized. + */ + WARN = 'aws:cdk:warning', + + /** + * Metadata key used to print ERROR-level messages by the toolkit when an app is syntheized. + */ + ERROR = 'aws:cdk:error', + + /** + * Represents the CloudFormation logical ID of a resource at a certain path. + */ + LOGICAL_ID = 'aws:cdk:logicalId', + + /** + * Represents tags of a stack. + */ + STACK_TAGS = 'aws:cdk:stack-tags' +} + +/** + * Type of cloud artifact. + */ +export enum ArtifactType { + /** + * Stub required because of JSII. + */ + NONE = 'none', // required due to a jsii bug + + /** + * The artifact is an AWS CloudFormation stack. + */ + AWS_CLOUDFORMATION_STACK = 'aws:cloudformation:stack', + + /** + * The artifact contains the CDK application's construct tree. + */ + CDK_TREE = 'cdk:tree', +} + +/** + * A metadata entry in a cloud assembly artifact. + */ +export interface MetadataEntry { + /** + * The type of the metadata entry. + */ + readonly type: string; + + /** + * The data. + * + * @default - no data. + */ + readonly data?: MetadataEntryData; + + /** + * A stack trace for when the entry was created. + * + * @default - no trace. + */ + readonly trace?: string[]; +} + +/** + * Information about the application's runtime components. + */ +export interface RuntimeInfo { + /** + * The list of libraries loaded in the application, associated with their versions. + */ + readonly libraries: { [name: string]: string }; +} + +/** + * Represents a missing piece of context. + */ +export interface MissingContext { + /** + * The missing context key. + */ + readonly key: string; + + /** + * The provider from which we expect this context key to be obtained. + */ + readonly provider: string; + + /** + * A set of provider-specific options. + */ + readonly props: { + account?: string; + region?: string; + [key: string]: any; + }; +} + +/** + * A manifest for a single artifact within the cloud assembly. + */ +export interface ArtifactManifest { + /** + * The type of artifact. + */ + readonly type: ArtifactType; + + /** + * The environment into which this artifact is deployed. + * + * @default - no envrionment. + */ + readonly environment?: string; // format: aws://account/region + + /** + * Associated metadata. + * + * @default - no metadata. + */ + readonly metadata?: { [path: string]: MetadataEntry[] }; + + /** + * IDs of artifacts that must be deployed before this artifact. + * + * @default - no dependencies. + */ + readonly dependencies?: string[]; + + /** + * The set of properties for this artifact (depends on type) + * + * @default - no properties. + */ + readonly properties?: { [name: string]: any }; +} + +/** + * A manifest which describes the cloud assembly. + */ +export interface AssemblyManifest { + /** + * Protocol version + */ + readonly version: string; + + /** + * The set of artifacts in this assembly. + * + * @default - no artifacts. + */ + readonly artifacts?: { [id: string]: ArtifactManifest }; + + /** + * Missing context information. If this field has values, it means that the + * cloud assembly is not complete and should not be deployed. + * + * @default - no missing context. + */ + readonly missing?: MissingContext[]; + + /** + * Runtime information. + * + * @default - no info. + */ + readonly runtime?: RuntimeInfo; +} diff --git a/packages/@aws-cdk/cloud-assembly-schema/package.json b/packages/@aws-cdk/cloud-assembly-schema/package.json new file mode 100644 index 0000000000000..457267053abb1 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/package.json @@ -0,0 +1,94 @@ +{ + "name": "@aws-cdk/cloud-assembly-schema", + "version": "0.0.0", + "description": "Cloud Assembly Schema", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.cloudassembly.schema", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "cdk-cloud-assembly-schema" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.CloudAssembly.Schema", + "packageId": "Amazon.CDK.CloudAssembly.Schema", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk", + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-cdk.cloud-assembly-schema", + "module": "aws_cdk.cloud_assembly_schema" + } + } + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "build+test+package": "npm run build+test && npm run package", + "build+test": "npm run build && npm test", + "compat": "cdk-compat", + "update-schema": "bash scripts/update-schema.sh" + }, + "cdk-build": { + "eslint": { + "ignore-pattern": [ + "scripts/*" + ] + } + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "jest": {}, + "license": "Apache-2.0", + "devDependencies": { + "@types/jest": "^25.2.1", + "@types/mock-fs": "^4.10.0", + "cdk-build-tools": "0.0.0", + "jest": "^24.9.0", + "mock-fs": "^4.11.0", + "pkglint": "0.0.0", + "typescript-json-schema": "^0.42.0" + }, + "repository": { + "url": "https://github.com/aws/aws-cdk.git", + "type": "git", + "directory": "packages/@aws-cdk/cloud-assembly-schema" + }, + "keywords": [ + "aws", + "cdk" + ], + "homepage": "https://github.com/aws/aws-cdk", + "bundledDependencies": [ + "jsonschema", + "semver" + ], + "engines": { + "node": ">= 10.12.0" + }, + "stability": "stable", + "awslint": { + "exclude": [] + }, + "dependencies": { + "jsonschema": "^1.2.5", + "semver": "^7.2.1" + }, + "awscdkio": { + "announce": false + } +} diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/README.md b/packages/@aws-cdk/cloud-assembly-schema/schema/README.md new file mode 100644 index 0000000000000..ae58ce74ee4e8 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/README.md @@ -0,0 +1,5 @@ +## Cloud Assembly JSON Schema + +**DO NOT MODIFY FILES IN THIS DIRECTORY BY HAND** + +To modify, run `yarn update-schema`. \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json new file mode 100644 index 0000000000000..f6221fddb2c76 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.schema.json @@ -0,0 +1,299 @@ +{ + "$ref": "#/definitions/AssemblyManifest", + "definitions": { + "AssemblyManifest": { + "description": "A manifest which describes the cloud assembly.", + "type": "object", + "properties": { + "version": { + "description": "Protocol version", + "type": "string" + }, + "artifacts": { + "description": "The set of artifacts in this assembly. (Default - no artifacts.)", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ArtifactManifest" + } + }, + "missing": { + "description": "Missing context information. If this field has values, it means that the\ncloud assembly is not complete and should not be deployed. (Default - no missing context.)", + "type": "array", + "items": { + "$ref": "#/definitions/MissingContext" + } + }, + "runtime": { + "description": "Runtime information. (Default - no info.)", + "$ref": "#/definitions/RuntimeInfo" + } + }, + "required": [ + "version" + ] + }, + "ArtifactManifest": { + "description": "A manifest for a single artifact within the cloud assembly.", + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/ArtifactType", + "description": "The type of artifact." + }, + "environment": { + "description": "The environment into which this artifact is deployed. (Default - no envrionment.)", + "type": "string" + }, + "metadata": { + "description": "Associated metadata. (Default - no metadata.)", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/MetadataEntry" + } + } + }, + "dependencies": { + "description": "IDs of artifacts that must be deployed before this artifact. (Default - no dependencies.)", + "type": "array", + "items": { + "type": "string" + } + }, + "properties": { + "description": "The set of properties for this artifact (depends on type) (Default - no properties.)", + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "type" + ] + }, + "ArtifactType": { + "description": "Type of cloud artifact.", + "enum": [ + "aws:cloudformation:stack", + "cdk:tree", + "none" + ], + "type": "string" + }, + "MetadataEntry": { + "description": "A metadata entry in a cloud assembly artifact.", + "type": "object", + "properties": { + "type": { + "description": "The type of the metadata entry.", + "type": "string" + }, + "data": { + "description": "The data. (Default - no data.)", + "anyOf": [ + { + "$ref": "#/definitions/FileAssetMetadataEntry" + }, + { + "$ref": "#/definitions/ContainerImageAssetMetadataEntry" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + } + }, + { + "type": "string" + }, + { + "description": "Free form data." + } + ] + }, + "trace": { + "description": "A stack trace for when the entry was created. (Default - no trace.)", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "type" + ] + }, + "FileAssetMetadataEntry": { + "description": "Metadata Entry spec for files.", + "type": "object", + "properties": { + "packaging": { + "description": "Requested packaging style", + "enum": [ + "file", + "zip" + ], + "type": "string" + }, + "s3BucketParameter": { + "description": "Name of parameter where S3 bucket should be passed in", + "type": "string" + }, + "s3KeyParameter": { + "description": "Name of parameter where S3 key should be passed in", + "type": "string" + }, + "artifactHashParameter": { + "description": "The name of the parameter where the hash of the bundled asset should be passed in.", + "type": "string" + }, + "id": { + "description": "Logical identifier for the asset", + "type": "string" + }, + "sourceHash": { + "description": "The hash of the source directory used to build the asset.", + "type": "string" + }, + "path": { + "description": "Path on disk to the asset", + "type": "string" + } + }, + "required": [ + "artifactHashParameter", + "id", + "packaging", + "path", + "s3BucketParameter", + "s3KeyParameter", + "sourceHash" + ] + }, + "ContainerImageAssetMetadataEntry": { + "description": "Metadata Entry spec for container images.", + "type": "object", + "properties": { + "packaging": { + "description": "Type of asset", + "type": "string", + "enum": [ + "container-image" + ] + }, + "imageNameParameter": { + "description": "ECR Repository name and repo digest (separated by \"@sha256:\") where this\nimage is stored. (Default undefined If not specified, `repositoryName` and `imageTag` are\nrequired because otherwise how will the stack know where to find the asset,\nha?)", + "type": "string" + }, + "repositoryName": { + "description": "ECR repository name, if omitted a default name based on the asset's ID is\nused instead. Specify this property if you need to statically address the\nimage, e.g. from a Kubernetes Pod. Note, this is only the repository name,\nwithout the registry and the tag parts. (Default - this parameter is REQUIRED after 1.21.0)", + "type": "string" + }, + "imageTag": { + "description": "The docker image tag to use for tagging pushed images. This field is\nrequired if `imageParameterName` is ommited (otherwise, the app won't be\nable to find the image). (Default - this parameter is REQUIRED after 1.21.0)", + "type": "string" + }, + "buildArgs": { + "description": "Build args to pass to the `docker build` command (Default no build args are passed)", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "target": { + "description": "Docker target to build to (Default no build target)", + "type": "string" + }, + "file": { + "description": "Path to the Dockerfile (relative to the directory). (Default - no file is passed)", + "type": "string" + }, + "id": { + "description": "Logical identifier for the asset", + "type": "string" + }, + "sourceHash": { + "description": "The hash of the source directory used to build the asset.", + "type": "string" + }, + "path": { + "description": "Path on disk to the asset", + "type": "string" + } + }, + "required": [ + "id", + "packaging", + "path", + "sourceHash" + ] + }, + "Tag": { + "description": "Metadata Entry spec for stack tag.", + "type": "object", + "properties": { + "key": { + "description": "Tag key.", + "type": "string" + }, + "value": { + "description": "Tag value.", + "type": "string" + } + }, + "required": [ + "key", + "value" + ] + }, + "MissingContext": { + "description": "Represents a missing piece of context.", + "type": "object", + "properties": { + "key": { + "description": "The missing context key.", + "type": "string" + }, + "provider": { + "description": "The provider from which we expect this context key to be obtained.", + "type": "string" + }, + "props": { + "description": "A set of provider-specific options.", + "type": "object", + "additionalProperties": {}, + "properties": { + "account": { + "type": "string" + }, + "region": { + "type": "string" + } + } + } + }, + "required": [ + "key", + "props", + "provider" + ] + }, + "RuntimeInfo": { + "description": "Information about the application's runtime components.", + "type": "object", + "properties": { + "libraries": { + "description": "The list of libraries loaded in the application, associated with their versions.", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "libraries" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json new file mode 100644 index 0000000000000..0e5bee35953f1 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/schema/cloud-assembly.version.json @@ -0,0 +1 @@ +{"version":"1.33.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.sh b/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.sh new file mode 100755 index 0000000000000..cde2aafa37aad --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -euo pipefail +scriptsdir=$(cd $(dirname $0) && pwd) +packagedir=$(realpath ${scriptsdir}/..) + +# Output +OUTPUT_DIR="${packagedir}/schema" +OUTPUT_FILE="${OUTPUT_DIR}/cloud-assembly.schema.json" + +mkdir -p ${OUTPUT_DIR} + +node -e "require('${packagedir}/scripts/update-schema.js').generate('${OUTPUT_FILE}', true)" diff --git a/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.ts b/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.ts new file mode 100644 index 0000000000000..b5b42a97065c6 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/scripts/update-schema.ts @@ -0,0 +1,107 @@ +import fs = require('fs'); +import path = require('path'); +import semver = require('semver'); +import tjs = require('typescript-json-schema'); + +function log(message: string) { + // tslint:disable-next-line:no-console + console.log(message); +} + +function bump() { + + const metadataPath = '../schema/cloud-assembly.version.json'; + + const metadata = require(metadataPath); + + const oldVersion = metadata.version; + const newVersion = semver.inc(oldVersion, 'major'); + + log(`Updating schema version: ${oldVersion} -> ${newVersion}`); + + const out = path.join(__dirname, metadataPath); + fs.writeFileSync(out, JSON.stringify({version: newVersion})); + +} + +export function generate(out: string, shouldBump: boolean) { + + const settings = { + required: true, + ref: true, + topRef: true, + noExtraProps: false, + out + }; + + const compilerOptions = { + strictNullChecks: true + }; + + const program = tjs.getProgramFromFiles([path.join(__dirname, "../lib/schema.d.ts")], compilerOptions); + const schema = tjs.generateSchema(program, 'AssemblyManifest', settings); + + augmentDescription(schema); + addAnyMetadataEntry(schema); + + if (shouldBump) { + bump(); + } + + if (out) { + log(`Generating schema to ${out}`); + fs.writeFileSync(out, JSON.stringify(schema, null, 4)); + } + + return schema; + +} + +/** + * Remove 'default' from the schema since its generated + * from the tsdocs, which are not necessarily actual values, + * but rather descriptive behavior. + * + * To keep this inforamtion in the schema, we append it to the + * 'description' of the property. + */ +function augmentDescription(schema: any) { + + function _recurse(o: any) { + for (const prop in o) { + + if (prop === 'description' && typeof o[prop] === 'string') { + + const description = o[prop]; + const defaultValue = o.default; + + if (!defaultValue) { + // property doesn't have a default value + // skip + continue; + } + + const descriptionWithDefault = `${description} (Default ${defaultValue})`; + + delete o.default; + o[prop] = descriptionWithDefault; + + } else if (typeof o[prop] === 'object') { + _recurse(o[prop]); + } + } + } + + _recurse(schema); + +} + +/** + * Patch the properties of MetadataEntry to allow + * specifying any free form data. This is needed since source + * code doesn't allow this in order to enforce stricter jsii + * compatibility checks. + */ +function addAnyMetadataEntry(schema: any) { + schema.definitions.MetadataEntry.properties.data.anyOf.push({description: "Free form data."}); +} diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/__snapshots__/manifest.test.js.snap b/packages/@aws-cdk/cloud-assembly-schema/test/__snapshots__/manifest.test.js.snap new file mode 100644 index 0000000000000..58289c740c914 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/test/__snapshots__/manifest.test.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`manifest load 1`] = ` +Object { + "version": "0.0.0", +} +`; diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/high-version/manifest.json b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/high-version/manifest.json new file mode 100644 index 0000000000000..ef6fc1c901429 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/high-version/manifest.json @@ -0,0 +1,3 @@ +{ + "version": "99.99.99" +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/invalid-artifact-type/manifest.json b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/invalid-artifact-type/manifest.json similarity index 84% rename from packages/@aws-cdk/cx-api/test/fixtures/invalid-artifact-type/manifest.json rename to packages/@aws-cdk/cloud-assembly-schema/test/fixtures/invalid-artifact-type/manifest.json index cac2614ce4198..ea1558cb1e6f9 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/invalid-artifact-type/manifest.json +++ b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/invalid-artifact-type/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "MyArt": { "type": "who:am:i", diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/invalid-nested-property/manifest.json b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/invalid-nested-property/manifest.json new file mode 100644 index 0000000000000..da1a33b17936e --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/invalid-nested-property/manifest.json @@ -0,0 +1,6 @@ +{ + "version": "0.0.0", + "runtime": { + "libraries": ["should", "be", "a", "map"] + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/invalid-version/manifest.json b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/invalid-version/manifest.json new file mode 100644 index 0000000000000..36b2250cf8d33 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/invalid-version/manifest.json @@ -0,0 +1,3 @@ +{ + "version": "version" +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/only-version/manifest.json b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/only-version/manifest.json new file mode 100644 index 0000000000000..c158d5be87422 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/only-version/manifest.json @@ -0,0 +1,3 @@ +{ + "version": "0.0.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/random-metadata/manifest.json b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/random-metadata/manifest.json new file mode 100644 index 0000000000000..cd2209c526595 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/random-metadata/manifest.json @@ -0,0 +1,35 @@ +{ + "version": "0.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "stack": { + "type": "aws:cloudformation:stack", + "metadata": { + "AwsCdkPlaygroundBatch": [ + { + "type": "random-array", + "data": ["42"], + "trace": ["trace"] + }, + { + "type": "random-number", + "data": 42, + "trace": ["trace"] + }, + { + "type": "random-map", + "data": { + "key": "value" + }, + "trace": ["trace"] + } + ] + } + } + } + } \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/unknown-property/manifest.json b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/unknown-property/manifest.json new file mode 100644 index 0000000000000..d1f0bca305c81 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/unknown-property/manifest.json @@ -0,0 +1,4 @@ +{ + "version": "0.0.0", + "who-am-i": "unknown" +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/with-stack-tags/manifest.json b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/with-stack-tags/manifest.json new file mode 100644 index 0000000000000..4d18eed47c8a0 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/test/fixtures/with-stack-tags/manifest.json @@ -0,0 +1,38 @@ +{ + "version": "0.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "stack": { + "type": "aws:cloudformation:stack", + "metadata": { + "AwsCdkPlaygroundBatch": [ + { + "type": "aws:cdk:stack-tags", + "data": [{ + "Key": "hello", + "Value": "world" + }], + "trace": ["trace"] + }, + { + "type": "aws:cdk:asset", + "data": { + "repositoryName": "repo", + "imageTag": "tag", + "id": "id", + "packaging": "container-image", + "path": "path", + "sourceHash": "hash" + }, + "trace": ["trace"] + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts b/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts new file mode 100644 index 0000000000000..d59028a453fb7 --- /dev/null +++ b/packages/@aws-cdk/cloud-assembly-schema/test/manifest.test.ts @@ -0,0 +1,179 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as semver from 'semver'; +import { AssemblyManifest, Manifest, StackTagsMetadataEntry } from '../lib'; + +const FIXTURES = path.join(__dirname, 'fixtures'); + +function fixture(name: string) { + return path.join(FIXTURES, name, 'manifest.json'); +} + +function clone(obj: any) { + return JSON.parse(JSON.stringify(obj)); +} + +function removeStringKeys(obj: any, keys: string[]) { + + function _recurse(o: any) { + for (const prop in o) { + if (keys.includes(prop) && typeof o[prop] === 'string') { + delete o[prop]; + } else if (typeof o[prop] === 'object') { + _recurse(o[prop]); + } + } + } + + const cloned = clone(obj); + _recurse(cloned); + + return cloned; + +} + +test('manifest save', () => { + + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'schema-tests')); + const manifestFile = path.join(outdir, 'manifest.json'); + + const assemblyManifest: AssemblyManifest = { + version: 'version' + }; + + Manifest.save(assemblyManifest, manifestFile); + + const saved = JSON.parse(fs.readFileSync(manifestFile, 'UTF-8')); + + expect(saved).toEqual(assemblyManifest); + +}); + +test('cloud-assembly.json.schema is correct', () => { + + // when we compare schemas we ignore changes the + // description that is generated from the ts docstrings. + const docStringFields = [ + 'description' + ]; + + // eslint-disable-next-line @typescript-eslint/no-require-imports + const schema = require('../scripts/update-schema.js'); + + const expected = removeStringKeys(schema.generate(), docStringFields); + + // eslint-disable-next-line @typescript-eslint/no-require-imports + const actual = removeStringKeys(require('../schema/cloud-assembly.schema.json'), docStringFields); + + try { + expect(actual).toEqual(expected); + } catch (err) { + // I couldn't for the life of me figure out how to provide additional error message + // to jest...any ideas? + err.message = `Whoops, Looks like the schema has changed. Did you forget to run 'yarn update-schema'?\n\n${err.message}`; + throw err; + } +}); + +test('manifest load', () => { + const loaded = Manifest.load(fixture('only-version')); + expect(loaded).toMatchSnapshot(); +}); + +test('manifest load fails for invalid nested property', () => { + expect(() => Manifest.load(fixture('invalid-nested-property'))).toThrow(/Invalid assembly manifest/); +}); + +test('manifest load fails for invalid artifact type', () => { + expect(() => Manifest.load(fixture('invalid-artifact-type'))).toThrow(/Invalid assembly manifest/); +}); + +test('manifest load fails on higher major version', () => { + expect(() => Manifest.load(fixture('high-version'))).toThrow(/Cloud assembly schema version mismatch/); +}); + +// once we start introducing minor version bumps that are considered +// non breaking, this test can be removed. +test('manifest load fails on higher minor version', () => { + + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'schema-tests')); + const manifestFile = path.join(outdir, 'manifest.json'); + + const newVersion = semver.inc(Manifest.version(), 'minor'); + expect(newVersion).toBeTruthy(); + + if (newVersion) { + const assemblyManifest: AssemblyManifest = { + version: newVersion + }; + + Manifest.save(assemblyManifest, manifestFile); + + expect(() => Manifest.load(manifestFile)).toThrow(/Cloud assembly schema version mismatch/); + } +}); + +// once we start introducing patch version bumps that are considered +// non breaking, this test can be removed. +test('manifest load fails on higher patch version', () => { + + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'schema-tests')); + const manifestFile = path.join(outdir, 'manifest.json'); + + const newVersion = semver.inc(Manifest.version(), 'patch'); + expect(newVersion).toBeTruthy(); + + if (newVersion) { + const assemblyManifest: AssemblyManifest = { + version: newVersion + }; + + Manifest.save(assemblyManifest, manifestFile); + + expect(() => Manifest.load(manifestFile)).toThrow(/Cloud assembly schema version mismatch/); + } +}); + +test('manifest load fails on invalid version', () => { + expect(() => Manifest.load(fixture('invalid-version'))).toThrow(/Invalid semver string/); +}); + +test('manifest load succeeds on unknown properties', () => { + const manifest = Manifest.load(fixture('unknown-property')); + expect(manifest.version).toEqual('0.0.0'); +}); + +test('stack-tags are deserialized properly', () => { + + const m: AssemblyManifest = Manifest.load(fixture('with-stack-tags')); + + if (m.artifacts?.stack?.metadata?.AwsCdkPlaygroundBatch[0].data) { + const entry = m.artifacts.stack.metadata.AwsCdkPlaygroundBatch[0].data as StackTagsMetadataEntry; + expect(entry[0].key).toEqual('hello'); + expect(entry[0].value).toEqual('world'); + } + expect(m.version).toEqual('0.0.0'); + +}); + +test('can access random metadata', () => { + + const loaded = Manifest.load(fixture('random-metadata')); + const randomArray = loaded.artifacts?.stack.metadata?.AwsCdkPlaygroundBatch[0].data; + const randomNumber = loaded.artifacts?.stack.metadata?.AwsCdkPlaygroundBatch[1].data; + const randomMap = loaded.artifacts?.stack.metadata?.AwsCdkPlaygroundBatch[2].data; + + expect(randomArray).toEqual(['42']); + expect(randomNumber).toEqual(42); + expect(randomMap).toEqual({ + key: 'value' + }); + + expect(randomMap).toBeTruthy(); + + if (randomMap) { + expect((randomMap as any).key).toEqual('value'); + } + +}); diff --git a/packages/@aws-cdk/cloudformation-diff/tsconfig.json b/packages/@aws-cdk/cloudformation-diff/tsconfig.json index 4ef356df31663..ffd93f2f3985b 100644 --- a/packages/@aws-cdk/cloudformation-diff/tsconfig.json +++ b/packages/@aws-cdk/cloudformation-diff/tsconfig.json @@ -22,7 +22,6 @@ "include": ["**/*.ts" ], "exclude": ["node_modules"], "references": [ - { "path": "../cfnspec" }, - { "path": "../cx-api" } + { "path": "../cfnspec" } ] } diff --git a/packages/@aws-cdk/core/lib/cfn-element.ts b/packages/@aws-cdk/core/lib/cfn-element.ts index df7a8bc5f4c93..990dd46ccd4d4 100644 --- a/packages/@aws-cdk/core/lib/cfn-element.ts +++ b/packages/@aws-cdk/core/lib/cfn-element.ts @@ -1,4 +1,4 @@ -import * as cxapi from '@aws-cdk/cx-api'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Construct } from './construct-compat'; import { Lazy } from './lazy'; import { Token } from './token'; @@ -61,7 +61,7 @@ export abstract class CfnElement extends Construct { displayHint: `${notTooLong(this.node.path)}.LogicalID` }); - this.node.addMetadata(cxapi.LOGICAL_ID_METADATA_KEY, this.logicalId, this.constructor); + this.node.addMetadata(cxschema.ArtifactMetadataEntryType.LOGICAL_ID, this.logicalId, this.constructor); } /** @@ -78,7 +78,7 @@ export abstract class CfnElement extends Construct { * node +internal+ entries filtered. */ public get creationStack(): string[] { - const trace = this.node.metadata.find(md => md.type === cxapi.LOGICAL_ID_METADATA_KEY)!.trace; + const trace = this.node.metadata.find(md => md.type === cxschema.ArtifactMetadataEntryType.LOGICAL_ID)!.trace; if (!trace) { return []; } diff --git a/packages/@aws-cdk/core/lib/construct-compat.ts b/packages/@aws-cdk/core/lib/construct-compat.ts index 5b3762bfbdb15..10d9aa0ff87fc 100644 --- a/packages/@aws-cdk/core/lib/construct-compat.ts +++ b/packages/@aws-cdk/core/lib/construct-compat.ts @@ -10,6 +10,7 @@ * This file, in its entirety, is expected to be removed in v2.0. */ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as constructs from 'constructs'; import { IAspect } from './aspect'; @@ -389,7 +390,7 @@ export class ConstructNode { * @param message The info message. */ public addInfo(message: string): void { - this._actualNode.addMetadata(cxapi.INFO_METADATA_KEY, message); + this._actualNode.addMetadata(cxschema.ArtifactMetadataEntryType.INFO, message); } /** @@ -399,7 +400,7 @@ export class ConstructNode { * @param message The warning message. */ public addWarning(message: string): void { - this._actualNode.addMetadata(cxapi.WARNING_METADATA_KEY, message); + this._actualNode.addMetadata(cxschema.ArtifactMetadataEntryType.WARN, message); } /** @@ -408,7 +409,7 @@ export class ConstructNode { * @param message The error message. */ public addError(message: string) { - this._actualNode.addMetadata(cxapi.ERROR_METADATA_KEY, message); + this._actualNode.addMetadata(cxschema.ArtifactMetadataEntryType.ERROR, message); } /** diff --git a/packages/@aws-cdk/core/lib/private/runtime-info.ts b/packages/@aws-cdk/core/lib/private/runtime-info.ts index 3fa221f9aaca7..6cc61cca23815 100644 --- a/packages/@aws-cdk/core/lib/private/runtime-info.ts +++ b/packages/@aws-cdk/core/lib/private/runtime-info.ts @@ -1,10 +1,10 @@ -import * as cxapi from '@aws-cdk/cx-api'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { major as nodeMajorVersion } from './node-version'; /** * Returns a list of loaded modules and their versions. */ -export function collectRuntimeInformation(): cxapi.RuntimeInfo { +export function collectRuntimeInformation(): cxschema.RuntimeInfo { const libraries: { [name: string]: string } = {}; for (const fileName of Object.keys(require.cache)) { diff --git a/packages/@aws-cdk/core/lib/private/tree-metadata.ts b/packages/@aws-cdk/core/lib/private/tree-metadata.ts index 70a847128ac27..05f666c14efb2 100644 --- a/packages/@aws-cdk/core/lib/private/tree-metadata.ts +++ b/packages/@aws-cdk/core/lib/private/tree-metadata.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { ArtifactType } from '@aws-cdk/cx-api'; +import { ArtifactType } from '@aws-cdk/cloud-assembly-schema'; import { Construct, IConstruct, ISynthesisSession } from '../construct-compat'; import { Stack } from '../stack'; import { IInspectable, TreeInspector } from '../tree'; diff --git a/packages/@aws-cdk/core/lib/stack.ts b/packages/@aws-cdk/core/lib/stack.ts index 1856da3a0aaba..104dd362b4196 100644 --- a/packages/@aws-cdk/core/lib/stack.ts +++ b/packages/@aws-cdk/core/lib/stack.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as crypto from 'crypto'; import * as fs from 'fs'; @@ -225,7 +226,7 @@ export class Stack extends Construct implements ITaggable { * This is returned when the stack is synthesized under the 'missing' attribute * and allows tooling to obtain the context and re-synthesize. */ - private readonly _missingContext = new Array(); + private readonly _missingContext = new Array(); /** * Includes all parameters synthesized for assets (lazy). @@ -539,7 +540,7 @@ export class Stack extends Construct implements ITaggable { if (!params) { params = new FileAssetParameters(this.assetParameters, asset.sourceHash); - const metadata: cxapi.FileAssetMetadataEntry = { + const metadata: cxschema.FileAssetMetadataEntry = { path: asset.fileName, id: asset.sourceHash, packaging: asset.packaging, @@ -550,7 +551,7 @@ export class Stack extends Construct implements ITaggable { artifactHashParameter: params.artifactHashParameter.logicalId, }; - this.node.addMetadata(cxapi.ASSET_METADATA, metadata); + this.node.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, metadata); } const bucketName = params.bucketNameParameter.valueAsString; @@ -580,7 +581,7 @@ export class Stack extends Construct implements ITaggable { // only add every image (identified by source hash) once for each stack that uses it. if (!this.addedImageAssets.has(assetId)) { - const metadata: cxapi.ContainerImageAssetMetadataEntry = { + const metadata: cxschema.ContainerImageAssetMetadataEntry = { repositoryName, imageTag, id: assetId, @@ -592,7 +593,7 @@ export class Stack extends Construct implements ITaggable { file: asset.dockerFile, }; - this.node.addMetadata(cxapi.ASSET_METADATA, metadata); + this.node.addMetadata(cxschema.ArtifactMetadataEntryType.ASSET, metadata); this.addedImageAssets.add(assetId); } @@ -784,7 +785,7 @@ export class Stack extends Construct implements ITaggable { } if (this.tags.hasTags()) { - this.node.addMetadata(cxapi.STACK_TAGS_METADATA_KEY, this.tags.renderTags()); + this.node.addMetadata(cxschema.ArtifactMetadataEntryType.STACK_TAGS, this.tags.renderTags()); } if (this.nestedStackParent) { @@ -842,7 +843,7 @@ export class Stack extends Construct implements ITaggable { // add an artifact that represents this stack builder.addArtifact(this.artifactId, { - type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, environment: this.environment, properties, dependencies: deps.length > 0 ? deps : undefined, @@ -983,7 +984,7 @@ export class Stack extends Construct implements ITaggable { } private collectMetadata() { - const output: { [id: string]: cxapi.MetadataEntry[] } = { }; + const output: { [id: string]: cxschema.MetadataEntry[] } = { }; const stack = this; visit(this); @@ -999,7 +1000,7 @@ export class Stack extends Construct implements ITaggable { if (node.node.metadata.length > 0) { // Make the path absolute - output[ConstructNode.PATH_SEP + node.node.path] = node.node.metadata.map(md => stack.resolve(md) as cxapi.MetadataEntry); + output[ConstructNode.PATH_SEP + node.node.path] = node.node.metadata.map(md => stack.resolve(md) as cxschema.MetadataEntry); } for (const child of node.node.children) { diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index b7d8495f18b51..c5edd311bf5f1 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -147,11 +147,13 @@ }, "dependencies": { "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "constructs": "^2.0.0" }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "constructs": "^2.0.0" }, "engines": { diff --git a/packages/@aws-cdk/core/test/private/test.tree-metadata.ts b/packages/@aws-cdk/core/test/private/test.tree-metadata.ts index 26c5f1885e9b1..b20a2f807c74f 100644 --- a/packages/@aws-cdk/core/test/private/test.tree-metadata.ts +++ b/packages/@aws-cdk/core/test/private/test.tree-metadata.ts @@ -1,4 +1,4 @@ -import * as cxapi from '@aws-cdk/cx-api'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs'; import { Test } from 'nodeunit'; import * as path from 'path'; @@ -282,7 +282,7 @@ export = { const treenode = app.node.findChild('Tree'); const warn = treenode.node.metadata.find((md) => { - return md.type === cxapi.WARNING_METADATA_KEY + return md.type === cxschema.ArtifactMetadataEntryType.WARN && /Forcing an inspect error/.test(md.data as string) && /mycfnresource/.test(md.data as string); }); diff --git a/packages/@aws-cdk/core/test/test.app.ts b/packages/@aws-cdk/core/test/test.app.ts index 74a5995a07f79..06375899ce178 100644 --- a/packages/@aws-cdk/core/test/test.app.ts +++ b/packages/@aws-cdk/core/test/test.app.ts @@ -294,6 +294,7 @@ export = { test.deepEqual(libs, { '@aws-cdk/core': version, '@aws-cdk/cx-api': version, + '@aws-cdk/cloud-assembly-schema': version, 'jsii-runtime': `node.js/${process.version}` }); diff --git a/packages/@aws-cdk/core/test/test.assets.ts b/packages/@aws-cdk/core/test/test.assets.ts index f29f264a4837d..1e17c7ec2d3bb 100644 --- a/packages/@aws-cdk/core/test/test.assets.ts +++ b/packages/@aws-cdk/core/test/test.assets.ts @@ -1,4 +1,4 @@ -import * as cxapi from '@aws-cdk/cx-api'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Test } from 'nodeunit'; import { FileAssetPackaging, Stack } from '../lib'; import { toCloudFormation } from './util'; @@ -16,12 +16,17 @@ export = { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxapi.ASSET_METADATA); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); - test.equal(assetMetadata && assetMetadata.data.path, 'file-name'); - test.equal(assetMetadata && assetMetadata.data.id, 'source-hash'); - test.equal(assetMetadata && assetMetadata.data.packaging, FileAssetPackaging.ZIP_DIRECTORY); - test.equal(assetMetadata && assetMetadata.data.sourceHash, 'source-hash'); + test.ok(assetMetadata && assetMetadata.data); + + if (assetMetadata && assetMetadata.data) { + const data = assetMetadata.data as cxschema.AssetMetadataEntry; + test.equal(data.path, 'file-name'); + test.equal(data.id, 'source-hash'); + test.equal(data.packaging, FileAssetPackaging.ZIP_DIRECTORY); + test.equal(data.sourceHash, 'source-hash'); + } test.deepEqual(toCloudFormation(stack), { Parameters: { @@ -56,13 +61,18 @@ export = { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxapi.ASSET_METADATA); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + + test.ok(assetMetadata && assetMetadata.data); - test.equal(assetMetadata && assetMetadata.data.packaging, 'container-image'); - test.equal(assetMetadata && assetMetadata.data.path, 'directory-name'); - test.equal(assetMetadata && assetMetadata.data.sourceHash, 'source-hash'); - test.equal(assetMetadata && assetMetadata.data.repositoryName, 'repository-name'); - test.equal(assetMetadata && assetMetadata.data.imageTag, 'source-hash'); + if (assetMetadata && assetMetadata.data) { + const data = assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry; + test.equal(data.packaging, 'container-image'); + test.equal(data.path, 'directory-name'); + test.equal(data.sourceHash, 'source-hash'); + test.equal(data.repositoryName, 'repository-name'); + test.equal(data.imageTag, 'source-hash'); + } test.deepEqual(toCloudFormation(stack), { }); test.done(); @@ -79,13 +89,18 @@ export = { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxapi.ASSET_METADATA); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); - test.equal(assetMetadata && assetMetadata.data.packaging, 'container-image'); - test.equal(assetMetadata && assetMetadata.data.path, 'directory-name'); - test.equal(assetMetadata && assetMetadata.data.sourceHash, 'source-hash'); - test.equal(assetMetadata && assetMetadata.data.repositoryName, 'aws-cdk/assets'); - test.equal(assetMetadata && assetMetadata.data.imageTag, 'source-hash'); + test.ok(assetMetadata && assetMetadata.data); + + if (assetMetadata && assetMetadata.data) { + const data = assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry; + test.equal(data.packaging, 'container-image'); + test.equal(data.path, 'directory-name'); + test.equal(data.sourceHash, 'source-hash'); + test.equal(data.repositoryName, 'aws-cdk/assets'); + test.equal(data.imageTag, 'source-hash'); + } test.deepEqual(toCloudFormation(stack), { }); test.done(); @@ -103,13 +118,18 @@ export = { }); // THEN - const assetMetadata = stack.node.metadata.find(({ type }) => type === cxapi.ASSET_METADATA); - - test.equal(assetMetadata && assetMetadata.data.packaging, 'container-image'); - test.equal(assetMetadata && assetMetadata.data.path, 'directory-name'); - test.equal(assetMetadata && assetMetadata.data.sourceHash, 'source-hash'); - test.equal(assetMetadata && assetMetadata.data.repositoryName, 'my-custom-repo-name'); - test.equal(assetMetadata && assetMetadata.data.imageTag, 'source-hash'); + const assetMetadata = stack.node.metadata.find(({ type }) => type === cxschema.ArtifactMetadataEntryType.ASSET); + + test.ok(assetMetadata && assetMetadata.data); + + if (assetMetadata && assetMetadata.data) { + const data = assetMetadata.data as cxschema.ContainerImageAssetMetadataEntry; + test.equal(data.packaging, 'container-image'); + test.equal(data.path, 'directory-name'); + test.equal(data.sourceHash, 'source-hash'); + test.equal(data.repositoryName, 'my-custom-repo-name'); + test.equal(data.imageTag, 'source-hash'); + } test.deepEqual(toCloudFormation(stack), { }); test.done(); diff --git a/packages/@aws-cdk/core/test/test.construct.ts b/packages/@aws-cdk/core/test/test.construct.ts index a98c19db2eaf5..b10f6fd46a7f1 100644 --- a/packages/@aws-cdk/core/test/test.construct.ts +++ b/packages/@aws-cdk/core/test/test.construct.ts @@ -1,4 +1,4 @@ -import * as cxapi from '@aws-cdk/cx-api'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Test } from 'nodeunit'; import { App as Root, Aws, Construct, ConstructNode, ConstructOrder, IConstruct, Lazy, ValidationError } from '../lib'; @@ -285,7 +285,7 @@ export = { const root = new Root(); const con = new Construct(root, 'MyConstruct'); con.node.addWarning('This construct is deprecated, use the other one instead'); - test.deepEqual(con.node.metadata[0].type, cxapi.WARNING_METADATA_KEY); + test.deepEqual(con.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.WARN); test.deepEqual(con.node.metadata[0].data, 'This construct is deprecated, use the other one instead'); test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); test.done(); @@ -295,7 +295,7 @@ export = { const root = new Root(); const con = new Construct(root, 'MyConstruct'); con.node.addError('Stop!'); - test.deepEqual(con.node.metadata[0].type, cxapi.ERROR_METADATA_KEY); + test.deepEqual(con.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.ERROR); test.deepEqual(con.node.metadata[0].data, 'Stop!'); test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); test.done(); @@ -305,7 +305,7 @@ export = { const root = new Root(); const con = new Construct(root, 'MyConstruct'); con.node.addInfo('Hey there, how do you do?'); - test.deepEqual(con.node.metadata[0].type, cxapi.INFO_METADATA_KEY); + test.deepEqual(con.node.metadata[0].type, cxschema.ArtifactMetadataEntryType.INFO); test.deepEqual(con.node.metadata[0].data, 'Hey there, how do you do?'); test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); test.done(); diff --git a/packages/@aws-cdk/core/test/test.synthesis.ts b/packages/@aws-cdk/core/test/test.synthesis.ts index e92529f6ff110..cf49b8eced1fc 100644 --- a/packages/@aws-cdk/core/test/test.synthesis.ts +++ b/packages/@aws-cdk/core/test/test.synthesis.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; import { Test } from 'nodeunit'; @@ -74,7 +75,7 @@ export = { protected synthesize(s: cdk.ISynthesisSession) { writeJson(s.assembly.outdir, 'foo.json', { bar: 123 }); s.assembly.addArtifact('my-random-construct', { - type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, environment: 'aws://12345/bar', properties: { templateFile: 'foo.json' @@ -94,7 +95,7 @@ export = { test.deepEqual(readJson(session.directory, 'foo.json'), { bar: 123 }); test.deepEqual(session.manifest, { - version: cxapi.CLOUD_ASSEMBLY_VERSION, + version: cxschema.Manifest.version(), artifacts: { 'Tree': { type: 'cdk:tree', @@ -127,7 +128,7 @@ export = { calls.push('synthesize'); session.assembly.addArtifact('art', { - type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, properties: { templateFile: 'hey.json', parameters: { diff --git a/packages/@aws-cdk/cx-api/README.md b/packages/@aws-cdk/cx-api/README.md index c7fb50e9f9ad2..2c3f3af56fdd5 100644 --- a/packages/@aws-cdk/cx-api/README.md +++ b/packages/@aws-cdk/cx-api/README.md @@ -1,4 +1,4 @@ -## Cloud Executable protocol +## Cloud Executable API --- diff --git a/packages/@aws-cdk/cx-api/lib/assets.ts b/packages/@aws-cdk/cx-api/lib/assets.ts index 9e99f95eed9c5..c15dbcd82776f 100644 --- a/packages/@aws-cdk/cx-api/lib/assets.ts +++ b/packages/@aws-cdk/cx-api/lib/assets.ts @@ -1,5 +1,3 @@ -export const ASSET_METADATA = 'aws:cdk:asset'; - /** * If this is set in the context, the aws:asset:xxx metadata entries will not be * added to the template. This is used, for example, when we run integrationt @@ -25,110 +23,3 @@ export const ASSET_RESOURCE_METADATA_PROPERTY_KEY = 'aws:asset:property'; * CloudFormation Template Parameter. */ export const ASSET_PREFIX_SEPARATOR = '||'; - -interface BaseAssetMetadataEntry { - /** - * Requested packaging style - */ - readonly packaging: string; - - /** - * Logical identifier for the asset - */ - readonly id: string; - - /** - * The hash of the source directory used to build the asset. - */ - readonly sourceHash: string; - - /** - * Path on disk to the asset - */ - readonly path: string; - -} - -export interface FileAssetMetadataEntry extends BaseAssetMetadataEntry { - /** - * Requested packaging style - */ - readonly packaging: 'zip' | 'file'; - - /** - * Name of parameter where S3 bucket should be passed in - */ - readonly s3BucketParameter: string; - - /** - * Name of parameter where S3 key should be passed in - */ - readonly s3KeyParameter: string; - - /** - * The name of the parameter where the hash of the bundled asset should be passed in. - */ - readonly artifactHashParameter: string; -} - -export interface ContainerImageAssetMetadataEntry extends BaseAssetMetadataEntry { - /** - * Type of asset - */ - readonly packaging: 'container-image'; - - /** - * ECR Repository name and repo digest (separated by "@sha256:") where this - * image is stored. - * - * @default undefined If not specified, `repositoryName` and `imageTag` are - * required because otherwise how will the stack know where to find the asset, - * ha? - * @deprecated specify `repositoryName` and `imageTag` instead, and then you - * know where the image will go. - */ - readonly imageNameParameter?: string; - - /** - * ECR repository name, if omitted a default name based on the asset's ID is - * used instead. Specify this property if you need to statically address the - * image, e.g. from a Kubernetes Pod. Note, this is only the repository name, - * without the registry and the tag parts. - * - * @default - this parameter is REQUIRED after 1.21.0 - */ - readonly repositoryName?: string; - - /** - * The docker image tag to use for tagging pushed images. This field is - * required if `imageParameterName` is ommited (otherwise, the app won't be - * able to find the image). - * - * @default - this parameter is REQUIRED after 1.21.0 - */ - readonly imageTag?: string; - - /** - * Build args to pass to the `docker build` command - * - * @default no build args are passed - */ - readonly buildArgs?: { [key: string]: string }; - - /** - * Docker target to build to - * - * @default no build target - */ - readonly target?: string; - - /** - * Path to the Dockerfile (relative to the directory). - * - * @default - no file is passed - */ - readonly file?: string; - -} - -export type AssetMetadataEntry = FileAssetMetadataEntry | ContainerImageAssetMetadataEntry; diff --git a/packages/@aws-cdk/cx-api/lib/cloud-artifact.ts b/packages/@aws-cdk/cx-api/lib/cloud-artifact.ts index 4161d2c446928..52e0bf1572f68 100644 --- a/packages/@aws-cdk/cx-api/lib/cloud-artifact.ts +++ b/packages/@aws-cdk/cx-api/lib/cloud-artifact.ts @@ -1,59 +1,9 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { CloudAssembly } from './cloud-assembly'; import { - ERROR_METADATA_KEY, - INFO_METADATA_KEY, - MetadataEntry, MetadataEntryResult, SynthesisMessage, - SynthesisMessageLevel, - WARNING_METADATA_KEY } from './metadata'; - -/** - * Type of cloud artifact. - */ -export enum ArtifactType { - NONE = 'none', // required due to a jsii bug - - /** - * The artifact is an AWS CloudFormation stack. - */ - AWS_CLOUDFORMATION_STACK = 'aws:cloudformation:stack', - - /** - * The artifact contains metadata generated out of the CDK application. - */ - CDK_TREE = 'cdk:tree', -} - -/** - * A manifest for a single artifact within the cloud assembly. - */ -export interface ArtifactManifest { - /** - * The type of artifact. - */ - readonly type: ArtifactType; - - /** - * The environment into which this artifact is deployed. - */ - readonly environment?: string; // format: aws://account/region - - /** - * Associated metadata. - */ - readonly metadata?: { [path: string]: MetadataEntry[] }; - - /** - * IDs of artifacts that must be deployed before this artifact. - */ - readonly dependencies?: string[]; - - /** - * The set of properties for this artifact (depends on type) - */ - readonly properties?: { [name: string]: any }; -} + SynthesisMessageLevel } from './metadata'; /** * Artifact properties for CloudFormation stacks. @@ -87,11 +37,11 @@ export class CloudArtifact { * @param artifact The artifact manifest * @returns the `CloudArtifact` that matches the artifact type or `undefined` if it's an artifact type that is unrecognized by this module. */ - public static fromManifest(assembly: CloudAssembly, id: string, artifact: ArtifactManifest): CloudArtifact | undefined { + public static fromManifest(assembly: CloudAssembly, id: string, artifact: cxschema.ArtifactManifest): CloudArtifact | undefined { switch (artifact.type) { - case ArtifactType.AWS_CLOUDFORMATION_STACK: + case cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK: return new CloudFormationStackArtifact(assembly, id, artifact); - case ArtifactType.CDK_TREE: + case cxschema.ArtifactType.CDK_TREE: return new TreeCloudArtifact(assembly, id, artifact); default: return undefined; @@ -101,7 +51,7 @@ export class CloudArtifact { /** * The artifact's manifest */ - public readonly manifest: ArtifactManifest; + public readonly manifest: cxschema.ArtifactManifest; /** * The set of messages extracted from the artifact's metadata. @@ -119,7 +69,7 @@ export class CloudArtifact { */ private _deps?: CloudArtifact[]; - protected constructor(public readonly assembly: CloudAssembly, public readonly id: string, manifest: ArtifactManifest) { + protected constructor(public readonly assembly: CloudAssembly, public readonly id: string, manifest: cxschema.ArtifactManifest) { this.manifest = manifest; this.messages = this.renderMessages(); this._dependencyIDs = manifest.dependencies || []; @@ -146,7 +96,7 @@ export class CloudArtifact { * @returns all the metadata entries of a specific type in this artifact. * @param type */ - public findMetadataByType(type: string) { + public findMetadataByType(type: string): MetadataEntryResult[] { const result = new Array(); for (const path of Object.keys(this.manifest.metadata || {})) { for (const entry of (this.manifest.metadata || {})[path]) { @@ -165,13 +115,13 @@ export class CloudArtifact { for (const entry of metadata) { let level: SynthesisMessageLevel; switch (entry.type) { - case WARNING_METADATA_KEY: + case cxschema.ArtifactMetadataEntryType.WARN: level = SynthesisMessageLevel.WARNING; break; - case ERROR_METADATA_KEY: + case cxschema.ArtifactMetadataEntryType.ERROR: level = SynthesisMessageLevel.ERROR; break; - case INFO_METADATA_KEY: + case cxschema.ArtifactMetadataEntryType.INFO: level = SynthesisMessageLevel.INFO; break; default: diff --git a/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts index 5e0c0ba02d8ab..20f676cf0e70f 100644 --- a/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts +++ b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts @@ -1,37 +1,11 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { ArtifactManifest, ArtifactType, CloudArtifact } from './cloud-artifact'; +import { CloudArtifact } from './cloud-artifact'; import { CloudFormationStackArtifact } from './cloudformation-artifact'; import { topologicalSort } from './toposort'; import { TreeCloudArtifact } from './tree-cloud-artifact'; -import { CLOUD_ASSEMBLY_VERSION, upgradeAssemblyManifest, verifyManifestVersion } from './versioning'; - -/** - * A manifest which describes the cloud assembly. - */ -export interface AssemblyManifest { - /** - * Protocol version - */ - readonly version: string; - - /** - * The set of artifacts in this assembly. - */ - readonly artifacts?: { [id: string]: ArtifactManifest }; - - /** - * Missing context information. If this field has values, it means that the - * cloud assembly is not complete and should not be deployed. - */ - readonly missing?: MissingContext[]; - - /** - * Runtime information. - */ - readonly runtime?: RuntimeInfo; -} /** * The name of the root manifest file of the assembly. @@ -60,12 +34,12 @@ export class CloudAssembly { /** * Runtime information such as module versions used to synthesize this assembly. */ - public readonly runtime: RuntimeInfo; + public readonly runtime: cxschema.RuntimeInfo; /** * The raw assembly manifest. */ - public readonly manifest: AssemblyManifest; + public readonly manifest: cxschema.AssemblyManifest; /** * Reads a cloud assembly from the specified directory. @@ -74,12 +48,8 @@ export class CloudAssembly { constructor(directory: string) { this.directory = directory; - const manifest = JSON.parse(fs.readFileSync(path.join(directory, MANIFEST_FILE), 'UTF-8')); - this.manifest = upgradeAssemblyManifest(manifest); - + this.manifest = cxschema.Manifest.load(path.join(directory, MANIFEST_FILE)); this.version = this.manifest.version; - verifyManifestVersion(this.version); - this.artifacts = this.renderArtifacts(); this.runtime = this.manifest.runtime || { libraries: { } }; @@ -152,11 +122,11 @@ export class CloudAssembly { * @returns a `TreeCloudArtifact` object if there is one defined in the manifest, `undefined` otherwise. */ public tree(): TreeCloudArtifact | undefined { - const trees = this.artifacts.filter(a => a.manifest.type === ArtifactType.CDK_TREE); + const trees = this.artifacts.filter(a => a.manifest.type === cxschema.ArtifactType.CDK_TREE); if (trees.length === 0) { return undefined; } else if (trees.length > 1) { - throw new Error(`Multiple artifacts of type ${ArtifactType.CDK_TREE} found in manifest`); + throw new Error(`Multiple artifacts of type ${cxschema.ArtifactType.CDK_TREE} found in manifest`); } const tree = trees[0]; @@ -208,8 +178,8 @@ export class CloudAssemblyBuilder { */ public readonly outdir: string; - private readonly artifacts: { [id: string]: ArtifactManifest } = { }; - private readonly missing = new Array(); + private readonly artifacts: { [id: string]: cxschema.ArtifactManifest } = { }; + private readonly missing = new Array(); /** * Initializes a cloud assembly builder. @@ -237,7 +207,7 @@ export class CloudAssemblyBuilder { * @param id The ID of the artifact. * @param manifest The artifact manifest */ - public addArtifact(id: string, manifest: ArtifactManifest) { + public addArtifact(id: string, manifest: cxschema.ArtifactManifest) { this.artifacts[id] = filterUndefined(manifest); } @@ -245,7 +215,7 @@ export class CloudAssemblyBuilder { * Reports that some context is missing in order for this cloud assembly to be fully synthesized. * @param missing Missing context information. */ - public addMissing(missing: MissingContext) { + public addMissing(missing: cxschema.MissingContext) { if (this.missing.every(m => m.key !== missing.key)) { this.missing.push(missing); } @@ -257,65 +227,74 @@ export class CloudAssemblyBuilder { * @param options */ public buildAssembly(options: AssemblyBuildOptions = { }): CloudAssembly { - const manifest: AssemblyManifest = filterUndefined({ - version: CLOUD_ASSEMBLY_VERSION, + + // explicitly initializing this type will help us detect + // breaking changes. (For example adding a required property will break compilation). + let manifest: cxschema.AssemblyManifest = { + version: cxschema.Manifest.version(), artifacts: this.artifacts, runtime: options.runtimeInfo, missing: this.missing.length > 0 ? this.missing : undefined - }); + }; + + // now we can filter + manifest = filterUndefined(manifest); const manifestFilePath = path.join(this.outdir, MANIFEST_FILE); - fs.writeFileSync(manifestFilePath, JSON.stringify(manifest, undefined, 2)); + cxschema.Manifest.save(manifest, manifestFilePath); // "backwards compatibility": in order for the old CLI to tell the user they // need a new version, we'll emit the legacy manifest with only "version". // this will result in an error "CDK Toolkit >= CLOUD_ASSEMBLY_VERSION is required in order to interact with this program." - fs.writeFileSync(path.join(this.outdir, 'cdk.out'), JSON.stringify({ version: CLOUD_ASSEMBLY_VERSION })); + fs.writeFileSync(path.join(this.outdir, 'cdk.out'), JSON.stringify({ version: manifest.version })); return new CloudAssembly(this.outdir); } + } -export interface AssemblyBuildOptions { - /** - * Include the specified runtime information (module versions) in manifest. - * @default - if this option is not specified, runtime info will not be included - */ - readonly runtimeInfo?: RuntimeInfo; +/** + * Backwards compatibility for when `RuntimeInfo` + * was defined here. This is necessary because its used as an input in the stable + * @aws-cdk/core library. + * + * @deprecated moved to package 'cloud-assembly-schema' + * @see core.ConstructNode.synth + */ +export interface RuntimeInfo extends cxschema.RuntimeInfo { + } /** - * Information about the application's runtime components. + * Backwards compatibility for when `MetadataEntry` + * was defined here. This is necessary because its used as an input in the stable + * @aws-cdk/core library. + * + * @deprecated moved to package 'cloud-assembly-schema' + * @see core.ConstructNode.metadata */ -export interface RuntimeInfo { - /** - * The list of libraries loaded in the application, associated with their versions. - */ - readonly libraries: { [name: string]: string }; +export interface MetadataEntry extends cxschema.MetadataEntry { + } /** - * Represents a missing piece of context. + * Backwards compatibility for when `MissingContext` + * was defined here. This is necessary because its used as an input in the stable + * @aws-cdk/core library. + * + * @deprecated moved to package 'cloud-assembly-schema' + * @see core.Stack.reportMissingContext */ -export interface MissingContext { - /** - * The missing context key. - */ - readonly key: string; +export interface MissingContext extends cxschema.MissingContext { - /** - * The provider from which we expect this context key to be obtained. - */ - readonly provider: string; +} +export interface AssemblyBuildOptions { /** - * A set of provider-specific options. + * Include the specified runtime information (module versions) in manifest. + * @default - if this option is not specified, runtime info will not be included */ - readonly props: { - account?: string; - region?: string; - [key: string]: any; - }; + readonly runtimeInfo?: RuntimeInfo; } /** diff --git a/packages/@aws-cdk/cx-api/lib/cloudformation-artifact.ts b/packages/@aws-cdk/cx-api/lib/cloudformation-artifact.ts index 85e2566e0ec36..cf2edc804bcf3 100644 --- a/packages/@aws-cdk/cx-api/lib/cloudformation-artifact.ts +++ b/packages/@aws-cdk/cx-api/lib/cloudformation-artifact.ts @@ -1,7 +1,7 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs'; import * as path from 'path'; -import { ASSET_METADATA, AssetMetadataEntry } from './assets'; -import { ArtifactManifest, AwsCloudFormationStackProperties, CloudArtifact } from './cloud-artifact'; +import { AwsCloudFormationStackProperties, CloudArtifact } from './cloud-artifact'; import { CloudAssembly } from './cloud-assembly'; import { Environment, EnvironmentUtils } from './environment'; @@ -24,7 +24,7 @@ export class CloudFormationStackArtifact extends CloudArtifact { /** * Any assets associated with this stack. */ - public readonly assets: AssetMetadataEntry[]; + public readonly assets: cxschema.AssetMetadataEntry[]; /** * CloudFormation parameters to pass to the stack. @@ -54,7 +54,7 @@ export class CloudFormationStackArtifact extends CloudArtifact { */ public readonly environment: Environment; - constructor(assembly: CloudAssembly, artifactId: string, artifact: ArtifactManifest) { + constructor(assembly: CloudAssembly, artifactId: string, artifact: cxschema.ArtifactManifest) { super(assembly, artifactId, artifact); if (!artifact.properties || !artifact.properties.templateFile) { @@ -70,7 +70,7 @@ export class CloudFormationStackArtifact extends CloudArtifact { this.stackName = properties.stackName || artifactId; this.template = JSON.parse(fs.readFileSync(path.join(this.assembly.directory, this.templateFile), 'utf-8')); - this.assets = this.findMetadataByType(ASSET_METADATA).map(e => e.data); + this.assets = this.findMetadataByType(cxschema.ArtifactMetadataEntryType.ASSET).map(e => e.data as cxschema.AssetMetadataEntry); this.displayName = this.stackName === artifactId ? this.stackName diff --git a/packages/@aws-cdk/cx-api/lib/index.ts b/packages/@aws-cdk/cx-api/lib/index.ts index afcc102b8f8ac..38629ffd7a17f 100644 --- a/packages/@aws-cdk/cx-api/lib/index.ts +++ b/packages/@aws-cdk/cx-api/lib/index.ts @@ -13,5 +13,3 @@ export * from './environment'; export * from './metadata'; export * from './features'; export * from './app'; - -export { CLOUD_ASSEMBLY_VERSION } from './versioning'; diff --git a/packages/@aws-cdk/cx-api/lib/metadata.ts b/packages/@aws-cdk/cx-api/lib/metadata.ts index 4eec14a69f3ce..c49c9d15cf403 100644 --- a/packages/@aws-cdk/cx-api/lib/metadata.ts +++ b/packages/@aws-cdk/cx-api/lib/metadata.ts @@ -1,60 +1,17 @@ - -/** - * Metadata key used to print INFO-level messages by the toolkit when an app is syntheized. - */ -export const INFO_METADATA_KEY = 'aws:cdk:info'; - -/** - * Metadata key used to print WARNING-level messages by the toolkit when an app is syntheized. - */ -export const WARNING_METADATA_KEY = 'aws:cdk:warning'; - -/** - * Metadata key used to print ERROR-level messages by the toolkit when an app is syntheized. - */ -export const ERROR_METADATA_KEY = 'aws:cdk:error'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; /** * The key used when CDK path is embedded in **CloudFormation template** metadata (not cdk metadata). */ export const PATH_METADATA_KEY = 'aws:cdk:path'; -/** - * Represents the CloudFormation logical ID of a resource at a certain path. - */ -export const LOGICAL_ID_METADATA_KEY = 'aws:cdk:logicalId'; - -/** - * Tag metadata key. - */ -export const STACK_TAGS_METADATA_KEY = 'aws:cdk:stack-tags'; - export enum SynthesisMessageLevel { INFO = 'info', WARNING = 'warning', ERROR = 'error' } -/** - * An metadata entry in the construct. - */ -export interface MetadataEntry { - /** - * The type of the metadata entry. - */ - readonly type: string; - - /** - * The data. - */ - readonly data?: any; - - /** - * A stack trace for when the entry was created. - */ - readonly trace?: string[]; -} -export interface MetadataEntryResult extends MetadataEntry { +export interface MetadataEntryResult extends cxschema.MetadataEntry { /** * The path in which this entry was defined. */ @@ -64,10 +21,10 @@ export interface MetadataEntryResult extends MetadataEntry { /** * Metadata associated with the objects in the stack's Construct tree */ -export type StackMetadata = { [path: string]: MetadataEntry[] }; +export type StackMetadata = { [path: string]: cxschema.MetadataEntry[] }; export interface SynthesisMessage { readonly level: SynthesisMessageLevel; readonly id: string; - readonly entry: MetadataEntry; + readonly entry: cxschema.MetadataEntry; } diff --git a/packages/@aws-cdk/cx-api/lib/tree-cloud-artifact.ts b/packages/@aws-cdk/cx-api/lib/tree-cloud-artifact.ts index 20533c1acc98d..b31bc6d22fe1b 100644 --- a/packages/@aws-cdk/cx-api/lib/tree-cloud-artifact.ts +++ b/packages/@aws-cdk/cx-api/lib/tree-cloud-artifact.ts @@ -1,10 +1,11 @@ -import { ArtifactManifest, CloudArtifact } from './cloud-artifact'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import { CloudArtifact } from './cloud-artifact'; import { CloudAssembly } from './cloud-assembly'; export class TreeCloudArtifact extends CloudArtifact { public readonly file: string; - constructor(assembly: CloudAssembly, name: string, artifact: ArtifactManifest) { + constructor(assembly: CloudAssembly, name: string, artifact: cxschema.ArtifactManifest) { super(assembly, name, artifact); const properties = (this.manifest.properties || {}); diff --git a/packages/@aws-cdk/cx-api/lib/versioning.ts b/packages/@aws-cdk/cx-api/lib/versioning.ts deleted file mode 100644 index a35747216548d..0000000000000 --- a/packages/@aws-cdk/cx-api/lib/versioning.ts +++ /dev/null @@ -1,101 +0,0 @@ -import * as semver from 'semver'; -import { AssemblyManifest } from './cloud-assembly'; - -// ---------------------------------------------------------------------- -// -// READ THIS FIRST WHEN CHANGING THIS FILE -// -// ---------------------------------------------------------------------- -// -// You need (and only need) to bump the CLOUD_ASSEMBLY_VERSION if the cloud -// assembly needs new features from the CDK CLI. Examples: new fields, new -// behavior, new artifact types. -// -// If that happens, you set the CLOUD_ASSEMBLY_VERSION to the *next* (not the -// current!) CDK version that will be released. This is done to produce -// useful error messages. -// -// When you do this, you will force users of a new library to upgrade the CLI -// (good), but UNLESS YOU ALSO IMPLEMENT 'upgradeAssemblyManifest' you will also -// force people who have installed a newer CLI to upgrade their libraries (bad!). -// Do that too, unless you have a very good reason not to. - -/** - * Bump this to the library version if and only if the CX protocol changes. - * - * We could also have used 1, 2, 3, ... here to indicate protocol versions, but - * those then still need to be mapped to software versions to be useful. So we - * might as well use the software version as protocol version and immediately - * generate a useful error message from this. - * - * Note that the versions are not compared in a semver way, they are used as - * opaque ordered tokens. - */ -export const CLOUD_ASSEMBLY_VERSION = '1.21.0'; - -/** - * Look at the type of response we get and upgrade it to the latest expected version - */ -export function verifyManifestVersion(manifetVersion: string) { - const frameworkVersion = parseSemver(manifetVersion); - const toolkitVersion = parseSemver(CLOUD_ASSEMBLY_VERSION); - - // if framework > cli, we require a newer cli version - if (semver.gt(frameworkVersion, toolkitVersion)) { - throw new Error(`A newer version of the CDK CLI (>= ${frameworkVersion}) is necessary to interact with this app`); - } - - // if framework < cli, we require a newer framework version - if (semver.lt(frameworkVersion, toolkitVersion)) { - throw new Error(`The CDK CLI you are using requires your app to use CDK modules with version >= ${CLOUD_ASSEMBLY_VERSION}`); - } -} - -/** - * Upgrade old manifest versions to later manifest version here (if possible). - * - * Use this to make the toolkit recognize old assembly versions. This function should - * add newly required fields with appropriate default values, etc. - */ -export function upgradeAssemblyManifest(manifest: AssemblyManifest): AssemblyManifest { - - if (manifest.version === '0.36.0') { - // Adding a new artifact type, old version will not have it so painless upgrade. - manifest = justUpgradeVersion(manifest, '1.10.0'); - } - - if (manifest.version === '1.10.0') { - // Two changes: - // * Backwards-compatible changes to the VPC provider - // * Added AMI context provider: old assemblies won't reference it. - manifest = justUpgradeVersion(manifest, '1.16.0'); - } - - if (manifest.version === '1.16.0') { - // Backwards compatible changes to ContainerImageAssetMetadataEntry: - // * Make `imageNameParameter` optional (new apps do not require it anymore because container images go to a well-known repository) - // * Add optional `imageTag` to allow apps to specify exactly where to store the image (required if `imageNameParameter` is not defined) - manifest = justUpgradeVersion(manifest, '1.21.0'); - } - - return manifest; -} - -function parseSemver(version: string) { - const ver = semver.coerce(version); - if (!ver) { - throw new Error(`Could not parse "${version}" as semver`); - } - - return ver; -} - -/** - * Return a copy of the manifest with just the version field updated - * - * Useful if there are protocol changes that are automatically backwards - * compatible. - */ -function justUpgradeVersion(manifest: AssemblyManifest, version: string): AssemblyManifest { - return Object.assign({}, manifest, { version }); -} diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index 122ebaa29f696..9525852f4b7d5 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -45,7 +45,11 @@ "organization": true }, "dependencies": { - "semver": "^7.2.1" + "semver": "^7.2.1", + "@aws-cdk/cloud-assembly-schema": "0.0.0" + }, + "peerDependencies": { + "@aws-cdk/cloud-assembly-schema": "0.0.0" }, "jest": { "moduleFileExtensions": [ diff --git a/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap b/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap index 3889ac3a1825e..aa6c729905fe5 100644 --- a/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap +++ b/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap @@ -20,26 +20,31 @@ Object { `; exports[`assembly with missing context 1`] = ` -Object { - "key": "missing:context:key", - "props": Object { - "foo": 123, +Array [ + Object { + "key": "missing:context:key", + "props": Object { + "foo": 123, + }, + "provider": "context-provider", }, - "provider": "context-provider", -} +] `; exports[`assets 1`] = ` Array [ Object { + "artifactHashParameter": "hashParameter", "id": "logical-id-of-the-asset", "packaging": "zip", "path": "asset-dir", + "s3BucketParameter": "bucketParameter", + "s3KeyParameter": "keyParameter", "sourceHash": "xoxoxox", }, Object { "id": "logical-id-of-the-asset-x1234", - "packaging": "docker", + "packaging": "container-image", "path": "docker-asset", "sourceHash": "docker-asset-source", }, @@ -48,7 +53,7 @@ Array [ exports[`empty assembly 1`] = ` Object { - "version": "1.21.0", + "version": "0.0.0", } `; @@ -57,7 +62,9 @@ Array [ Object { "entry": Object { "data": "boom", - "trace": "bam", + "trace": Array [ + "bam", + ], "type": "aws:cdk:warning", }, "id": "foo", @@ -66,7 +73,9 @@ Array [ Object { "entry": Object { "data": "error!!", - "trace": "bam!Error", + "trace": Array [ + "bam!Error", + ], "type": "aws:cdk:error", }, "id": "foo", @@ -75,7 +84,9 @@ Array [ Object { "entry": Object { "data": "info?", - "trace": "bam!Error", + "trace": Array [ + "bam!Error", + ], "type": "aws:cdk:info", }, "id": "bar", diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts index 1771cb2b122e6..f503484422752 100644 --- a/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts @@ -1,8 +1,8 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { ArtifactType, CloudAssemblyBuilder } from '../lib'; -import { CLOUD_ASSEMBLY_VERSION } from '../lib/versioning'; +import { CloudAssemblyBuilder } from '../lib'; test('cloud assembly builder', () => { // GIVEN @@ -12,11 +12,11 @@ test('cloud assembly builder', () => { // WHEN session.addArtifact('my-first-artifact', { - type: ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, environment: 'aws://1222344/us-east-1', dependencies: ['minimal-artifact'], metadata: { - foo: [ { data: 123, type: 'foo', trace: [] } ] + foo: [ { data: '123', type: 'foo', trace: [] } ] }, properties: { templateFile, @@ -28,7 +28,7 @@ test('cloud assembly builder', () => { }); session.addArtifact('tree-artifact', { - type: ArtifactType.CDK_TREE, + type: cxschema.ArtifactType.CDK_TREE, properties: { file: 'foo.tree.json' } @@ -44,7 +44,7 @@ test('cloud assembly builder', () => { }); session.addArtifact('minimal-artifact', { - type: ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, environment: 'aws://111/helo-world', properties: { templateFile @@ -65,7 +65,7 @@ test('cloud assembly builder', () => { // THEN // verify the manifest looks right expect(manifest).toStrictEqual({ - version: CLOUD_ASSEMBLY_VERSION, + version: cxschema.Manifest.version(), missing: [ { key: 'foo', provider: 'context-provider', props: { a: 'A', b: 2 } } ], @@ -80,7 +80,7 @@ test('cloud assembly builder', () => { type: 'aws:cloudformation:stack', environment: 'aws://1222344/us-east-1', dependencies: ['minimal-artifact'], - metadata: { foo: [ { data: 123, type: 'foo', trace: [] } ] }, + metadata: { foo: [ { data: '123', type: 'foo', trace: [] } ] }, properties: { templateFile: 'foo.template.json', parameters: { diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts index b4a1fa610ff5d..d917217ecfd74 100644 --- a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts @@ -1,6 +1,5 @@ import * as path from 'path'; import { CloudAssembly } from '../lib'; -import { CLOUD_ASSEMBLY_VERSION, verifyManifestVersion } from '../lib/versioning'; const FIXTURES = path.join(__dirname, 'fixtures'); @@ -9,7 +8,7 @@ test('empty assembly', () => { expect(assembly.artifacts).toEqual([]); expect(assembly.runtime).toEqual({ libraries: { } }); expect(assembly.stacks).toEqual([]); - expect(assembly.version).toEqual(CLOUD_ASSEMBLY_VERSION); + expect(assembly.version).toEqual('0.0.0'); expect(assembly.manifest).toMatchSnapshot(); expect(assembly.tree()).toBeUndefined(); }); @@ -20,7 +19,6 @@ test('assembly with a single cloudformation stack and tree metadata', () => { expect(assembly.stacks).toHaveLength(1); expect(assembly.manifest.missing).toBeUndefined(); expect(assembly.runtime).toEqual({ libraries: { } }); - expect(assembly.version).toEqual(CLOUD_ASSEMBLY_VERSION); const stack = assembly.stacks[0]; expect(stack.manifest).toMatchSnapshot(); @@ -64,11 +62,6 @@ test('assembly with multiple stacks', () => { expect(assembly.artifacts).toHaveLength(2); }); -test('fails for invalid artifact type', () => { - const assembly = new CloudAssembly(path.join(FIXTURES, 'invalid-artifact-type')); - expect(assembly.tryGetArtifact('MyArt')).toBeUndefined(); -}); - test('fails for invalid environment format', () => { expect(() => new CloudAssembly(path.join(FIXTURES, 'invalid-env-format'))) .toThrow('Unable to parse environment specification'); @@ -112,13 +105,6 @@ test('fails for invalid dependencies', () => { expect(() => new CloudAssembly(path.join(FIXTURES, 'invalid-depends'))).toThrow('Artifact StackC depends on non-existing artifact StackX'); }); -test('verifyManifestVersion', () => { - verifyManifestVersion(CLOUD_ASSEMBLY_VERSION); - // tslint:disable-next-line:max-line-length - expect(() => verifyManifestVersion('0.31.0')).toThrow(`The CDK CLI you are using requires your app to use CDK modules with version >= ${CLOUD_ASSEMBLY_VERSION}`); - expect(() => verifyManifestVersion('99.99.99')).toThrow('A newer version of the CDK CLI (>= 99.99.99) is necessary to interact with this app'); -}); - test('stack artifacts can specify an explicit stack name that is different from the artifact id', () => { const assembly = new CloudAssembly(path.join(FIXTURES, 'explicit-stack-name')); diff --git a/packages/@aws-cdk/cx-api/test/fixtures/assets/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/assets/manifest.json index 09d80d54137a8..8c52c21ba9129 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/assets/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/assets/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "MyStackName": { "type": "aws:cloudformation:stack", @@ -15,19 +15,22 @@ "packaging": "zip", "id": "logical-id-of-the-asset", "sourceHash": "xoxoxox", - "path": "asset-dir" + "path": "asset-dir", + "artifactHashParameter": "hashParameter", + "s3BucketParameter": "bucketParameter", + "s3KeyParameter": "keyParameter" }, - "trace": "bam" + "trace": ["bam"] }, { "type": "aws:cdk:asset", "data": { - "packaging": "docker", + "packaging": "container-image", "id": "logical-id-of-the-asset-x1234", "sourceHash": "docker-asset-source", "path": "docker-asset" }, - "trace": "bam:ssss" + "trace": ["bam:ssss"] } ] } diff --git a/packages/@aws-cdk/cx-api/test/fixtures/depends/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/depends/manifest.json index 1b54c4293e4e1..a9d9463a63842 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/depends/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/depends/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "StackA": { "type": "aws:cloudformation:stack", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/empty/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/empty/manifest.json index 4a05635736bd4..c158d5be87422 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/empty/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/empty/manifest.json @@ -1,3 +1,3 @@ { - "version": "1.10.0" + "version": "0.0.0" } \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/explicit-stack-name/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/explicit-stack-name/manifest.json index e2ddbe6480221..44f5005e3446b 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/explicit-stack-name/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/explicit-stack-name/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "Tree": { "type": "cdk:tree", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/manifest.json index aae72db5708b4..1add9a85ee121 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "StackA": { "type": "aws:cloudformation:stack", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/invalid-env-format/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/invalid-env-format/manifest.json index bb53c2d56c3ce..7a09d59deecb9 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/invalid-env-format/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/invalid-env-format/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "MyStackName": { "type": "aws:cloudformation:stack", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/invalid-manifest-type-cloudformation/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/invalid-manifest-type-cloudformation/manifest.json index 7aa310b98da24..bc17a8cd5b5fd 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/invalid-manifest-type-cloudformation/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/invalid-manifest-type-cloudformation/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "MyStackName": { "type": "aws:cloudformation:stack", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/invalid-manifest-type-tree/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/invalid-manifest-type-tree/manifest.json index 9731f80738448..1aba8f840e432 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/invalid-manifest-type-tree/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/invalid-manifest-type-tree/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "Tree1": { "type": "cdk:tree", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/manifest.json index 8fae3045a07df..3126c3f17ae42 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "MyStackName": { "type": "aws:cloudformation:stack", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/messages/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/messages/manifest.json index 211384f1499df..5be0b37f60d9d 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/messages/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/messages/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "MyStackName": { "type": "aws:cloudformation:stack", @@ -9,12 +9,12 @@ }, "metadata": { "foo": [ - { "type": "aws:cdk:warning", "data": "boom", "trace": "bam" }, - { "type": "aws:cdk:error", "data": "error!!", "trace": "bam!Error" } + { "type": "aws:cdk:warning", "data": "boom", "trace": ["bam"] }, + { "type": "aws:cdk:error", "data": "error!!", "trace": ["bam!Error"] } ], "bar": [ - { "type": "aws:cdk:info", "data": "info?", "trace": "bam!Error" }, - { "type": "aws:foo", "data": "info?", "trace": "bam!Error" } + { "type": "aws:cdk:info", "data": "info?", "trace": ["bam!Error"] }, + { "type": "aws:foo", "data": "info?", "trace": ["bam!Error"] } ] } } diff --git a/packages/@aws-cdk/cx-api/test/fixtures/missing-context/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/missing-context/manifest.json index c9f7224c9805d..1ff5e00933bb0 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/missing-context/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/missing-context/manifest.json @@ -1,10 +1,10 @@ { - "version": "1.10.0", - "missing": { + "version": "0.0.0", + "missing": [{ "key": "missing:context:key", "provider": "context-provider", "props": { "foo": 123 } - }, + }], "artifacts": { "MyStackName": { "type": "aws:cloudformation:stack", @@ -16,9 +16,6 @@ "MyStackName1234": { "type": "aws:cloudformation:stack", "environment": "aws://37736633/us-region-1", - "missing": { - "missing:context:key2": { "foo": 6688 } - }, "properties": { "templateFile": "template.json" } diff --git a/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks-same-name/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks-same-name/manifest.json index 229bc36a48ec8..d6dfe8f443830 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks-same-name/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks-same-name/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "stack1": { "type": "aws:cloudformation:stack", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/manifest.json index 55343294180eb..6d4a54915030f 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "MyStackName": { "type": "aws:cloudformation:stack", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/single-stack-0.36/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/single-stack-0.36/manifest.json index 91a78467c002a..b62b16726d762 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/single-stack-0.36/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/single-stack-0.36/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "Tree": { "type": "cdk:tree", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/single-stack/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/single-stack/manifest.json index 91a78467c002a..b62b16726d762 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/single-stack/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/single-stack/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "Tree": { "type": "cdk:tree", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/stack-without-params/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/stack-without-params/manifest.json index ca5888808ea12..e65d503231914 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/stack-without-params/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/stack-without-params/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "MyStackName": { "type": "aws:cloudformation:stack", diff --git a/packages/@aws-cdk/cx-api/test/fixtures/tree-no-file-property/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/tree-no-file-property/manifest.json index 9061e14e868b4..53f36cd322b4c 100644 --- a/packages/@aws-cdk/cx-api/test/fixtures/tree-no-file-property/manifest.json +++ b/packages/@aws-cdk/cx-api/test/fixtures/tree-no-file-property/manifest.json @@ -1,5 +1,5 @@ { - "version": "1.10.0", + "version": "0.0.0", "artifacts": { "Tree": { "type": "cdk:tree", diff --git a/packages/aws-cdk/lib/api/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap-environment.ts index da9e37edccb0a..86ec15f5f0c02 100644 --- a/packages/aws-cdk/lib/api/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap-environment.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; import * as os from 'os'; @@ -137,7 +138,7 @@ export async function bootstrapEnvironment(environment: cxapi.Environment, sdkPr await fs.writeJson(path.join(builder.outdir, templateFile), template, { spaces: 2 }); builder.addArtifact(toolkitStackName, { - type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, environment: cxapi.EnvironmentUtils.format(environment.account, environment.region), properties: { templateFile diff --git a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts index a395c72d2105c..ec77521b223c6 100644 --- a/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts +++ b/packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs-extra'; import * as os from 'os'; @@ -26,7 +27,7 @@ export async function bootstrapEnvironment2( bootstrapTemplateObject); builder.addArtifact(toolkitStackName, { - type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, environment: cxapi.EnvironmentUtils.format(environment.account, environment.region), properties: { templateFile, diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index f7d6cfb6b7862..633490583d9fd 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as childProcess from 'child_process'; import * as fs from 'fs-extra'; @@ -49,7 +50,7 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom // bypass "synth" if app points to a cloud assembly if (await fs.pathExists(app) && (await fs.stat(app)).isDirectory()) { debug('--app points to a cloud assembly, so we bypass synth'); - return new cxapi.CloudAssembly(app); + return createAssembly(app); } const commandLine = await guessExecutable(appToArray(app)); @@ -64,14 +65,27 @@ export async function execProgram(aws: SdkProvider, config: Configuration): Prom env[cxapi.OUTDIR_ENV] = outdir; // Send version information - env[cxapi.CLI_ASM_VERSION_ENV] = cxapi.CLOUD_ASSEMBLY_VERSION; + env[cxapi.CLI_ASM_VERSION_ENV] = cxschema.Manifest.version(); env[cxapi.CLI_VERSION_ENV] = versionNumber(); debug('env:', env); await exec(); - return new cxapi.CloudAssembly(outdir); + return createAssembly(outdir); + + function createAssembly(appDir: string) { + try { + return new cxapi.CloudAssembly(appDir); + } catch (error) { + if (error.message.includes(cxschema.VERSION_MISMATCH)) { + // this means the CLI version is too old. + // we instruct the user to upgrade. + throw new Error(`${error.message}.\nPlease upgrade your CLI in order to interact with this app.`); + } + throw error; + } + } async function exec() { return new Promise((ok, fail) => { diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 3b73ccfb138e3..538295c369b00 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -314,6 +314,7 @@ export async function destroyStack(options: DestroyStackOptions) { return; } const monitor = options.quiet ? undefined : new StackActivityMonitor(cfn, deployName, options.stack).start(); + try { await cfn.deleteStack({ StackName: deployName, RoleARN: options.roleArn }).promise(); const destroyedStack = await waitForStack(cfn, deployName, false); diff --git a/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts b/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts index d68e88aa99630..056f285695a45 100644 --- a/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts +++ b/packages/aws-cdk/lib/api/util/cloudformation/stack-activity-monitor.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as aws from 'aws-sdk'; import * as colors from 'colors/safe'; @@ -237,12 +238,12 @@ export class StackActivityMonitor { this.lastPrintTime = +Infinity; } - private findMetadataFor(logicalId: string | undefined): { entry: cxapi.MetadataEntry, path: string } | undefined { + private findMetadataFor(logicalId: string | undefined): { entry: cxschema.MetadataEntry, path: string } | undefined { const metadata = this.stack.manifest.metadata; if (!logicalId || !metadata) { return undefined; } for (const path of Object.keys(metadata)) { const entry = metadata[path] - .filter(e => e.type === cxapi.LOGICAL_ID_METADATA_KEY) + .filter(e => e.type === cxschema.ArtifactMetadataEntryType.LOGICAL_ID) .find(e => e.data === logicalId); if (entry) { return { entry, path }; diff --git a/packages/aws-cdk/lib/assets.ts b/packages/aws-cdk/lib/assets.ts index d23a1e2f6dd53..588cfca917d87 100644 --- a/packages/aws-cdk/lib/assets.ts +++ b/packages/aws-cdk/lib/assets.ts @@ -1,5 +1,6 @@ // tslint:disable-next-line:max-line-length import * as asset_schema from '@aws-cdk/cdk-assets-schema'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors'; import * as path from 'path'; @@ -51,7 +52,7 @@ export async function addMetadataAssetsToManifest(stack: cxapi.CloudFormationSta } // tslint:disable-next-line:max-line-length -async function prepareAsset(asset: cxapi.AssetMetadataEntry, assetManifest: AssetManifestBuilder, toolkitInfo: ToolkitInfo): Promise> { +async function prepareAsset(asset: cxschema.AssetMetadataEntry, assetManifest: AssetManifestBuilder, toolkitInfo: ToolkitInfo): Promise> { switch (asset.packaging) { case 'zip': case 'file': @@ -69,7 +70,7 @@ async function prepareAsset(asset: cxapi.AssetMetadataEntry, assetManifest: Asse } function prepareFileAsset( - asset: cxapi.FileAssetMetadataEntry, + asset: cxschema.FileAssetMetadataEntry, assetManifest: AssetManifestBuilder, toolkitInfo: ToolkitInfo, packaging: asset_schema.FileAssetPackaging): Record { @@ -99,7 +100,7 @@ function prepareFileAsset( } async function prepareDockerImageAsset( - asset: cxapi.ContainerImageAssetMetadataEntry, + asset: cxschema.ContainerImageAssetMetadataEntry, assetManifest: AssetManifestBuilder, toolkitInfo: ToolkitInfo): Promise> { diff --git a/packages/aws-cdk/lib/cdk-toolkit.ts b/packages/aws-cdk/lib/cdk-toolkit.ts index e22ce09da9930..abfed39b3c3e0 100644 --- a/packages/aws-cdk/lib/cdk-toolkit.ts +++ b/packages/aws-cdk/lib/cdk-toolkit.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors/safe'; import * as fs from 'fs-extra'; @@ -591,10 +592,23 @@ export interface DestroyOptions { * @returns an array with the tags available in the stack metadata. */ function tagsForStack(stack: cxapi.CloudFormationStackArtifact): Tag[] { - const tagLists = stack.findMetadataByType(cxapi.STACK_TAGS_METADATA_KEY).map(x => x.data); + const tagLists = stack.findMetadataByType(cxschema.ArtifactMetadataEntryType.STACK_TAGS).map( + // the tags in the cloud assembly are stored differently + // unfortunately. + x => toCloudFormationTags(x.data as cxschema.Tag[])); return Array.prototype.concat([], ...tagLists); } +/** + * Transform tags as they are retrieved from the cloud assembly, + * to the way that CloudFormation expects them. (Different casing). + */ +function toCloudFormationTags(tags: cxschema.Tag[]): Tag[] { + return tags.map(t => { + return { Key: t.key, Value: t.value }; + }); +} + export interface Tag { readonly Key: string; readonly Value: string; diff --git a/packages/aws-cdk/lib/context-providers/index.ts b/packages/aws-cdk/lib/context-providers/index.ts index 8a9357bd57fb1..b582c5789939e 100644 --- a/packages/aws-cdk/lib/context-providers/index.ts +++ b/packages/aws-cdk/lib/context-providers/index.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { SdkProvider } from '../api'; import { debug } from '../logging'; @@ -16,7 +17,7 @@ export type ProviderMap = {[name: string]: ProviderConstructor}; * Iterate over the list of missing context values and invoke the appropriate providers from the map to retrieve them */ export async function provideContextValues( - missingValues: cxapi.MissingContext[], + missingValues: cxschema.MissingContext[], context: Context, sdk: SdkProvider) { diff --git a/packages/aws-cdk/lib/diff.ts b/packages/aws-cdk/lib/diff.ts index 1d64d4a9dd1db..866f691c203a7 100644 --- a/packages/aws-cdk/lib/diff.ts +++ b/packages/aws-cdk/lib/diff.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cfnDiff from '@aws-cdk/cloudformation-diff'; import * as cxapi from '@aws-cdk/cx-api'; import * as colors from 'colors/safe'; @@ -85,8 +86,8 @@ function difRequiresApproval(diff: cfnDiff.TemplateDiff, requireApproval: Requir function buildLogicalToPathMap(stack: cxapi.CloudFormationStackArtifact) { const map: { [id: string]: string } = {}; - for (const md of stack.findMetadataByType(cxapi.LOGICAL_ID_METADATA_KEY)) { - map[md.data] = md.path; + for (const md of stack.findMetadataByType(cxschema.ArtifactMetadataEntryType.LOGICAL_ID)) { + map[md.data as string] = md.path; } return map; -} +} \ No newline at end of file diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 6602d01997d82..91281e67af566 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -63,12 +63,14 @@ "mockery": "^2.1.0", "pkglint": "0.0.0", "sinon": "^9.0.2", - "ts-jest": "^25.3.1" + "ts-jest": "^25.3.1", + "ts-mock-imports": "^1.2.6" }, "dependencies": { "@aws-cdk/cdk-assets-schema": "0.0.0", "@aws-cdk/cloudformation-diff": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/region-info": "0.0.0", "archiver": "^3.1.1", "aws-sdk": "^2.656.0", diff --git a/packages/aws-cdk/test/api/cloud-assembly.test.ts b/packages/aws-cdk/test/api/cloud-assembly.test.ts index e1187f831981c..26b33191c7ab5 100644 --- a/packages/aws-cdk/test/api/cloud-assembly.test.ts +++ b/packages/aws-cdk/test/api/cloud-assembly.test.ts @@ -1,4 +1,4 @@ -import * as cxapi from '@aws-cdk/cx-api'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { DefaultSelection } from '../../lib/api/cxapp/cloud-assembly'; import { MockCloudExecutable } from '../util'; @@ -74,7 +74,7 @@ async function testCloudAssembly({ env }: { env?: string, versionReporting?: boo metadata: { '/resource': [ { - type: cxapi.ERROR_METADATA_KEY, + type: cxschema.ArtifactMetadataEntryType.ERROR, data: 'this is an error' } ] diff --git a/packages/aws-cdk/test/api/cloud-executable.test.ts b/packages/aws-cdk/test/api/cloud-executable.test.ts index 4f4017985fb0d..922ee58b6639c 100644 --- a/packages/aws-cdk/test/api/cloud-executable.test.ts +++ b/packages/aws-cdk/test/api/cloud-executable.test.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { DefaultSelection } from '../../lib/api/cxapp/cloud-assembly'; import { registerContextProvider } from '../../lib/context-providers'; @@ -86,7 +87,7 @@ async function testCloudExecutable({ env, versionReporting = true }: { env?: str metadata: { '/resource': [ { - type: cxapi.ERROR_METADATA_KEY, + type: cxschema.ArtifactMetadataEntryType.ERROR, data: 'this is an error' } ] diff --git a/packages/aws-cdk/test/api/exec.test.ts b/packages/aws-cdk/test/api/exec.test.ts index 614b639610321..bd951c8582eae 100644 --- a/packages/aws-cdk/test/api/exec.test.ts +++ b/packages/aws-cdk/test/api/exec.test.ts @@ -1,5 +1,9 @@ jest.mock('child_process'); +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cdk from '@aws-cdk/core'; +import * as semver from 'semver'; import * as sinon from 'sinon'; +import { ImportMock } from 'ts-mock-imports'; import { execProgram } from '../../lib/api/cxapp/exec'; import { setVerbose } from '../../lib/logging'; import { Configuration } from '../../lib/settings'; @@ -34,6 +38,80 @@ afterEach(() => { bockfs.restore(); }); +// We need to increase the default 5s jest +// timeout for async tests because the 'execProgram' invocation +// might take a while :\ +const TEN_SECOND_TIMEOUT = 10000; + +function createApp(): cdk.App { + const app = new cdk.App({outdir: 'cdk.out'}); + const stack = new cdk.Stack(app, 'Stack'); + + new cdk.CfnResource(stack, 'Role', { + type: 'AWS::IAM::Role', + properties: { + RoleName: 'Role' + } + }); + + return app; +} + +test('cli throws when manifest version > schema version', async () => { + + const app = createApp(); + const currentSchemaVersion = cxschema.Manifest.version(); + const mockManifestVersion = semver.inc(currentSchemaVersion, 'major'); + + // this mock will cause the framework to use a greater schema version than the real one, + // and should cause the CLI to fail. + const mockVersionNumber = ImportMock.mockFunction(cxschema.Manifest, 'version', mockManifestVersion); + try { + app.synth(); + } finally { + mockVersionNumber.restore(); + } + + const expectedError = `Cloud assembly schema version mismatch: Maximum schema version supported is ${currentSchemaVersion}, but found ${mockManifestVersion}.` + + '\nPlease upgrade your CLI in order to interact with this app.'; + + config.settings.set(['app'], 'cdk.out'); + + await expect(execProgram(sdkProvider, config)).rejects.toEqual(new Error(expectedError)); + +}, TEN_SECOND_TIMEOUT); + +test('cli does not throw when manifest version = schema version', async () => { + + const app = createApp(); + app.synth(); + + config.settings.set(['app'], 'cdk.out'); + + await execProgram(sdkProvider, config); + +}, TEN_SECOND_TIMEOUT); + +test('cli does not throw when manifest version < schema version', async () => { + + const app = createApp(); + const currentSchemaVersion = cxschema.Manifest.version(); + + app.synth(); + + config.settings.set(['app'], 'cdk.out'); + + // this mock will cause the cli to think its exepcted schema version is + // greater that the version created in the manifest, which is what we are testing for. + const mockVersionNumber = ImportMock.mockFunction(cxschema.Manifest, 'version', semver.inc(currentSchemaVersion, 'major')); + try { + await execProgram(sdkProvider, config); + } finally { + mockVersionNumber.restore(); + } + +}, TEN_SECOND_TIMEOUT); + test('validates --app key is present', async () => { // GIVEN no config key for `app` await expect(execProgram(sdkProvider, config)).rejects.toThrow( @@ -118,5 +196,5 @@ function writeOutputAssembly() { const asm = testAssembly({ stacks: [] }); - bockfs.write('/home/project/cdk.out/manifest.json', JSON.stringify(asm)); + bockfs.write('/home/project/cdk.out/manifest.json', JSON.stringify(asm.manifest)); } diff --git a/packages/aws-cdk/test/assets.test.ts b/packages/aws-cdk/test/assets.test.ts index cea8ae8db6ada..5ce14421cd8d8 100644 --- a/packages/aws-cdk/test/assets.test.ts +++ b/packages/aws-cdk/test/assets.test.ts @@ -1,4 +1,4 @@ -import { AssetMetadataEntry } from '@aws-cdk/cx-api'; +import { AssetMetadataEntry } from '@aws-cdk/cloud-assembly-schema'; import { ToolkitInfo } from '../lib'; import { addMetadataAssetsToManifest } from '../lib/assets'; import { AssetManifestBuilder } from '../lib/util/asset-manifest-builder'; diff --git a/packages/aws-cdk/test/cdk-toolkit.test.ts b/packages/aws-cdk/test/cdk-toolkit.test.ts index 6d358a0b71f57..964ca7b78c3b6 100644 --- a/packages/aws-cdk/test/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cdk-toolkit.test.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import { CloudFormationDeployments, DeployStackOptions } from '../lib/api/cloudformation-deployments'; import { DeployStackResult } from '../lib/api/deploy-stack'; @@ -63,9 +64,9 @@ class MockStack { metadata: { '/Test-Stack-A': [ { - type: cxapi.STACK_TAGS_METADATA_KEY, + type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, data: [ - { Key: 'Foo', Value: 'Bar' } as Tag + { key: 'Foo', value: 'Bar' } ] } ] @@ -78,9 +79,9 @@ class MockStack { metadata: { '/Test-Stack-B': [ { - type: cxapi.STACK_TAGS_METADATA_KEY, + type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, data: [ - { Key: 'Baz', Value: 'Zinga!' } as Tag + { key: 'Baz', value: 'Zinga!' } ] } ] diff --git a/packages/aws-cdk/test/diff.test.ts b/packages/aws-cdk/test/diff.test.ts index bd43408ef4ab3..2c510f481667c 100644 --- a/packages/aws-cdk/test/diff.test.ts +++ b/packages/aws-cdk/test/diff.test.ts @@ -1,4 +1,4 @@ -import * as cxapi from '@aws-cdk/cx-api'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import { Writable } from 'stream'; import { NodeStringDecoder, StringDecoder } from 'string_decoder'; import { CloudFormationDeployments } from '../lib/api/cloudformation-deployments'; @@ -26,7 +26,7 @@ beforeEach(() => { metadata: { '/resource': [ { - type: cxapi.ERROR_METADATA_KEY, + type: cxschema.ArtifactMetadataEntryType.ERROR, data: 'this is an error' } ] diff --git a/packages/aws-cdk/test/util.ts b/packages/aws-cdk/test/util.ts index 0490dd0fb5948..b6887ef9f389e 100644 --- a/packages/aws-cdk/test/util.ts +++ b/packages/aws-cdk/test/util.ts @@ -1,3 +1,4 @@ +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; import * as cxapi from '@aws-cdk/cx-api'; import * as fs from 'fs'; import * as path from 'path'; @@ -11,12 +12,12 @@ export interface TestStackArtifact { env?: string, depends?: string[]; metadata?: cxapi.StackMetadata; - assets?: cxapi.AssetMetadataEntry[]; + assets?: cxschema.AssetMetadataEntry[]; } export interface TestAssembly { stacks: TestStackArtifact[]; - missing?: cxapi.MissingContext[]; + missing?: cxschema.MissingContext[]; } export class MockCloudExecutable extends CloudExecutable { @@ -38,6 +39,10 @@ export class MockCloudExecutable extends CloudExecutable { } } +function clone(obj: any) { + return JSON.parse(JSON.stringify(obj)); +} + export function testAssembly(assembly: TestAssembly): cxapi.CloudAssembly { const builder = new cxapi.CloudAssemblyBuilder(); @@ -45,10 +50,12 @@ export function testAssembly(assembly: TestAssembly): cxapi.CloudAssembly { const templateFile = `${stack.stackName}.template.json`; fs.writeFileSync(path.join(builder.outdir, templateFile), JSON.stringify(stack.template, undefined, 2)); - const metadata: { [path: string]: cxapi.MetadataEntry[] } = { ...stack.metadata }; + // we call patchStackTags here to simulate the tags formatter + // that is used when building real manifest files. + const metadata: { [path: string]: cxschema.MetadataEntry[] } = patchStackTags({ ...stack.metadata }); for (const asset of stack.assets || []) { metadata[asset.id] = [ - { type: cxapi.ASSET_METADATA, data: asset } + { type: cxschema.ArtifactMetadataEntryType.ASSET, data: asset } ]; } @@ -57,7 +64,7 @@ export function testAssembly(assembly: TestAssembly): cxapi.CloudAssembly { } builder.addArtifact(stack.stackName, { - type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK, + type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK, environment: stack.env || 'aws://12345/here', dependencies: stack.depends, @@ -71,6 +78,32 @@ export function testAssembly(assembly: TestAssembly): cxapi.CloudAssembly { return builder.buildAssembly(); } +/** + * Transform stack tags from how they are decalred in source code (lower cased) + * to how they are stored on disk (upper cased). In real synthesis this is done + * by a special tags formatter. + * + * @see @aws-cdk/core/lib/stack.ts + */ +function patchStackTags(metadata: { [path: string]: cxschema.MetadataEntry[] }): { [path: string]: cxschema.MetadataEntry[] } { + + const cloned = clone(metadata) as { [path: string]: cxschema.MetadataEntry[] }; + + for (const metadataEntries of Object.values(cloned)) { + for (const metadataEntry of metadataEntries) { + if (metadataEntry.type === cxschema.ArtifactMetadataEntryType.STACK_TAGS && metadataEntry.data) { + + const metadataAny = metadataEntry as any; + + metadataAny.data = metadataAny.data.map((t: any) => { + return { Key: t.key, Value: t.value }; + }); + } + } + } + return cloned; +} + export function testStack(stack: TestStackArtifact) { const assembly = testAssembly({ stacks: [stack] }); return assembly.getStackByName(stack.stackName); diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 8f353dd13aaff..8f9fadd39e665 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -168,6 +168,7 @@ "@aws-cdk/core": "0.0.0", "@aws-cdk/custom-resources": "0.0.0", "@aws-cdk/cx-api": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/region-info": "0.0.0", "constructs": "^2.0.0", "fs-extra": "^8.1.0", diff --git a/packages/monocdk-experiment/gen.js b/packages/monocdk-experiment/gen.js index ce2735b5a937d..4a68e0fc81a0e 100644 --- a/packages/monocdk-experiment/gen.js +++ b/packages/monocdk-experiment/gen.js @@ -21,6 +21,7 @@ const include_dev_deps = [ const exclude_files = [ 'test', + 'scripts', 'node_modules', 'package.json', 'tsconfig.json', diff --git a/packages/monocdk-experiment/package.json b/packages/monocdk-experiment/package.json index 9fc49326660a8..69cb54d87d3e8 100644 --- a/packages/monocdk-experiment/package.json +++ b/packages/monocdk-experiment/package.json @@ -161,6 +161,7 @@ "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/region-info": "0.0.0", "@types/node": "^10.17.18", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/aws-chatbot": "0.0.0", "@aws-cdk/aws-codestarconnections": "0.0.0", "@aws-cdk/aws-cassandra": "0.0.0", diff --git a/tools/cdk-build-tools/bin/cdk-build.ts b/tools/cdk-build-tools/bin/cdk-build.ts index 334447564151f..987bde5d5e1ff 100644 --- a/tools/cdk-build-tools/bin/cdk-build.ts +++ b/tools/cdk-build-tools/bin/cdk-build.ts @@ -46,6 +46,10 @@ async function main() { } await compileCurrentPackage(timers, options, { eslint: args.eslint, jsii: args.jsii, tsc: args.tsc, tslint: args.tslint }); + + if (options.post) { + await shell(options.post, { timers }); + } } const timers = new Timers(); diff --git a/tools/cdk-build-tools/lib/package-info.ts b/tools/cdk-build-tools/lib/package-info.ts index 0ad4fd5644cc9..481def920bb8d 100644 --- a/tools/cdk-build-tools/lib/package-info.ts +++ b/tools/cdk-build-tools/lib/package-info.ts @@ -126,6 +126,13 @@ export interface CDKBuildOptions { */ pre?: string[]; + /** + * An optional command (formatted as a list of strings) to run after building + * + * (Schema generator for example) + */ + post?: string[]; + /** * An optional command (formatted as a list of strings) to run before testing. */ diff --git a/tools/cdk-build-tools/package.json b/tools/cdk-build-tools/package.json index 9b0fb4879dc77..2b1a75213839c 100644 --- a/tools/cdk-build-tools/package.json +++ b/tools/cdk-build-tools/package.json @@ -21,8 +21,9 @@ "build": "tsc -b && tslint -p . && chmod +x bin/cdk-build && chmod +x bin/cdk-test && chmod +x bin/cdk-watch && chmod +x bin/cdk-awslint && pkglint", "watch": "tsc -b -w", "pkglint": "pkglint -f", + "test": "echo success", "build+test+package": "npm run build+test", - "build+test": "npm run build" + "build+test": "npm run build && npm test" }, "author": { "name": "Amazon Web Services", diff --git a/tools/pkglint/lib/rules.ts b/tools/pkglint/lib/rules.ts index b8fdf9c569aae..94551fa66a9f6 100644 --- a/tools/pkglint/lib/rules.ts +++ b/tools/pkglint/lib/rules.ts @@ -676,6 +676,7 @@ export class MustDependonCdkByPointVersions extends ValidationRule { '@aws-cdk/cfnspec', '@aws-cdk/cdk-assets-schema', '@aws-cdk/cx-api', + '@aws-cdk/cloud-assembly-schema', '@aws-cdk/region-info' ]; diff --git a/tools/pkglint/package.json b/tools/pkglint/package.json index 40c8e19e58eec..8936a47516fe5 100644 --- a/tools/pkglint/package.json +++ b/tools/pkglint/package.json @@ -17,8 +17,9 @@ }, "scripts": { "build": "tsc -b && tslint -p . && chmod +x bin/pkglint", - "build+test": "npm run build", - "build+test+package": "npm run build", + "test": "echo success", + "build+test": "npm run build && npm test", + "build+test+package": "npm run build+test", "watch": "tsc -b -w", "lint": "tsc -b && tslint -p . --force" }, diff --git a/tools/prlint/package.json b/tools/prlint/package.json index b51a6787878e4..0467d3d8bd049 100644 --- a/tools/prlint/package.json +++ b/tools/prlint/package.json @@ -16,5 +16,10 @@ "dependencies": { "github-api": "^3.3.0", "make-runnable": "^1.3.6" + }, + "scripts": { + "build": "echo success", + "test": "echo sucees", + "build+test": "npm run build test && npm run test" } } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b4d01e5a69c8e..c11e50ee9a495 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2566,6 +2566,11 @@ anymatch@^3.0.3: normalize-path "^3.0.0" picomatch "^2.0.4" +app-root-path@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.2.1.tgz#d0df4a682ee408273583d43f6f79e9892624bc9a" + integrity sha512-91IFKeKk7FjfmezPKkwtaRvSpnUc4gDwPAjA1YZ9Gn0q0PPeW+vbeUsZuyDwjI7+QTHhcLen2v25fi/AmhvbJA== + append-transform@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" @@ -2799,7 +2804,7 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -aws-sdk-mock@^5.1.0: +aws-sdk-mock@^5.0.0, aws-sdk-mock@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/aws-sdk-mock/-/aws-sdk-mock-5.1.0.tgz#6f2c0bd670d7f378c906a8dd806f812124db71aa" integrity sha512-Wa5eCSo8HX0Snqb7FdBylaXMmfrAWoWZ+d7MFhiYsgHPvNvMEGjV945FF2qqE1U0Tolr1ALzik1fcwgaOhqUWQ== @@ -2808,7 +2813,7 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.637.0, aws-sdk@^2.656.0: +aws-sdk@^2.596.0, aws-sdk@^2.637.0, aws-sdk@^2.656.0: version "2.656.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.656.0.tgz#0d74664ddbf30701073be9f9913ee7266afef3b4" integrity sha512-UzqDvvt6i7gpuzEdK0GT/JOfBJcsCPranzZWdQ9HR4+5E0m5kf5gybZ6OX+UseIAE2/WND6Dv0aHgiI21AKenw== @@ -4758,11 +4763,21 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== +dotenv-json@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dotenv-json/-/dotenv-json-1.0.0.tgz#fc7f672aafea04bed33818733b9f94662332815c" + integrity sha512-jAssr+6r4nKhKRudQ0HOzMskOFFi9+ubXWwmrSGJFgTvpjyPXCXsCsYbjif6mXp7uxA7xY3/LGaiTQukZzSbOQ== + dotenv@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow== +dotenv@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + dotgitignore@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dotgitignore/-/dotgitignore-2.1.0.tgz#a4b15a4e4ef3cf383598aaf1dfa4a04bcc089b7b" @@ -4997,6 +5012,11 @@ escodegen@~1.9.0: optionalDependencies: source-map "~0.6.1" +eslint-config-standard@^14.1.0: + version "14.1.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea" + integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== + eslint-import-resolver-node@^0.3.2, eslint-import-resolver-node@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" @@ -5024,7 +5044,15 @@ eslint-module-utils@^2.4.1: debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-import@^2.20.2: +eslint-plugin-es@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz#0f5f5da5f18aa21989feebe8a73eadefb3432976" + integrity sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ== + dependencies: + eslint-utils "^1.4.2" + regexpp "^3.0.0" + +eslint-plugin-import@^2.19.1, eslint-plugin-import@^2.20.2: version "2.20.2" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== @@ -5042,6 +5070,28 @@ eslint-plugin-import@^2.20.2: read-pkg-up "^2.0.0" resolve "^1.12.0" +eslint-plugin-node@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6" + integrity sha512-1CSyM/QCjs6PXaT18+zuAXsjXGIGo5Rw630rSKwokSs2jrYURQc4R5JZpoanNCqwNmepg+0eZ9L7YiRUJb8jiQ== + dependencies: + eslint-plugin-es "^2.0.0" + eslint-utils "^1.4.2" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-promise@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" + integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== + +eslint-plugin-standard@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" + integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== + eslint-scope@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" @@ -5050,7 +5100,7 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.3: +eslint-utils@^1.4.2, eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== @@ -5875,7 +5925,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -6248,6 +6298,11 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.1: + version "5.1.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" + integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -7872,6 +7927,13 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= + dependencies: + jsonify "~0.0.0" + json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -7898,6 +7960,11 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -7962,6 +8029,24 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +lambda-leak@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lambda-leak/-/lambda-leak-2.0.0.tgz#771985d3628487f6e885afae2b54510dcfb2cd7e" + integrity sha1-dxmF02KEh/boha+uK1RRDc+yzX4= + +lambda-tester@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/lambda-tester/-/lambda-tester-3.6.0.tgz#ceb7d4f4f0da768487a05cff37dcd088508b5247" + integrity sha512-F2ZTGWCLyIR95o/jWK46V/WnOCFAEUG/m/V7/CLhPJ7PCM+pror1rZ6ujP3TkItSGxUfpJi0kqwidw+M/nEqWw== + dependencies: + app-root-path "^2.2.1" + dotenv "^8.0.0" + dotenv-json "^1.0.0" + lambda-leak "^2.0.0" + semver "^6.1.1" + uuid "^3.3.2" + vandium-utils "^1.1.1" + lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -8550,7 +8635,7 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@~1.2.0: +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -8611,6 +8696,13 @@ mkdirp@*, mkdirp@1.x: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.3.tgz#4cf2e30ad45959dddea53ad97d518b6c8205e1ea" integrity sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g== +mkdirp@0.x: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -8752,6 +8844,17 @@ nise@^4.0.1: just-extend "^4.0.2" path-to-regexp "^1.7.0" +nock@^11.7.0: + version "11.9.1" + resolved "https://registry.yarnpkg.com/nock/-/nock-11.9.1.tgz#2b026c5beb6d0dbcb41e7e4cefa671bc36db9c61" + integrity sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + lodash "^4.17.13" + mkdirp "^0.5.0" + propagate "^2.0.0" + nock@^12.0.3: version "12.0.3" resolved "https://registry.yarnpkg.com/nock/-/nock-12.0.3.tgz#83f25076dbc4c9aa82b5cdf54c9604c7a778d1c9" @@ -10809,7 +10912,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.1.5, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.4.0: +resolve@1.x, resolve@^1.1.5, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.4.0: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== @@ -10973,12 +11076,12 @@ semver-intersect@^1.4.0: dependencies: semver "^5.0.0" -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.3.0, semver@6.x, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@6.3.0, semver@6.x, semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -12131,6 +12234,22 @@ trivial-deferred@^1.0.1: resolved "https://registry.yarnpkg.com/trivial-deferred/-/trivial-deferred-1.0.1.tgz#376d4d29d951d6368a6f7a0ae85c2f4d5e0658f3" integrity sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM= +ts-jest@^24.2.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.3.0.tgz#b97814e3eab359ea840a1ac112deae68aa440869" + integrity sha512-Hb94C/+QRIgjVZlJyiWwouYUF+siNJHJHknyspaOcZ+OQAIdFG/UrdQVXw/0B8Z3No34xkUXZJpOTy9alOWdVQ== + dependencies: + bs-logger "0.x" + buffer-from "1.x" + fast-json-stable-stringify "2.x" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + mkdirp "0.x" + resolve "1.x" + semver "^5.5" + yargs-parser "10.x" + ts-jest@^25.3.1: version "25.3.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.3.1.tgz#58e2ed3506e4e4487c0b9b532846a5cade9656ba" @@ -12148,7 +12267,7 @@ ts-jest@^25.3.1: semver "6.x" yargs-parser "18.x" -ts-mock-imports@^1.3.0: +ts-mock-imports@^1.2.6, ts-mock-imports@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ts-mock-imports/-/ts-mock-imports-1.3.0.tgz#ed9b743349f3c27346afe5b7454ffd2bcaa2302d" integrity sha512-cCrVcRYsp84eDvPict0ZZD/D7ppQ0/JSx4ve6aEU8DjlsaWRJWV6ADMovp2sCuh6pZcduLFoIYhKTDU2LARo7Q== @@ -12273,7 +12392,18 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.3.3, typescript@~3.8.3: +typescript-json-schema@^0.42.0: + version "0.42.0" + resolved "https://registry.yarnpkg.com/typescript-json-schema/-/typescript-json-schema-0.42.0.tgz#695f212a72d91d47c0605371dc697597b7817c1b" + integrity sha512-9WO+lVmlph7Ecb7lPd9tU84XFUQh44kpAf3cWe/Ym4G5EKw/SS6XGpi1DZDthvxqkIdNSDlWi7FhKfxuIV/3yw== + dependencies: + "@types/json-schema" "^7.0.3" + glob "~7.1.4" + json-stable-stringify "^1.0.1" + typescript "^3.5.3" + yargs "^14.0.0" + +typescript@^3.3.3, typescript@^3.5.3, typescript@~3.8.3: version "3.8.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== @@ -12544,6 +12674,11 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" +vandium-utils@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vandium-utils/-/vandium-utils-1.2.0.tgz#44735de4b7641a05de59ebe945f174e582db4f59" + integrity sha1-RHNd5LdkGgXeWevpRfF05YLbT1k= + vendors@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" @@ -12904,6 +13039,13 @@ yapool@^1.0.0: resolved "https://registry.yarnpkg.com/yapool/-/yapool-1.0.0.tgz#f693f29a315b50d9a9da2646a7a6645c96985b6a" integrity sha1-9pPymjFbUNmp2iZGp6ZkXJaYW2o= +yargs-parser@10.x, yargs-parser@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" + integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== + dependencies: + camelcase "^4.1.0" + yargs-parser@18.x, yargs-parser@^18.1.1: version "18.1.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.2.tgz#2f482bea2136dbde0861683abea7756d30b504f1" @@ -12912,13 +13054,6 @@ yargs-parser@18.x, yargs-parser@^18.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" - integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ== - dependencies: - camelcase "^4.1.0" - yargs-parser@^13.0.0, yargs-parser@^13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"