diff --git a/buildspec-pr.yaml b/buildspec-pr.yaml index 639c21a87dabe..98db30c019f7e 100644 --- a/buildspec-pr.yaml +++ b/buildspec-pr.yaml @@ -8,6 +8,8 @@ phases: # Start docker daemon inside the container - nohup /usr/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2& - timeout 15 sh -c "until docker info; do echo .; sleep 1; done" + # login to DockerHub to avoid throttling + - docker login -u ${DOCKERHUB_USERNAME} -p ${DOCKERHUB_PASSWORD} # Install yarn if it wasn't already present in the image - yarn --version || npm -g install yarn diff --git a/packages/@aws-cdk/aws-apigatewayv2/README.md b/packages/@aws-cdk/aws-apigatewayv2/README.md index 9d94f4ed3f8da..14ce142487587 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2/README.md @@ -1,15 +1,16 @@ ## AWS::APIGatewayv2 Construct Library - --- -![cfn-resources: Stable](https://img.shields.io/badge/cfn--resources-stable-success.svg?style=for-the-badge) - -> All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. +| Features | Stability | +| --- | --- | +| CFN Resources | ![Stable](https://img.shields.io/badge/stable-success.svg?style=for-the-badge) | +| Higher level constructs for HTTP APIs | ![Experimental](https://img.shields.io/badge/experimental-important.svg?style=for-the-badge) | +| Higher level constructs for Websocket APIs | ![Not Implemented](https://img.shields.io/badge/not--implemented-black.svg?style=for-the-badge) | -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) +> **CFN Resources:** All classes with the `Cfn` prefix in this module ([CFN Resources](https://docs.aws.amazon.com/cdk/latest/guide/constructs.html#constructs_lib)) are always stable and safe to use. -> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. +> **Experimental:** Higher level constructs in this module that are marked as experimental are under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. --- diff --git a/packages/@aws-cdk/aws-apigatewayv2/package.json b/packages/@aws-cdk/aws-apigatewayv2/package.json index bc22bfc9ed2f7..d136d4e9c4ce2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/package.json +++ b/packages/@aws-cdk/aws-apigatewayv2/package.json @@ -99,7 +99,16 @@ ] }, "stability": "experimental", - "maturity": "experimental", + "features": [ + { + "name": "Higher level constructs for HTTP APIs", + "stability": "Experimental" + }, + { + "name": "Higher level constructs for Websocket APIs", + "stability": "Not Implemented" + } + ], "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 58dceef0bc992..f1a6ea42cc73c 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -4,7 +4,7 @@ import { ManagedPolicy, Role, ServicePrincipal, Grant, IGrantable } from '@aws-c import { CfnResource, Construct, Duration, IResolvable, Stack } from '@aws-cdk/core'; import { CfnApiKey, CfnGraphQLApi, CfnGraphQLSchema } from './appsync.generated'; import { IGraphqlApi, GraphqlApiBase } from './graphqlapi-base'; -import { ObjectType, ObjectTypeProps } from './schema-types'; +import { ObjectType, ObjectTypeProps } from './schema-intermediate'; /** * enum with all possible values for AppSync authorization type diff --git a/packages/@aws-cdk/aws-appsync/lib/index.ts b/packages/@aws-cdk/aws-appsync/lib/index.ts index d418c63f0232c..341312977c5d3 100644 --- a/packages/@aws-cdk/aws-appsync/lib/index.ts +++ b/packages/@aws-cdk/aws-appsync/lib/index.ts @@ -4,6 +4,8 @@ export * from './key'; export * from './data-source'; export * from './mapping-template'; export * from './resolver'; -export * from './schema-types'; +export * from './schema-intermediate'; +export * from './schema-field'; +export * from './schema-base'; export * from './graphqlapi'; export * from './graphqlapi-base'; diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-base.ts b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts new file mode 100644 index 0000000000000..0e9c9b82910cc --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema-base.ts @@ -0,0 +1,88 @@ +/** + * Enum containing the Types that can be used to define ObjectTypes + */ +export enum Type { + /** + * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. + * + * Often used as a key for a cache and not intended to be human-readable. + */ + ID = 'ID', + /** + * `String` scalar type is a free-form human-readable text. + */ + STRING = 'String', + /** + * `Int` scalar type is a signed non-fractional numerical value. + */ + INT = 'Int', + /** + * `Float` scalar type is a signed double-precision fractional value. + */ + FLOAT = 'Float', + /** + * `Boolean` scalar type is a boolean value: true or false. + */ + BOOLEAN = 'Boolean', + + /** + * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates + */ + AWS_DATE = 'AWSDate', + /** + * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. + * + * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Times + */ + AWS_TIME = 'AWSTime', + /** + * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. + * + * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. + * + * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations + */ + AWS_DATE_TIME = 'AWSDateTime', + /** + * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. + * + * Timestamps are serialized and deserialized as numbers. + */ + AWS_TIMESTAMP = 'AWSTimestamp', + /** + * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) + */ + AWS_EMAIL = 'AWSEmail', + /** + * `AWSJson` scalar type represents a JSON string. + */ + AWS_JSON = 'AWSJSON', + /** + * `AWSURL` scalar type represetns a valid URL string. + * + * URLs wihtout schemes or contain double slashes are considered invalid. + */ + AWS_URL = 'AWSURL', + /** + * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. + * + * The number can specify a country code at the beginning, but is not required for US phone numbers. + */ + AWS_PHONE = 'AWSPhone', + /** + * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. + */ + AWS_IP_ADDRESS = 'AWSIPAddress', + + /** + * Type used for Intermediate Types + * (i.e. an interface or an object type) + */ + INTERMEDIATE = 'INTERMEDIATE', +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts similarity index 53% rename from packages/@aws-cdk/aws-appsync/lib/schema-types.ts rename to packages/@aws-cdk/aws-appsync/lib/schema-field.ts index 2649fb41bc64b..6e4ddd6b0239a 100644 --- a/packages/@aws-cdk/aws-appsync/lib/schema-types.ts +++ b/packages/@aws-cdk/aws-appsync/lib/schema-field.ts @@ -1,204 +1,5 @@ -/** - * Directives for types - * - * i.e. @aws_iam or @aws_subscribe - * - * @experimental - */ -export class Directive { - /** - * Add the @aws_iam directive - */ - public static iam(): Directive{ - return new Directive('@aws_iam'); - } - - /** - * Add a custom directive - * - * @param statement - the directive statement to append - * Note: doesn't guarantee functionality - */ - public static custom(statement: string): Directive { - return new Directive(statement); - } - - /** - * the directive statement - */ - public readonly statement: string; - - private constructor(statement: string) { this.statement = statement; } -} - -/** - * Properties for configuring an Intermediate Type - * - * @param definition - the variables and types that define this type - * i.e. { string: GraphqlType, string: GraphqlType } - * - * @experimental - */ -export interface IntermediateTypeProps { - /** - * the attributes of this type - */ - readonly definition: { [key: string]: GraphqlType }; -} - -/** - * Interface Types are abstract types that includes a certain set of fields - * that other types must include if they implement the interface. - * - * @experimental - */ -export class InterfaceType { - /** - * the name of this type - */ - public readonly name: string; - /** - * the attributes of this type - */ - public readonly definition: { [key: string]: GraphqlType }; - - public constructor(name: string, props: IntermediateTypeProps) { - this.name = name; - this.definition = props.definition; - } - - /** - * Create an GraphQL Type representing this Intermediate Type - * - * @param options the options to configure this attribute - * - isList - * - isRequired - * - isRequiredList - */ - public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ - return GraphqlType.intermediate({ - isList: options?.isList, - isRequired: options?.isRequired, - isRequiredList: options?.isRequiredList, - intermediateType: this, - }); - } - - /** - * Generate the string of this object type - */ - public toString(): string { - let schemaAddition = `interface ${this.name} {\n`; - Object.keys(this.definition).forEach( (key) => { - const attribute = this.definition[key]; - schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; - }); - return `${schemaAddition}}`; - } -} - -/** - * Properties for configuring an Object Type - * - * @param definition - the variables and types that define this type - * i.e. { string: GraphqlType, string: GraphqlType } - * @param interfaceTypes - the interfaces that this object type implements - * @param directives - the directives for this object type - * - * @experimental - */ -export interface ObjectTypeProps extends IntermediateTypeProps { - /** - * The Interface Types this Object Type implements - * - * @default - no interface types - */ - readonly interfaceTypes?: InterfaceType[]; - /** - * the directives for this object type - * - * @default - no directives - */ - readonly directives?: Directive[]; -} - -/** - * Object Types are types declared by you. - * - * @experimental - */ -export class ObjectType extends InterfaceType { - /** - * A method to define Object Types from an interface - */ - public static implementInterface(name: string, props: ObjectTypeProps): ObjectType { - if (!props.interfaceTypes || !props.interfaceTypes.length) { - throw new Error('Static function `implementInterface` requires an interfaceType to implement'); - } - return new ObjectType(name, { - interfaceTypes: props.interfaceTypes, - definition: props.interfaceTypes.reduce((def, interfaceType) => { - return Object.assign({}, def, interfaceType.definition); - }, props.definition), - directives: props.directives, - }); - } - /** - * The Interface Types this Object Type implements - * - * @default - no interface types - */ - public readonly interfaceTypes?: InterfaceType[]; - /** - * the directives for this object type - * - * @default - no directives - */ - public readonly directives?: Directive[]; - - public constructor(name: string, props: ObjectTypeProps) { - super(name, props); - this.interfaceTypes = props.interfaceTypes; - this.directives = props.directives; - } - - /** - * Generate the string of this object type - */ - public toString(): string { - let title = this.name; - if(this.interfaceTypes && this.interfaceTypes.length){ - title = `${title} implements`; - this.interfaceTypes.map((interfaceType) => { - title = `${title} ${interfaceType.name},`; - }); - title = title.slice(0, -1); - } - const directives = this.generateDirectives(this.directives); - let schemaAddition = `type ${title} ${directives}{\n`; - Object.keys(this.definition).forEach( (key) => { - const attribute = this.definition[key]; - schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; - }); - return `${schemaAddition}}`; - } - - /** - * Utility function to generate directives - * - * @param directives the directives of a given type - * @param delimiter the separator betweeen directives - * @default - ' ' - */ - private generateDirectives(directives?: Directive[], delimiter?: string): string{ - let schemaAddition = ''; - if (!directives){ return schemaAddition; } - directives.map((directive) => { - schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; - }); - return schemaAddition; - } -} +import { Type } from './schema-base'; +import { InterfaceType } from './schema-intermediate'; /** * Base options for GraphQL Types @@ -512,93 +313,4 @@ export class GraphqlType { type = this.isRequiredList ? `${type}!` : type; return type; } -} - -/** - * Enum containing the Types that can be used to define ObjectTypes - */ -export enum Type { - /** - * `ID` scalar type is a unique identifier. `ID` type is serialized similar to `String`. - * - * Often used as a key for a cache and not intended to be human-readable. - */ - ID = 'ID', - /** - * `String` scalar type is a free-form human-readable text. - */ - STRING = 'String', - /** - * `Int` scalar type is a signed non-fractional numerical value. - */ - INT = 'Int', - /** - * `Float` scalar type is a signed double-precision fractional value. - */ - FLOAT = 'Float', - /** - * `Boolean` scalar type is a boolean value: true or false. - */ - BOOLEAN = 'Boolean', - - /** - * `AWSDate` scalar type represents a valid extended `ISO 8601 Date` string. - * - * In other words, accepts date strings in the form of `YYYY-MM-DD`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates - */ - AWS_DATE = 'AWSDate', - /** - * `AWSTime` scalar type represents a valid extended `ISO 8601 Time` string. - * - * In other words, accepts date strings in the form of `hh:mm:ss.sss`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Times - */ - AWS_TIME = 'AWSTime', - /** - * `AWSDateTime` scalar type represents a valid extended `ISO 8601 DateTime` string. - * - * In other words, accepts date strings in the form of `YYYY-MM-DDThh:mm:ss.sssZ`. It accepts time zone offsets. - * - * @see https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations - */ - AWS_DATE_TIME = 'AWSDateTime', - /** - * `AWSTimestamp` scalar type represents the number of seconds since `1970-01-01T00:00Z`. - * - * Timestamps are serialized and deserialized as numbers. - */ - AWS_TIMESTAMP = 'AWSTimestamp', - /** - * `AWSEmail` scalar type represents an email address string (i.e.`username@example.com`) - */ - AWS_EMAIL = 'AWSEmail', - /** - * `AWSJson` scalar type represents a JSON string. - */ - AWS_JSON = 'AWSJSON', - /** - * `AWSURL` scalar type represetns a valid URL string. - * - * URLs wihtout schemes or contain double slashes are considered invalid. - */ - AWS_URL = 'AWSURL', - /** - * `AWSPhone` scalar type represents a valid phone number. Phone numbers maybe be whitespace delimited or hyphenated. - * - * The number can specify a country code at the beginning, but is not required for US phone numbers. - */ - AWS_PHONE = 'AWSPhone', - /** - * `AWSIPAddress` scalar type respresents a valid `IPv4` of `IPv6` address string. - */ - AWS_IP_ADDRESS = 'AWSIPAddress', - - /** - * Type used for Intermediate Types - * (i.e. an interface or an object type) - */ - INTERMEDIATE = 'INTERMEDIATE', } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts new file mode 100644 index 0000000000000..92e78d8a359da --- /dev/null +++ b/packages/@aws-cdk/aws-appsync/lib/schema-intermediate.ts @@ -0,0 +1,203 @@ +import { BaseGraphqlTypeOptions, GraphqlType } from './schema-field'; + +/** + * Directives for types + * + * i.e. @aws_iam or @aws_subscribe + * + * @experimental + */ +export class Directive { + /** + * Add the @aws_iam directive + */ + public static iam(): Directive{ + return new Directive('@aws_iam'); + } + + /** + * Add a custom directive + * + * @param statement - the directive statement to append + * Note: doesn't guarantee functionality + */ + public static custom(statement: string): Directive { + return new Directive(statement); + } + + /** + * the directive statement + */ + public readonly statement: string; + + private constructor(statement: string) { this.statement = statement; } +} + +/** + * Properties for configuring an Intermediate Type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } + * + * @experimental + */ +export interface IntermediateTypeProps { + /** + * the attributes of this type + */ + readonly definition: { [key: string]: GraphqlType }; +} + +/** + * Interface Types are abstract types that includes a certain set of fields + * that other types must include if they implement the interface. + * + * @experimental + */ +export class InterfaceType { + /** + * the name of this type + */ + public readonly name: string; + /** + * the attributes of this type + */ + public readonly definition: { [key: string]: GraphqlType }; + + public constructor(name: string, props: IntermediateTypeProps) { + this.name = name; + this.definition = props.definition; + } + + /** + * Create an GraphQL Type representing this Intermediate Type + * + * @param options the options to configure this attribute + * - isList + * - isRequired + * - isRequiredList + */ + public attribute(options?: BaseGraphqlTypeOptions): GraphqlType{ + return GraphqlType.intermediate({ + isList: options?.isList, + isRequired: options?.isRequired, + isRequiredList: options?.isRequiredList, + intermediateType: this, + }); + } + + /** + * Generate the string of this object type + */ + public toString(): string { + let schemaAddition = `interface ${this.name} {\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } +} + +/** + * Properties for configuring an Object Type + * + * @param definition - the variables and types that define this type + * i.e. { string: GraphqlType, string: GraphqlType } + * @param interfaceTypes - the interfaces that this object type implements + * @param directives - the directives for this object type + * + * @experimental + */ +export interface ObjectTypeProps extends IntermediateTypeProps { + /** + * The Interface Types this Object Type implements + * + * @default - no interface types + */ + readonly interfaceTypes?: InterfaceType[]; + /** + * the directives for this object type + * + * @default - no directives + */ + readonly directives?: Directive[]; +} + +/** + * Object Types are types declared by you. + * + * @experimental + */ +export class ObjectType extends InterfaceType { + /** + * A method to define Object Types from an interface + */ + public static implementInterface(name: string, props: ObjectTypeProps): ObjectType { + if (!props.interfaceTypes || !props.interfaceTypes.length) { + throw new Error('Static function `implementInterface` requires an interfaceType to implement'); + } + return new ObjectType(name, { + interfaceTypes: props.interfaceTypes, + definition: props.interfaceTypes.reduce((def, interfaceType) => { + return Object.assign({}, def, interfaceType.definition); + }, props.definition), + directives: props.directives, + }); + } + /** + * The Interface Types this Object Type implements + * + * @default - no interface types + */ + public readonly interfaceTypes?: InterfaceType[]; + /** + * the directives for this object type + * + * @default - no directives + */ + public readonly directives?: Directive[]; + + public constructor(name: string, props: ObjectTypeProps) { + super(name, props); + this.interfaceTypes = props.interfaceTypes; + this.directives = props.directives; + } + + /** + * Generate the string of this object type + */ + public toString(): string { + let title = this.name; + if(this.interfaceTypes && this.interfaceTypes.length){ + title = `${title} implements`; + this.interfaceTypes.map((interfaceType) => { + title = `${title} ${interfaceType.name},`; + }); + title = title.slice(0, -1); + } + const directives = this.generateDirectives(this.directives); + let schemaAddition = `type ${title} ${directives}{\n`; + Object.keys(this.definition).forEach( (key) => { + const attribute = this.definition[key]; + schemaAddition = `${schemaAddition} ${key}: ${attribute.toString()}\n`; + }); + return `${schemaAddition}}`; + } + + /** + * Utility function to generate directives + * + * @param directives the directives of a given type + * @param delimiter the separator betweeen directives + * @default - ' ' + */ + private generateDirectives(directives?: Directive[], delimiter?: string): string{ + let schemaAddition = ''; + if (!directives){ return schemaAddition; } + directives.map((directive) => { + schemaAddition = `${schemaAddition}${directive.statement}${delimiter ?? ' '}`; + }); + return schemaAddition; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront-origins/package.json b/packages/@aws-cdk/aws-cloudfront-origins/package.json index c67cc58d43705..779c2fa82b8ef 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/package.json +++ b/packages/@aws-cdk/aws-cloudfront-origins/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "pkglint": "0.0.0" diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index de2316ad245ac..78776e0812550 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -132,7 +132,7 @@ export interface DistributionProps { /** * Enable access logging for the distribution. * - * @default - false, unless `loggingBucket` is specified. + * @default - false, unless `logBucket` is specified. */ readonly enableLogging?: boolean; diff --git a/packages/@aws-cdk/aws-cloudfront/package.json b/packages/@aws-cdk/aws-cloudfront/package.json index ad24a82a2d4b3..dc2462ac0949a 100644 --- a/packages/@aws-cdk/aws-cloudfront/package.json +++ b/packages/@aws-cdk/aws-cloudfront/package.json @@ -64,7 +64,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-cloudtrail/package.json b/packages/@aws-cdk/aws-cloudtrail/package.json index ad09e05dae7b7..782cf6b1a7122 100644 --- a/packages/@aws-cdk/aws-cloudtrail/package.json +++ b/packages/@aws-cdk/aws-cloudtrail/package.json @@ -64,7 +64,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 910a8413645d5..e6a312d4b048b 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -213,18 +213,36 @@ The CodeBuild library supports both Linux and Windows images via the `LinuxBuildImage` and `WindowsBuildImage` classes, respectively. You can specify one of the predefined Windows/Linux images by using one -of the constants such as `WindowsBuildImage.WINDOWS_BASE_2_0` or -`LinuxBuildImage.STANDARD_2_0`. +of the constants such as `WindowsBuildImage.WIN_SERVER_CORE_2019_BASE`, +`WindowsBuildImage.WINDOWS_BASE_2_0` or `LinuxBuildImage.STANDARD_2_0`. Alternatively, you can specify a custom image using one of the static methods on -`XxxBuildImage`: +`LinuxBuildImage`: -* Use `.fromDockerRegistry(image[, { secretsManagerCredentials }])` to reference an image in any public or private Docker registry. -* Use `.fromEcrRepository(repo[, tag])` to reference an image available in an +* `LinuxBuildImage.fromDockerRegistry(image[, { secretsManagerCredentials }])` to reference an image in any public or private Docker registry. +* `LinuxBuildImage.fromEcrRepository(repo[, tag])` to reference an image available in an ECR repository. -* Use `.fromAsset(directory)` to use an image created from a +* `LinuxBuildImage.fromAsset(parent, id, props)` to use an image created from a local asset. -* Use `.fromCodeBuildImageId(id)` to reference a pre-defined, CodeBuild-provided Docker image. +* `LinuxBuildImage.fromCodeBuildImageId(id)` to reference a pre-defined, CodeBuild-provided Docker image. + +or one of the corresponding methods on `WindowsBuildImage`: + +* `WindowsBuildImage.fromDockerRegistry(image[, { secretsManagerCredentials }, imageType])` +* `WindowsBuildImage.fromEcrRepository(repo[, tag, imageType])` +* `WindowsBuildImage.fromAsset(parent, id, props, [, imageType])` + +Note that the `WindowsBuildImage` version of the static methods accepts an optional parameter of type `WindowsImageType`, +which can be either `WindowsImageType.STANDARD`, the default, or `WindowsImageType.SERVER_2019`: + +```typescript +new codebuild.Project(this, 'Project', { + environment: { + buildImage: codebuild.WindowsBuildImage.fromEcrRepository(ecrRepository, 'v1.0', codebuild.WindowsImageType.SERVER_2019), + }, + ... +}) +``` The following example shows how to define an image from a Docker asset: diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 153108f7bd24e..a18149a1d3209 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -1442,6 +1442,21 @@ export class LinuxBuildImage implements IBuildImage { } } +/** + * Environment type for Windows Docker images + */ +export enum WindowsImageType { + /** + * The standard environment type, WINDOWS_CONTAINER + */ + STANDARD = 'WINDOWS_CONTAINER', + + /** + * The WINDOWS_SERVER_2019_CONTAINER environment type + */ + SERVER_2019 = 'WINDOWS_SERVER_2019_CONTAINER' +} + /** * Construction properties of {@link WindowsBuildImage}. * Module-private, as the constructor of {@link WindowsBuildImage} is private. @@ -1451,6 +1466,7 @@ interface WindowsBuildImageProps { readonly imagePullPrincipalType?: ImagePullPrincipalType; readonly secretsManagerCredentials?: secretsmanager.ISecret; readonly repository?: ecr.IRepository; + readonly imageType?: WindowsImageType; } /** @@ -1460,9 +1476,9 @@ interface WindowsBuildImageProps { * * You can also specify a custom image using one of the static methods: * - * - WindowsBuildImage.fromDockerRegistry(image[, { secretsManagerCredentials }]) - * - WindowsBuildImage.fromEcrRepository(repo[, tag]) - * - WindowsBuildImage.fromAsset(parent, id, props) + * - WindowsBuildImage.fromDockerRegistry(image[, { secretsManagerCredentials }, imageType]) + * - WindowsBuildImage.fromEcrRepository(repo[, tag, imageType]) + * - WindowsBuildImage.fromAsset(parent, id, props, [, imageType]) * * @see https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html */ @@ -1486,14 +1502,29 @@ export class WindowsBuildImage implements IBuildImage { imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD, }); + /** + * The standard CodeBuild image `aws/codebuild/windows-base:2019-1.0`, which is + * based off Windows Server Core 2019. + */ + public static readonly WIN_SERVER_CORE_2019_BASE: IBuildImage = new WindowsBuildImage({ + imageId: 'aws/codebuild/windows-base:2019-1.0', + imagePullPrincipalType: ImagePullPrincipalType.CODEBUILD, + imageType: WindowsImageType.SERVER_2019, + }); + /** * @returns a Windows build image from a Docker Hub image. */ - public static fromDockerRegistry(name: string, options: DockerImageOptions): IBuildImage { + public static fromDockerRegistry( + name: string, + options: DockerImageOptions = {}, + imageType: WindowsImageType = WindowsImageType.STANDARD): IBuildImage { + return new WindowsBuildImage({ ...options, imageId: name, imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, + imageType, }); } @@ -1508,10 +1539,15 @@ export class WindowsBuildImage implements IBuildImage { * @param repository The ECR repository * @param tag Image tag (default "latest") */ - public static fromEcrRepository(repository: ecr.IRepository, tag: string = 'latest'): IBuildImage { + public static fromEcrRepository( + repository: ecr.IRepository, + tag: string = 'latest', + imageType: WindowsImageType = WindowsImageType.STANDARD): IBuildImage { + return new WindowsBuildImage({ imageId: repository.repositoryUriForTag(tag), imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, + imageType, repository, }); } @@ -1519,16 +1555,22 @@ export class WindowsBuildImage implements IBuildImage { /** * Uses an Docker image asset as a Windows build image. */ - public static fromAsset(scope: Construct, id: string, props: DockerImageAssetProps): IBuildImage { + public static fromAsset( + scope: Construct, + id: string, + props: DockerImageAssetProps, + imageType: WindowsImageType = WindowsImageType.STANDARD): IBuildImage { + const asset = new DockerImageAsset(scope, id, props); return new WindowsBuildImage({ imageId: asset.imageUri, imagePullPrincipalType: ImagePullPrincipalType.SERVICE_ROLE, + imageType, repository: asset.repository, }); } - public readonly type = 'WINDOWS_CONTAINER'; + public readonly type: string; public readonly defaultComputeType = ComputeType.MEDIUM; public readonly imageId: string; public readonly imagePullPrincipalType?: ImagePullPrincipalType; @@ -1536,6 +1578,7 @@ export class WindowsBuildImage implements IBuildImage { public readonly repository?: ecr.IRepository; private constructor(props: WindowsBuildImageProps) { + this.type = (props.imageType ?? WindowsImageType.STANDARD).toString(); this.imageId = props.imageId; this.imagePullPrincipalType = props.imagePullPrincipalType; this.secretsManagerCredentials = props.secretsManagerCredentials; diff --git a/packages/@aws-cdk/aws-codebuild/package.json b/packages/@aws-cdk/aws-codebuild/package.json index e218e0644d518..7933267770f29 100644 --- a/packages/@aws-cdk/aws-codebuild/package.json +++ b/packages/@aws-cdk/aws-codebuild/package.json @@ -70,7 +70,7 @@ "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/aws-sqs": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts index 291b5a865dba0..bf52b1d7c86aa 100644 --- a/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts +++ b/packages/@aws-cdk/aws-codebuild/test/test.codebuild.ts @@ -1508,6 +1508,28 @@ export = { test.done(); }, + 'Windows2019 image': { + 'WIN_SERVER_CORE_2016_BASE': { + 'has type WINDOWS_SERVER_2019_CONTAINER and default ComputeType MEDIUM'(test: Test) { + const stack = new cdk.Stack(); + new codebuild.PipelineProject(stack, 'Project', { + environment: { + buildImage: codebuild.WindowsBuildImage.WIN_SERVER_CORE_2019_BASE, + }, + }); + + expect(stack).to(haveResourceLike('AWS::CodeBuild::Project', { + 'Environment': { + 'Type': 'WINDOWS_SERVER_2019_CONTAINER', + 'ComputeType': 'BUILD_GENERAL1_MEDIUM', + }, + })); + + test.done(); + }, + }, + }, + 'ARM image': { 'AMAZON_LINUX_2_ARM': { 'has type ARM_CONTAINER and default ComputeType LARGE'(test: Test) { diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 55f2a8022e0ab..3b0f7cc4602d1 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -70,7 +70,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts index a73299761bbae..f55766315c070 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/servicecatalog/deploy-action.ts @@ -3,15 +3,6 @@ import * as iam from '@aws-cdk/aws-iam'; import { Construct } from '@aws-cdk/core'; import { Action } from '../action'; -/** TODO: - * 1.) Support cross account deployments - * 2.) Fix least privilege - * 3.) Handle CREATION of a new product - * 4.) Handle MAINTENANCE of a provisioned product - * 5.) Test/support product types beyond CLOUD_FORMATION_TEMPLATE - * 6.) Valid test cases! - */ - /** * Construction properties of the {@link ServiceCatalogDeployAction ServiceCatalog deploy CodePipeline Action}. * @@ -69,7 +60,7 @@ export class ServiceCatalogDeployAction extends Action { }, inputs: [props.templatePath.artifact], }); - this.templatePath = props.templatePath.location; + this.templatePath = props.templatePath.fileName; this.productVersionName = props.productVersionName; this.productVersionDescription = props.productVersionDescription; this.productId = props.productId; diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/test.servicecatalog-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/test.servicecatalog-action.ts index 06c46cff3adae..8c4101552c37f 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/test.servicecatalog-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/servicecatalog/test.servicecatalog-action.ts @@ -8,7 +8,7 @@ import * as cpactions from '../../lib'; /* eslint-disable quote-props */ export = { - 'addAction succesfully leads to creation of codepipeline service catalog action'(test: Test) { + 'addAction succesfully leads to creation of codepipeline service catalog action with properly formatted TemplateFilePath'(test: Test) { // GIVEN const stack = new TestFixture(); // WHEN @@ -34,7 +34,7 @@ export = { 'Version': '1', }, 'Configuration': { - 'TemplateFilePath': 'SourceArtifact::template.yaml', + 'TemplateFilePath': 'template.yaml', 'ProductVersionDescription': 'This is a description of the version.', 'ProductVersionName': 'VersionName', 'ProductType': 'CLOUD_FORMATION_TEMPLATE', @@ -79,7 +79,7 @@ export = { 'Version': '1', }, 'Configuration': { - 'TemplateFilePath': 'SourceArtifact::template.yaml', + 'TemplateFilePath': 'template.yaml', 'ProductVersionName': 'VersionName', 'ProductType': 'CLOUD_FORMATION_TEMPLATE', 'ProductId': 'prod-xxxxxxxxx', diff --git a/packages/@aws-cdk/aws-dynamodb/package.json b/packages/@aws-cdk/aws-dynamodb/package.json index 852e7d393c9c0..e3272b104b146 100644 --- a/packages/@aws-cdk/aws-dynamodb/package.json +++ b/packages/@aws-cdk/aws-dynamodb/package.json @@ -65,7 +65,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/jest": "^26.0.4", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 30124b9c838fa..db3ecb9b3d24b 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -218,6 +218,21 @@ container.addPortMappings({ }) ``` +To add data volumes to a task definition, call `addVolume()`: + +```ts +const volume = ecs.Volume("Volume", { + // Use an Elastic FileSystem + name: "mydatavolume", + efsVolumeConfiguration: ecs.EfsVolumeConfiguration({ + fileSystemId: "EFS" + // ... other options here ... + }) +}); + +const container = fargateTaskDefinition.addVolume("mydatavolume"); +``` + To use a TaskDefinition that can be used with either Amazon EC2 or AWS Fargate launch types, use the `TaskDefinition` construct. diff --git a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts index a1656c85a1de1..1d1480ee8831d 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -617,6 +617,19 @@ export interface Volume { * To use bind mounts, specify a host instead. */ readonly dockerVolumeConfiguration?: DockerVolumeConfiguration; + + /** + * This property is specified when you are using Amazon EFS. + * + * When specifying Amazon EFS volumes in tasks using the Fargate launch type, + * Fargate creates a supervisor container that is responsible for managing the Amazon EFS volume. + * The supervisor container uses a small amount of the task's memory. + * The supervisor container is visible when querying the task metadata version 4 endpoint, + * but is not visible in CloudWatch Container Insights. + * + * @default No Elastic FileSystem is setup + */ + readonly efsVolumeConfiguration?: EfsVolumeConfiguration; } /** @@ -707,6 +720,71 @@ export interface DockerVolumeConfiguration { readonly scope: Scope; } +/** + * The authorization configuration details for the Amazon EFS file system. + */ +export interface AuthorizationConfig { + /** + * The access point ID to use. + * If an access point is specified, the root directory value will be + * relative to the directory set for the access point. + * If specified, transit encryption must be enabled in the EFSVolumeConfiguration. + * + * @default No id + */ + readonly accessPointId?: string; + /** + * Whether or not to use the Amazon ECS task IAM role defined + * in a task definition when mounting the Amazon EFS file system. + * If enabled, transit encryption must be enabled in the EFSVolumeConfiguration. + * + * Valid values: ENABLED | DISABLED + * + * @default If this parameter is omitted, the default value of DISABLED is used. + */ + readonly iam?: string; +} + +/** + * The configuration for an Elastic FileSystem volume. + */ +export interface EfsVolumeConfiguration { + /** + * The Amazon EFS file system ID to use. + */ + readonly fileSystemId: string; + /** + * The directory within the Amazon EFS file system to mount as the root directory inside the host. + * Specifying / will have the same effect as omitting this parameter. + * + * @default The root of the Amazon EFS volume + */ + readonly rootDirectory?: string; + /** + * Whether or not to enable encryption for Amazon EFS data in transit between + * the Amazon ECS host and the Amazon EFS server. + * Transit encryption must be enabled if Amazon EFS IAM authorization is used. + * + * Valid values: ENABLED | DISABLED + * + * @default DISABLED + */ + readonly transitEncryption?: string; + /** + * The port to use when sending encrypted data between + * the Amazon ECS host and the Amazon EFS server. EFS mount helper uses. + * + * @default Port selection strategy that the Amazon EFS mount helper uses. + */ + readonly transitEncryptionPort?: number; + /** + * The authorization configuration details for the Amazon EFS file system. + * + * @default No configuration. + */ + readonly authorizationConfig?: AuthorizationConfig; +} + /** * The scope for the Docker volume that determines its lifecycle. * Docker volumes that are scoped to a task are automatically provisioned when the task starts and destroyed when the task stops. diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 2a89eb8abb50f..3e36f73bb6266 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -153,6 +153,7 @@ "docs-public-apis:@aws-cdk/aws-ecs.ScratchSpace.sourcePath", "props-default-doc:@aws-cdk/aws-ecs.Tmpfs.mountOptions", "props-default-doc:@aws-cdk/aws-ecs.Volume.dockerVolumeConfiguration", + "props-default-doc:@aws-cdk/aws-ecs.Volume.efsVolumeConfiguration", "props-default-doc:@aws-cdk/aws-ecs.Volume.host", "docs-public-apis:@aws-cdk/aws-ecs.Capability.ALL", "docs-public-apis:@aws-cdk/aws-ecs.Capability.AUDIT_CONTROL", diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts index fe25648da8afa..9688e649b545c 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-task-definition.ts @@ -981,6 +981,39 @@ export = { test.done(); }, + + 'correctly sets efsVolumeConfiguration'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const volume = { + name: 'scratch', + efsVolumeConfiguration: { + fileSystemId: 'local', + }, + }; + + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { + volumes: [volume], + }); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::ECS::TaskDefinition', { + Family: 'Ec2TaskDef', + Volumes: [{ + Name: 'scratch', + EfsVolumeConfiguration: { + FileSystemId: 'local', + }, + }], + })); + + test.done(); + }, }, 'throws when setting proxyConfiguration without networkMode AWS_VPC'(test: Test) { diff --git a/packages/@aws-cdk/aws-eks/package.json b/packages/@aws-cdk/aws-eks/package.json index 523f7e750e7bb..bbd740310c616 100644 --- a/packages/@aws-cdk/aws-eks/package.json +++ b/packages/@aws-cdk/aws-eks/package.json @@ -65,7 +65,7 @@ "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", "@types/yaml": "1.2.0", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-events-targets/package.json b/packages/@aws-cdk/aws-events-targets/package.json index 07c1d0d7770a1..029617ab9598a 100644 --- a/packages/@aws-cdk/aws-events-targets/package.json +++ b/packages/@aws-cdk/aws-events-targets/package.json @@ -68,7 +68,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-codecommit": "0.0.0", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index f40c923910277..97174e0b559db 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -71,7 +71,7 @@ "@types/lodash": "^4.14.157", "@types/nodeunit": "^0.0.31", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 1cc20d5b3a50b..f381c66cc167e 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -106,7 +106,7 @@ Example for max storage configuration: ```ts const instance = new rds.DatabaseInstance(this, 'Instance', { - engine: rds.DatabaseInstanceEngine.ORACLE_SE1, + engine: rds.DatabaseInstanceEngine.ORACLE_SE1, // optional, defaults to m5.large instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), masterUsername: 'syscdk', @@ -121,7 +121,7 @@ a source database respectively: ```ts new rds.DatabaseInstanceFromSnapshot(stack, 'Instance', { snapshotIdentifier: 'my-snapshot', - engine: rds.DatabaseInstanceEngine.POSTGRES, + engine: rds.DatabaseInstanceEngine.POSTGRES, // optional, defaults to m5.large instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.LARGE), vpc, @@ -164,6 +164,7 @@ const writeAddress = cluster.clusterEndpoint.socketAddress; // "HOSTNAME:PORT" ``` For an instance database: + ```ts const address = instance.instanceEndpoint.socketAddress; // "HOSTNAME:PORT" ``` @@ -229,10 +230,10 @@ S3](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Int data into S3](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Integrating.SaveIntoS3.html). For Aurora PostgreSQL, read more about [loading data from -S3](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.Migrating.html) and [saving +S3](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraPostgreSQL.Migrating.html) and [saving data into S3](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/postgresql-s3-export.html). -The following snippet sets up a database cluster with different S3 buckets where the data is imported and exported - +The following snippet sets up a database cluster with different S3 buckets where the data is imported and exported - ```ts import * as s3 from '@aws-cdk/aws-s3'; @@ -271,3 +272,33 @@ const proxy = dbInstance.addProxy('proxy', { vpc, }); ``` + +### Exporting Logs + +You can publish database logs to Amazon CloudWatch Logs. With CloudWatch Logs, you can perform real-time analysis of the log data, +store the data in highly durable storage, and manage the data with the CloudWatch Logs Agent. This is available for both database +instances and clusters; the types of logs available depend on the database type and engine being used. + +```ts +// Exporting logs from a cluster +const cluster = new rds.DatabaseCluster(this, 'Database', { + engine: rds.DatabaseClusterEngine.aurora({ + version: rds.AuroraEngineVersion.VER_1_17_9, // different version class for each engine type + }, + // ... + cloudwatchLogsExports: ['error', 'general', 'slowquery', 'audit'], // Export all available MySQL-based logs + cloudwatchLogsRetention: logs.RetentionDays.THREE_MONTHS, // Optional - default is to never expire logs + cloudwatchLogsRetentionRole: myLogsPublishingRole, // Optional - a role will be created if not provided + // ... +}); + +// Exporting logs from an instance +const instance = new rds.DatabaseInstance(this, 'Instance', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_12_3, + }), + // ... + cloudwatchLogsExports: ['postgresql'], // Export the PostgreSQL logs + // ... +}); +``` diff --git a/packages/@aws-cdk/aws-rds/lib/cluster.ts b/packages/@aws-cdk/aws-rds/lib/cluster.ts index 2318fb1d9762e..abe117dba585d 100644 --- a/packages/@aws-cdk/aws-rds/lib/cluster.ts +++ b/packages/@aws-cdk/aws-rds/lib/cluster.ts @@ -1,6 +1,8 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import { IRole, ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import { CfnDeletionPolicy, Construct, Duration, RemovalPolicy, Resource, Token } from '@aws-cdk/core'; @@ -123,6 +125,31 @@ export interface DatabaseClusterProps { */ readonly removalPolicy?: RemovalPolicy; + /** + * The list of log types that need to be enabled for exporting to + * CloudWatch Logs. + * + * @default - no log exports + */ + readonly cloudwatchLogsExports?: string[]; + + /** + * The number of days log events are kept in CloudWatch Logs. When updating + * this property, unsetting it doesn't remove the log retention policy. To + * remove the retention policy, set the value to `Infinity`. + * + * @default - logs never expire + */ + readonly cloudwatchLogsRetention?: logs.RetentionDays; + + /** + * The IAM role for the Lambda function associated with the custom resource + * that sets the retention policy. + * + * @default - a new role is created. + */ + readonly cloudwatchLogsRetentionRole?: IRole; + /** * The interval, in seconds, between points when Amazon RDS collects enhanced * monitoring metrics for the DB instances. @@ -438,6 +465,7 @@ export class DatabaseCluster extends DatabaseClusterBase { preferredBackupWindow: props.backup && props.backup.preferredWindow, preferredMaintenanceWindow: props.preferredMaintenanceWindow, databaseName: props.defaultDatabaseName, + enableCloudwatchLogsExports: props.cloudwatchLogsExports, // Encryption kmsKeyId: props.storageEncryptionKey && props.storageEncryptionKey.keyArn, storageEncrypted: props.storageEncryptionKey ? true : props.storageEncrypted, @@ -461,6 +489,8 @@ export class DatabaseCluster extends DatabaseClusterBase { this.clusterEndpoint = new Endpoint(cluster.attrEndpointAddress, portAttribute); this.clusterReadEndpoint = new Endpoint(cluster.attrReadEndpointAddress, portAttribute); + this.setLogRetention(props); + if (secret) { this.secret = secret.attach(this); } @@ -574,6 +604,18 @@ export class DatabaseCluster extends DatabaseClusterBase { target: this, }); } + + private setLogRetention(props: DatabaseClusterProps) { + if (props.cloudwatchLogsExports && props.cloudwatchLogsRetention) { + for (const log of props.cloudwatchLogsExports) { + new lambda.LogRetention(this, `LogRetention${log}`, { + logGroupName: `/aws/rds/cluster/${this.clusterIdentifier}/${log}`, + retention: props.cloudwatchLogsRetention, + role: props.cloudwatchLogsRetentionRole, + }); + } + } + } } /** diff --git a/packages/@aws-cdk/aws-rds/test/test.cluster.ts b/packages/@aws-cdk/aws-rds/test/test.cluster.ts index b0edd7da40322..80aa285266bff 100644 --- a/packages/@aws-cdk/aws-rds/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-rds/test/test.cluster.ts @@ -2,6 +2,7 @@ import { ABSENT, countResources, expect, haveResource, haveResourceLike, Resourc import * as ec2 from '@aws-cdk/aws-ec2'; import { ManagedPolicy, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; +import * as logs from '@aws-cdk/aws-logs'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; @@ -1082,6 +1083,78 @@ export = { test.done(); }, + 'can set CloudWatch log exports'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + masterUser: { + username: 'admin', + password: cdk.SecretValue.plainText('tooshort'), + }, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + }, + cloudwatchLogsExports: ['error', 'general', 'slowquery', 'audit'], + }); + + // THEN + expect(stack).to(haveResourceLike('AWS::RDS::DBCluster', { + EnableCloudwatchLogsExports: ['error', 'general', 'slowquery', 'audit'], + })); + + test.done(); + }, + + 'can set CloudWatch log retention'(test: Test) { + // GIVEN + const stack = testStack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new DatabaseCluster(stack, 'Database', { + engine: DatabaseClusterEngine.AURORA, + masterUser: { + username: 'admin', + password: cdk.SecretValue.plainText('tooshort'), + }, + instanceProps: { + instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL), + vpc, + }, + cloudwatchLogsExports: ['error', 'general'], + cloudwatchLogsRetention: logs.RetentionDays.THREE_MONTHS, + }); + + // THEN + expect(stack).to(haveResource('Custom::LogRetention', { + ServiceToken: { + 'Fn::GetAtt': [ + 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A', + 'Arn', + ], + }, + LogGroupName: { 'Fn::Join': ['', ['/aws/rds/cluster/', { Ref: 'DatabaseB269D8BB' }, '/error']] }, + RetentionInDays: 90, + })); + expect(stack).to(haveResource('Custom::LogRetention', { + ServiceToken: { + 'Fn::GetAtt': [ + 'LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aFD4BFC8A', + 'Arn', + ], + }, + LogGroupName: { 'Fn::Join': ['', ['/aws/rds/cluster/', { Ref: 'DatabaseB269D8BB' }, '/general']] }, + RetentionInDays: 90, + })); + + test.done(); + }, + 'does not throw (but adds a node error) if a (dummy) VPC does not have sufficient subnets'(test: Test) { // GIVEN const stack = testStack(); diff --git a/packages/@aws-cdk/aws-route53/package.json b/packages/@aws-cdk/aws-route53/package.json index 8015171860f6f..a7a03cf97d162 100644 --- a/packages/@aws-cdk/aws-route53/package.json +++ b/packages/@aws-cdk/aws-route53/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@aws-cdk/assert": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index e8f8ad2ac66e1..fb3419ebbf1ae 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -65,7 +65,7 @@ "@aws-cdk/assert": "0.0.0", "@aws-cdk/aws-s3": "0.0.0", "@types/nodeunit": "^0.0.31", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", "cfn2ts": "0.0.0", diff --git a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json index e5595ee2b6466..85f9b4bfb03b9 100644 --- a/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json +++ b/packages/@aws-cdk/cfnspec/spec-source/000_CloudFormationResourceSpecification.json @@ -13430,6 +13430,58 @@ } } }, + "AWS::ECS::TaskDefinition.EfsVolumeConfiguration": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "Properties": { + "FileSystemId": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "PrimitiveType": "String", + "Required": true, + "UpdateType": "Immutable" + }, + "RootDirectory": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "TransitEncryption": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "TransitEncryptionPort": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "PrimitiveType": "Integer", + "Required": false, + "UpdateType": "Immutable" + }, + "AuthorizationConfig": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "Type": "AuthorizationConfig", + "Required": false, + "UpdateType": "Immutable" + } + } + }, + "AWS::ECS::TaskDefinition.AuthorizationConfig": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "Properties": { + "AccessPointId": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + }, + "Iam": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "PrimitiveType": "String", + "Required": false, + "UpdateType": "Immutable" + } + } + }, "AWS::ECS::TaskDefinition.FirelensConfiguration": { "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-firelensconfiguration.html", "Properties": { @@ -13857,6 +13909,12 @@ "PrimitiveType": "String", "Required": false, "UpdateType": "Immutable" + }, + "EfsVolumeConfiguration": { + "Documentation": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/efs-volumes.html#specify-efs-config", + "Required": false, + "Type": "EfsVolumeConfiguration", + "UpdateType": "Immutable" } } }, diff --git a/packages/@aws-cdk/custom-resources/package.json b/packages/@aws-cdk/custom-resources/package.json index bb78aa17ec8f5..e63efb5b42ffb 100644 --- a/packages/@aws-cdk/custom-resources/package.json +++ b/packages/@aws-cdk/custom-resources/package.json @@ -73,7 +73,7 @@ "@types/aws-lambda": "^8.10.39", "@types/fs-extra": "^8.1.0", "@types/sinon": "^9.0.4", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "aws-sdk-mock": "^5.1.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", diff --git a/packages/aws-cdk/lib/api/deploy-stack.ts b/packages/aws-cdk/lib/api/deploy-stack.ts index 454ff8452b566..cca1fe45922b8 100644 --- a/packages/aws-cdk/lib/api/deploy-stack.ts +++ b/packages/aws-cdk/lib/api/deploy-stack.ts @@ -265,7 +265,7 @@ export async function deployStack(options: DeployStackOptions): Promise; + private readPromise?: Promise; - private readonly printer: ActivityPrinterBase; + /** + * Pagination token for the next page of stack events + * + * Retained between ticks in order to avoid being O(N^2) in the length of + * the stack event log + */ + private nextToken?: string; constructor( private readonly cfn: aws.CloudFormation, private readonly stackName: string, - private readonly stack: cxapi.CloudFormationStackArtifact, - options: StackActivityMonitorProps = {}) { - - const stream = process.stderr; - - const props: PrinterProps = { - resourceTypeColumnWidth: calcMaxResourceTypeLength(this.stack.template), - resourcesTotal: options.resourcesTotal, - stream, - }; - - const isWindows = process.platform === 'win32'; - const verbose = options.logLevel ?? logLevel; - // On some CI systems (such as CircleCI) output still reports as a TTY so we also - // need an individual check for whether we're running on CI. - // see: https://discuss.circleci.com/t/circleci-terminal-is-a-tty-but-term-is-not-set/9965 - const fancyOutputAvailable = !isWindows && stream.isTTY && !options.ci; - - this.printer = fancyOutputAvailable && !verbose - ? new CurrentActivityPrinter(props) - : new HistoryActivityPrinter(props); - } + private readonly printer: IActivityPrinter, + private readonly stack?: cxapi.CloudFormationStackArtifact, + ) {} public start() { this.active = true; this.printer.start(); - this.scheduleNextTick(); + this.scheduleNextTick(true); return this; } @@ -115,15 +127,17 @@ export class StackActivityMonitor { if (this.readPromise) { // We're currently reading events, wait for it to finish and print them before continuing. await this.readPromise; - this.flushEvents(); + this.printer.print(); } } - private scheduleNextTick() { + private scheduleNextTick(newPageAvailable: boolean) { if (!this.active) { return; } - this.tickTimer = setTimeout(() => this.tick().then(), this.printer.updateSleep); + + const sleepMs = newPageAvailable ? 0 : this.printer.updateSleep; + this.tickTimer = setTimeout(() => void(this.tick()), sleepMs); } private async tick() { @@ -131,40 +145,24 @@ export class StackActivityMonitor { return; } + let newPage = false; try { - this.readPromise = this.readEvents(); - await this.readPromise; + this.readPromise = this.readNewEvents((a) => this.printer.addActivity(a)); + newPage = (await this.readPromise).nextPage; this.readPromise = undefined; // We might have been stop()ped while the network call was in progress. if (!this.active) { return; } - this.flushEvents(); + this.printer.print(); } catch (e) { error('Error occurred while monitoring stack: %s', e); } - this.scheduleNextTick(); - } - - /** - * Flushes all unflushed events sorted by timestamp. - */ - private flushEvents() { - Object.keys(this.activity) - .map(a => this.activity[a]) - .filter(a => a.event.Timestamp.valueOf() > this.startTime) - .filter(a => !a.flushed) - .sort((lhs, rhs) => lhs.event.Timestamp.valueOf() - rhs.event.Timestamp.valueOf()) - .forEach(a => { - a.flushed = true; - this.printer.addActivity(a); - }); - - this.printer.print(); + this.scheduleNextTick(newPage); } private findMetadataFor(logicalId: string | undefined): ResourceMetadata | undefined { - const metadata = this.stack.manifest.metadata; + const metadata = this.stack?.manifest?.metadata; if (!logicalId || !metadata) { return undefined; } for (const path of Object.keys(metadata)) { const entry = metadata[path] @@ -180,39 +178,53 @@ export class StackActivityMonitor { return undefined; } - private async readEvents(nextToken?: string): Promise { - const output = await this.cfn.describeStackEvents({ StackName: this.stackName, NextToken: nextToken }).promise() - .catch( e => { - if (e.code === 'ValidationError' && e.message === `Stack [${this.stackName}] does not exist`) { - return undefined; - } - throw e; - }); - - let events = output && output.StackEvents || []; - let allNew = true; + /** + * Reads a single page stack events from the stack, invoking a callback on the new ones (in order) + * + * Returns whether there is a next page of events availablle. + */ + private async readNewEvents(cb: (x: StackActivity) => void): Promise { + let response; - // merge events into the activity and dedup by event id - for (const e of events) { - if (e.EventId in this.activity) { - allNew = false; - break; + try { + response = await this.cfn.describeStackEvents({ StackName: this.stackName, NextToken: this.nextToken }).promise(); + } catch (e) { + if (e.code === 'ValidationError' && e.message === `Stack [${this.stackName}] does not exist`) { + return { nextPage: false }; } + throw e; + } - this.activity[e.EventId] = { - flushed: false, - event: e, - metadata: this.findMetadataFor(e.LogicalResourceId), - }; + for (const event of response?.StackEvents ?? []) { + // Already seen this one + if (event.EventId in this.activity) { continue; } + + // Event from before we were interested in 'em + if (event.Timestamp.valueOf() < this.startTime) { continue; } + + // Invoke callback + cb(this.activity[event.EventId] = { + event: event, + metadata: this.findMetadataFor(event.LogicalResourceId), + }); } - // only read next page if all the events we read are new events. otherwise, we can rest. - if (allNew && output && output.NextToken) { - await new Promise(cb => setTimeout(cb, this.pageSleep)); - events = events.concat(await this.readEvents(output.NextToken)); + // Replace the "next token" if we have one, so that we start paginating from the next + // page on the next call. + // + // Crucially -- this token will ONLY be returned if there is a next page to + // read already. If not, we're at the end of the list of events. + // + // If there's no next page to read, we don't reset our paging token though. There might + // be a new page to read in the future, and we don't want to have to page through + // pages 1-N on the next call just to get page (N+1). In that case simply retain the + // current token, requesting page N again until (N+1) appears. + if (response?.NextToken) { + this.nextToken = response.NextToken; } - return events; + // Return whether there is a new page available + return { nextPage: response?.NextToken !== undefined }; } private simplifyConstructPath(path: string) { @@ -227,6 +239,10 @@ export class StackActivityMonitor { } } +interface ReadEventsResult { + readonly nextPage: boolean; +} + function padRight(n: number, x: string): string { return x + ' '.repeat(Math.max(0, n - x.length)); } @@ -267,7 +283,16 @@ interface PrinterProps { readonly stream: NodeJS.WriteStream; } -abstract class ActivityPrinterBase { +export interface IActivityPrinter { + readonly updateSleep: number; + + addActivity(activity: StackActivity): void; + print(): void; + start(): void; + stop(): void; +} + +abstract class ActivityPrinterBase implements IActivityPrinter { /** * Fetch new activity every 5 seconds */ diff --git a/packages/aws-cdk/package.json b/packages/aws-cdk/package.json index 5323c386f5205..f390a4f682951 100644 --- a/packages/aws-cdk/package.json +++ b/packages/aws-cdk/package.json @@ -71,7 +71,7 @@ "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/region-info": "0.0.0", "archiver": "^4.0.2", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "camelcase": "^6.0.0", "cdk-assets": "0.0.0", "colors": "^1.4.0", diff --git a/packages/aws-cdk/test/util/stack-monitor.test.ts b/packages/aws-cdk/test/util/stack-monitor.test.ts new file mode 100644 index 0000000000000..f13a5fc8f0bf0 --- /dev/null +++ b/packages/aws-cdk/test/util/stack-monitor.test.ts @@ -0,0 +1,57 @@ +import { StackActivityMonitor, IActivityPrinter, StackActivity } from '../../lib/api/util/cloudformation/stack-activity-monitor'; +import { sleep } from '../integ/cli/aws-helpers'; +import { MockSdk } from './mock-sdk'; + +let sdk: MockSdk; +let printer: FakePrinter; +beforeEach(() => { + sdk = new MockSdk(); + printer = new FakePrinter(); +}); + +test('retain page token between ticks', async () => { + let finished = false; + sdk.stubCloudFormation({ + describeStackEvents: (jest.fn() as jest.Mock) + // First call, return a page token + .mockImplementationOnce((request) => { + expect(request.NextToken).toBeUndefined(); + return { NextToken: 'token' }; + }) + // Second call, expect page token, return no page + .mockImplementationOnce(request => { + expect(request.NextToken).toEqual('token'); + return { }; + }) + // Third call, ensure we still get the same page token + .mockImplementationOnce(request => { + expect(request.NextToken).toEqual('token'); + finished = true; + return { }; + }), + }); + + const monitor = new StackActivityMonitor(sdk.cloudFormation(), 'StackName', printer).start(); + await waitForCondition(() => finished); + await monitor.stop(); +}); + + +class FakePrinter implements IActivityPrinter { + public updateSleep: number = 0; + public readonly activities: StackActivity[] = []; + + public addActivity(activity: StackActivity): void { + this.activities.push(activity); + } + + public print(): void { } + public start(): void { } + public stop(): void { } +} + +async function waitForCondition(cb: () => boolean): Promise { + while (!cb()) { + await sleep(10); + } +} \ No newline at end of file diff --git a/packages/cdk-assets/package.json b/packages/cdk-assets/package.json index d5b1ce7fd1c0e..9964a898d2b41 100644 --- a/packages/cdk-assets/package.json +++ b/packages/cdk-assets/package.json @@ -47,7 +47,7 @@ "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "archiver": "^4.0.2", - "aws-sdk": "^2.715.0", + "aws-sdk": "^2.735.0", "glob": "^7.1.6", "yargs": "^15.3.1" }, diff --git a/yarn.lock b/yarn.lock index 97fd3da2ffda9..fce72fd7dba06 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4108,10 +4108,10 @@ aws-sdk-mock@^5.1.0: sinon "^9.0.1" traverse "^0.6.6" -aws-sdk@^2.637.0, aws-sdk@^2.715.0: - version "2.715.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.715.0.tgz#b890892098e0a4d9e7189ed341267d4a9a6e856b" - integrity sha512-O6ytb66IXFCowp0Ng2bSPM6v/cZVOhjJWFTR1CG4ieG4IroAaVgB3YQKkfPKA0Cy9B/Ovlsm7B737iuroKsd0w== +aws-sdk@^2.637.0, aws-sdk@^2.735.0: + version "2.735.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.735.0.tgz#56533efc29a68f9bb46ed9566bdc2669a35b7003" + integrity sha512-nu5Cz2YcjHv2kgqtxW/DO0lz4Yc8g+xSB0Lb7Dp5iBEfyWLGGnhn5u4ALeFO9UUxLuSieirT1BR18BWSWH/Hlg== dependencies: buffer "4.9.2" events "1.1.1"