From e33629a41aa1b762935341ad98cdea2f246754e1 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 28 Jan 2019 16:41:39 +0100 Subject: [PATCH 01/21] Implement service integration for SNS & SQS --- packages/@aws-cdk/aws-sns/lib/index.ts | 1 + packages/@aws-cdk/aws-sns/lib/publish-task.ts | 103 ++++++++++++++ packages/@aws-cdk/aws-sns/package.json | 2 + .../aws-sns/test/test.publish-task.ts | 30 ++++ packages/@aws-cdk/aws-sqs/lib/index.ts | 1 + .../@aws-cdk/aws-sqs/lib/send-message-task.ts | 128 ++++++++++++++++++ packages/@aws-cdk/aws-sqs/package.json | 2 + .../aws-sqs/test/test.send-message-task.ts | 32 +++++ packages/@aws-cdk/aws-stepfunctions/README.md | 40 ++++++ .../aws-stepfunctions/lib/states/task.ts | 76 ++++++++--- .../test/test.states-language.ts | 34 ++++- 11 files changed, 426 insertions(+), 23 deletions(-) create mode 100644 packages/@aws-cdk/aws-sns/lib/publish-task.ts create mode 100644 packages/@aws-cdk/aws-sns/test/test.publish-task.ts create mode 100644 packages/@aws-cdk/aws-sqs/lib/send-message-task.ts create mode 100644 packages/@aws-cdk/aws-sqs/test/test.send-message-task.ts diff --git a/packages/@aws-cdk/aws-sns/lib/index.ts b/packages/@aws-cdk/aws-sns/lib/index.ts index 2b1cd1f7ce1c6..eb60bcebe55fc 100644 --- a/packages/@aws-cdk/aws-sns/lib/index.ts +++ b/packages/@aws-cdk/aws-sns/lib/index.ts @@ -2,6 +2,7 @@ export * from './policy'; export * from './topic'; export * from './topic-ref'; export * from './subscription'; +export * from './publish-task'; // AWS::SNS CloudFormation Resources: export * from './sns.generated'; diff --git a/packages/@aws-cdk/aws-sns/lib/publish-task.ts b/packages/@aws-cdk/aws-sns/lib/publish-task.ts new file mode 100644 index 0000000000000..9e0e82b5f1e75 --- /dev/null +++ b/packages/@aws-cdk/aws-sns/lib/publish-task.ts @@ -0,0 +1,103 @@ +import iam = require('@aws-cdk/aws-iam'); +import stepfunctions = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); +import { ITopic } from './topic-ref'; + +/** + * Properties for PublishTask + */ +export interface PublishTaskProps extends stepfunctions.BasicTaskProps { + /** + * The topic to publish to + */ + topic: ITopic; + + /** + * The text message to send to the queue. + * + * Exactly one of `message`, `messageObject` and `messagePath` is required. + */ + message?: string; + + /** + * JSONPath expression of the message to send to the queue + * + * Exactly one of `message`, `messageObject` and `messagePath` is required. + */ + messagePath?: string; + + /** + * Object to be JSON-encoded and used as message + * + * Exactly one of `message`, `messageObject` and `messagePath` is required. + */ + messageObject?: string; + + /** + * If true, send a different message to every subscription type + * + * If this is set to true, message must be a JSON object with a + * "default" key and a key for every subscription type (such as "sqs", + * "email", etc.) The values are strings representing the messages + * being sent to every subscription type. + * + * @see https://docs.aws.amazon.com/sns/latest/api/API_Publish.html#API_Publish_RequestParameters + */ + messagePerSubscriptionType?: boolean; + + /** + * Message subject + */ + subject?: string; + + /** + * JSONPath expression of subject + */ + subjectPath?: string; +} + +/** + * A StepFunctions Task to publish to an SNS Topic + */ +export class PublishTask extends stepfunctions.Task { + constructor(scope: cdk.Construct, id: string, props: PublishTaskProps) { + if ((props.message !== undefined ? 1 : 0) + + (props.messagePath !== undefined ? 1 : 0) + + (props.messageObject !== undefined ? 1 : 0) !== 1) { + throw new Error(`Supply exactly one of 'message', 'messageObject' or 'messagePath'`); + } + + if (props.subject !== undefined && props.subjectPath !== undefined) { + throw new Error(`Supply either 'subject' or 'subjectPath'`); + } + + super(scope, id, { + ...props, + resource: new PublishTaskResource(props), + parameters: { + "TopicArn": props.topic.topicArn, + "Message": props.messageObject + ? new cdk.Token(() => this.node.stringifyJson(props.messageObject)) + : props.message, + "Message.$": props.messagePath, + "MessageStructure": props.messagePerSubscriptionType ? "json" : undefined, + "Subject": props.subject, + "Subject.$": props.subjectPath + } + }); + } +} + +class PublishTaskResource implements stepfunctions.IStepFunctionsTaskResource { + constructor(private readonly props: PublishTaskProps) { + } + + public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { + return { + resourceArn: 'arn:aws:states:::sns:publish', + policyStatements: [new iam.PolicyStatement() + .addAction('sns:Publish') + .addResource(this.props.topic.topicArn)], + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index 08db238b2e80c..5a57d553c03ac 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -72,6 +72,7 @@ "@aws-cdk/aws-lambda": "^0.22.0", "@aws-cdk/aws-s3-notifications": "^0.22.0", "@aws-cdk/aws-sqs": "^0.22.0", + "@aws-cdk/aws-stepfunctions": "^0.22.0", "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", @@ -83,6 +84,7 @@ "@aws-cdk/aws-lambda": "^0.22.0", "@aws-cdk/aws-s3-notifications": "^0.22.0", "@aws-cdk/aws-sqs": "^0.22.0", + "@aws-cdk/aws-stepfunctions": "^0.22.0", "@aws-cdk/cdk": "^0.22.0" }, "engines": { diff --git a/packages/@aws-cdk/aws-sns/test/test.publish-task.ts b/packages/@aws-cdk/aws-sns/test/test.publish-task.ts new file mode 100644 index 0000000000000..01035ed72c1e0 --- /dev/null +++ b/packages/@aws-cdk/aws-sns/test/test.publish-task.ts @@ -0,0 +1,30 @@ +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import sns = require('../lib'); + +export = { + 'publish to SNS'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // WHEN + const pub = new sns.PublishTask(stack, 'Publish', { + topic, + message: 'Send this message' + }); + + // THEN + test.deepEqual(stack.node.resolve(pub.toStateJson()), { + Type: 'Task', + Resource: 'arn:aws:states:::sns:publish', + End: true, + Parameters: { + TopicArn: { Ref: 'TopicBFC7AF6E' }, + Message: 'Send this message' + }, + }); + + test.done(); + } +}; diff --git a/packages/@aws-cdk/aws-sqs/lib/index.ts b/packages/@aws-cdk/aws-sqs/lib/index.ts index aeb73f0030e1c..741f787b4c4e0 100644 --- a/packages/@aws-cdk/aws-sqs/lib/index.ts +++ b/packages/@aws-cdk/aws-sqs/lib/index.ts @@ -1,6 +1,7 @@ export * from './policy'; export * from './queue'; export * from './queue-ref'; +export * from './send-message-task'; // AWS::SQS CloudFormation Resources: export * from './sqs.generated'; diff --git a/packages/@aws-cdk/aws-sqs/lib/send-message-task.ts b/packages/@aws-cdk/aws-sqs/lib/send-message-task.ts new file mode 100644 index 0000000000000..240aeb399e78f --- /dev/null +++ b/packages/@aws-cdk/aws-sqs/lib/send-message-task.ts @@ -0,0 +1,128 @@ +import iam = require('@aws-cdk/aws-iam'); +import stepfunctions = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); +import { IQueue } from './queue-ref'; + +/** + * Properties for SendMessageTask + */ +export interface SendMessageTaskProps extends stepfunctions.BasicTaskProps { + /** + * The topic to send a message to to + */ + queue: IQueue; + + /** + * The message body to send to the queue. + * + * Exactly one of `messageBody` and `messageBodyPath` is required. + */ + messageBody?: string; + + /** + * JSONPath for the message body to send to the queue. + * + * Exactly one of `messageBody` and `messageBodyPath` is required. + */ + messageBodyPath?: string; + + /** + * The length of time, in seconds, for which to delay a specific message. + * + * Valid values are 0-900 seconds. + * + * @default Default value of the queue is used + */ + delaySeconds?: number; + + /** + * JSONPath expression for delaySeconds setting + * + * @default Default value of the queue is used + */ + delaySecondsPath?: string; + + /** + * The token used for deduplication of sent messages. + * + * @default Use content-based deduplication + */ + messageDeduplicationId?: string; + + /** + * JSONPath expression for deduplication ID + * + * @default Use content-based deduplication + */ + messageDeduplicationIdPath?: string; + + /** + * The tag that specifies that a message belongs to a specific message group. + * + * Required for FIFO queues. FIFO ordering applies to messages in the same message + * group. + * + * @default No group ID + */ + messageGroupId?: string; + + /** + * JSONPath expression for message group ID + * + * @default No group ID + */ + messageGroupIdPath?: string; +} + +/** + * A StepFunctions Task to send a message to an SQS Queue + */ +export class SendMessageTask extends stepfunctions.Task { + constructor(scope: cdk.Construct, id: string, props: SendMessageTaskProps) { + if ((props.messageBody !== undefined) === (props.messageBodyPath !== undefined)) { + throw new Error(`Supply exactly one of 'messageBody' and 'messageBodyPath'`); + } + + if ((props.delaySeconds !== undefined) && (props.delaySecondsPath !== undefined)) { + throw new Error(`Supply either of 'delaySeconds' or 'delaySecondsPath'`); + } + + if ((props.messageDeduplicationId !== undefined) && (props.messageDeduplicationIdPath !== undefined)) { + throw new Error(`Supply either of 'messageDeduplicationId' or 'messageDeduplicationIdPath'`); + } + + if ((props.messageGroupId !== undefined) && (props.messageGroupIdPath !== undefined)) { + throw new Error(`Supply either of 'messageGroupId' or 'messageGroupIdPath'`); + } + + super(scope, id, { + ...props, + resource: new SendMessageTaskResource(props), + parameters: { + 'QueueUrl': props.queue.queueUrl, + 'MessageBody': props.messageBody, + 'MessageBody.$': props.messageBodyPath, + 'DelaySeconds': props.delaySeconds, + 'DelaySeconds.$': props.delaySecondsPath, + 'MessageDeduplicationId': props.messageDeduplicationId, + 'MessageDeduplicationId.$': props.messageDeduplicationIdPath, + 'MessageGroupId': props.messageGroupId, + 'MessageGroupId.$': props.messageGroupIdPath, + } + }); + } +} + +class SendMessageTaskResource implements stepfunctions.IStepFunctionsTaskResource { + constructor(private readonly props: SendMessageTaskProps) { + } + + public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { + return { + resourceArn: 'arn:aws:states:::sqs:sendMessage', + policyStatements: [new iam.PolicyStatement() + .addAction('sns:Publish') + .addResource(this.props.queue.queueArn)], + }; + } +} diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index 811e66a343287..e4e926db9a35e 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -67,6 +67,7 @@ "@aws-cdk/aws-iam": "^0.22.0", "@aws-cdk/aws-kms": "^0.22.0", "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/aws-stepfunctions": "^0.22.0", "@aws-cdk/cdk": "^0.22.0" }, "homepage": "https://github.com/awslabs/aws-cdk", @@ -75,6 +76,7 @@ "@aws-cdk/aws-iam": "^0.22.0", "@aws-cdk/aws-kms": "^0.22.0", "@aws-cdk/aws-s3-notifications": "^0.22.0", + "@aws-cdk/aws-stepfunctions": "^0.22.0", "@aws-cdk/cdk": "^0.22.0" }, "engines": { diff --git a/packages/@aws-cdk/aws-sqs/test/test.send-message-task.ts b/packages/@aws-cdk/aws-sqs/test/test.send-message-task.ts new file mode 100644 index 0000000000000..b49540ebb9cbf --- /dev/null +++ b/packages/@aws-cdk/aws-sqs/test/test.send-message-task.ts @@ -0,0 +1,32 @@ +import cdk = require('@aws-cdk/cdk'); +import { Test } from 'nodeunit'; +import sqs = require('../lib'); + +export = { + 'publish to queue'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const queue = new sqs.Queue(stack, 'Queue'); + + // WHEN + const pub = new sqs.SendMessageTask(stack, 'Send', { + queue, + messageBody: 'Send this message', + messageDeduplicationIdPath: '$.deduping', + }); + + // THEN + test.deepEqual(stack.node.resolve(pub.toStateJson()), { + Type: 'Task', + Resource: 'arn:aws:states:::sqs:sendMessage', + End: true, + Parameters: { + 'QueueUrl': { Ref: 'Queue4A7E3555' }, + 'MessageBody': 'Send this message', + 'MessageDeduplicationId.$': '$.deduping' + }, + }); + + test.done(); + } +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index a19b3a0a74869..365435afea4b6 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -327,6 +327,46 @@ const definition = stepfunctions.Chain // ... ``` +## Service Integrations + +Some packages contain specialized subclasses of `Task` that can be used to +invoke those services as part of a workflow. The same effect can be +achieved by using `Task` directly, but these subclasses usually make it +easier. + +### Sending a message to an SNS topic + +Use the `PublishTask` task in the SNS library: + +```ts +import sns = require('@aws-cdk/aws-sns'); + +// ... + +const topic = new sns.Topic(this, 'Topic'); +const task = new sns.PublishTask(this, 'Publish', { + topic, + message: 'A message to send to the queue' +}); + +### Sending a message to an SQS queue + +Use the `SendMessageTask` task in the SQS library: + +```ts +import sqs = require('@aws-cdk/aws-sqs'); + +// ... + +const queue = new sns.Queue(this, 'Queue'); +const task = new sns.SendMessageTask(this, 'Send', { + queue, + messageBodyPath: '$.message', + // Only for FIFO queues + messageGroupId: '$.messageGroupId', +}); +``` + ## State Machine Fragments It is possible to define reusable (or abstracted) mini-state machines by diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index a6ed6025de560..3d5aeb6c10981 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -7,16 +7,9 @@ import { CatchProps, IChainable, INextable, RetryProps } from '../types'; import { renderJsonPath, State, StateType } from './state'; /** - * Properties for defining a Task state + * Props that are common to all tasks */ -export interface TaskProps { - /** - * The resource that represents the work to be executed - * - * Can be either a Lambda Function or an Activity. - */ - resource: IStepFunctionsTaskResource; - +export interface BasicTaskProps { /** * An optional description for this state * @@ -34,16 +27,6 @@ export interface TaskProps { */ inputPath?: string; - /** - * Parameters pass a collection of key-value pairs, either static values or JSONPath expressions that select from the input. - * - * @see - * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-parameters - * - * @default No parameters - */ - parameters?: { [name: string]: any }; - /** * JSONPath expression to select part of the state to be the output to this state. * @@ -72,6 +55,32 @@ export interface TaskProps { * @default 60 */ timeoutSeconds?: number; +} + +/** + * Properties for defining a Task state + */ +export interface TaskProps extends BasicTaskProps { + /** + * The resource that represents the work to be executed + * + * Can be either a Lambda Function or an Activity. + */ + resource: IStepFunctionsTaskResource; + + /** + * Parameters pass a collection of key-value pairs, either static values or JSONPath expressions that select from the input. + * + * What is passed here will be merged with any default parameters + * configured by the `resource`. For example, a DynamoDB table target + * will + * + * @see + * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-parameters + * + * @default No parameters + */ + parameters?: { [name: string]: any }; /** * Maximum time between heart beats @@ -88,8 +97,12 @@ export interface TaskProps { /** * Define a Task state in the state machine * - * Reaching a Task state causes some work to be executed, represented - * by the Task's resource property. + * Reaching a Task state causes some work to be executed, represented by the + * Task's resource property. Task constructs represent a generic Amazon + * States Language Task. + * + * For some resource types, more specific subclasses of Task may be available + * which are more convenient to use. */ export class Task extends State implements INextable { public readonly endStates: INextable[]; @@ -140,10 +153,20 @@ export class Task extends State implements INextable { * Return the Amazon States Language object for this state */ public toStateJson(): object { + const inOutParams = this.renderInputOutput(); + + // Mix resource-defined and user-supplied Parameters, user wins. + let Parameters; + if (this.resourceProps.parameters || inOutParams.Parameters) { + Parameters = this.resourceProps.parameters || {}; + cdk.deepMerge(Parameters, inOutParams.Parameters || {}); + } + return { ...this.renderNextEnd(), ...this.renderRetryCatch(), - ...this.renderInputOutput(), + ...inOutParams, + Parameters, Type: StateType.Task, Comment: this.comment, Resource: this.resourceProps.resourceArn, @@ -290,6 +313,15 @@ export interface StepFunctionsTaskResourceProps { */ policyStatements?: iam.PolicyStatement[]; + /** + * Parameters to pass to the Resource + * + * Will be merged with parameters that the user supplies. + * + * @default No parameters + */ + parameters?: {[key: string]: any}; + /** * Prefix for singular metric names of activity actions * diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts index 5b15006d36395..41dd3f93296c5 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts @@ -680,6 +680,34 @@ export = { test.done(); }, }, + + 'Task Parameters': { + 'Combine task and resource parameters, user wins'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const task = new stepfunctions.Task(stack, 'Task', { + resource: new FakeResource({ param1: 'Value1', param3: 'ResourceValue' }), + parameters: { + param2: 'Value2', + param3: 'UserValue', + } + }); + + test.deepEqual(render(task), { + StartAt: 'Task', + States: { + Task: { + End: true, + Parameters: { param1: 'Value1', param2: 'Value2', param3: 'UserValue' }, + Type: 'Task', + Resource: 'resource' + } + } + }); + + test.done(); + }, + }, }; class ReusableStateMachine extends stepfunctions.StateMachineFragment { @@ -721,9 +749,13 @@ class SimpleChain extends stepfunctions.StateMachineFragment { } class FakeResource implements stepfunctions.IStepFunctionsTaskResource { + constructor(private readonly parameters?: {[key: string]: any}) { + } + public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { return { - resourceArn: 'resource' + resourceArn: 'resource', + parameters: this.parameters, }; } } From 28d74ce60f9c8f49ba7b96dc14c8a5138c913de1 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 29 Jan 2019 10:37:16 +0100 Subject: [PATCH 02/21] WIP --- .../aws-ecs/lib/base/base-run-task.ts | 97 +++++++++++++++++++ packages/@aws-cdk/aws-ecs/lib/cluster.ts | 21 ++++ packages/@aws-cdk/aws-ecs/package.json | 2 + 3 files changed, 120 insertions(+) create mode 100644 packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts new file mode 100644 index 0000000000000..0f8f5a9dcd8c0 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts @@ -0,0 +1,97 @@ +import iam = require('@aws-cdk/aws-iam'); +import stepfunctions = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); +import { ICluster } from '../cluster'; +import { TaskDefinition } from './task-definition'; + +/** + * Properties for SendMessageTask + */ +export interface RunTaskProps extends stepfunctions.BasicTaskProps { + /** + * The topic to run the task on + */ + cluster: ICluster; + + /** + * Task Definition used for running tasks in the service + */ + taskDefinition: TaskDefinition; + + /** + * Container setting overrides + * + * Key is the name of the container to override, value is the + * values you want to override. + */ + containerOverrides: {[name: string]: ContainerOverrides}; +} + +export interface ContainerOverrides { + /** + * Command to run inside the container + * + * @default Default command + */ + command?: string[]; + + /** + * Variables to set in the container's environment + */ + environment: {[key: string]: string}; + + /** + * The number of cpu units reserved for the container + * + * @Default The default value from the task definition. + */ + cpu?: number; + + /** + * Hard memory limit on the container + * + * @Default The default value from the task definition. + */ + memoryLimit?: number; + + /** + * Soft memory limit on the container + * + * @Default The default value from the task definition. + */ + memoryReservation?: number; +} + +/** + * A StepFunctions Task to run a Task on ECS or Fargate + */ +export class BaseRunTask extends stepfunctions.Task { + protected readonly parameters: {[key: string]: any} = {}; + + constructor(scope: cdk.Construct, id: string, props: RunTaskProps) { + super(scope, id, { + ...props, + resource: new RunTaskResource(props), + parameters: new cdk.Token(() => ({ + Cluster: props.cluster.clusterArn, + TaskDefinition: props.taskDefinition.taskDefinitionArn, + ...this.parameters + })) + }); + } +} + +class RunTaskResource implements stepfunctions.IStepFunctionsTaskResource { + constructor(private readonly props: RunTaskProps) { + } + + public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { + return { + resourceArn: 'arn:aws:states:::ecs:runTask.sync', + policyStatements: [new iam.PolicyStatement() + .addAction('ecs:RunTask') + // FIXME: Need to be ClusterARN + .addResource(this.props.cluster.clusterArn)], + }; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index dfc6d5cc3fe0f..1c9673a738044 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -233,6 +233,11 @@ export interface ICluster extends cdk.IConstruct { */ readonly clusterName: string; + /** + * ARN of the cluster + */ + readonly clusterArn: string; + /** * VPC that the cluster instances are running in */ @@ -263,6 +268,11 @@ export interface ClusterImportProps { */ clusterName: string; + /** + * ARN of the cluster + */ + clusterArn?: string; + /** * VPC that the cluster instances are running in */ @@ -290,6 +300,11 @@ class ImportedCluster extends cdk.Construct implements ICluster { */ public readonly clusterName: string; + /** + * ARN of the cluster + */ + public readonly clusterArn: string; + /** * VPC that the cluster instances are running in */ @@ -311,6 +326,12 @@ class ImportedCluster extends cdk.Construct implements ICluster { this.vpc = ec2.VpcNetwork.import(this, "vpc", props.vpc); this.hasEc2Capacity = props.hasEc2Capacity !== false; + this.clusterArn = props.clusterArn !== undefined ? props.clusterArn : cdk.Stack.find(this).formatArn({ + service: 'ecs', + resource: 'cluster', + resourceName: props.clusterName, + }); + let i = 1; for (const sgProps of props.securityGroups) { this.connections.addSecurityGroup(ec2.SecurityGroup.import(this, `SecurityGroup${i}`, sgProps)); diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index d7d4c2daf22ec..88b802c0e2c3b 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -78,6 +78,7 @@ "@aws-cdk/aws-logs": "^0.22.0", "@aws-cdk/aws-route53": "^0.22.0", "@aws-cdk/aws-sns": "^0.22.0", + "@aws-cdk/aws-stepfunctions": "^0.22.0", "@aws-cdk/cdk": "^0.22.0", "@aws-cdk/cx-api": "^0.22.0" }, @@ -95,6 +96,7 @@ "@aws-cdk/aws-iam": "^0.22.0", "@aws-cdk/aws-logs": "^0.22.0", "@aws-cdk/aws-route53": "^0.22.0", + "@aws-cdk/aws-stepfunctions": "^0.22.0", "@aws-cdk/cdk": "^0.22.0" }, "engines": { From df9fb7b66e35abbcd11b0cf452265808ca603b2c Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 29 Jan 2019 11:38:12 +0100 Subject: [PATCH 03/21] WIP --- .../aws-ecs/lib/base/base-run-task.ts | 149 ++++++++- .../@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts | 289 ++++++++++++++++++ packages/@aws-cdk/aws-ecs/lib/index.ts | 2 + 3 files changed, 431 insertions(+), 9 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts index 0f8f5a9dcd8c0..37d0a14b184ee 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts @@ -7,7 +7,7 @@ import { TaskDefinition } from './task-definition'; /** * Properties for SendMessageTask */ -export interface RunTaskProps extends stepfunctions.BasicTaskProps { +export interface BaseRunTaskProps extends stepfunctions.BasicTaskProps { /** * The topic to run the task on */ @@ -24,10 +24,24 @@ export interface RunTaskProps extends stepfunctions.BasicTaskProps { * Key is the name of the container to override, value is the * values you want to override. */ - containerOverrides: {[name: string]: ContainerOverrides}; + containerOverrides?: ContainerOverride[]; } -export interface ContainerOverrides { +export interface ContainerOverride { + /** + * Name of the container inside the task definition + * + * Exactly one of `containerName` and `containerNamePath` is required. + */ + containerName?: string; + + /** + * JSONPath expression for the name of the container inside the task definition + * + * Exactly one of `containerName` and `containerNamePath` is required. + */ + containerNamePath?: string; + /** * Command to run inside the container * @@ -35,10 +49,17 @@ export interface ContainerOverrides { */ command?: string[]; + /** + * JSON expression for command to run inside the container + * + * @default Default command + */ + commandPath?: string; + /** * Variables to set in the container's environment */ - environment: {[key: string]: string}; + environment?: TaskEnvironmentVariable[]; /** * The number of cpu units reserved for the container @@ -47,6 +68,13 @@ export interface ContainerOverrides { */ cpu?: number; + /** + * JSON expression for the number of CPU units + * + * @Default The default value from the task definition. + */ + cpuPath?: string; + /** * Hard memory limit on the container * @@ -54,12 +82,59 @@ export interface ContainerOverrides { */ memoryLimit?: number; + /** + * JSON expression path for the hard memory limit + * + * @Default The default value from the task definition. + */ + memoryLimitPath?: string; + /** * Soft memory limit on the container * * @Default The default value from the task definition. */ memoryReservation?: number; + + /** + * JSONExpr path for memory limit on the container + * + * @Default The default value from the task definition. + */ + memoryReservationPath?: number; +} + +/** + * An environment variable to be set in the container run as a task + */ +export interface TaskEnvironmentVariable { + /** + * Name for the environment variable + * + * Exactly one of `name` and `namePath` must be specified. + */ + name?: string; + + /** + * JSONExpr for the name of the variable + * + * Exactly one of `name` and `namePath` must be specified. + */ + namePath?: string; + + /** + * Value of the environment variable + * + * Exactly one of `value` and `valuePath` must be specified. + */ + value?: string; + + /** + * JSONPath expr for the environment variable + * + * Exactly one of `value` and `valuePath` must be specified. + */ + valuePath?: string; } /** @@ -68,7 +143,7 @@ export interface ContainerOverrides { export class BaseRunTask extends stepfunctions.Task { protected readonly parameters: {[key: string]: any} = {}; - constructor(scope: cdk.Construct, id: string, props: RunTaskProps) { + constructor(scope: cdk.Construct, id: string, props: BaseRunTaskProps) { super(scope, id, { ...props, resource: new RunTaskResource(props), @@ -78,20 +153,76 @@ export class BaseRunTask extends stepfunctions.Task { ...this.parameters })) }); + + this.parameters.Overrides = this.renderOverrides(props.containerOverrides); + } + + private renderOverrides(containerOverrides?: ContainerOverride[]) { + if (!containerOverrides) { return undefined; } + + const ret = new Array(); + for (const override of containerOverrides) { + ret.push({ + ...extractRequired(override, 'containerName', 'Name'), + ...extractOptional(override, 'command', 'Command'), + ...extractOptional(override, 'cpu', 'Cpu'), + ...extractOptional(override, 'memoryLimit', 'Memory'), + ...extractOptional(override, 'memoryReservation', 'MemoryReservation'), + Environment: override.environment && override.environment.map(e => ({ + ...extractRequired(e, 'name', 'Name'), + ...extractRequired(e, 'value', 'Value'), + })) + }); + } + + return { ContainerOverrides: ret }; } } class RunTaskResource implements stepfunctions.IStepFunctionsTaskResource { - constructor(private readonly props: RunTaskProps) { + constructor(private readonly props: BaseRunTaskProps) { } public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { return { resourceArn: 'arn:aws:states:::ecs:runTask.sync', - policyStatements: [new iam.PolicyStatement() + policyStatements: [ + new iam.PolicyStatement() .addAction('ecs:RunTask') - // FIXME: Need to be ClusterARN - .addResource(this.props.cluster.clusterArn)], + .addResource(this.props.cluster.clusterArn), + new iam.PolicyStatement() + .addAction('iam:PassRole') + .addResource(new cdk.Token(() => this.taskExecutionRole() ? this.taskExecutionRole()!.roleArn : 'x').toString()) + ], }; } + + private taskExecutionRole(): iam.IRole | undefined { + return (this.props.taskDefinition as any).executionRole; + } +} + +function extractRequired(obj: any, srcKey: string, dstKey: string) { + if ((obj[srcKey] !== undefined) === (obj[srcKey + 'Path'] !== undefined)) { + throw new Error(`Supply exactly one of '${srcKey}' or '${srcKey}Path'`); + } + return mapValue(obj, srcKey, dstKey); +} + +function extractOptional(obj: any, srcKey: string, dstKey: string) { + if ((obj[srcKey] !== undefined) && (obj[srcKey + 'Path'] !== undefined)) { + throw new Error(`Supply only one of '${srcKey}' or '${srcKey}Path'`); + } + return mapValue(obj, srcKey, dstKey); +} + +function mapValue(obj: any, srcKey: string, dstKey: string) { + if (obj[srcKey + 'Path'] !== undefined && !obj[srcKey + 'Path'].startswith('$.')) { + throw new Error(`Value for '${srcKey}Path' must start with '$.', got: '${obj[srcKey + 'Path']}'`); + } + + return { + [dstKey]: obj[srcKey], + [dstKey + '.$']: obj[srcKey + 'Path'] + }; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts new file mode 100644 index 0000000000000..cc1d2cc218515 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts @@ -0,0 +1,289 @@ +import cloudwatch = require ('@aws-cdk/aws-cloudwatch'); +import ec2 = require('@aws-cdk/aws-ec2'); +import elb = require('@aws-cdk/aws-elasticloadbalancing'); +import cdk = require('@aws-cdk/cdk'); +import { BaseService, BaseServiceProps } from '../base/base-service'; +import { NetworkMode, TaskDefinition } from '../base/task-definition'; +import { ICluster } from '../cluster'; +import { CfnService } from '../ecs.generated'; +import { isEc2Compatible } from '../util'; +import { BaseRunTaskProps } from '../base/base-run-task'; + +/** + * Properties to define an ECS service + */ +export interface Ec2RunTaskProps extends BaseRunTaskProps { + /** + * In what subnets to place the task's ENIs + * + * (Only applicable in case the TaskDefinition is configured for AwsVpc networking) + * + * @default Private subnets + */ + vpcPlacement?: ec2.VpcPlacementStrategy; + + /** + * Existing security group to use for the task's ENIs + * + * (Only applicable in case the TaskDefinition is configured for AwsVpc networking) + * + * @default A new security group is created + */ + securityGroup?: ec2.ISecurityGroup; + + /** + * Whether to start services on distinct instances + * + * @default true + */ + placeOnDistinctInstances?: boolean; + + /** + * Deploy exactly one task on each instance in your cluster. + * + * When using this strategy, do not specify a desired number of tasks or any + * task placement strategies. + * + * @default false + */ + daemon?: boolean; +} + +/** + * Start a service on an ECS cluster + */ +export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { + /** + * Name of the cluster + */ + public readonly clusterName: string; + + private readonly constraints: CfnService.PlacementConstraintProperty[]; + private readonly strategies: CfnService.PlacementStrategyProperty[]; + private readonly daemon: boolean; + private readonly cluster: ICluster; + + constructor(scope: cdk.Construct, id: string, props: Ec2ServiceProps) { + if (props.daemon && props.desiredCount !== undefined) { + throw new Error('Daemon mode launches one task on every instance. Don\'t supply desiredCount.'); + } + + if (!isEc2Compatible(props.taskDefinition.compatibility)) { + throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); + } + + super(scope, id, { + ...props, + // If daemon, desiredCount must be undefined and that's what we want. Otherwise, default to 1. + desiredCount: props.daemon || props.desiredCount !== undefined ? props.desiredCount : 1, + }, + { + cluster: props.cluster.clusterName, + taskDefinition: props.taskDefinition.taskDefinitionArn, + launchType: 'EC2', + placementConstraints: new cdk.Token(() => this.constraints), + placementStrategies: new cdk.Token(() => this.strategies), + schedulingStrategy: props.daemon ? 'DAEMON' : 'REPLICA', + }, props.cluster.clusterName, props.taskDefinition); + + this.cluster = props.cluster; + this.clusterName = props.cluster.clusterName; + this.constraints = []; + this.strategies = []; + this.daemon = props.daemon || false; + + if (props.taskDefinition.networkMode === NetworkMode.AwsVpc) { + this.configureAwsVpcNetworking(props.cluster.vpc, false, props.vpcPlacement, props.securityGroup); + } else { + // Either None, Bridge or Host networking. Copy SecurityGroup from ASG. + validateNoNetworkingProps(props); + this.connections.addSecurityGroup(...props.cluster.connections.securityGroups); + } + + if (props.placeOnDistinctInstances) { + this.constraints.push({ type: 'distinctInstance' }); + } + + if (!this.taskDefinition.defaultContainer) { + throw new Error('A TaskDefinition must have at least one essential container'); + } + } + + /** + * Place services only on instances matching the given query expression + * + * You can specify multiple expressions in one call. The tasks will only + * be placed on instances matching all expressions. + * + * @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-query-language.html + */ + public placeOnMemberOf(...expressions: string[]) { + for (const expression of expressions) { + this.constraints.push({ type: 'memberOf', expression }); + } + } + + /** + * Try to place tasks spread across instance attributes. + * + * You can use one of the built-in attributes found on `BuiltInAttributes` + * or supply your own custom instance attributes. If more than one attribute + * is supplied, spreading is done in order. + * + * @default attributes instanceId + */ + public placeSpreadAcross(...fields: string[]) { + if (this.daemon) { + throw new Error("Can't configure spreading placement for a service with daemon=true"); + } + + if (fields.length === 0) { + fields = [BuiltInAttributes.InstanceId]; + } + for (const field of fields) { + this.strategies.push({ type: 'spread', field }); + } + } + + /** + * Try to place tasks on instances with the least amount of indicated resource available + * + * This ensures the total consumption of this resource is lowest. + */ + public placePackedBy(resource: BinPackResource) { + if (this.daemon) { + throw new Error("Can't configure packing placement for a service with daemon=true"); + } + + this.strategies.push({ type: 'binpack', field: resource }); + } + + /** + * Place tasks randomly across the available instances. + */ + public placeRandomly() { + if (this.daemon) { + throw new Error("Can't configure random placement for a service with daemon=true"); + } + + this.strategies.push({ type: 'random' }); + } + + /** + * Register this service as the target of a Classic Load Balancer + * + * Don't call this. Call `loadBalancer.addTarget()` instead. + */ + public attachToClassicLB(loadBalancer: elb.LoadBalancer): void { + if (this.taskDefinition.networkMode === NetworkMode.Bridge) { + throw new Error("Cannot use a Classic Load Balancer if NetworkMode is Bridge. Use Host or AwsVpc instead."); + } + if (this.taskDefinition.networkMode === NetworkMode.None) { + throw new Error("Cannot use a load balancer if NetworkMode is None. Use Host or AwsVpc instead."); + } + + this.loadBalancers.push({ + loadBalancerName: loadBalancer.loadBalancerName, + containerName: this.taskDefinition.defaultContainer!.node.id, + containerPort: this.taskDefinition.defaultContainer!.containerPort, + }); + } + + /** + * Return the given named metric for this Service + */ + public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/ECS', + metricName, + dimensions: { ClusterName: this.clusterName, ServiceName: this.serviceName }, + ...props + }); + } + + /** + * Metric for cluster Memory utilization + * + * @default average over 5 minutes + */ + public metricMemoryUtilization(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric('MemoryUtilization', props ); + } + + /** + * Metric for cluster CPU utilization + * + * @default average over 5 minutes + */ + public metricCpuUtilization(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric('CPUUtilization', props); + } + + /** + * Validate this Ec2Service + */ + protected validate(): string[] { + const ret = super.validate(); + if (!this.cluster.hasEc2Capacity) { + ret.push('Cluster for this service needs Ec2 capacity. Call addXxxCapacity() on the cluster.'); + } + return ret; + } +} + +/** + * Validate combinations of networking arguments + */ +function validateNoNetworkingProps(props: Ec2ServiceProps) { + if (props.vpcPlacement !== undefined || props.securityGroup !== undefined) { + throw new Error('vpcPlacement and securityGroup can only be used in AwsVpc networking mode'); + } +} + +/** + * Built-in container instance attributes + */ +export class BuiltInAttributes { + /** + * The Instance ID of the instance + */ + public static readonly InstanceId = 'instanceId'; + + /** + * The AZ where the instance is running + */ + public static readonly AvailabilityZone = 'attribute:ecs.availability-zone'; + + /** + * The AMI ID of the instance + */ + public static readonly AmiId = 'attribute:ecs.ami-id'; + + /** + * The instance type + */ + public static readonly InstanceType = 'attribute:ecs.instance-type'; + + /** + * The OS type + * + * Either 'linux' or 'windows'. + */ + public static readonly OsType = 'attribute:ecs.os-type'; +} + +/** + * Instance resource used for bin packing + */ +export enum BinPackResource { + /** + * Fill up hosts' CPU allocations first + */ + Cpu = 'cpu', + + /** + * Fill up hosts' memory allocations first + */ + Memory = 'memory', +} + diff --git a/packages/@aws-cdk/aws-ecs/lib/index.ts b/packages/@aws-cdk/aws-ecs/lib/index.ts index 15efbab29225d..2489a25c3785b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/index.ts +++ b/packages/@aws-cdk/aws-ecs/lib/index.ts @@ -1,6 +1,7 @@ export * from './base/base-service'; export * from './base/scalable-task-count'; export * from './base/task-definition'; +export * from './base/base-run-task'; export * from './container-definition'; export * from './container-image'; @@ -8,6 +9,7 @@ export * from './cluster'; export * from './ec2/ec2-service'; export * from './ec2/ec2-task-definition'; +export * from './ec2/ec2-run-task'; export * from './fargate/fargate-service'; export * from './fargate/fargate-task-definition'; From b1dcd17ed0d5a7a32fd5636a68ef75a749257592 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 31 Jan 2019 14:15:51 +0100 Subject: [PATCH 04/21] Add support for running ECS tasks in StepFunctions --- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 33 +- .../test/integ.import-default-vpc.lit.ts | 6 +- .../aws-ecs/lib/base/base-run-task.ts | 97 +- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 8 +- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 17 + .../@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts | 204 +--- .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 6 +- .../aws-ecs/lib/fargate/fargate-run-task.ts | 61 ++ .../aws-ecs/lib/fargate/fargate-service.ts | 4 +- packages/@aws-cdk/aws-ecs/lib/index.ts | 1 + .../test/ec2/integ.lb-awsvpc-nw.expected.json | 6 +- .../test/ec2/integ.lb-bridge-nw.expected.json | 6 +- .../integ.stepfunctions-task.expected.json | 937 ++++++++++++++++++ .../test/ec2/integ.stepfunctions-task.ts | 50 + .../test/eventhandler-image/Dockerfile | 5 + .../aws-ecs/test/eventhandler-image/index.py | 6 + .../integ.stepfunctions-task.expected.json | 619 ++++++++++++ .../test/fargate/integ.stepfunctions-task.ts | 50 + .../@aws-cdk/aws-iam/lib/policy-document.ts | 4 + .../aws-iam/test/test.policy-document.ts | 16 + packages/@aws-cdk/aws-stepfunctions/README.md | 23 + .../aws-stepfunctions/lib/states/task.ts | 21 +- .../test/test.states-language.ts | 36 +- 23 files changed, 1957 insertions(+), 259 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecs/lib/fargate/fargate-run-task.ts create mode 100644 packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.expected.json create mode 100644 packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.ts create mode 100644 packages/@aws-cdk/aws-ecs/test/eventhandler-image/Dockerfile create mode 100644 packages/@aws-cdk/aws-ecs/test/eventhandler-image/index.py create mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.expected.json create mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.ts diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts index e7c696de6bd26..5aade25103268 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts @@ -63,7 +63,7 @@ export interface IVpcNetwork extends IConstruct, IDependable { /** * Return the subnets appropriate for the placement strategy */ - subnets(placement?: VpcPlacementStrategy): IVpcSubnet[]; + subnets(placement?: VpcPlacementStrategy, required?: SubnetQuery): IVpcSubnet[]; /** * Return whether the given subnet is one of this VPC's public subnets. @@ -90,7 +90,7 @@ export enum SubnetType { * This can be good for subnets with RDS or * Elasticache endpoints */ - Isolated = 1, + Isolated = 'Isolated', /** * Subnet that routes to the internet, but not vice versa. @@ -104,7 +104,7 @@ export enum SubnetType { * experimental cost conscious accounts or accounts where HA outbound * traffic is not needed. */ - Private = 2, + Private = 'Private', /** * Subnet connected to the Internet @@ -116,7 +116,7 @@ export enum SubnetType { * * Public subnets route outbound traffic via an Internet Gateway. */ - Public = 3 + Public = 'Public' } /** @@ -192,7 +192,7 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { /** * Return the subnets appropriate for the placement strategy */ - public subnets(placement: VpcPlacementStrategy = {}): IVpcSubnet[] { + public subnets(placement: VpcPlacementStrategy = {}, required = SubnetQuery.Required): IVpcSubnet[] { if (placement.subnetsToUse !== undefined && placement.subnetName !== undefined) { throw new Error('At most one of subnetsToUse and subnetName can be supplied'); } @@ -210,11 +210,17 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { // Select by type if (placement.subnetsToUse === undefined) { return this.privateSubnets; } - return { + const selected = { [SubnetType.Isolated]: this.isolatedSubnets, [SubnetType.Private]: this.privateSubnets, [SubnetType.Public]: this.publicSubnets, }[placement.subnetsToUse]; + + if (required === SubnetQuery.Required && selected.length === 0) { + throw new Error(`No subnets in this VPC match ${JSON.stringify(placement)}. Please select a different set of subnets.`); + } + + return selected; } /** @@ -334,4 +340,19 @@ class DependencyList implements IDependable { public get dependencyElements(): IDependable[] { return this.dependenclyElements; } +} + +/** + * Options for querying subnets + */ +export enum SubnetQuery { + /** + * Require at least one subnet + */ + Required = 'Required', + + /** + * Allow a subnet query to match no subnets + */ + AllowNone = 'AllowNone' } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts index 19fcff0cb4d04..92262b5934b81 100644 --- a/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts +++ b/packages/@aws-cdk/aws-ec2/test/integ.import-default-vpc.lit.ts @@ -18,7 +18,9 @@ new ec2.SecurityGroup(stack, 'SecurityGroup', { }); // Try subnet selection -new cdk.Output(stack, 'PublicSubnets', { value: 'ids:' + vpc.subnets({ subnetsToUse: ec2.SubnetType.Public }).map(s => s.subnetId).join(',') }); -new cdk.Output(stack, 'PrivateSubnets', { value: 'ids:' + vpc.subnets({ subnetsToUse: ec2.SubnetType.Private }).map(s => s.subnetId).join(',') }); +// tslint:disable-next-line:max-line-length +new cdk.Output(stack, 'PublicSubnets', { value: 'ids:' + vpc.subnets({ subnetsToUse: ec2.SubnetType.Public }, ec2.SubnetQuery.AllowNone).map(s => s.subnetId).join(',') }); +// tslint:disable-next-line:max-line-length +new cdk.Output(stack, 'PrivateSubnets', { value: 'ids:' + vpc.subnets({ subnetsToUse: ec2.SubnetType.Private }, ec2.SubnetQuery.AllowNone).map(s => s.subnetId).join(',') }); app.run(); diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts index 37d0a14b184ee..9aa6b9110b743 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts @@ -1,3 +1,4 @@ +import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import stepfunctions = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); @@ -25,6 +26,13 @@ export interface BaseRunTaskProps extends stepfunctions.BasicTaskProps { * values you want to override. */ containerOverrides?: ContainerOverride[]; + + /** + * Whether to wait for the task to complete and return the response + * + * @default true + */ + synchronous?: boolean; } export interface ContainerOverride { @@ -140,8 +148,14 @@ export interface TaskEnvironmentVariable { /** * A StepFunctions Task to run a Task on ECS or Fargate */ -export class BaseRunTask extends stepfunctions.Task { - protected readonly parameters: {[key: string]: any} = {}; +export class BaseRunTask extends stepfunctions.Task implements ec2.IConnectable { + /** + * Manage allowed network traffic for this service + */ + public readonly connections: ec2.Connections = new ec2.Connections(); + + protected networkConfiguration?: any; + protected readonly _parameters: {[key: string]: any} = {}; constructor(scope: cdk.Construct, id: string, props: BaseRunTaskProps) { super(scope, id, { @@ -150,11 +164,35 @@ export class BaseRunTask extends stepfunctions.Task { parameters: new cdk.Token(() => ({ Cluster: props.cluster.clusterArn, TaskDefinition: props.taskDefinition.taskDefinitionArn, - ...this.parameters + NetworkConfiguration: this.networkConfiguration, + ...this._parameters })) }); - this.parameters.Overrides = this.renderOverrides(props.containerOverrides); + this._parameters.Overrides = this.renderOverrides(props.containerOverrides); + } + + protected configureAwsVpcNetworking( + vpc: ec2.IVpcNetwork, + assignPublicIp?: boolean, + vpcPlacement?: ec2.VpcPlacementStrategy, + securityGroup?: ec2.ISecurityGroup) { + if (vpcPlacement === undefined) { + vpcPlacement = { subnetsToUse: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; + } + if (securityGroup === undefined) { + securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); + } + const subnets = vpc.subnets(vpcPlacement); + this.connections.addSecurityGroup(securityGroup); + + this.networkConfiguration = { + AwsvpcConfiguration: { + AssignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', + Subnets: subnets.map(x => x.subnetId), + SecurityGroups: new cdk.Token(() => [securityGroup!.securityGroupId]), + } + }; } private renderOverrides(containerOverrides?: ContainerOverride[]) { @@ -183,22 +221,47 @@ class RunTaskResource implements stepfunctions.IStepFunctionsTaskResource { constructor(private readonly props: BaseRunTaskProps) { } - public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { + public asStepFunctionsTaskResource(callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { + const stack = cdk.Stack.find(callingTask); + const sync = this.props.synchronous !== false; + + // https://docs.aws.amazon.com/step-functions/latest/dg/ecs-iam.html + const policyStatements = [ + new iam.PolicyStatement() + .addAction('ecs:RunTask') + .addResource(this.props.taskDefinition.taskDefinitionArn), + new iam.PolicyStatement() + .addActions('ecs:StopTask', 'ecs:DescribeTasks') + .addAllResources(), + new iam.PolicyStatement() + .addAction('iam:PassRole') + .addResources(...new cdk.Token(() => this.taskExecutionRoles().map(r => r.roleArn)).toList()) + ]; + + if (sync) { + policyStatements.push(new iam.PolicyStatement() + .addActions("events:PutTargets", "events:PutRule", "events:DescribeRule") + .addResource(stack.formatArn({ + service: 'events', + resource: 'rule', + resourceName: 'StepFunctionsGetEventsForECSTaskRule' + }))); + } + return { - resourceArn: 'arn:aws:states:::ecs:runTask.sync', - policyStatements: [ - new iam.PolicyStatement() - .addAction('ecs:RunTask') - .addResource(this.props.cluster.clusterArn), - new iam.PolicyStatement() - .addAction('iam:PassRole') - .addResource(new cdk.Token(() => this.taskExecutionRole() ? this.taskExecutionRole()!.roleArn : 'x').toString()) - ], + resourceArn: 'arn:aws:states:::ecs:runTask' + (sync ? '.sync' : ''), + policyStatements, }; } - private taskExecutionRole(): iam.IRole | undefined { - return (this.props.taskDefinition as any).executionRole; + private taskExecutionRoles(): iam.IRole[] { + // Need to be able to pass both Task and Execution role, apparently + const ret = new Array(); + ret.push(this.props.taskDefinition.taskRole); + if ((this.props.taskDefinition as any).executionRole) { + ret.push((this.props.taskDefinition as any).executionRole); + } + return ret; } } @@ -217,7 +280,7 @@ function extractOptional(obj: any, srcKey: string, dstKey: string) { } function mapValue(obj: any, srcKey: string, dstKey: string) { - if (obj[srcKey + 'Path'] !== undefined && !obj[srcKey + 'Path'].startswith('$.')) { + if (obj[srcKey + 'Path'] !== undefined && !obj[srcKey + 'Path'].startsWith('$.')) { throw new Error(`Value for '${srcKey}Path' must start with '$.', got: '${obj[srcKey + 'Path']}'`); } diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index cc3e5cefd8e48..2922fb9e3ad65 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -56,7 +56,7 @@ export interface BaseServiceProps { * Base class for Ecs and Fargate services */ export abstract class BaseService extends cdk.Construct - implements elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget, cdk.IDependable { + implements ec2.IConnectable, elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget, cdk.IDependable { /** * CloudFormation resources generated by this service @@ -185,7 +185,7 @@ export abstract class BaseService extends cdk.Construct // tslint:disable-next-line:max-line-length protected configureAwsVpcNetworking(vpc: ec2.IVpcNetwork, assignPublicIp?: boolean, vpcPlacement?: ec2.VpcPlacementStrategy, securityGroup?: ec2.ISecurityGroup) { if (vpcPlacement === undefined) { - vpcPlacement = { subnetsToUse: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; + vpcPlacement = { subnetsToUse: ec2.SubnetType.Private }; } if (securityGroup === undefined) { securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); @@ -193,6 +193,10 @@ export abstract class BaseService extends cdk.Construct const subnets = vpc.subnets(vpcPlacement); this.connections.addSecurityGroup(securityGroup); + if (assignPublicIp === undefined && subnets.every(s => vpc.isPublicSubnet(s))) { + assignPublicIp = true; + } + this.networkConfiguration = { awsvpcConfiguration: { assignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 1c9673a738044..c4cdcf81671fd 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -83,6 +83,8 @@ export class Cluster extends cdk.Construct implements ICluster { minSize: options.minCapacity, maxSize: options.maxCapacity, desiredCapacity: options.instanceCount, + associatePublicIpAddress: options.associatePublicIpAddress, + vpcPlacement: options.vpcPlacement, }); this.addAutoScalingGroupCapacity(autoScalingGroup, options); @@ -399,4 +401,19 @@ export interface AddDefaultAutoScalingGroupOptions extends AddAutoScalingGroupCa * @default Same as instanceCount */ minCapacity?: number; + + /** + * Whether instances in the Auto Scaling Group should have public + * IP addresses associated with them. + * + * @default Use subnet settings + */ + associatePublicIpAddress?: boolean; + + /** + * Where to place instances within the VPC + * + * @default Private subnets + */ + vpcPlacement?: ec2.VpcPlacementStrategy; } diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts index cc1d2cc218515..8686b2e77a35e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts @@ -1,16 +1,13 @@ -import cloudwatch = require ('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); -import elb = require('@aws-cdk/aws-elasticloadbalancing'); import cdk = require('@aws-cdk/cdk'); -import { BaseService, BaseServiceProps } from '../base/base-service'; -import { NetworkMode, TaskDefinition } from '../base/task-definition'; +import { BaseRunTask, BaseRunTaskProps } from '../base/base-run-task'; +import { NetworkMode } from '../base/task-definition'; import { ICluster } from '../cluster'; -import { CfnService } from '../ecs.generated'; import { isEc2Compatible } from '../util'; -import { BaseRunTaskProps } from '../base/base-run-task'; +import { BinPackResource, BuiltInAttributes } from './ec2-service'; /** - * Properties to define an ECS service + * Properties to run an ECS task on EC2 in StepFunctionsan ECS */ export interface Ec2RunTaskProps extends BaseRunTaskProps { /** @@ -34,63 +31,37 @@ export interface Ec2RunTaskProps extends BaseRunTaskProps { /** * Whether to start services on distinct instances * - * @default true - */ - placeOnDistinctInstances?: boolean; - - /** - * Deploy exactly one task on each instance in your cluster. - * - * When using this strategy, do not specify a desired number of tasks or any - * task placement strategies. - * * @default false */ - daemon?: boolean; + placeOnDistinctInstances?: boolean; } /** - * Start a service on an ECS cluster + * Run an ECS/EC2 Task in a StepFunctions workflow */ -export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { - /** - * Name of the cluster - */ - public readonly clusterName: string; - - private readonly constraints: CfnService.PlacementConstraintProperty[]; - private readonly strategies: CfnService.PlacementStrategyProperty[]; - private readonly daemon: boolean; +export class Ec2RunTask extends BaseRunTask { + private readonly constraints: any[]; + private readonly strategies: any[]; private readonly cluster: ICluster; - constructor(scope: cdk.Construct, id: string, props: Ec2ServiceProps) { - if (props.daemon && props.desiredCount !== undefined) { - throw new Error('Daemon mode launches one task on every instance. Don\'t supply desiredCount.'); - } - + constructor(scope: cdk.Construct, id: string, props: Ec2RunTaskProps) { if (!isEc2Compatible(props.taskDefinition.compatibility)) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } - super(scope, id, { - ...props, - // If daemon, desiredCount must be undefined and that's what we want. Otherwise, default to 1. - desiredCount: props.daemon || props.desiredCount !== undefined ? props.desiredCount : 1, - }, - { - cluster: props.cluster.clusterName, - taskDefinition: props.taskDefinition.taskDefinitionArn, - launchType: 'EC2', - placementConstraints: new cdk.Token(() => this.constraints), - placementStrategies: new cdk.Token(() => this.strategies), - schedulingStrategy: props.daemon ? 'DAEMON' : 'REPLICA', - }, props.cluster.clusterName, props.taskDefinition); + if (!props.taskDefinition.defaultContainer) { + throw new Error('A TaskDefinition must have at least one essential container'); + } + + super(scope, id, props); this.cluster = props.cluster; - this.clusterName = props.cluster.clusterName; this.constraints = []; this.strategies = []; - this.daemon = props.daemon || false; + + this._parameters.LaunchType = 'EC2'; + this._parameters.PlacementConstraints = new cdk.Token(() => this.constraints.length > 0 ? this.constraints : undefined); + this._parameters.PlacementStrategy = new cdk.Token(() => this.constraints.length > 0 ? this.strategies : undefined); if (props.taskDefinition.networkMode === NetworkMode.AwsVpc) { this.configureAwsVpcNetworking(props.cluster.vpc, false, props.vpcPlacement, props.securityGroup); @@ -100,17 +71,20 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { this.connections.addSecurityGroup(...props.cluster.connections.securityGroups); } + // False for now because I'm getting the error + // StateMachine (StateMachine2E01A3A5) Invalid State Machine Definition: + // 'SCHEMA_VALIDATION_FAILED: The value for 'PlacementConstraintType' must + // be one of the values: [distinctInstance, memberOf] but was + // 'distinctInstance' at /States/RunEc2/Parameters' (Service: + // AWSStepFunctions; Status Code: 400; Error Code: InvalidDefinition; + // Request ID: ad672a90-2558-11e9-ae36-d99a98e3de72) if (props.placeOnDistinctInstances) { - this.constraints.push({ type: 'distinctInstance' }); - } - - if (!this.taskDefinition.defaultContainer) { - throw new Error('A TaskDefinition must have at least one essential container'); + this.constraints.push({ Type: 'distinctInstance' }); } } /** - * Place services only on instances matching the given query expression + * Place task only on instances matching the given query expression * * You can specify multiple expressions in one call. The tasks will only * be placed on instances matching all expressions. @@ -119,7 +93,7 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { */ public placeOnMemberOf(...expressions: string[]) { for (const expression of expressions) { - this.constraints.push({ type: 'memberOf', expression }); + this.constraints.push({ Type: 'memberOf', expression }); } } @@ -133,15 +107,11 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { * @default attributes instanceId */ public placeSpreadAcross(...fields: string[]) { - if (this.daemon) { - throw new Error("Can't configure spreading placement for a service with daemon=true"); - } - if (fields.length === 0) { fields = [BuiltInAttributes.InstanceId]; } for (const field of fields) { - this.strategies.push({ type: 'spread', field }); + this.strategies.push({ Type: 'spread', Field: field }); } } @@ -151,72 +121,14 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { * This ensures the total consumption of this resource is lowest. */ public placePackedBy(resource: BinPackResource) { - if (this.daemon) { - throw new Error("Can't configure packing placement for a service with daemon=true"); - } - - this.strategies.push({ type: 'binpack', field: resource }); + this.strategies.push({ Type: 'binpack', Field: resource }); } /** * Place tasks randomly across the available instances. */ public placeRandomly() { - if (this.daemon) { - throw new Error("Can't configure random placement for a service with daemon=true"); - } - - this.strategies.push({ type: 'random' }); - } - - /** - * Register this service as the target of a Classic Load Balancer - * - * Don't call this. Call `loadBalancer.addTarget()` instead. - */ - public attachToClassicLB(loadBalancer: elb.LoadBalancer): void { - if (this.taskDefinition.networkMode === NetworkMode.Bridge) { - throw new Error("Cannot use a Classic Load Balancer if NetworkMode is Bridge. Use Host or AwsVpc instead."); - } - if (this.taskDefinition.networkMode === NetworkMode.None) { - throw new Error("Cannot use a load balancer if NetworkMode is None. Use Host or AwsVpc instead."); - } - - this.loadBalancers.push({ - loadBalancerName: loadBalancer.loadBalancerName, - containerName: this.taskDefinition.defaultContainer!.node.id, - containerPort: this.taskDefinition.defaultContainer!.containerPort, - }); - } - - /** - * Return the given named metric for this Service - */ - public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return new cloudwatch.Metric({ - namespace: 'AWS/ECS', - metricName, - dimensions: { ClusterName: this.clusterName, ServiceName: this.serviceName }, - ...props - }); - } - - /** - * Metric for cluster Memory utilization - * - * @default average over 5 minutes - */ - public metricMemoryUtilization(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('MemoryUtilization', props ); - } - - /** - * Metric for cluster CPU utilization - * - * @default average over 5 minutes - */ - public metricCpuUtilization(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.metric('CPUUtilization', props); + this.strategies.push({ Type: 'random' }); } /** @@ -234,56 +146,8 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { /** * Validate combinations of networking arguments */ -function validateNoNetworkingProps(props: Ec2ServiceProps) { +function validateNoNetworkingProps(props: Ec2RunTaskProps) { if (props.vpcPlacement !== undefined || props.securityGroup !== undefined) { throw new Error('vpcPlacement and securityGroup can only be used in AwsVpc networking mode'); } -} - -/** - * Built-in container instance attributes - */ -export class BuiltInAttributes { - /** - * The Instance ID of the instance - */ - public static readonly InstanceId = 'instanceId'; - - /** - * The AZ where the instance is running - */ - public static readonly AvailabilityZone = 'attribute:ecs.availability-zone'; - - /** - * The AMI ID of the instance - */ - public static readonly AmiId = 'attribute:ecs.ami-id'; - - /** - * The instance type - */ - public static readonly InstanceType = 'attribute:ecs.instance-type'; - - /** - * The OS type - * - * Either 'linux' or 'windows'. - */ - public static readonly OsType = 'attribute:ecs.os-type'; -} - -/** - * Instance resource used for bin packing - */ -export enum BinPackResource { - /** - * Fill up hosts' CPU allocations first - */ - Cpu = 'cpu', - - /** - * Fill up hosts' memory allocations first - */ - Memory = 'memory', -} - +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index 80628ad98ccb6..f96c41436c928 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -41,7 +41,7 @@ export interface Ec2ServiceProps extends BaseServiceProps { securityGroup?: ec2.ISecurityGroup; /** - * Whether to start services on distinct instances + * Whether to start tasks on distinct instances * * @default true */ @@ -109,7 +109,7 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { this.connections.addSecurityGroup(...props.cluster.connections.securityGroups); } - if (props.placeOnDistinctInstances) { + if (props.placeOnDistinctInstances !== false) { this.constraints.push({ type: 'distinctInstance' }); } @@ -119,7 +119,7 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { } /** - * Place services only on instances matching the given query expression + * Place tasks only on instances matching the given query expression * * You can specify multiple expressions in one call. The tasks will only * be placed on instances matching all expressions. diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-run-task.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-run-task.ts new file mode 100644 index 0000000000000..9543cfb90881c --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-run-task.ts @@ -0,0 +1,61 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import cdk = require('@aws-cdk/cdk'); +import { BaseRunTask, BaseRunTaskProps } from '../base/base-run-task'; +import { isFargateCompatible } from '../util'; +import { FargatePlatformVersion } from './fargate-service'; + +/** + * Properties to define an ECS service + */ +export interface FargateRunTaskProps extends BaseRunTaskProps { + /** + * Assign public IP addresses to each task + * + * @default false + */ + assignPublicIp?: boolean; + + /** + * In what subnets to place the task's ENIs + * + * @default Private subnet if assignPublicIp, public subnets otherwise + */ + vpcPlacement?: ec2.VpcPlacementStrategy; + + /** + * Existing security group to use for the tasks + * + * @default A new security group is created + */ + securityGroup?: ec2.ISecurityGroup; + + /** + * Fargate platform version to run this service on + * + * Unless you have specific compatibility requirements, you don't need to + * specify this. + * + * @default Latest + */ + platformVersion?: FargatePlatformVersion; +} + +/** + * Start a service on an ECS cluster + */ +export class FargateRunTask extends BaseRunTask { + constructor(scope: cdk.Construct, id: string, props: FargateRunTaskProps) { + if (!isFargateCompatible(props.taskDefinition.compatibility)) { + throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); + } + + if (!props.taskDefinition.defaultContainer) { + throw new Error('A TaskDefinition must have at least one essential container'); + } + + super(scope, id, props); + + this._parameters.LaunchType = 'FARGATE'; + this.configureAwsVpcNetworking(props.cluster.vpc, props.assignPublicIp, props.vpcPlacement, props.securityGroup); + } +} diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index 66e0d9600cd98..6322414a14bca 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -22,14 +22,14 @@ export interface FargateServiceProps extends BaseServiceProps { /** * Assign public IP addresses to each task * - * @default false + * @default Use subnet default */ assignPublicIp?: boolean; /** * In what subnets to place the task's ENIs * - * @default Private subnet if assignPublicIp, public subnets otherwise + * @default Private subnets */ vpcPlacement?: ec2.VpcPlacementStrategy; diff --git a/packages/@aws-cdk/aws-ecs/lib/index.ts b/packages/@aws-cdk/aws-ecs/lib/index.ts index 2489a25c3785b..367e52fbdbba1 100644 --- a/packages/@aws-cdk/aws-ecs/lib/index.ts +++ b/packages/@aws-cdk/aws-ecs/lib/index.ts @@ -13,6 +13,7 @@ export * from './ec2/ec2-run-task'; export * from './fargate/fargate-service'; export * from './fargate/fargate-task-definition'; +export * from './fargate/fargate-run-task'; export * from './linux-parameters'; export * from './load-balanced-service-base'; diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json index fc5ddba7d30ee..3dc562f049f7b 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json @@ -798,7 +798,11 @@ ] } }, - "PlacementConstraints": [], + "PlacementConstraints": [ + { + "Type": "distinctInstance" + } + ], "PlacementStrategies": [], "SchedulingStrategy": "REPLICA" }, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json index 4e80a7b3cdb0f..cbdcdf79684fd 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json @@ -799,7 +799,11 @@ } } ], - "PlacementConstraints": [], + "PlacementConstraints": [ + { + "Type": "distinctInstance" + } + ], "PlacementStrategies": [], "SchedulingStrategy": "REPLICA" }, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.expected.json new file mode 100644 index 0000000000000..630b75193813f --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.expected.json @@ -0,0 +1,937 @@ +{ + "Resources": { + "FargateCluster7CCD5F93": { + "Type": "AWS::ECS::Cluster" + }, + "FargateClusterDefaultAutoScalingGroupInstanceSecurityGroup42AF8A40": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup" + } + ], + "VpcId": "vpc-60900905" + } + }, + "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecs:CreateCluster", + "ecs:DeregisterContainerInstance", + "ecs:DiscoverPollEndpoint", + "ecs:Poll", + "ecs:RegisterContainerInstance", + "ecs:StartTelemetrySession", + "ecs:Submit*", + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E", + "Roles": [ + { + "Ref": "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7" + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupInstanceProfile2C0FEF3B": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7" + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupLaunchConfig57306899": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": "ami-1234", + "InstanceType": "t2.micro", + "IamInstanceProfile": { + "Ref": "FargateClusterDefaultAutoScalingGroupInstanceProfile2C0FEF3B" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupInstanceSecurityGroup42AF8A40", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\necho ECS_CLUSTER=", + { + "Ref": "FargateCluster7CCD5F93" + }, + " >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config" + ] + ] + } + } + }, + "DependsOn": [ + "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7", + "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E" + ] + }, + "FargateClusterDefaultAutoScalingGroupASG36A4948F": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "DesiredCapacity": "1", + "LaunchConfigurationName": { + "Ref": "FargateClusterDefaultAutoScalingGroupLaunchConfig57306899" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup" + } + ], + "VPCZoneIdentifier": [ + "subnet-e19455ca", + "subnet-e0c24797", + "subnet-ccd77395" + ] + }, + "UpdatePolicy": { + "AutoScalingReplacingUpdate": { + "WillReplace": true + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA": { + "Type": "AWS::SNS::Topic" + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookTopicFunctionSubscription129830E9": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8", + "Arn" + ] + }, + "Protocol": "lambda", + "TopicArn": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" + } + } + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "autoscaling:CompleteLifecycleAction", + "ec2:DescribeInstances", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceStatus", + "ec2:DescribeHosts" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecs:ListContainerInstances", + "ecs:SubmitContainerStateChange", + "ecs:SubmitTaskStateChange", + "ecs:DescribeContainerInstances", + "ecs:UpdateContainerInstancesState", + "ecs:ListTasks", + "ecs:DescribeTasks" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343", + "Roles": [ + { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32" + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n while has_tasks(cluster, instance_arn):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\n\ndef has_tasks(cluster, instance_arn):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n tasks = instance['runningTasksCount'] + instance['pendingTasksCount']\n print('Instance %s has %s tasks' % (instance_arn, tasks))\n\n return tasks > 0\n\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n" + }, + "Handler": "index.lambda_handler", + "Role": { + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32", + "Arn" + ] + }, + "Runtime": "python3.6", + "Environment": { + "Variables": { + "CLUSTER": { + "Ref": "FargateCluster7CCD5F93" + } + } + }, + "Timeout": 310 + }, + "DependsOn": [ + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32", + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343" + ] + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicA1F1F9E9": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8" + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" + } + } + }, + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "autoscaling.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D", + "Roles": [ + { + "Ref": "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D" + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHook2AE13680": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "FargateClusterDefaultAutoScalingGroupASG36A4948F" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", + "DefaultResult": "CONTINUE", + "HeartbeatTimeout": 300, + "NotificationTargetARN": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" + }, + "RoleARN": { + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D", + "Arn" + ] + } + }, + "DependsOn": [ + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D", + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D" + ] + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 4, + { + "Fn::Split": [ + ":", + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + ] + } + ] + }, + ".dkr.ecr.", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + ] + } + ] + }, + ".amazonaws.com/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + }, + ":", + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + ] + ] + }, + "Links": [], + "LinuxParameters": { + "Capabilities": { + "Add": [], + "Drop": [] + }, + "Devices": [], + "Tmpfs": [] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "TaskLoggingLogGroupC7E938D4" + }, + "awslogs-stream-prefix": "EventDemo", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Memory": 256, + "MountPoints": [], + "Name": "TheContainer", + "PortMappings": [], + "Ulimits": [], + "VolumesFrom": [] + } + ], + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + }, + "Family": "awsecsinteg2TaskDef1F38909D", + "NetworkMode": "bridge", + "PlacementConstraints": [], + "RequiresCompatibilities": [ + "EC2" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "Volumes": [] + } + }, + "TaskDefExecutionRoleB4775C97": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDefExecutionRoleDefaultPolicy0DBB737A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + }, + { + "Action": [ + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TaskLoggingLogGroupC7E938D4", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TaskDefExecutionRoleDefaultPolicy0DBB737A", + "Roles": [ + { + "Ref": "TaskDefExecutionRoleB4775C97" + } + ] + } + }, + "EventImageAdoptRepositoryDFAAC242": { + "Type": "Custom::ECRAdoptedRepository", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", + "Arn" + ] + }, + "RepositoryName": { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:GetRepositoryPolicy", + "ecr:SetRepositoryPolicy", + "ecr:DeleteRepository", + "ecr:ListImages", + "ecr:BatchDeleteImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", + "Roles": [ + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" + } + ] + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "handler.handler", + "Role": { + "Fn::GetAtt": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Timeout": 300 + }, + "DependsOn": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C" + ] + }, + "TaskLoggingLogGroupC7E938D4": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 365 + }, + "DeletionPolicy": "Retain" + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ecs:RunTask", + "Effect": "Allow", + "Resource": { + "Ref": "TaskDef54694570" + } + }, + { + "Action": [ + "ecs:StopTask", + "ecs:DescribeTasks" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + } + ] + }, + { + "Action": [ + "events:PutTargets", + "events:PutRule", + "events:DescribeRule" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":rule/StepFunctionsGetEventsForECSTaskRule" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"RunEc2\"},\"RunEc2\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", + { + "Fn::GetAtt": [ + "FargateCluster7CCD5F93", + "Arn" + ] + }, + "\",\"TaskDefinition\":\"", + { + "Ref": "TaskDef54694570" + }, + "\",\"Overrides\":{\"ContainerOverrides\":[{\"Name\":\"TheContainer\",\"Environment\":[{\"Name\":\"SOME_KEY\",\"Value.$\":\"$.SomeKey\"}]}]},\"LaunchType\":\"EC2\"},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::ecs:runTask.sync\"}}}" + ] + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + } + } + } + }, + "Parameters": { + "EventImageImageNameE972A8B1": { + "Type": "String", + "Description": "ECR repository name and tag asset \"aws-ecs-integ2/EventImage\"" + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6": { + "Type": "String", + "Description": "S3 bucket for asset \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276": { + "Type": "String", + "Description": "S3 key for asset version \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.ts new file mode 100644 index 0000000000000..f9ccc90080595 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.ts @@ -0,0 +1,50 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import sfn = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); +import path = require('path'); +import ecs = require('../../lib'); + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ2'); + +const vpc = ec2.VpcNetwork.importFromContext(stack, 'Vpc', { + isDefault: true +}); + +const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); +cluster.addDefaultAutoScalingGroupCapacity({ + instanceType: new ec2.InstanceType('t2.micro'), + vpcPlacement: { subnetsToUse: ec2.SubnetType.Public }, +}); + +// Build task definition +const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); +taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, '..', 'eventhandler-image') }), + memoryLimitMiB: 256, + logging: new ecs.AwsLogDriver(stack, 'TaskLogging', { streamPrefix: 'EventDemo' }) +}); + +// Build state machine +const definition = new sfn.Pass(stack, 'Start', { + result: { SomeKey: 'SomeValue' } +}).next(new ecs.Ec2RunTask(stack, 'RunEc2', { + cluster, taskDefinition, + containerOverrides: [ + { + containerName: 'TheContainer', + environment: [ + { + name: 'SOME_KEY', + valuePath: '$.SomeKey' + } + ] + } + ] +})); + +new sfn.StateMachine(stack, 'StateMachine', { + definition, +}); + +app.run(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/eventhandler-image/Dockerfile b/packages/@aws-cdk/aws-ecs/test/eventhandler-image/Dockerfile new file mode 100644 index 0000000000000..123b5670febc8 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/eventhandler-image/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.6 +EXPOSE 8000 +WORKDIR /src +ADD . /src +CMD python3 index.py diff --git a/packages/@aws-cdk/aws-ecs/test/eventhandler-image/index.py b/packages/@aws-cdk/aws-ecs/test/eventhandler-image/index.py new file mode 100644 index 0000000000000..c4cab119afc2d --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/eventhandler-image/index.py @@ -0,0 +1,6 @@ +#!/usr/bin/python +import os +import pprint + +print('Hello from ECS!') +pprint.pprint(dict(os.environ)) diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.expected.json new file mode 100644 index 0000000000000..61ca415d2f552 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.expected.json @@ -0,0 +1,619 @@ +{ + "Resources": { + "FargateCluster7CCD5F93": { + "Type": "AWS::ECS::Cluster" + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 4, + { + "Fn::Split": [ + ":", + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + ] + } + ] + }, + ".dkr.ecr.", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + ] + } + ] + }, + ".amazonaws.com/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + }, + ":", + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + ] + ] + }, + "Links": [], + "LinuxParameters": { + "Capabilities": { + "Add": [], + "Drop": [] + }, + "Devices": [], + "Tmpfs": [] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "TaskLoggingLogGroupC7E938D4" + }, + "awslogs-stream-prefix": "EventDemo", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Memory": 256, + "MountPoints": [], + "Name": "TheContainer", + "PortMappings": [], + "Ulimits": [], + "VolumesFrom": [] + } + ], + "Cpu": "256", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + }, + "Family": "awsecsinteg2TaskDef1F38909D", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "Volumes": [] + } + }, + "TaskDefExecutionRoleB4775C97": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDefExecutionRoleDefaultPolicy0DBB737A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + }, + { + "Action": [ + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TaskLoggingLogGroupC7E938D4", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TaskDefExecutionRoleDefaultPolicy0DBB737A", + "Roles": [ + { + "Ref": "TaskDefExecutionRoleB4775C97" + } + ] + } + }, + "EventImageAdoptRepositoryDFAAC242": { + "Type": "Custom::ECRAdoptedRepository", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", + "Arn" + ] + }, + "RepositoryName": { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:GetRepositoryPolicy", + "ecr:SetRepositoryPolicy", + "ecr:DeleteRepository", + "ecr:ListImages", + "ecr:BatchDeleteImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", + "Roles": [ + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" + } + ] + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "handler.handler", + "Role": { + "Fn::GetAtt": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Timeout": 300 + }, + "DependsOn": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C" + ] + }, + "TaskLoggingLogGroupC7E938D4": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 365 + }, + "DeletionPolicy": "Retain" + }, + "RunFargateSecurityGroup709740F2": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ2/RunFargate/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": "vpc-60900905" + } + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ecs:RunTask", + "Effect": "Allow", + "Resource": { + "Ref": "TaskDef54694570" + } + }, + { + "Action": [ + "ecs:StopTask", + "ecs:DescribeTasks" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + } + ] + }, + { + "Action": [ + "events:PutTargets", + "events:PutRule", + "events:DescribeRule" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":rule/StepFunctionsGetEventsForECSTaskRule" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"RunFargate\"},\"RunFargate\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", + { + "Fn::GetAtt": [ + "FargateCluster7CCD5F93", + "Arn" + ] + }, + "\",\"TaskDefinition\":\"", + { + "Ref": "TaskDef54694570" + }, + "\",\"NetworkConfiguration\":{\"AwsvpcConfiguration\":{\"AssignPublicIp\":\"ENABLED\",\"Subnets\":[\"subnet-e19455ca\",\"subnet-e0c24797\",\"subnet-ccd77395\"],\"SecurityGroups\":[\"", + { + "Fn::GetAtt": [ + "RunFargateSecurityGroup709740F2", + "GroupId" + ] + }, + "\"]}},\"Overrides\":{\"ContainerOverrides\":[{\"Name\":\"TheContainer\",\"Environment\":[{\"Name\":\"SOME_KEY\",\"Value.$\":\"$.SomeKey\"}]}]},\"LaunchType\":\"FARGATE\"},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::ecs:runTask.sync\"}}}" + ] + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + } + } + } + }, + "Parameters": { + "EventImageImageNameE972A8B1": { + "Type": "String", + "Description": "ECR repository name and tag asset \"aws-ecs-integ2/EventImage\"" + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6": { + "Type": "String", + "Description": "S3 bucket for asset \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276": { + "Type": "String", + "Description": "S3 key for asset version \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.ts new file mode 100644 index 0000000000000..214a9394344f1 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.ts @@ -0,0 +1,50 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import sfn = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); +import path = require('path'); +import ecs = require('../../lib'); + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ2'); + +const vpc = ec2.VpcNetwork.importFromContext(stack, 'Vpc', { + isDefault: true +}); + +const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); + +// Build task definition +const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef', { + memoryMiB: '512', + cpu: '256' +}); +taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, '..', 'eventhandler-image') }), + memoryLimitMiB: 256, + logging: new ecs.AwsLogDriver(stack, 'TaskLogging', { streamPrefix: 'EventDemo' }) +}); + +// Build state machine +const definition = new sfn.Pass(stack, 'Start', { + result: { SomeKey: 'SomeValue' } +}).next(new ecs.FargateRunTask(stack, 'RunFargate', { + cluster, taskDefinition, + assignPublicIp: true, + containerOverrides: [ + { + containerName: 'TheContainer', + environment: [ + { + name: 'SOME_KEY', + valuePath: '$.SomeKey' + } + ] + } + ] +})); + +new sfn.StateMachine(stack, 'StateMachine', { + definition, +}); + +app.run(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/lib/policy-document.ts b/packages/@aws-cdk/aws-iam/lib/policy-document.ts index 3a30676b18a59..3b49cfcc908d5 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-document.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-document.ts @@ -391,6 +391,10 @@ export class PolicyStatement extends cdk.Token { return undefined; } + if (cdk.unresolved(values)) { + return values; + } + if (Array.isArray(values)) { if (!values || values.length === 0) { return undefined; diff --git a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts index 97eeef42f95aa..1b95e0bae0778 100644 --- a/packages/@aws-cdk/aws-iam/test/test.policy-document.ts +++ b/packages/@aws-cdk/aws-iam/test/test.policy-document.ts @@ -258,6 +258,22 @@ export = { test.done(); }, + 'addResources() will not break a list-encoded Token'(test: Test) { + const stack = new Stack(); + + const statement = new PolicyStatement() + .addActions(...new Token(() => ['a', 'b', 'c']).toList()) + .addResources(...new Token(() => ['x', 'y', 'z']).toList()); + + test.deepEqual(stack.node.resolve(statement), { + Effect: 'Allow', + Action: ['a', 'b', 'c'], + Resource: ['x', 'y', 'z'], + }); + + test.done(); + }, + 'addCanonicalUserPrincipal can be used to add cannonical user principals'(test: Test) { const stack = new Stack(); const p = new PolicyDocument(); diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 365435afea4b6..0801d55024773 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -348,6 +348,7 @@ const task = new sns.PublishTask(this, 'Publish', { topic, message: 'A message to send to the queue' }); +``` ### Sending a message to an SQS queue @@ -367,6 +368,28 @@ const task = new sns.SendMessageTask(this, 'Send', { }); ``` +### Running an ECS task + +Use the `FargateRunTask` or `Ec2RunTask` tasks in the ECS library: + +```ts +const task = new ecs.FargateRunTask(stack, 'RunFargate', { + cluster, + taskDefinition, + containerOverrides: [ + { + containerName: 'TheContainer', + environment: [ + { + name: 'CONTAINER_INPUT', + valuePath: '$.valueFromStateData' + } + ] + } + ] +})); +``` + ## State Machine Fragments It is possible to define reusable (or abstracted) mini-state machines by diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index 3d5aeb6c10981..b5c112024c134 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -153,20 +153,10 @@ export class Task extends State implements INextable { * Return the Amazon States Language object for this state */ public toStateJson(): object { - const inOutParams = this.renderInputOutput(); - - // Mix resource-defined and user-supplied Parameters, user wins. - let Parameters; - if (this.resourceProps.parameters || inOutParams.Parameters) { - Parameters = this.resourceProps.parameters || {}; - cdk.deepMerge(Parameters, inOutParams.Parameters || {}); - } - return { ...this.renderNextEnd(), ...this.renderRetryCatch(), - ...inOutParams, - Parameters, + ...this.renderInputOutput(), Type: StateType.Task, Comment: this.comment, Resource: this.resourceProps.resourceArn, @@ -313,15 +303,6 @@ export interface StepFunctionsTaskResourceProps { */ policyStatements?: iam.PolicyStatement[]; - /** - * Parameters to pass to the Resource - * - * Will be merged with parameters that the user supplies. - * - * @default No parameters - */ - parameters?: {[key: string]: any}; - /** * Prefix for singular metric names of activity actions * diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts index 41dd3f93296c5..ee1002c63b835 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts @@ -680,34 +680,6 @@ export = { test.done(); }, }, - - 'Task Parameters': { - 'Combine task and resource parameters, user wins'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const task = new stepfunctions.Task(stack, 'Task', { - resource: new FakeResource({ param1: 'Value1', param3: 'ResourceValue' }), - parameters: { - param2: 'Value2', - param3: 'UserValue', - } - }); - - test.deepEqual(render(task), { - StartAt: 'Task', - States: { - Task: { - End: true, - Parameters: { param1: 'Value1', param2: 'Value2', param3: 'UserValue' }, - Type: 'Task', - Resource: 'resource' - } - } - }); - - test.done(); - }, - }, }; class ReusableStateMachine extends stepfunctions.StateMachineFragment { @@ -749,14 +721,8 @@ class SimpleChain extends stepfunctions.StateMachineFragment { } class FakeResource implements stepfunctions.IStepFunctionsTaskResource { - constructor(private readonly parameters?: {[key: string]: any}) { - } - public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { - return { - resourceArn: 'resource', - parameters: this.parameters, - }; + return { resourceArn: 'resource' }; } } From a4e74e8bdda10a64983f81d3608e4a1823540f9f Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 31 Jan 2019 15:15:58 +0100 Subject: [PATCH 05/21] Make FunctionTask and ActivityTask to match new naming pattern --- .../@aws-cdk/aws-lambda/lib/function-task.ts | 28 +++ packages/@aws-cdk/aws-lambda/lib/index.ts | 1 + packages/@aws-cdk/aws-stepfunctions/README.md | 160 +++++++++--------- .../aws-stepfunctions/lib/activity-task.ts | 40 +++++ .../aws-stepfunctions/lib/activity.ts | 14 +- .../@aws-cdk/aws-stepfunctions/lib/index.ts | 1 + .../aws-stepfunctions/lib/state-machine.ts | 8 + .../aws-stepfunctions/lib/states/task.ts | 18 ++ .../@aws-cdk/aws-stepfunctions/package.json | 5 +- 9 files changed, 192 insertions(+), 83 deletions(-) create mode 100644 packages/@aws-cdk/aws-lambda/lib/function-task.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions/lib/activity-task.ts diff --git a/packages/@aws-cdk/aws-lambda/lib/function-task.ts b/packages/@aws-cdk/aws-lambda/lib/function-task.ts new file mode 100644 index 0000000000000..1d259110e6866 --- /dev/null +++ b/packages/@aws-cdk/aws-lambda/lib/function-task.ts @@ -0,0 +1,28 @@ +import stepfunctions = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); +import { IFunction } from './lambda-ref'; + +/** + * Properties for FunctionTask + */ +export interface FunctionTaskProps extends stepfunctions.BasicTaskProps { + /** + * The function to run + */ + function: IFunction; +} + +/** + * A StepFunctions Task to invoke a Lambda function. + * + * A Function can be used directly as a Resource, but this class mirrors + * integration with other AWS services via a specific class instance. + */ +export class FunctionTask extends stepfunctions.Task { + constructor(scope: cdk.Construct, id: string, props: FunctionTaskProps) { + super(scope, id, { + ...props, + resource: props.function, + }); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/index.ts b/packages/@aws-cdk/aws-lambda/lib/index.ts index dd5d5fd46e668..da1b542c38dc7 100644 --- a/packages/@aws-cdk/aws-lambda/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/index.ts @@ -7,6 +7,7 @@ export * from './pipeline-action'; export * from './runtime'; export * from './code'; export * from './lambda-version'; +export * from './function-task'; export * from './singleton-lambda'; export * from './event-source'; export * from './event-source-mapping'; diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 0801d55024773..413b36be87009 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -11,16 +11,16 @@ example](https://docs.aws.amazon.com/step-functions/latest/dg/job-status-poller- const submitLambda = new lambda.Function(this, 'SubmitLambda', { ... }); const getStatusLambda = new lambda.Function(this, 'CheckLambda', { ... }); -const submitJob = new stepfunctions.Task(this, 'Submit Job', { - resource: submitLambda, +const submitJob = new lambda.FunctionTask(this, 'Submit Job', { + function: submitLambda, // Put Lambda's result here in the execution's state object resultPath: '$.guid', }); const waitX = new stepfunctions.Wait(this, 'Wait X Seconds', { secondsPath: '$.wait_time' }); -const getStatus = new stepfunctions.Task(this, 'Get Job Status', { - resource: getStatusLambda, +const getStatus = new lambda.FunctionTask(this, 'Get Job Status', { + function: getStatusLambda, // Pass just the field named "guid" into the Lambda, put the // Lambda's result in a field called "status" inputPath: '$.guid', @@ -32,8 +32,8 @@ const jobFailed = new stepfunctions.Fail(this, 'Job Failed', { error: 'DescribeJob returned FAILED', }); -const finalStatus = new stepfunctions.Task(this, 'Get Final Job Status', { - resource: getStatusLambda, +const finalStatus = new lambda.FunctionTask(this, 'Get Final Job Status', { + function: getStatusLambda, // Use "guid" field as input, output of the Lambda becomes the // entire state machine output. inputPath: '$.guid', @@ -67,9 +67,9 @@ var getStatusLambda = new Function(this, "CheckLambda", new FunctionProps // ... }); -var submitJob = new Task(this, "Submit Job", new TaskProps +var submitJob = new FunctionTask(this, "Submit Job", new TaskProps { - Resource = submitLambda, + Function = submitLambda, ResultPath = "$.guid" }); @@ -78,9 +78,9 @@ var waitX = new Wait(this, "Wait X Seconds", new WaitProps SecondsPath = "$.wait_time" }); -var getStatus = new Task(this, "Get Job Status", new TaskProps +var getStatus = new FunctionTask(this, "Get Job Status", new TaskProps { - Resource = getStatusLambda, + Function = getStatusLambda, InputPath = "$.guid", ResultPath = "$.status" }); @@ -91,9 +91,9 @@ var jobFailed = new Fail(this, "Job Failed", new FailProps Error = "DescribeJob returned FAILED" }); -var finalStatus = new Task(this, "Get Final Job Status", new TaskProps +var finalStatus = new FunctionTask(this, "Get Final Job Status", new TaskProps { - Resource = getStatusLambda, + Function = getStatusLambda, // Use "guid" field as input, output of the Lambda becomes the // entire state machine output. InputPath = "$.guid" @@ -140,7 +140,7 @@ This library comes with a set of classes that model the [Amazon States Language](https://states-language.net/spec.html). The following State classes are supported: -* `Task` +* `Task` (with subclasses for every specific task, see below) * `Pass` * `Wait` * `Choice` @@ -154,13 +154,21 @@ information, see the States Language spec. ### Task -A `Task` represents some work that needs to be done. It takes a `resource` -property that is either a Lambda `Function` or a Step Functions `Activity` -(A Lambda Function runs your task's code on AWS Lambda, whereas an `Activity` -is used to run your task's code on an arbitrary compute fleet you manage). +A `Task` represents some work that needs to be done. There are specific +subclasses of `Task` which represent integrations with other AWS services +that Step Functions supports. For example: + +* `lambda.FunctionTask` -- call a Lambda Function +* `stepfunctions.ActivityTask` -- start an Activity (Activities represent a work + queue that you poll on a compute fleet you manage yourself) +* `sns.PublishTask` -- publish a message to an SNS topic +* `sqs.SendMessageTask` -- send a message to an SQS queue +* `ecs.FargateRunTask`/`ecs.Ec2RunTask` -- run a container task + +#### Lambda example ```ts -const task = new stepfunctions.Task(this, 'Invoke The Lambda', { +const task = new lambda.FunctionTask(this, 'Invoke The Lambda', { resource: myLambda, inputPath: '$.input', timeoutSeconds: 300, @@ -179,6 +187,60 @@ task.addCatch(errorHandlerState); task.next(nextState); ``` +#### SNS example + +```ts +import sns = require('@aws-cdk/aws-sns'); + +// ... + +const topic = new sns.Topic(this, 'Topic'); +const task = new sns.PublishTask(this, 'Publish', { + topic, + message: 'A message to send to the queue' +}); +``` + +#### SQS example + +```ts +import sqs = require('@aws-cdk/aws-sqs'); + +// ... + +const queue = new sns.Queue(this, 'Queue'); +const task = new sns.SendMessageTask(this, 'Send', { + queue, + messageBodyPath: '$.message', + // Only for FIFO queues + messageGroupId: '$.messageGroupId', +}); +``` + +#### ECS example + +```ts +import ecs = require('@aws-cdk/aws-ecs'); + +// See examples in ECS library for initialization of 'cluster' and 'taskDefinition' + +const task = new ecs.FargateRunTask(stack, 'RunFargate', { + cluster, + taskDefinition, + containerOverrides: [ + { + containerName: 'TheContainer', + environment: [ + { + name: 'CONTAINER_INPUT', + valuePath: '$.valueFromStateData' + } + ] + } + ] +})); +``` + ### Pass A `Pass` state does no work, but it can optionally transform the execution's @@ -327,68 +389,6 @@ const definition = stepfunctions.Chain // ... ``` -## Service Integrations - -Some packages contain specialized subclasses of `Task` that can be used to -invoke those services as part of a workflow. The same effect can be -achieved by using `Task` directly, but these subclasses usually make it -easier. - -### Sending a message to an SNS topic - -Use the `PublishTask` task in the SNS library: - -```ts -import sns = require('@aws-cdk/aws-sns'); - -// ... - -const topic = new sns.Topic(this, 'Topic'); -const task = new sns.PublishTask(this, 'Publish', { - topic, - message: 'A message to send to the queue' -}); -``` - -### Sending a message to an SQS queue - -Use the `SendMessageTask` task in the SQS library: - -```ts -import sqs = require('@aws-cdk/aws-sqs'); - -// ... - -const queue = new sns.Queue(this, 'Queue'); -const task = new sns.SendMessageTask(this, 'Send', { - queue, - messageBodyPath: '$.message', - // Only for FIFO queues - messageGroupId: '$.messageGroupId', -}); -``` - -### Running an ECS task - -Use the `FargateRunTask` or `Ec2RunTask` tasks in the ECS library: - -```ts -const task = new ecs.FargateRunTask(stack, 'RunFargate', { - cluster, - taskDefinition, - containerOverrides: [ - { - containerName: 'TheContainer', - environment: [ - { - name: 'CONTAINER_INPUT', - valuePath: '$.valueFromStateData' - } - ] - } - ] -})); -``` ## State Machine Fragments diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity-task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity-task.ts new file mode 100644 index 0000000000000..23efebcf3776b --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity-task.ts @@ -0,0 +1,40 @@ +import cdk = require('@aws-cdk/cdk'); +import { IActivity } from './activity'; +import { BasicTaskProps, Task } from './states/task'; + +/** + * Properties for FunctionTask + */ +export interface ActivityTaskProps extends BasicTaskProps { + /** + * The function to run + */ + activity: IActivity; + + /** + * Maximum time between heart beats + * + * If the time between heart beats takes longer than this, a 'Timeout' error is raised. + * + * This is only relevant when using an Activity type as resource. + * + * @default No heart beat timeout + */ + heartbeatSeconds?: number; +} + +/** + * A StepFunctions Task to invoke a Lambda function. + * + * A Function can be used directly as a Resource, but this class mirrors + * integration with other AWS services via a specific class instance. + */ +export class ActivityTask extends Task { + constructor(scope: cdk.Construct, id: string, props: ActivityTaskProps) { + super(scope, id, { + ...props, + resource: props.activity, + heartbeatSeconds: props.heartbeatSeconds + }); + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index ef0fe1b9db326..ebefef3b30f07 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -15,7 +15,7 @@ export interface ActivityProps { /** * Define a new StepFunctions activity */ -export class Activity extends cdk.Construct implements IStepFunctionsTaskResource { +export class Activity extends cdk.Construct implements IActivity { public readonly activityArn: string; public readonly activityName: string; @@ -144,3 +144,15 @@ export class Activity extends cdk.Construct implements IStepFunctionsTaskResourc return name; } } + +export interface IActivity extends cdk.IConstruct, IStepFunctionsTaskResource { + /** + * The ARN of the activity + */ + readonly activityArn: string; + + /** + * The name of the activity + */ + readonly activityName: string; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions/lib/index.ts index 8cf9837545852..497758663dad4 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/index.ts @@ -1,4 +1,5 @@ export * from './activity'; +export * from './activity-task'; export * from './types'; export * from './condition'; export * from './state-machine'; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index 7744cc7e2e914..77f5330e2fb3f 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -209,6 +209,11 @@ export interface IStateMachine extends cdk.IConstruct { */ readonly stateMachineArn: string; + /** + * The name of the state machine + */ + readonly stateMachineName: string; + /** * Export this state machine */ @@ -227,9 +232,12 @@ export interface StateMachineImportProps { class ImportedStateMachine extends cdk.Construct implements IStateMachine { public readonly stateMachineArn: string; + public readonly stateMachineName: string; + constructor(scope: cdk.Construct, id: string, private readonly props: StateMachineImportProps) { super(scope, id); this.stateMachineArn = props.stateMachineArn; + this.stateMachineName = cdk.Stack.find(this).parseArn(props.stateMachineArn).resourceName!; } public export() { diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index b5c112024c134..9513b9380c3bf 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -323,4 +323,22 @@ export interface StepFunctionsTaskResourceProps { * @default No metrics */ metricDimensions?: cloudwatch.DimensionHash; +} + +/** + * Generic Task Resource + * + * Can be used to integrate with resource types for which there is no + * specialized CDK class available yet. + */ +export class GenericTaskResource implements IStepFunctionsTaskResource { + constructor(private readonly arn: string, private readonly policyStatements?: iam.PolicyStatement[]) { + } + + public asStepFunctionsTaskResource(_callingTask: Task): StepFunctionsTaskResourceProps { + return { + resourceArn: this.arn, + policyStatements: this.policyStatements + }; + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/package.json b/packages/@aws-cdk/aws-stepfunctions/package.json index ada893e0378e4..a5ead666bad93 100644 --- a/packages/@aws-cdk/aws-stepfunctions/package.json +++ b/packages/@aws-cdk/aws-stepfunctions/package.json @@ -78,7 +78,8 @@ }, "awslint": { "exclude": [ - "resource-attribute:@aws-cdk/aws-stepfunctions.IStateMachine.stateMachineName" + "import-props-interface:@aws-cdk/aws-stepfunctions.ActivityImportProps", + "export:@aws-cdk/aws-stepfunctions.IActivity" ] } -} \ No newline at end of file +} From 29c6e0c19335e25ff1a028157cf0f7e79e7325d4 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 23 Apr 2019 16:09:35 +0200 Subject: [PATCH 06/21] WIP --- packages/@aws-cdk/assert/jest.ts | 39 + packages/@aws-cdk/assert/package-lock.json | 4475 +++++++++++++++ packages/@aws-cdk/assert/package.json | 2 + .../aws-ecs/lib/base/task-definition.ts | 51 +- .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 3 +- .../aws-ecs/lib/fargate/fargate-service.ts | 3 +- packages/@aws-cdk/aws-ecs/lib/index.ts | 3 - packages/@aws-cdk/aws-ecs/lib/util.ts | 9 - packages/@aws-cdk/aws-ecs/package.json | 6 +- .../@aws-cdk/aws-lambda/lib/function-base.ts | 16 +- .../@aws-cdk/aws-lambda/lib/function-task.ts | 28 - packages/@aws-cdk/aws-lambda/lib/index.ts | 1 - packages/@aws-cdk/aws-lambda/package.json | 2 - packages/@aws-cdk/aws-sns/lib/index.ts | 1 - packages/@aws-cdk/aws-sns/package.json | 2 - .../aws-sns/test/test.publish-task.ts | 30 - packages/@aws-cdk/aws-sqs/lib/index.ts | 1 - packages/@aws-cdk/aws-sqs/package.json | 4 +- .../aws-sqs/test/test.send-message-task.ts | 32 - .../aws-stepfunctions-tasks/.gitignore | 16 + .../aws-stepfunctions-tasks/.npmignore | 17 + .../@aws-cdk/aws-stepfunctions-tasks/LICENSE | 201 + .../@aws-cdk/aws-stepfunctions-tasks/NOTICE | 2 + .../aws-stepfunctions-tasks/README.md | 3 + .../lib}/base-run-task.ts | 90 +- .../lib}/ec2-run-task.ts | 17 +- .../lib}/fargate-run-task.ts | 9 +- .../aws-stepfunctions-tasks/lib/index.ts | 7 + .../lib/invoke-activity.ts | 142 + .../lib/invoke-function.ts | 137 + .../lib/nested-state-machine.ts | 3 + .../lib/publish-to-topic.ts} | 24 +- .../lib/send-to-queue.ts} | 26 +- .../aws-stepfunctions-tasks/package-lock.json | 5064 +++++++++++++++++ .../aws-stepfunctions-tasks/package.json | 105 + .../test/invoke-activity.test.ts | 64 + .../test/publish-to-topic.test.ts | 26 + .../test/send-to-queue.test.ts | 28 + packages/@aws-cdk/aws-stepfunctions/README.md | 27 +- .../aws-stepfunctions/lib/activity-task.ts | 40 - .../aws-stepfunctions/lib/activity.ts | 17 +- .../@aws-cdk/aws-stepfunctions/lib/index.ts | 1 - .../aws-stepfunctions/lib/states/task.ts | 194 +- .../test/integ.job-poller.ts | 6 +- .../aws-stepfunctions/test/test.activity.ts | 28 - .../aws-stepfunctions/test/test.metrics.ts | 44 - .../test/test.state-machine-resources.ts | 57 +- .../test/test.states-language.ts | 36 +- 48 files changed, 10517 insertions(+), 622 deletions(-) create mode 100644 packages/@aws-cdk/assert/jest.ts delete mode 100644 packages/@aws-cdk/aws-ecs/lib/util.ts delete mode 100644 packages/@aws-cdk/aws-lambda/lib/function-task.ts delete mode 100644 packages/@aws-cdk/aws-sns/test/test.publish-task.ts delete mode 100644 packages/@aws-cdk/aws-sqs/test/test.send-message-task.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/LICENSE create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/NOTICE create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/README.md rename packages/@aws-cdk/{aws-ecs/lib/base => aws-stepfunctions-tasks/lib}/base-run-task.ts (85%) rename packages/@aws-cdk/{aws-ecs/lib/ec2 => aws-stepfunctions-tasks/lib}/ec2-run-task.ts (89%) rename packages/@aws-cdk/{aws-ecs/lib/fargate => aws-stepfunctions-tasks/lib}/fargate-run-task.ts (83%) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/nested-state-machine.ts rename packages/@aws-cdk/{aws-sns/lib/publish-task.ts => aws-stepfunctions-tasks/lib/publish-to-topic.ts} (84%) rename packages/@aws-cdk/{aws-sqs/lib/send-message-task.ts => aws-stepfunctions-tasks/lib/send-to-queue.ts} (86%) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/package.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts delete mode 100644 packages/@aws-cdk/aws-stepfunctions/lib/activity-task.ts delete mode 100644 packages/@aws-cdk/aws-stepfunctions/test/test.metrics.ts diff --git a/packages/@aws-cdk/assert/jest.ts b/packages/@aws-cdk/assert/jest.ts new file mode 100644 index 0000000000000..d848d6fb70d4f --- /dev/null +++ b/packages/@aws-cdk/assert/jest.ts @@ -0,0 +1,39 @@ +import { Stack } from "@aws-cdk/cdk"; +import { SynthesizedStack } from "@aws-cdk/cx-api"; +import { haveResource, ResourcePart } from "./lib/assertions/have-resource"; +import { expect as ourExpect } from './lib/expect'; + +declare global { + namespace jest { + interface Matchers { + toHaveResource(resourceType: string, + properties?: any, + comparison?: ResourcePart, + allowValueExtension?: boolean): R; + } + } +} + +expect.extend({ + toHaveResource(actual: SynthesizedStack | Stack, + resourceType: string, + properties?: any, + comparison?: ResourcePart, + allowValueExtension: boolean = false) { + + const assertion = haveResource(resourceType, properties, comparison, allowValueExtension); + const inspector = ourExpect(actual); + const pass = assertion.assertUsing(inspector); + if (pass) { + return { + pass, + message: `Expected ${JSON.stringify(inspector.value, null, 2)} not to match ${assertion.description}`, + }; + } else { + return { + pass, + message: `Expected ${JSON.stringify(inspector.value, null, 2)} to match ${assertion.description}`, + }; + } + } +}); \ No newline at end of file diff --git a/packages/@aws-cdk/assert/package-lock.json b/packages/@aws-cdk/assert/package-lock.json index a100d81037d21..df90c4de00875 100644 --- a/packages/@aws-cdk/assert/package-lock.json +++ b/packages/@aws-cdk/assert/package-lock.json @@ -4,16 +4,3900 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.3.tgz", + "integrity": "sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helpers": "^7.4.3", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "@babel/generator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", + "requires": { + "@babel/types": "^7.4.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", + "requires": { + "@babel/types": "^7.4.0" + } + }, + "@babel/helpers": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.3.tgz", + "integrity": "sha512-BMh7X0oZqb36CfyhvtbSmcWc3GXocfxv3yNsAEuM0l+fAqSO22rQrUpijr3oE/10jCTrB6/0b9kzmG4VetCj8Q==", + "requires": { + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz", + "integrity": "sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==" + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", + "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/traverse": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz", + "integrity": "sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/types": "^7.4.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "@babel/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "@cnakazawa/watch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", + "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@jest/console": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", + "integrity": "sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==", + "requires": { + "@jest/source-map": "^24.3.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + } + }, + "@jest/core": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.7.1.tgz", + "integrity": "sha512-ivlZ8HX/FOASfHcb5DJpSPFps8ydfUYzLZfgFFqjkLijYysnIEOieg72YRhO4ZUB32xu40hsSMmaw+IGYeKONA==", + "requires": { + "@jest/console": "^24.7.1", + "@jest/reporters": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-changed-files": "^24.7.0", + "jest-config": "^24.7.1", + "jest-haste-map": "^24.7.1", + "jest-message-util": "^24.7.1", + "jest-regex-util": "^24.3.0", + "jest-resolve-dependencies": "^24.7.1", + "jest-runner": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-snapshot": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "jest-watcher": "^24.7.1", + "micromatch": "^3.1.10", + "p-each-series": "^1.0.0", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "rimraf": "^2.5.4", + "strip-ansi": "^5.0.0" + } + }, + "@jest/environment": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.7.1.tgz", + "integrity": "sha512-wmcTTYc4/KqA+U5h1zQd5FXXynfa7VGP2NfF+c6QeGJ7c+2nStgh65RQWNX62SC716dTtqheTRrZl0j+54oGHw==", + "requires": { + "@jest/fake-timers": "^24.7.1", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "jest-mock": "^24.7.0" + } + }, + "@jest/fake-timers": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.7.1.tgz", + "integrity": "sha512-4vSQJDKfR2jScOe12L9282uiwuwQv9Lk7mgrCSZHA9evB9efB/qx8i0KJxsAKtp8fgJYBJdYY7ZU6u3F4/pyjA==", + "requires": { + "@jest/types": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-mock": "^24.7.0" + } + }, + "@jest/reporters": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.7.1.tgz", + "integrity": "sha512-bO+WYNwHLNhrjB9EbPL4kX/mCCG4ZhhfWmO3m4FSpbgr7N83MFejayz30kKjgqr7smLyeaRFCBQMbXpUgnhAJw==", + "requires": { + "@jest/environment": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-api": "^2.1.1", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-source-maps": "^3.0.1", + "jest-haste-map": "^24.7.1", + "jest-resolve": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-util": "^24.7.1", + "jest-worker": "^24.6.0", + "node-notifier": "^5.2.1", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + } + }, + "@jest/source-map": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz", + "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==", + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.7.1.tgz", + "integrity": "sha512-3U7wITxstdEc2HMfBX7Yx3JZgiNBubwDqQMh+BXmZXHa3G13YWF3p6cK+5g0hGkN3iufg/vGPl3hLxQXD74Npg==", + "requires": { + "@jest/console": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/test-sequencer": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.7.1.tgz", + "integrity": "sha512-84HQkCpVZI/G1zq53gHJvSmhUer4aMYp9tTaffW28Ih5OxfCg8hGr3nTSbL1OhVDRrFZwvF+/R9gY6JRkDUpUA==", + "requires": { + "@jest/test-result": "^24.7.1", + "jest-haste-map": "^24.7.1", + "jest-runner": "^24.7.1", + "jest-runtime": "^24.7.1" + } + }, + "@jest/transform": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.7.1.tgz", + "integrity": "sha512-EsOUqP9ULuJ66IkZQhI5LufCHlTbi7hrcllRMUEV/tOgqBVQi93+9qEvkX0n8mYpVXQ8VjwmICeRgg58mrtIEw==", + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.7.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.7.1", + "jest-regex-util": "^24.3.0", + "jest-util": "^24.7.1", + "micromatch": "^3.1.10", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + } + }, + "@jest/types": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.7.0.tgz", + "integrity": "sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/yargs": "^12.0.9" + } + }, + "@types/babel__core": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.1.tgz", + "integrity": "sha512-+hjBtgcFPYyCTo0A15+nxrCVJL7aC6Acg87TXd5OW3QhHswdrOLoles+ldL2Uk8q++7yIfl4tURtztccdeeyOw==", + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz", + "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==", + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.6.tgz", + "integrity": "sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==", + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" + }, + "@types/jest": { + "version": "24.0.11", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.11.tgz", + "integrity": "sha512-2kLuPC5FDnWIDvaJBzsGTBQaBbnDweznicvK7UGYzlIJP4RJR2a4A/ByLUXEyEgag6jz8eHdlWExGDtH3EYUXQ==", + "dev": true, + "requires": { + "@types/jest-diff": "*" + } + }, + "@types/jest-diff": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz", + "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==", + "dev": true + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" + }, + "@types/yargs": { + "version": "12.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", + "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==" + }, + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + }, + "acorn-globals": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", + "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==" + } + } + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==" + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==" + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "requires": { + "lodash": "^4.17.11" + } + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "babel-jest": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.7.1.tgz", + "integrity": "sha512-GPnLqfk8Mtt0i4OemjWkChi73A3ALs4w2/QbG64uAj8b5mmwzxc7jbJVRZt8NJkxi6FopVHog9S3xX6UJKb2qg==", + "requires": { + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.6.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.3.tgz", + "integrity": "sha512-IFyehbvRRwdBlI1lDp+FaMsWNnEndEk7065IB8NhzBX+ZKLPwPodgk4I5Gobw/8SNUUzso2Dv3hbqRh88eiSCQ==", + "requires": { + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.2.0", + "test-exclude": "^5.2.2" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz", + "integrity": "sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==", + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", + "integrity": "sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==", + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.6.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=" + } + } + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "requires": { + "node-int64": "^0.4.0" + } + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "requires": { + "rsvp": "^4.8.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "optional": true + }, + "compare-versions": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", + "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==" + }, + "cssstyle": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", + "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", + "requires": { + "cssom": "0.3.x" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "requires": { + "strip-bom": "^3.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=" + }, + "diff-sequences": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz", + "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==" + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "escodegen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + }, + "exec-sh": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", + "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==" + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expect": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.7.1.tgz", + "integrity": "sha512-mGfvMTPduksV3xoI0xur56pQsg2vJjNf5+a+bXOjqCkiCBbmCayrBbHS/75y9K430cfqyocPr2ZjiNiRx4SRKw==", + "requires": { + "@jest/types": "^24.7.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.3.0", + "jest-matcher-utils": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-regex-util": "^24.3.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "requires": { + "bser": "^2.0.0" + } + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz", + "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==", + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==" + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + }, + "handlebars": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-api": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.5.tgz", + "integrity": "sha512-meYk1BwDp59Pfse1TvPrkKYgVqAufbdBLEVoqvu/hLLKSaQ054ZTksbNepyc223tMnWdm6AdK2URIJJRqdP87g==", + "requires": { + "async": "^2.6.1", + "compare-versions": "^3.2.1", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.4", + "istanbul-lib-hook": "^2.0.6", + "istanbul-lib-instrument": "^3.2.0", + "istanbul-lib-report": "^2.0.7", + "istanbul-lib-source-maps": "^3.0.5", + "istanbul-reports": "^2.2.3", + "js-yaml": "^3.13.0", + "make-dir": "^2.1.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-LXTBICkMARVgo579kWDm8SqfB6nvSDKNqIOBEjmJRnL04JvoMHCYGWaMddQnseJYtkEuEvO/sIcOxPLk9gERug==" + }, + "istanbul-lib-hook": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.6.tgz", + "integrity": "sha512-829DKONApZ7UCiPXcOYWSgkFXa4+vNYoNOt3F+4uDJLKL1OotAoVwvThoEj1i8jmOj7odbYcR3rnaHu+QroaXg==", + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.2.0.tgz", + "integrity": "sha512-06IM3xShbNW4NgZv5AP4QH0oHqf1/ivFo8eFys0ZjPXHGldHJQWb3riYOKXqmOqfxXBfxu4B+g/iuhOPZH0RJg==", + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.4", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==" + } + } + }, + "istanbul-lib-report": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.7.tgz", + "integrity": "sha512-wLH6beJBFbRBLiTlMOBxmb85cnVM1Vyl36N48e4e/aTKSM3WbOx7zbVIH1SQ537fhhsPbX0/C5JB4qsmyRXXyA==", + "requires": { + "istanbul-lib-coverage": "^2.0.4", + "make-dir": "^2.1.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.5.tgz", + "integrity": "sha512-eDhZ7r6r1d1zQPVZehLc3D0K14vRba/eBYkz3rw16DLOrrTzve9RmnkcwrrkWVgO1FL3EK5knujVe5S8QHE9xw==", + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.4", + "make-dir": "^2.1.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "istanbul-reports": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.3.tgz", + "integrity": "sha512-T6EbPuc8Cb620LWAYyZ4D8SSn06dY9i1+IgUX2lTH8gbwflMc9Obd33zHTyNX653ybjpamAHS9toKS3E6cGhTw==", + "requires": { + "handlebars": "^4.1.0" + } + }, + "jest": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.7.1.tgz", + "integrity": "sha512-AbvRar5r++izmqo5gdbAjTeA6uNRGoNRuj5vHB0OnDXo2DXWZJVuaObiGgtlvhKb+cWy2oYbQSfxv7Q7GjnAtA==", + "requires": { + "import-local": "^2.0.0", + "jest-cli": "^24.7.1" + }, + "dependencies": { + "jest-cli": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.7.1.tgz", + "integrity": "sha512-32OBoSCVPzcTslGFl6yVCMzB2SqX3IrWwZCY5mZYkb0D2WsogmU3eV2o8z7+gRQa4o4sZPX/k7GU+II7CxM6WQ==", + "requires": { + "@jest/core": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "jest-config": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "prompts": "^2.0.1", + "realpath-native": "^1.1.0", + "yargs": "^12.0.2" + } + } + } + }, + "jest-changed-files": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.7.0.tgz", + "integrity": "sha512-33BgewurnwSfJrW7T5/ZAXGE44o7swLslwh8aUckzq2e17/2Os1V0QU506ZNik3hjs8MgnEMKNkcud442NCDTw==", + "requires": { + "@jest/types": "^24.7.0", + "execa": "^1.0.0", + "throat": "^4.0.0" + } + }, + "jest-config": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.7.1.tgz", + "integrity": "sha512-8FlJNLI+X+MU37j7j8RE4DnJkvAghXmBWdArVzypW6WxfGuxiL/CCkzBg0gHtXhD2rxla3IMOSUAHylSKYJ83g==", + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^24.7.1", + "@jest/types": "^24.7.0", + "babel-jest": "^24.7.1", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.7.1", + "jest-environment-node": "^24.7.1", + "jest-get-type": "^24.3.0", + "jest-jasmine2": "^24.7.1", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.7.0", + "realpath-native": "^1.1.0" + } + }, + "jest-diff": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.7.0.tgz", + "integrity": "sha512-ULQZ5B1lWpH70O4xsANC4tf4Ko6RrpwhE3PtG6ERjMg1TiYTC2Wp4IntJVGro6a8HG9luYHhhmF4grF0Pltckg==", + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.3.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.7.0" + } + }, + "jest-docblock": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz", + "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==", + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.7.1.tgz", + "integrity": "sha512-4fsS8fEfLa3lfnI1Jw6NxjhyRTgfpuOVTeUZZFyVYqeTa4hPhr2YkToUhouuLTrL2eMGOfpbdMyRx0GQ/VooKA==", + "requires": { + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.3.0", + "jest-util": "^24.7.1", + "pretty-format": "^24.7.0" + } + }, + "jest-environment-jsdom": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.7.1.tgz", + "integrity": "sha512-Gnhb+RqE2JuQGb3kJsLF8vfqjt3PHKSstq4Xc8ic+ax7QKo4Z0RWGucU3YV+DwKR3T9SYc+3YCUQEJs8r7+Jxg==", + "requires": { + "@jest/environment": "^24.7.1", + "@jest/fake-timers": "^24.7.1", + "@jest/types": "^24.7.0", + "jest-mock": "^24.7.0", + "jest-util": "^24.7.1", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.7.1.tgz", + "integrity": "sha512-GJJQt1p9/C6aj6yNZMvovZuxTUd+BEJprETdvTKSb4kHcw4mFj8777USQV0FJoJ4V3djpOwA5eWyPwfq//PFBA==", + "requires": { + "@jest/environment": "^24.7.1", + "@jest/fake-timers": "^24.7.1", + "@jest/types": "^24.7.0", + "jest-mock": "^24.7.0", + "jest-util": "^24.7.1" + } + }, + "jest-get-type": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", + "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==" + }, + "jest-haste-map": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.7.1.tgz", + "integrity": "sha512-g0tWkzjpHD2qa03mTKhlydbmmYiA2KdcJe762SbfFo/7NIMgBWAA0XqQlApPwkWOF7Cxoi/gUqL0i6DIoLpMBw==", + "requires": { + "@jest/types": "^24.7.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.4.0", + "jest-util": "^24.7.1", + "jest-worker": "^24.6.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.7.1.tgz", + "integrity": "sha512-Y/9AOJDV1XS44wNwCaThq4Pw3gBPiOv/s6NcbOAkVRRUEPu+36L2xoPsqQXsDrxoBerqeyslpn2TpCI8Zr6J2w==", + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.7.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.7.1", + "jest-matcher-utils": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-snapshot": "^24.7.1", + "jest-util": "^24.7.1", + "pretty-format": "^24.7.0", + "throat": "^4.0.0" + } + }, + "jest-leak-detector": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.7.0.tgz", + "integrity": "sha512-zV0qHKZGXtmPVVzT99CVEcHE9XDf+8LwiE0Ob7jjezERiGVljmqKFWpV2IkG+rkFIEUHFEkMiICu7wnoPM/RoQ==", + "requires": { + "pretty-format": "^24.7.0" + } + }, + "jest-matcher-utils": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.7.0.tgz", + "integrity": "sha512-158ieSgk3LNXeUhbVJYRXyTPSCqNgVXOp/GT7O94mYd3pk/8+odKTyR1JLtNOQSPzNi8NFYVONtvSWA/e1RDXg==", + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.7.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.7.0" + } + }, + "jest-message-util": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.7.1.tgz", + "integrity": "sha512-dk0gqVtyqezCHbcbk60CdIf+8UHgD+lmRHifeH3JRcnAqh4nEyPytSc9/L1+cQyxC+ceaeP696N4ATe7L+omcg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.7.0.tgz", + "integrity": "sha512-6taW4B4WUcEiT2V9BbOmwyGuwuAFT2G8yghF7nyNW1/2gq5+6aTqSPcS9lS6ArvEkX55vbPAS/Jarx5LSm4Fng==", + "requires": { + "@jest/types": "^24.7.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==" + }, + "jest-regex-util": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", + "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==" + }, + "jest-resolve": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.7.1.tgz", + "integrity": "sha512-Bgrc+/UUZpGJ4323sQyj85hV9d+ANyPNu6XfRDUcyFNX1QrZpSoM0kE4Mb2vZMAYTJZsBFzYe8X1UaOkOELSbw==", + "requires": { + "@jest/types": "^24.7.0", + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "jest-pnp-resolver": "^1.2.1", + "realpath-native": "^1.1.0" + } + }, + "jest-resolve-dependencies": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.7.1.tgz", + "integrity": "sha512-2Eyh5LJB2liNzfk4eo7bD1ZyBbqEJIyyrFtZG555cSWW9xVHxII2NuOkSl1yUYTAYCAmM2f2aIT5A7HzNmubyg==", + "requires": { + "@jest/types": "^24.7.0", + "jest-regex-util": "^24.3.0", + "jest-snapshot": "^24.7.1" + } + }, + "jest-runner": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.7.1.tgz", + "integrity": "sha512-aNFc9liWU/xt+G9pobdKZ4qTeG/wnJrJna3VqunziDNsWT3EBpmxXZRBMKCsNMyfy+A/XHiV+tsMLufdsNdgCw==", + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.4.2", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-config": "^24.7.1", + "jest-docblock": "^24.3.0", + "jest-haste-map": "^24.7.1", + "jest-jasmine2": "^24.7.1", + "jest-leak-detector": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-resolve": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-util": "^24.7.1", + "jest-worker": "^24.6.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + } + }, + "jest-runtime": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.7.1.tgz", + "integrity": "sha512-0VAbyBy7tll3R+82IPJpf6QZkokzXPIS71aDeqh+WzPRXRCNz6StQ45otFariPdJ4FmXpDiArdhZrzNAC3sj6A==", + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.7.1", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/yargs": "^12.0.2", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.7.1", + "jest-haste-map": "^24.7.1", + "jest-message-util": "^24.7.1", + "jest-mock": "^24.7.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.7.1", + "jest-snapshot": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "yargs": "^12.0.2" + } + }, + "jest-serializer": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz", + "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==" + }, + "jest-snapshot": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.7.1.tgz", + "integrity": "sha512-8Xk5O4p+JsZZn4RCNUS3pxA+ORKpEKepE+a5ejIKrId9CwrVN0NY+vkqEkXqlstA5NMBkNahXkR/4qEBy0t5yA==", + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "expect": "^24.7.1", + "jest-diff": "^24.7.0", + "jest-matcher-utils": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-resolve": "^24.7.1", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^24.7.0", + "semver": "^5.5.0" + } + }, + "jest-util": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.7.1.tgz", + "integrity": "sha512-/KilOue2n2rZ5AnEBYoxOXkeTu6vi7cjgQ8MXEkih0oeAXT6JkS3fr7/j8+engCjciOU1Nq5loMSKe0A1oeX0A==", + "requires": { + "@jest/console": "^24.7.1", + "@jest/fake-timers": "^24.7.1", + "@jest/source-map": "^24.3.0", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + } + }, + "jest-validate": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.7.0.tgz", + "integrity": "sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA==", + "requires": { + "@jest/types": "^24.7.0", + "camelcase": "^5.0.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.3.0", + "leven": "^2.1.0", + "pretty-format": "^24.7.0" + } + }, + "jest-watcher": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.7.1.tgz", + "integrity": "sha512-Wd6TepHLRHVKLNPacEsBwlp9raeBIO+01xrN24Dek4ggTS8HHnOzYSFnvp+6MtkkJ3KfMzy220KTi95e2rRkrw==", + "requires": { + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/yargs": "^12.0.9", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.7.1", + "string-length": "^2.0.0" + } + }, + "jest-worker": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.6.0.tgz", + "integrity": "sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==", + "requires": { + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "requires": { + "minimist": "^1.2.0" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "requires": { + "invert-kv": "^2.0.0" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "requires": { + "tmpl": "1.0.x" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "requires": { + "readable-stream": "^2.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" + }, + "node-notifier": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "nwsapi": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.3.tgz", + "integrity": "sha512-RowAaJGEgYXEZfQ7tvvdtAQUKPyTR6T6wNu0fwlNsGQYr/h3yQc6oI8WnVZh3Y/Sylwc+dtAlvPqfFZjhTyk3A==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + } + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "requires": { + "p-reduce": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "pretty-format": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", + "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", + "requires": { + "@jest/types": "^24.7.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "prompts": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.4.tgz", + "integrity": "sha512-HTzM3UWp/99A0gk51gAegwo1QRYA7xjcZufMNe33rCclFszUYAuHe1fIN/3ZmiHeGPkUsNaRyQm1hHOfM0PKxA==", + "requires": { + "kleur": "^3.0.2", + "sisteransi": "^1.0.0" + } + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "requires": { + "util.promisify": "^1.0.0" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "resolve": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", + "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, + "rsvp": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.4.tgz", + "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sisteransi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", + "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==" + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, "source-map-support": { "version": "0.5.12", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", @@ -22,6 +3906,597 @@ "buffer-from": "^1.0.0", "source-map": "^0.6.0" } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" + }, + "test-exclude": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.2.tgz", + "integrity": "sha512-N2pvaLpT8guUpb5Fe1GJlmvmzH3x+DAKmmyEQmFP792QcLYoGE1syxztSvPD1V8yPe6VrcCt6YGQVjSRjCASsA==", + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=" + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "requires": { + "punycode": "^2.1.0" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uglify-js": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.8.tgz", + "integrity": "sha512-GFSjB1nZIzoIq70qvDRtWRORHX3vFkAnyK/rDExc0BN7r9+/S+Voz3t/fwJuVfjppAMz+ceR2poE7tkhvnVwQQ==", + "optional": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + } + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } } diff --git a/packages/@aws-cdk/assert/package.json b/packages/@aws-cdk/assert/package.json index 2c26f19cb4197..6e2a85736f3c7 100644 --- a/packages/@aws-cdk/assert/package.json +++ b/packages/@aws-cdk/assert/package.json @@ -25,12 +25,14 @@ "license": "Apache-2.0", "devDependencies": { "cdk-build-tools": "^0.28.0", + "@types/jest": "^24.0.11", "pkglint": "^0.28.0" }, "dependencies": { "@aws-cdk/cdk": "^0.28.0", "@aws-cdk/cloudformation-diff": "^0.28.0", "@aws-cdk/cx-api": "^0.28.0", + "jest": "^24.7.1", "source-map-support": "^0.5.12" }, "repository": { 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 d66e119c15eb1..3814b989dd583 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -2,7 +2,6 @@ import iam = require('@aws-cdk/aws-iam'); import { Construct, Resource, Token } from '@aws-cdk/cdk'; import { ContainerDefinition, ContainerDefinitionOptions } from '../container-definition'; import { CfnTaskDefinition } from '../ecs.generated'; -import { isEc2Compatible, isFargateCompatible } from '../util'; /** * Properties common to all Task definitions @@ -99,6 +98,20 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { * Base class for Ecs and Fargate task definitions */ export class TaskDefinition extends Resource { + /** + * Return true if the given task definition can be run on an EC2 cluster + */ + public static isEc2Compatible(taskDefinition: TaskDefinition): boolean { + return [Compatibility.Ec2, Compatibility.Ec2AndFargate].includes(taskDefinition.compatibility); + } + + /** + * Return true if the given task definition can be run on a Fargate cluster + */ + public static isFargateCompatible(taskDefinition: TaskDefinition): boolean { + return [Compatibility.Fargate, Compatibility.Ec2AndFargate].includes(taskDefinition.compatibility); + } + /** * The family name of this task definition */ @@ -166,16 +179,16 @@ export class TaskDefinition extends Resource { } this.networkMode = props.networkMode !== undefined ? props.networkMode : - isFargateCompatible(this.compatibility) ? NetworkMode.AwsVpc : NetworkMode.Bridge; - if (isFargateCompatible(this.compatibility) && this.networkMode !== NetworkMode.AwsVpc) { + TaskDefinition.isFargateCompatible(this) ? NetworkMode.AwsVpc : NetworkMode.Bridge; + if (TaskDefinition.isFargateCompatible(this) && this.networkMode !== NetworkMode.AwsVpc) { throw new Error(`Fargate tasks can only have AwsVpc network mode, got: ${this.networkMode}`); } - if (props.placementConstraints && props.placementConstraints.length > 0 && isFargateCompatible(this.compatibility)) { + if (props.placementConstraints && props.placementConstraints.length > 0 && TaskDefinition.isFargateCompatible(this)) { throw new Error('Cannot set placement constraints on tasks that run on Fargate'); } - if (isFargateCompatible(this.compatibility) && (!props.cpu || !props.memoryMiB)) { + if (TaskDefinition.isFargateCompatible(this) && (!props.cpu || !props.memoryMiB)) { throw new Error(`Fargate-compatible tasks require both CPU (${props.cpu}) and memory (${props.memoryMiB}) specifications`); } @@ -280,6 +293,20 @@ export class TaskDefinition extends Resource { return this.executionRole; } + /** + * Return true if the task definition can be run on an EC2 cluster + */ + public get isEc2Compatible(): boolean { + return isEc2Compatible(this.compatibility); + } + + /** + * Return true if the task definition can be run on a Fargate cluster + */ + public get isFargateCompatible(): boolean { + return isFargateCompatible(this.compatibility); + } + /** * Validate this task definition */ @@ -477,3 +504,17 @@ export interface ITaskDefinitionExtension { */ extend(taskDefinition: TaskDefinition): void; } + +/** + * Return true if the given task definition can be run on an EC2 cluster + */ +function isEc2Compatible(compatibility: Compatibility): boolean { + return [Compatibility.Ec2, Compatibility.Ec2AndFargate].includes(compatibility); +} + +/** + * Return true if the given task definition can be run on a Fargate cluster + */ +function isFargateCompatible(compatibility: Compatibility): boolean { + return [Compatibility.Fargate, Compatibility.Ec2AndFargate].includes(compatibility); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index 47a103a6fe796..5bf0657557bdc 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -5,7 +5,6 @@ import cdk = require('@aws-cdk/cdk'); import { BaseService, BaseServiceProps } from '../base/base-service'; import { NetworkMode, TaskDefinition } from '../base/task-definition'; import { CfnService } from '../ecs.generated'; -import { isEc2Compatible } from '../util'; /** * Properties to define an ECS service @@ -78,7 +77,7 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { throw new Error('Minimum healthy percent must be 0 for daemon mode.'); } - if (!isEc2Compatible(props.taskDefinition.compatibility)) { + if (!TaskDefinition.isEc2Compatible(props.taskDefinition)) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index 27c70d0cb832e..53e39cd82c259 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -2,7 +2,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); import { BaseService, BaseServiceProps } from '../base/base-service'; import { TaskDefinition } from '../base/task-definition'; -import { isFargateCompatible } from '../util'; /** * Properties to define a Fargate service @@ -50,7 +49,7 @@ export interface FargateServiceProps extends BaseServiceProps { */ export class FargateService extends BaseService { constructor(scope: cdk.Construct, id: string, props: FargateServiceProps) { - if (!isFargateCompatible(props.taskDefinition.compatibility)) { + if (!TaskDefinition.isFargateCompatible(props.taskDefinition)) { throw new Error('Supplied TaskDefinition is not configured for compatibility with Fargate'); } diff --git a/packages/@aws-cdk/aws-ecs/lib/index.ts b/packages/@aws-cdk/aws-ecs/lib/index.ts index 212e93430f7c0..2e575685d7bff 100644 --- a/packages/@aws-cdk/aws-ecs/lib/index.ts +++ b/packages/@aws-cdk/aws-ecs/lib/index.ts @@ -1,7 +1,6 @@ export * from './base/base-service'; export * from './base/scalable-task-count'; export * from './base/task-definition'; -export * from './base/base-run-task'; export * from './container-definition'; export * from './container-image'; @@ -9,12 +8,10 @@ export * from './cluster'; export * from './ec2/ec2-service'; export * from './ec2/ec2-task-definition'; -export * from './ec2/ec2-run-task'; export * from './ec2/ec2-event-rule-target'; export * from './fargate/fargate-service'; export * from './fargate/fargate-task-definition'; -export * from './fargate/fargate-run-task'; export * from './linux-parameters'; export * from './load-balanced-service-base'; diff --git a/packages/@aws-cdk/aws-ecs/lib/util.ts b/packages/@aws-cdk/aws-ecs/lib/util.ts deleted file mode 100644 index e12251822e338..0000000000000 --- a/packages/@aws-cdk/aws-ecs/lib/util.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Compatibility } from "./base/task-definition"; - -export function isEc2Compatible(comp: Compatibility) { - return comp === Compatibility.Ec2 || comp === Compatibility.Ec2AndFargate; -} - -export function isFargateCompatible(comp: Compatibility) { - return comp === Compatibility.Fargate || comp === Compatibility.Ec2AndFargate; -} diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 4eb0f9075cfc3..b15c80e1aaa53 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -102,7 +102,11 @@ "@aws-cdk/aws-logs": "^0.28.0", "@aws-cdk/aws-route53": "^0.28.0", "@aws-cdk/aws-stepfunctions": "^0.28.0", - "@aws-cdk/cdk": "^0.28.0" + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/aws-cloudformation": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/cx-api": "^0.28.0" }, "engines": { "node": ">= 8.10.0" diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 07fcc0ea7f64c..5de9855754cc8 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -3,7 +3,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import iam = require('@aws-cdk/aws-iam'); import logs = require('@aws-cdk/aws-logs'); import s3n = require('@aws-cdk/aws-s3-notifications'); -import stepfunctions = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); import { IResource, Resource } from '@aws-cdk/cdk'; import { IEventSource } from './event-source'; @@ -11,7 +10,7 @@ import { CfnPermission } from './lambda.generated'; import { Permission } from './permission'; export interface IFunction extends IResource, logs.ILogSubscriptionDestination, - s3n.IBucketNotificationDestination, ec2.IConnectable, stepfunctions.IStepFunctionsTaskResource, iam.IGrantable { + s3n.IBucketNotificationDestination, ec2.IConnectable, iam.IGrantable { /** * Logical ID of this Function. @@ -287,19 +286,6 @@ export abstract class FunctionBase extends Resource implements IFunction { }; } - public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { - return { - resourceArn: this.functionArn, - metricPrefixSingular: 'LambdaFunction', - metricPrefixPlural: 'LambdaFunctions', - metricDimensions: { LambdaFunctionArn: this.functionArn }, - policyStatements: [new iam.PolicyStatement() - .addResource(this.functionArn) - .addActions("lambda:InvokeFunction") - ] - }; - } - /** * Adds an event source to this function. * diff --git a/packages/@aws-cdk/aws-lambda/lib/function-task.ts b/packages/@aws-cdk/aws-lambda/lib/function-task.ts deleted file mode 100644 index 1d259110e6866..0000000000000 --- a/packages/@aws-cdk/aws-lambda/lib/function-task.ts +++ /dev/null @@ -1,28 +0,0 @@ -import stepfunctions = require('@aws-cdk/aws-stepfunctions'); -import cdk = require('@aws-cdk/cdk'); -import { IFunction } from './lambda-ref'; - -/** - * Properties for FunctionTask - */ -export interface FunctionTaskProps extends stepfunctions.BasicTaskProps { - /** - * The function to run - */ - function: IFunction; -} - -/** - * A StepFunctions Task to invoke a Lambda function. - * - * A Function can be used directly as a Resource, but this class mirrors - * integration with other AWS services via a specific class instance. - */ -export class FunctionTask extends stepfunctions.Task { - constructor(scope: cdk.Construct, id: string, props: FunctionTaskProps) { - super(scope, id, { - ...props, - resource: props.function, - }); - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda/lib/index.ts b/packages/@aws-cdk/aws-lambda/lib/index.ts index da31866358d2d..8def6e736af0e 100644 --- a/packages/@aws-cdk/aws-lambda/lib/index.ts +++ b/packages/@aws-cdk/aws-lambda/lib/index.ts @@ -6,7 +6,6 @@ export * from './permission'; export * from './runtime'; export * from './code'; export * from './lambda-version'; -export * from './function-task'; export * from './singleton-lambda'; export * from './event-source'; export * from './event-source-mapping'; diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index 368c2ba886655..078a86354683e 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -86,7 +86,6 @@ "@aws-cdk/aws-s3": "^0.28.0", "@aws-cdk/aws-s3-notifications": "^0.28.0", "@aws-cdk/aws-sqs": "^0.28.0", - "@aws-cdk/aws-stepfunctions": "^0.28.0", "@aws-cdk/cdk": "^0.28.0", "@aws-cdk/cx-api": "^0.28.0" }, @@ -101,7 +100,6 @@ "@aws-cdk/aws-s3": "^0.28.0", "@aws-cdk/aws-s3-notifications": "^0.28.0", "@aws-cdk/aws-sqs": "^0.28.0", - "@aws-cdk/aws-stepfunctions": "^0.28.0", "@aws-cdk/cdk": "^0.28.0", "@aws-cdk/cx-api": "^0.28.0" }, diff --git a/packages/@aws-cdk/aws-sns/lib/index.ts b/packages/@aws-cdk/aws-sns/lib/index.ts index b939b835891fd..46e1f3418aff5 100644 --- a/packages/@aws-cdk/aws-sns/lib/index.ts +++ b/packages/@aws-cdk/aws-sns/lib/index.ts @@ -2,7 +2,6 @@ export * from './policy'; export * from './topic'; export * from './topic-base'; export * from './subscription'; -export * from './publish-task'; // AWS::SNS CloudFormation Resources: export * from './sns.generated'; diff --git a/packages/@aws-cdk/aws-sns/package.json b/packages/@aws-cdk/aws-sns/package.json index bfa538d17cde9..b6712107a2add 100644 --- a/packages/@aws-cdk/aws-sns/package.json +++ b/packages/@aws-cdk/aws-sns/package.json @@ -77,7 +77,6 @@ "@aws-cdk/aws-lambda": "^0.28.0", "@aws-cdk/aws-s3-notifications": "^0.28.0", "@aws-cdk/aws-sqs": "^0.28.0", - "@aws-cdk/aws-stepfunctions": "^0.28.0", "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", @@ -89,7 +88,6 @@ "@aws-cdk/aws-lambda": "^0.28.0", "@aws-cdk/aws-s3-notifications": "^0.28.0", "@aws-cdk/aws-sqs": "^0.28.0", - "@aws-cdk/aws-stepfunctions": "^0.28.0", "@aws-cdk/cdk": "^0.28.0" }, "engines": { diff --git a/packages/@aws-cdk/aws-sns/test/test.publish-task.ts b/packages/@aws-cdk/aws-sns/test/test.publish-task.ts deleted file mode 100644 index 01035ed72c1e0..0000000000000 --- a/packages/@aws-cdk/aws-sns/test/test.publish-task.ts +++ /dev/null @@ -1,30 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { Test } from 'nodeunit'; -import sns = require('../lib'); - -export = { - 'publish to SNS'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const topic = new sns.Topic(stack, 'Topic'); - - // WHEN - const pub = new sns.PublishTask(stack, 'Publish', { - topic, - message: 'Send this message' - }); - - // THEN - test.deepEqual(stack.node.resolve(pub.toStateJson()), { - Type: 'Task', - Resource: 'arn:aws:states:::sns:publish', - End: true, - Parameters: { - TopicArn: { Ref: 'TopicBFC7AF6E' }, - Message: 'Send this message' - }, - }); - - test.done(); - } -}; diff --git a/packages/@aws-cdk/aws-sqs/lib/index.ts b/packages/@aws-cdk/aws-sqs/lib/index.ts index b4de907b6765c..310312d5298a9 100644 --- a/packages/@aws-cdk/aws-sqs/lib/index.ts +++ b/packages/@aws-cdk/aws-sqs/lib/index.ts @@ -1,6 +1,5 @@ export * from './policy'; export * from './queue'; -export * from './send-message-task'; export * from './queue-base'; // AWS::SQS CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-sqs/package.json b/packages/@aws-cdk/aws-sqs/package.json index b8c9ae13c92d4..473051cbbf302 100644 --- a/packages/@aws-cdk/aws-sqs/package.json +++ b/packages/@aws-cdk/aws-sqs/package.json @@ -69,19 +69,19 @@ }, "dependencies": { "@aws-cdk/aws-autoscaling-api": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", "@aws-cdk/aws-iam": "^0.28.0", "@aws-cdk/aws-kms": "^0.28.0", "@aws-cdk/aws-s3-notifications": "^0.28.0", - "@aws-cdk/aws-stepfunctions": "^0.28.0", "@aws-cdk/cdk": "^0.28.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { "@aws-cdk/aws-autoscaling-api": "^0.28.0", + "@aws-cdk/aws-cloudwatch": "^0.28.0", "@aws-cdk/aws-iam": "^0.28.0", "@aws-cdk/aws-kms": "^0.28.0", "@aws-cdk/aws-s3-notifications": "^0.28.0", - "@aws-cdk/aws-stepfunctions": "^0.28.0", "@aws-cdk/cdk": "^0.28.0" }, "engines": { diff --git a/packages/@aws-cdk/aws-sqs/test/test.send-message-task.ts b/packages/@aws-cdk/aws-sqs/test/test.send-message-task.ts deleted file mode 100644 index b49540ebb9cbf..0000000000000 --- a/packages/@aws-cdk/aws-sqs/test/test.send-message-task.ts +++ /dev/null @@ -1,32 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { Test } from 'nodeunit'; -import sqs = require('../lib'); - -export = { - 'publish to queue'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const queue = new sqs.Queue(stack, 'Queue'); - - // WHEN - const pub = new sqs.SendMessageTask(stack, 'Send', { - queue, - messageBody: 'Send this message', - messageDeduplicationIdPath: '$.deduping', - }); - - // THEN - test.deepEqual(stack.node.resolve(pub.toStateJson()), { - Type: 'Task', - Resource: 'arn:aws:states:::sqs:sendMessage', - End: true, - Parameters: { - 'QueueUrl': { Ref: 'Queue4A7E3555' }, - 'MessageBody': 'Send this message', - 'MessageDeduplicationId.$': '$.deduping' - }, - }); - - test.done(); - } -}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore b/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore new file mode 100644 index 0000000000000..205e21fe7353b --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore @@ -0,0 +1,16 @@ +*.js +tsconfig.json +tslint.json +*.js.map +*.d.ts +*.generated.ts +dist +lib/generated/resources.ts +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore b/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore new file mode 100644 index 0000000000000..3f1f9c81eee9b --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore @@ -0,0 +1,17 @@ +# Don't include original .ts files when doing `npm pack` +*.ts +!*.d.ts +coverage +.nyc_output +*.tgz + +dist +.LAST_PACKAGE +.LAST_BUILD +!*.js + +# Include .jsii +!.jsii + +*.snk +tsconfig.* \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/LICENSE b/packages/@aws-cdk/aws-stepfunctions-tasks/LICENSE new file mode 100644 index 0000000000000..46c185646b439 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/NOTICE b/packages/@aws-cdk/aws-stepfunctions-tasks/NOTICE new file mode 100644 index 0000000000000..8585168af8b7d --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/NOTICE @@ -0,0 +1,2 @@ +AWS Cloud Development Kit (AWS CDK) +Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/README.md b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md new file mode 100644 index 0000000000000..9db5348842748 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/README.md @@ -0,0 +1,3 @@ +# Tasks for AWS CloudWatch StepFunctions + +See the README of the `@aws-cdk/aws-stepfunctions` library. diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts similarity index 85% rename from packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts index 9aa6b9110b743..bb8e4692c8d01 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts @@ -1,9 +1,8 @@ import ec2 = require('@aws-cdk/aws-ec2'); +import ecs = require('@aws-cdk/aws-ecs'); import iam = require('@aws-cdk/aws-iam'); import stepfunctions = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); -import { ICluster } from '../cluster'; -import { TaskDefinition } from './task-definition'; /** * Properties for SendMessageTask @@ -12,12 +11,12 @@ export interface BaseRunTaskProps extends stepfunctions.BasicTaskProps { /** * The topic to run the task on */ - cluster: ICluster; + cluster: ecs.ICluster; /** * Task Definition used for running tasks in the service */ - taskDefinition: TaskDefinition; + taskDefinition: ecs.TaskDefinition; /** * Container setting overrides @@ -156,11 +155,13 @@ export class BaseRunTask extends stepfunctions.Task implements ec2.IConnectable protected networkConfiguration?: any; protected readonly _parameters: {[key: string]: any} = {}; + protected readonly taskDefinition: ecs.TaskDefinition; + private readonly sync: boolean; constructor(scope: cdk.Construct, id: string, props: BaseRunTaskProps) { super(scope, id, { ...props, - resource: new RunTaskResource(props), + resourceArn: 'arn:aws:states:::ecs:runTask' + (props.synchronous !== false ? '.sync' : ''), parameters: new cdk.Token(() => ({ Cluster: props.cluster.clusterArn, TaskDefinition: props.taskDefinition.taskDefinitionArn, @@ -169,67 +170,44 @@ export class BaseRunTask extends stepfunctions.Task implements ec2.IConnectable })) }); + this.sync = props.synchronous !== false; this._parameters.Overrides = this.renderOverrides(props.containerOverrides); + this.taskDefinition = props.taskDefinition; } protected configureAwsVpcNetworking( vpc: ec2.IVpcNetwork, assignPublicIp?: boolean, - vpcPlacement?: ec2.VpcPlacementStrategy, + subnetSelection?: ec2.VpcSubnetSelection, securityGroup?: ec2.ISecurityGroup) { - if (vpcPlacement === undefined) { - vpcPlacement = { subnetsToUse: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; + if (subnetSelection === undefined) { + subnetSelection = { subnetsToUse: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; } if (securityGroup === undefined) { securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); } - const subnets = vpc.subnets(vpcPlacement); + const subnets = vpc.selectSubnets(subnetSelection); this.connections.addSecurityGroup(securityGroup); this.networkConfiguration = { AwsvpcConfiguration: { AssignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', - Subnets: subnets.map(x => x.subnetId), + Subnets: subnets.subnetIds, SecurityGroups: new cdk.Token(() => [securityGroup!.securityGroupId]), } }; } - private renderOverrides(containerOverrides?: ContainerOverride[]) { - if (!containerOverrides) { return undefined; } - - const ret = new Array(); - for (const override of containerOverrides) { - ret.push({ - ...extractRequired(override, 'containerName', 'Name'), - ...extractOptional(override, 'command', 'Command'), - ...extractOptional(override, 'cpu', 'Cpu'), - ...extractOptional(override, 'memoryLimit', 'Memory'), - ...extractOptional(override, 'memoryReservation', 'MemoryReservation'), - Environment: override.environment && override.environment.map(e => ({ - ...extractRequired(e, 'name', 'Name'), - ...extractRequired(e, 'value', 'Value'), - })) - }); - } - - return { ContainerOverrides: ret }; - } -} - -class RunTaskResource implements stepfunctions.IStepFunctionsTaskResource { - constructor(private readonly props: BaseRunTaskProps) { - } + protected onBindToGraph(graph: stepfunctions.StateGraph) { + super.onBindToGraph(graph); - public asStepFunctionsTaskResource(callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { - const stack = cdk.Stack.find(callingTask); - const sync = this.props.synchronous !== false; + const stack = this.node.stack; // https://docs.aws.amazon.com/step-functions/latest/dg/ecs-iam.html const policyStatements = [ new iam.PolicyStatement() .addAction('ecs:RunTask') - .addResource(this.props.taskDefinition.taskDefinitionArn), + .addResource(this.taskDefinition.taskDefinitionArn), new iam.PolicyStatement() .addActions('ecs:StopTask', 'ecs:DescribeTasks') .addAllResources(), @@ -238,28 +216,44 @@ class RunTaskResource implements stepfunctions.IStepFunctionsTaskResource { .addResources(...new cdk.Token(() => this.taskExecutionRoles().map(r => r.roleArn)).toList()) ]; - if (sync) { + if (this.sync) { policyStatements.push(new iam.PolicyStatement() .addActions("events:PutTargets", "events:PutRule", "events:DescribeRule") .addResource(stack.formatArn({ service: 'events', resource: 'rule', resourceName: 'StepFunctionsGetEventsForECSTaskRule' - }))); + }))); } + } - return { - resourceArn: 'arn:aws:states:::ecs:runTask' + (sync ? '.sync' : ''), - policyStatements, - }; + private renderOverrides(containerOverrides?: ContainerOverride[]) { + if (!containerOverrides) { return undefined; } + + const ret = new Array(); + for (const override of containerOverrides) { + ret.push({ + ...extractRequired(override, 'containerName', 'Name'), + ...extractOptional(override, 'command', 'Command'), + ...extractOptional(override, 'cpu', 'Cpu'), + ...extractOptional(override, 'memoryLimit', 'Memory'), + ...extractOptional(override, 'memoryReservation', 'MemoryReservation'), + Environment: override.environment && override.environment.map(e => ({ + ...extractRequired(e, 'name', 'Name'), + ...extractRequired(e, 'value', 'Value'), + })) + }); + } + + return { ContainerOverrides: ret }; } private taskExecutionRoles(): iam.IRole[] { // Need to be able to pass both Task and Execution role, apparently const ret = new Array(); - ret.push(this.props.taskDefinition.taskRole); - if ((this.props.taskDefinition as any).executionRole) { - ret.push((this.props.taskDefinition as any).executionRole); + ret.push(this.taskDefinition.taskRole); + if ((this.taskDefinition as any).executionRole) { + ret.push((this.taskDefinition as any).executionRole); } return ret; } diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ec2-run-task.ts similarity index 89% rename from packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/ec2-run-task.ts index 8686b2e77a35e..8cd04b38ce8aa 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ec2-run-task.ts @@ -1,10 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); +import ecs = require('@aws-cdk/aws-ecs'); import cdk = require('@aws-cdk/cdk'); -import { BaseRunTask, BaseRunTaskProps } from '../base/base-run-task'; -import { NetworkMode } from '../base/task-definition'; -import { ICluster } from '../cluster'; -import { isEc2Compatible } from '../util'; -import { BinPackResource, BuiltInAttributes } from './ec2-service'; +import { BaseRunTask, BaseRunTaskProps } from './base-run-task'; /** * Properties to run an ECS task on EC2 in StepFunctionsan ECS @@ -42,10 +39,10 @@ export interface Ec2RunTaskProps extends BaseRunTaskProps { export class Ec2RunTask extends BaseRunTask { private readonly constraints: any[]; private readonly strategies: any[]; - private readonly cluster: ICluster; + private readonly cluster: ecs.ICluster; constructor(scope: cdk.Construct, id: string, props: Ec2RunTaskProps) { - if (!isEc2Compatible(props.taskDefinition.compatibility)) { + if (!props.taskDefinition.isEc2Compatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } @@ -63,7 +60,7 @@ export class Ec2RunTask extends BaseRunTask { this._parameters.PlacementConstraints = new cdk.Token(() => this.constraints.length > 0 ? this.constraints : undefined); this._parameters.PlacementStrategy = new cdk.Token(() => this.constraints.length > 0 ? this.strategies : undefined); - if (props.taskDefinition.networkMode === NetworkMode.AwsVpc) { + if (props.taskDefinition.networkMode === ecs.NetworkMode.AwsVpc) { this.configureAwsVpcNetworking(props.cluster.vpc, false, props.vpcPlacement, props.securityGroup); } else { // Either None, Bridge or Host networking. Copy SecurityGroup from ASG. @@ -108,7 +105,7 @@ export class Ec2RunTask extends BaseRunTask { */ public placeSpreadAcross(...fields: string[]) { if (fields.length === 0) { - fields = [BuiltInAttributes.InstanceId]; + fields = [ecs.BuiltInAttributes.InstanceId]; } for (const field of fields) { this.strategies.push({ Type: 'spread', Field: field }); @@ -120,7 +117,7 @@ export class Ec2RunTask extends BaseRunTask { * * This ensures the total consumption of this resource is lowest. */ - public placePackedBy(resource: BinPackResource) { + public placePackedBy(resource: ecs.BinPackResource) { this.strategies.push({ Type: 'binpack', Field: resource }); } diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/fargate-run-task.ts similarity index 83% rename from packages/@aws-cdk/aws-ecs/lib/fargate/fargate-run-task.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/fargate-run-task.ts index 9543cfb90881c..45126cd65bdc0 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/fargate-run-task.ts @@ -1,8 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); +import ecs = require('@aws-cdk/aws-ecs'); import cdk = require('@aws-cdk/cdk'); -import { BaseRunTask, BaseRunTaskProps } from '../base/base-run-task'; -import { isFargateCompatible } from '../util'; -import { FargatePlatformVersion } from './fargate-service'; +import { BaseRunTask, BaseRunTaskProps } from './base-run-task'; /** * Properties to define an ECS service @@ -37,7 +36,7 @@ export interface FargateRunTaskProps extends BaseRunTaskProps { * * @default Latest */ - platformVersion?: FargatePlatformVersion; + platformVersion?: ecs.FargatePlatformVersion; } /** @@ -45,7 +44,7 @@ export interface FargateRunTaskProps extends BaseRunTaskProps { */ export class FargateRunTask extends BaseRunTask { constructor(scope: cdk.Construct, id: string, props: FargateRunTaskProps) { - if (!isFargateCompatible(props.taskDefinition.compatibility)) { + if (!props.taskDefinition.isFargateCompatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts new file mode 100644 index 0000000000000..665ddf6f26f11 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -0,0 +1,7 @@ +export * from './invoke-function'; +export * from './invoke-activity'; +export * from './nested-state-machine'; +export * from './publish-to-topic'; +export * from './send-to-queue'; +export * from './ec2-run-task'; +export * from './fargate-run-task'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts new file mode 100644 index 0000000000000..0594463680d2d --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts @@ -0,0 +1,142 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); +import stepfunctions = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); + +const METRIC_PREFIX_SINGULAR = 'Activity'; +const METRIC_PREFIX_PLURAL = 'Activities'; + +/** + * Properties for FunctionTask + */ +export interface InvokeActivityProps extends stepfunctions.BasicTaskProps { + /** + * The activity to invoke + */ + activity: stepfunctions.IActivity; + + /** + * Maximum time between heart beats + * + * If the time between heart beats takes longer than this, a 'Timeout' error is raised. + * + * @default No heart beat timeout + */ + readonly heartbeatSeconds?: number; +} + +/** + * A StepFunctions Task to invoke a Lambda function. + * + * A Function can be used directly as a Resource, but this class mirrors + * integration with other AWS services via a specific class instance. + */ +export class InvokeActivity extends stepfunctions.Task { + private readonly activityArn: string; + + constructor(scope: cdk.Construct, id: string, props: InvokeActivityProps) { + super(scope, id, { + ...props, + resourceArn: props.activity.activityArn, + heartbeatSeconds: props.heartbeatSeconds, + // No IAM permissions necessary, execution role implicitly has Activity permissions. + }); + + this.activityArn = props.activity.activityArn; + } + + /** + * Return the given named metric for this Task + * + * @default sum over 5 minutes + */ + public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/States', + metricName, + dimensions: { ActivityArn: this.activityArn }, + statistic: 'sum', + ...props + }); + } + + /** + * The interval, in milliseconds, between the time the Task starts and the time it closes. + * + * @default average over 5 minutes + */ + public metricRunTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_SINGULAR + 'RunTime', { statistic: 'avg', ...props }); + } + + /** + * The interval, in milliseconds, for which the activity stays in the schedule state. + * + * @default average over 5 minutes + */ + public metricScheduleTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_SINGULAR + 'ScheduleTime', { statistic: 'avg', ...props }); + } + + /** + * The interval, in milliseconds, between the time the activity is scheduled and the time it closes. + * + * @default average over 5 minutes + */ + public metricTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_SINGULAR + 'Time', { statistic: 'avg', ...props }); + } + + /** + * Metric for the number of times this activity is scheduled + * + * @default sum over 5 minutes + */ + public metricScheduled(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'Scheduled', props); + } + + /** + * Metric for the number of times this activity times out + * + * @default sum over 5 minutes + */ + public metricTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'TimedOut', props); + } + + /** + * Metric for the number of times this activity is started + * + * @default sum over 5 minutes + */ + public metricStarted(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'Started', props); + } + + /** + * Metric for the number of times this activity succeeds + * + * @default sum over 5 minutes + */ + public metricSucceeded(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'Succeeded', props); + } + + /** + * Metric for the number of times this activity fails + * + * @default sum over 5 minutes + */ + public metricFailed(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'Failed', props); + } + + /** + * Metric for the number of times the heartbeat times out for this activity + * + * @default sum over 5 minutes + */ + public metricHeartbeatTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'HeartbeatTimedOut', props); + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts new file mode 100644 index 0000000000000..77e990b81dbe6 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts @@ -0,0 +1,137 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); +import iam = require('@aws-cdk/aws-iam'); +import lambda = require('@aws-cdk/aws-lambda'); +import stepfunctions = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); + +const METRIC_PREFIX_SINGULAR = 'LambdaFunction'; +const METRIC_PREFIX_PLURAL = 'LambdaFunctions'; + +/** + * Properties for FunctionTask + */ +export interface InvokeFunctionProps extends stepfunctions.BasicTaskProps { + /** + * The function to run + */ + function: lambda.IFunction; +} + +/** + * A StepFunctions Task to invoke a Lambda function. + * + * A Function can be used directly as a Resource, but this class mirrors + * integration with other AWS services via a specific class instance. + */ +export class InvokeFunction extends stepfunctions.Task { + private readonly functionArn: string; + + constructor(scope: cdk.Construct, id: string, props: InvokeFunctionProps) { + super(scope, id, { + ...props, + resourceArn: props.function.functionArn, + policyStatements: [new iam.PolicyStatement() + .addResource(props.function.functionArn) + .addActions("lambda:InvokeFunction") + ] + }); + + this.functionArn = props.function.functionArn; + } + + /** + * Return the given named metric for this Task + * + * @default sum over 5 minutes + */ + public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/States', + metricName, + dimensions: { LambdaFunctionArn: this.functionArn }, + statistic: 'sum', + ...props + }); + } + + /** + * The interval, in milliseconds, between the time the Task starts and the time it closes. + * + * @default average over 5 minutes + */ + public metricRunTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_SINGULAR + 'RunTime', { statistic: 'avg', ...props }); + } + + /** + * The interval, in milliseconds, for which the activity stays in the schedule state. + * + * @default average over 5 minutes + */ + public metricScheduleTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_SINGULAR + 'ScheduleTime', { statistic: 'avg', ...props }); + } + + /** + * The interval, in milliseconds, between the time the activity is scheduled and the time it closes. + * + * @default average over 5 minutes + */ + public metricTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_SINGULAR + 'Time', { statistic: 'avg', ...props }); + } + + /** + * Metric for the number of times this activity is scheduled + * + * @default sum over 5 minutes + */ + public metricScheduled(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'Scheduled', props); + } + + /** + * Metric for the number of times this activity times out + * + * @default sum over 5 minutes + */ + public metricTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'TimedOut', props); + } + + /** + * Metric for the number of times this activity is started + * + * @default sum over 5 minutes + */ + public metricStarted(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'Started', props); + } + + /** + * Metric for the number of times this activity succeeds + * + * @default sum over 5 minutes + */ + public metricSucceeded(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'Succeeded', props); + } + + /** + * Metric for the number of times this activity fails + * + * @default sum over 5 minutes + */ + public metricFailed(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'Failed', props); + } + + /** + * Metric for the number of times the heartbeat times out for this activity + * + * @default sum over 5 minutes + */ + public metricHeartbeatTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + return this.metric(METRIC_PREFIX_PLURAL + 'HeartbeatTimedOut', props); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/nested-state-machine.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/nested-state-machine.ts new file mode 100644 index 0000000000000..e87701835b966 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/nested-state-machine.ts @@ -0,0 +1,3 @@ + +export class NestedStateMachine { +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sns/lib/publish-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts similarity index 84% rename from packages/@aws-cdk/aws-sns/lib/publish-task.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts index 9e0e82b5f1e75..195e0659d1cd8 100644 --- a/packages/@aws-cdk/aws-sns/lib/publish-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts @@ -1,7 +1,7 @@ import iam = require('@aws-cdk/aws-iam'); +import sns = require('@aws-cdk/aws-sns'); import stepfunctions = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); -import { ITopic } from './topic-ref'; /** * Properties for PublishTask @@ -10,7 +10,7 @@ export interface PublishTaskProps extends stepfunctions.BasicTaskProps { /** * The topic to publish to */ - topic: ITopic; + topic: sns.ITopic; /** * The text message to send to the queue. @@ -73,7 +73,11 @@ export class PublishTask extends stepfunctions.Task { super(scope, id, { ...props, - resource: new PublishTaskResource(props), + resourceArn: 'arn:aws:states:::sns:publish', + policyStatements: [new iam.PolicyStatement() + .addAction('sns:Publish') + .addResource(props.topic.topicArn) + ], parameters: { "TopicArn": props.topic.topicArn, "Message": props.messageObject @@ -86,18 +90,4 @@ export class PublishTask extends stepfunctions.Task { } }); } -} - -class PublishTaskResource implements stepfunctions.IStepFunctionsTaskResource { - constructor(private readonly props: PublishTaskProps) { - } - - public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { - return { - resourceArn: 'arn:aws:states:::sns:publish', - policyStatements: [new iam.PolicyStatement() - .addAction('sns:Publish') - .addResource(this.props.topic.topicArn)], - }; - } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-sqs/lib/send-message-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts similarity index 86% rename from packages/@aws-cdk/aws-sqs/lib/send-message-task.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts index 240aeb399e78f..ccb00e0f464fd 100644 --- a/packages/@aws-cdk/aws-sqs/lib/send-message-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts @@ -1,7 +1,7 @@ import iam = require('@aws-cdk/aws-iam'); +import sqs = require('@aws-cdk/aws-sqs'); import stepfunctions = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); -import { IQueue } from './queue-ref'; /** * Properties for SendMessageTask @@ -10,7 +10,7 @@ export interface SendMessageTaskProps extends stepfunctions.BasicTaskProps { /** * The topic to send a message to to */ - queue: IQueue; + queue: sqs.IQueue; /** * The message body to send to the queue. @@ -97,7 +97,11 @@ export class SendMessageTask extends stepfunctions.Task { super(scope, id, { ...props, - resource: new SendMessageTaskResource(props), + resourceArn: 'arn:aws:states:::sqs:sendMessage', + policyStatements: [new iam.PolicyStatement() + .addAction('sns:Publish') + .addResource(props.queue.queueArn) + ], parameters: { 'QueueUrl': props.queue.queueUrl, 'MessageBody': props.messageBody, @@ -111,18 +115,4 @@ export class SendMessageTask extends stepfunctions.Task { } }); } -} - -class SendMessageTaskResource implements stepfunctions.IStepFunctionsTaskResource { - constructor(private readonly props: SendMessageTaskProps) { - } - - public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { - return { - resourceArn: 'arn:aws:states:::sqs:sendMessage', - policyStatements: [new iam.PolicyStatement() - .addAction('sns:Publish') - .addResource(this.props.queue.queueArn)], - }; - } -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json new file mode 100644 index 0000000000000..895967b36848e --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package-lock.json @@ -0,0 +1,5064 @@ +{ + "name": "@aws-cdk/aws-stepfunctions-tasks", + "version": "0.28.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.3.tgz", + "integrity": "sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helpers": "^7.4.3", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.0.tgz", + "integrity": "sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz", + "integrity": "sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==", + "dev": true, + "requires": { + "@babel/types": "^7.4.0" + } + }, + "@babel/helpers": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.3.tgz", + "integrity": "sha512-BMh7X0oZqb36CfyhvtbSmcWc3GXocfxv3yNsAEuM0l+fAqSO22rQrUpijr3oE/10jCTrB6/0b9kzmG4VetCj8Q==", + "dev": true, + "requires": { + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.3.tgz", + "integrity": "sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==", + "dev": true + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/template": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.0.tgz", + "integrity": "sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.0", + "@babel/types": "^7.4.0" + } + }, + "@babel/traverse": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.3.tgz", + "integrity": "sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/types": "^7.4.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", + "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "@cnakazawa/watch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", + "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@jest/console": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", + "integrity": "sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==", + "dev": true, + "requires": { + "@jest/source-map": "^24.3.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + } + }, + "@jest/core": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.7.1.tgz", + "integrity": "sha512-ivlZ8HX/FOASfHcb5DJpSPFps8ydfUYzLZfgFFqjkLijYysnIEOieg72YRhO4ZUB32xu40hsSMmaw+IGYeKONA==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/reporters": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-changed-files": "^24.7.0", + "jest-config": "^24.7.1", + "jest-haste-map": "^24.7.1", + "jest-message-util": "^24.7.1", + "jest-regex-util": "^24.3.0", + "jest-resolve-dependencies": "^24.7.1", + "jest-runner": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-snapshot": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "jest-watcher": "^24.7.1", + "micromatch": "^3.1.10", + "p-each-series": "^1.0.0", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "rimraf": "^2.5.4", + "strip-ansi": "^5.0.0" + } + }, + "@jest/environment": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.7.1.tgz", + "integrity": "sha512-wmcTTYc4/KqA+U5h1zQd5FXXynfa7VGP2NfF+c6QeGJ7c+2nStgh65RQWNX62SC716dTtqheTRrZl0j+54oGHw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^24.7.1", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "jest-mock": "^24.7.0" + } + }, + "@jest/fake-timers": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.7.1.tgz", + "integrity": "sha512-4vSQJDKfR2jScOe12L9282uiwuwQv9Lk7mgrCSZHA9evB9efB/qx8i0KJxsAKtp8fgJYBJdYY7ZU6u3F4/pyjA==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-mock": "^24.7.0" + } + }, + "@jest/reporters": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.7.1.tgz", + "integrity": "sha512-bO+WYNwHLNhrjB9EbPL4kX/mCCG4ZhhfWmO3m4FSpbgr7N83MFejayz30kKjgqr7smLyeaRFCBQMbXpUgnhAJw==", + "dev": true, + "requires": { + "@jest/environment": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-api": "^2.1.1", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-source-maps": "^3.0.1", + "jest-haste-map": "^24.7.1", + "jest-resolve": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-util": "^24.7.1", + "jest-worker": "^24.6.0", + "node-notifier": "^5.2.1", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + } + }, + "@jest/source-map": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz", + "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.7.1.tgz", + "integrity": "sha512-3U7wITxstdEc2HMfBX7Yx3JZgiNBubwDqQMh+BXmZXHa3G13YWF3p6cK+5g0hGkN3iufg/vGPl3hLxQXD74Npg==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/test-sequencer": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.7.1.tgz", + "integrity": "sha512-84HQkCpVZI/G1zq53gHJvSmhUer4aMYp9tTaffW28Ih5OxfCg8hGr3nTSbL1OhVDRrFZwvF+/R9gY6JRkDUpUA==", + "dev": true, + "requires": { + "@jest/test-result": "^24.7.1", + "jest-haste-map": "^24.7.1", + "jest-runner": "^24.7.1", + "jest-runtime": "^24.7.1" + } + }, + "@jest/transform": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.7.1.tgz", + "integrity": "sha512-EsOUqP9ULuJ66IkZQhI5LufCHlTbi7hrcllRMUEV/tOgqBVQi93+9qEvkX0n8mYpVXQ8VjwmICeRgg58mrtIEw==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.7.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.7.1", + "jest-regex-util": "^24.3.0", + "jest-util": "^24.7.1", + "micromatch": "^3.1.10", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + } + }, + "@jest/types": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.7.0.tgz", + "integrity": "sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/yargs": "^12.0.9" + } + }, + "@types/babel__core": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.1.tgz", + "integrity": "sha512-+hjBtgcFPYyCTo0A15+nxrCVJL7aC6Acg87TXd5OW3QhHswdrOLoles+ldL2Uk8q++7yIfl4tURtztccdeeyOw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz", + "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.6.tgz", + "integrity": "sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz", + "integrity": "sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg==", + "dev": true + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "@types/yargs": { + "version": "12.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", + "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==", + "dev": true + }, + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-globals": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", + "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-jest": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.7.1.tgz", + "integrity": "sha512-GPnLqfk8Mtt0i4OemjWkChi73A3ALs4w2/QbG64uAj8b5mmwzxc7jbJVRZt8NJkxi6FopVHog9S3xX6UJKb2qg==", + "dev": true, + "requires": { + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.6.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.2.tgz", + "integrity": "sha512-U3ZVajC+Z69Gim7ZzmD4Wcsq76i/1hqDamBfowc1tWzWjybRy70iWfngP2ME+1CrgcgZ/+muIbPY/Yi0dxdIkQ==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.2.0", + "test-exclude": "^5.2.2" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz", + "integrity": "sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==", + "dev": true, + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", + "integrity": "sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.6.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true, + "optional": true + }, + "compare-versions": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", + "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", + "dev": true + }, + "cssstyle": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", + "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "diff-sequences": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz", + "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==", + "dev": true + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "exec-sh": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", + "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expect": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.7.1.tgz", + "integrity": "sha512-mGfvMTPduksV3xoI0xur56pQsg2vJjNf5+a+bXOjqCkiCBbmCayrBbHS/75y9K430cfqyocPr2ZjiNiRx4SRKw==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.3.0", + "jest-matcher-utils": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-regex-util": "^24.3.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "dev": true, + "requires": { + "bser": "^2.0.0" + } + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "^7.0.3", + "minimatch": "^3.0.3" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.8.tgz", + "integrity": "sha512-tPvHgPGB7m40CZ68xqFGkKuzN+RnpGmSV+hgeKxhRpbxdqKXUFJGC3yonBOLzQBcJyGpdZFDfCsdOC2KFsXzeA==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz", + "integrity": "sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "handlebars": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-api": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.5.tgz", + "integrity": "sha512-meYk1BwDp59Pfse1TvPrkKYgVqAufbdBLEVoqvu/hLLKSaQ054ZTksbNepyc223tMnWdm6AdK2URIJJRqdP87g==", + "dev": true, + "requires": { + "async": "^2.6.1", + "compare-versions": "^3.2.1", + "fileset": "^2.0.3", + "istanbul-lib-coverage": "^2.0.4", + "istanbul-lib-hook": "^2.0.6", + "istanbul-lib-instrument": "^3.2.0", + "istanbul-lib-report": "^2.0.7", + "istanbul-lib-source-maps": "^3.0.5", + "istanbul-reports": "^2.2.3", + "js-yaml": "^3.13.0", + "make-dir": "^2.1.0", + "minimatch": "^3.0.4", + "once": "^1.4.0" + } + }, + "istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-LXTBICkMARVgo579kWDm8SqfB6nvSDKNqIOBEjmJRnL04JvoMHCYGWaMddQnseJYtkEuEvO/sIcOxPLk9gERug==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.6.tgz", + "integrity": "sha512-829DKONApZ7UCiPXcOYWSgkFXa4+vNYoNOt3F+4uDJLKL1OotAoVwvThoEj1i8jmOj7odbYcR3rnaHu+QroaXg==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.2.0.tgz", + "integrity": "sha512-06IM3xShbNW4NgZv5AP4QH0oHqf1/ivFo8eFys0ZjPXHGldHJQWb3riYOKXqmOqfxXBfxu4B+g/iuhOPZH0RJg==", + "dev": true, + "requires": { + "@babel/generator": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "istanbul-lib-coverage": "^2.0.4", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.7.tgz", + "integrity": "sha512-wLH6beJBFbRBLiTlMOBxmb85cnVM1Vyl36N48e4e/aTKSM3WbOx7zbVIH1SQ537fhhsPbX0/C5JB4qsmyRXXyA==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.4", + "make-dir": "^2.1.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.5.tgz", + "integrity": "sha512-eDhZ7r6r1d1zQPVZehLc3D0K14vRba/eBYkz3rw16DLOrrTzve9RmnkcwrrkWVgO1FL3EK5knujVe5S8QHE9xw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.4", + "make-dir": "^2.1.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.3.tgz", + "integrity": "sha512-T6EbPuc8Cb620LWAYyZ4D8SSn06dY9i1+IgUX2lTH8gbwflMc9Obd33zHTyNX653ybjpamAHS9toKS3E6cGhTw==", + "dev": true, + "requires": { + "handlebars": "^4.1.0" + } + }, + "jest": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.7.1.tgz", + "integrity": "sha512-AbvRar5r++izmqo5gdbAjTeA6uNRGoNRuj5vHB0OnDXo2DXWZJVuaObiGgtlvhKb+cWy2oYbQSfxv7Q7GjnAtA==", + "dev": true, + "requires": { + "import-local": "^2.0.0", + "jest-cli": "^24.7.1" + }, + "dependencies": { + "jest-cli": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.7.1.tgz", + "integrity": "sha512-32OBoSCVPzcTslGFl6yVCMzB2SqX3IrWwZCY5mZYkb0D2WsogmU3eV2o8z7+gRQa4o4sZPX/k7GU+II7CxM6WQ==", + "dev": true, + "requires": { + "@jest/core": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "jest-config": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "prompts": "^2.0.1", + "realpath-native": "^1.1.0", + "yargs": "^12.0.2" + } + } + } + }, + "jest-changed-files": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.7.0.tgz", + "integrity": "sha512-33BgewurnwSfJrW7T5/ZAXGE44o7swLslwh8aUckzq2e17/2Os1V0QU506ZNik3hjs8MgnEMKNkcud442NCDTw==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "execa": "^1.0.0", + "throat": "^4.0.0" + } + }, + "jest-config": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.7.1.tgz", + "integrity": "sha512-8FlJNLI+X+MU37j7j8RE4DnJkvAghXmBWdArVzypW6WxfGuxiL/CCkzBg0gHtXhD2rxla3IMOSUAHylSKYJ83g==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^24.7.1", + "@jest/types": "^24.7.0", + "babel-jest": "^24.7.1", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.7.1", + "jest-environment-node": "^24.7.1", + "jest-get-type": "^24.3.0", + "jest-jasmine2": "^24.7.1", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.7.0", + "realpath-native": "^1.1.0" + } + }, + "jest-diff": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.7.0.tgz", + "integrity": "sha512-ULQZ5B1lWpH70O4xsANC4tf4Ko6RrpwhE3PtG6ERjMg1TiYTC2Wp4IntJVGro6a8HG9luYHhhmF4grF0Pltckg==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.3.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.7.0" + } + }, + "jest-docblock": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz", + "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==", + "dev": true, + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.7.1.tgz", + "integrity": "sha512-4fsS8fEfLa3lfnI1Jw6NxjhyRTgfpuOVTeUZZFyVYqeTa4hPhr2YkToUhouuLTrL2eMGOfpbdMyRx0GQ/VooKA==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.3.0", + "jest-util": "^24.7.1", + "pretty-format": "^24.7.0" + } + }, + "jest-environment-jsdom": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.7.1.tgz", + "integrity": "sha512-Gnhb+RqE2JuQGb3kJsLF8vfqjt3PHKSstq4Xc8ic+ax7QKo4Z0RWGucU3YV+DwKR3T9SYc+3YCUQEJs8r7+Jxg==", + "dev": true, + "requires": { + "@jest/environment": "^24.7.1", + "@jest/fake-timers": "^24.7.1", + "@jest/types": "^24.7.0", + "jest-mock": "^24.7.0", + "jest-util": "^24.7.1", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.7.1.tgz", + "integrity": "sha512-GJJQt1p9/C6aj6yNZMvovZuxTUd+BEJprETdvTKSb4kHcw4mFj8777USQV0FJoJ4V3djpOwA5eWyPwfq//PFBA==", + "dev": true, + "requires": { + "@jest/environment": "^24.7.1", + "@jest/fake-timers": "^24.7.1", + "@jest/types": "^24.7.0", + "jest-mock": "^24.7.0", + "jest-util": "^24.7.1" + } + }, + "jest-get-type": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", + "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", + "dev": true + }, + "jest-haste-map": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.7.1.tgz", + "integrity": "sha512-g0tWkzjpHD2qa03mTKhlydbmmYiA2KdcJe762SbfFo/7NIMgBWAA0XqQlApPwkWOF7Cxoi/gUqL0i6DIoLpMBw==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.4.0", + "jest-util": "^24.7.1", + "jest-worker": "^24.6.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.7.1.tgz", + "integrity": "sha512-Y/9AOJDV1XS44wNwCaThq4Pw3gBPiOv/s6NcbOAkVRRUEPu+36L2xoPsqQXsDrxoBerqeyslpn2TpCI8Zr6J2w==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.7.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.7.1", + "jest-matcher-utils": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-snapshot": "^24.7.1", + "jest-util": "^24.7.1", + "pretty-format": "^24.7.0", + "throat": "^4.0.0" + } + }, + "jest-leak-detector": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.7.0.tgz", + "integrity": "sha512-zV0qHKZGXtmPVVzT99CVEcHE9XDf+8LwiE0Ob7jjezERiGVljmqKFWpV2IkG+rkFIEUHFEkMiICu7wnoPM/RoQ==", + "dev": true, + "requires": { + "pretty-format": "^24.7.0" + } + }, + "jest-matcher-utils": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.7.0.tgz", + "integrity": "sha512-158ieSgk3LNXeUhbVJYRXyTPSCqNgVXOp/GT7O94mYd3pk/8+odKTyR1JLtNOQSPzNi8NFYVONtvSWA/e1RDXg==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.7.0", + "jest-get-type": "^24.3.0", + "pretty-format": "^24.7.0" + } + }, + "jest-message-util": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.7.1.tgz", + "integrity": "sha512-dk0gqVtyqezCHbcbk60CdIf+8UHgD+lmRHifeH3JRcnAqh4nEyPytSc9/L1+cQyxC+ceaeP696N4ATe7L+omcg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.7.0.tgz", + "integrity": "sha512-6taW4B4WUcEiT2V9BbOmwyGuwuAFT2G8yghF7nyNW1/2gq5+6aTqSPcS9lS6ArvEkX55vbPAS/Jarx5LSm4Fng==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "dev": true + }, + "jest-regex-util": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", + "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", + "dev": true + }, + "jest-resolve": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.7.1.tgz", + "integrity": "sha512-Bgrc+/UUZpGJ4323sQyj85hV9d+ANyPNu6XfRDUcyFNX1QrZpSoM0kE4Mb2vZMAYTJZsBFzYe8X1UaOkOELSbw==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "jest-pnp-resolver": "^1.2.1", + "realpath-native": "^1.1.0" + } + }, + "jest-resolve-dependencies": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.7.1.tgz", + "integrity": "sha512-2Eyh5LJB2liNzfk4eo7bD1ZyBbqEJIyyrFtZG555cSWW9xVHxII2NuOkSl1yUYTAYCAmM2f2aIT5A7HzNmubyg==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "jest-regex-util": "^24.3.0", + "jest-snapshot": "^24.7.1" + } + }, + "jest-runner": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.7.1.tgz", + "integrity": "sha512-aNFc9liWU/xt+G9pobdKZ4qTeG/wnJrJna3VqunziDNsWT3EBpmxXZRBMKCsNMyfy+A/XHiV+tsMLufdsNdgCw==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.7.1", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "chalk": "^2.4.2", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-config": "^24.7.1", + "jest-docblock": "^24.3.0", + "jest-haste-map": "^24.7.1", + "jest-jasmine2": "^24.7.1", + "jest-leak-detector": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-resolve": "^24.7.1", + "jest-runtime": "^24.7.1", + "jest-util": "^24.7.1", + "jest-worker": "^24.6.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + } + }, + "jest-runtime": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.7.1.tgz", + "integrity": "sha512-0VAbyBy7tll3R+82IPJpf6QZkokzXPIS71aDeqh+WzPRXRCNz6StQ45otFariPdJ4FmXpDiArdhZrzNAC3sj6A==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.7.1", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/yargs": "^12.0.2", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.7.1", + "jest-haste-map": "^24.7.1", + "jest-message-util": "^24.7.1", + "jest-mock": "^24.7.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.7.1", + "jest-snapshot": "^24.7.1", + "jest-util": "^24.7.1", + "jest-validate": "^24.7.0", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "yargs": "^12.0.2" + } + }, + "jest-serializer": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz", + "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==", + "dev": true + }, + "jest-snapshot": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.7.1.tgz", + "integrity": "sha512-8Xk5O4p+JsZZn4RCNUS3pxA+ORKpEKepE+a5ejIKrId9CwrVN0NY+vkqEkXqlstA5NMBkNahXkR/4qEBy0t5yA==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^24.7.0", + "chalk": "^2.0.1", + "expect": "^24.7.1", + "jest-diff": "^24.7.0", + "jest-matcher-utils": "^24.7.0", + "jest-message-util": "^24.7.1", + "jest-resolve": "^24.7.1", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^24.7.0", + "semver": "^5.5.0" + } + }, + "jest-util": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.7.1.tgz", + "integrity": "sha512-/KilOue2n2rZ5AnEBYoxOXkeTu6vi7cjgQ8MXEkih0oeAXT6JkS3fr7/j8+engCjciOU1Nq5loMSKe0A1oeX0A==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/fake-timers": "^24.7.1", + "@jest/source-map": "^24.3.0", + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + } + }, + "jest-validate": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.7.0.tgz", + "integrity": "sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "camelcase": "^5.0.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.3.0", + "leven": "^2.1.0", + "pretty-format": "^24.7.0" + } + }, + "jest-watcher": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.7.1.tgz", + "integrity": "sha512-Wd6TepHLRHVKLNPacEsBwlp9raeBIO+01xrN24Dek4ggTS8HHnOzYSFnvp+6MtkkJ3KfMzy220KTi95e2rRkrw==", + "dev": true, + "requires": { + "@jest/test-result": "^24.7.1", + "@jest/types": "^24.7.0", + "@types/yargs": "^12.0.9", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.7.1", + "string-length": "^2.0.0" + } + }, + "jest-worker": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.6.0.tgz", + "integrity": "sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==", + "dev": true, + "requires": { + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "neo-async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwsapi": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.3.tgz", + "integrity": "sha512-RowAaJGEgYXEZfQ7tvvdtAQUKPyTR6T6wNu0fwlNsGQYr/h3yQc6oI8WnVZh3Y/Sylwc+dtAlvPqfFZjhTyk3A==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "requires": { + "p-reduce": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "pretty-format": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", + "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", + "dev": true, + "requires": { + "@jest/types": "^24.7.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "prompts": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.4.tgz", + "integrity": "sha512-HTzM3UWp/99A0gk51gAegwo1QRYA7xjcZufMNe33rCclFszUYAuHe1fIN/3ZmiHeGPkUsNaRyQm1hHOfM0PKxA==", + "dev": true, + "requires": { + "kleur": "^3.0.2", + "sisteransi": "^1.0.0" + } + }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "dev": true, + "requires": { + "util.promisify": "^1.0.0" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "dev": true, + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rsvp": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.4.tgz", + "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sisteransi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", + "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, + "test-exclude": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.2.tgz", + "integrity": "sha512-N2pvaLpT8guUpb5Fe1GJlmvmzH3x+DAKmmyEQmFP792QcLYoGE1syxztSvPD1V8yPe6VrcCt6YGQVjSRjCASsA==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "dev": true + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uglify-js": { + "version": "3.5.6", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.6.tgz", + "integrity": "sha512-YDKRX8F0Y+Jr7LhoVk0n4G7ltR3Y7qFAj+DtVBthlOgCcIj1hyMigCfousVfn9HKmvJ+qiFlLDwaHx44/e5ZKw==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json new file mode 100644 index 0000000000000..96b551a330e66 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -0,0 +1,105 @@ +{ + "name": "@aws-cdk/aws-stepfunctions-tasks", + "version": "0.28.0", + "description": "Task integrations for AWS StepFunctions", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awscdk.services.stepfunctions.tasks", + "maven": { + "groupId": "software.amazon.awscdk", + "artifactId": "stepfunctions-tasks" + } + }, + "dotnet": { + "namespace": "Amazon.CDK.AWS.StepFunctions.Tasks", + "packageId": "Amazon.CDK.AWS.StepFunctions.Tasks", + "signAssembly": true, + "assemblyOriginatorKeyFile": "../../key.snk" + }, + "sphinx": {}, + "python": { + "distName": "aws-cdk.aws-stepfunctions-tasks", + "module": "aws_cdk.aws_stepfunctions.tasks" + } + } + }, + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-cdk.git", + "directory": "packages/@aws-cdk/aws-stepfunctions-tasks" + }, + "scripts": { + "build": "cdk-build", + "watch": "cdk-watch", + "lint": "cdk-lint", + "test": "cdk-test", + "integ": "cdk-integ", + "pkglint": "pkglint -f", + "package": "cdk-package", + "awslint": "cdk-awslint", + "cfn2ts": "cfn2ts" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ], + "coverageThreshold": { + "global": { + "branches": 80, + "statements": 80 + } + } + }, + "keywords": [ + "aws", + "cdk", + "cloudlib", + "aws-cloudlib", + "aws-clib", + "cloudwatch", + "events" + ], + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "devDependencies": { + "@aws-cdk/assert": "^0.28.0", + "cdk-build-tools": "^0.28.0", + "cdk-integ-tools": "^0.28.0", + "pkglint": "^0.28.0", + "jest": "^24.7.1" + }, + "dependencies": { + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-ecs": "^0.28.0", + "@aws-cdk/aws-stepfunctions": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0" + }, + "homepage": "https://github.com/awslabs/aws-cdk", + "peerDependencies": { + "@aws-cdk/aws-cloudwatch": "^0.28.0", + "@aws-cdk/aws-ec2": "^0.28.0", + "@aws-cdk/aws-ecs": "^0.28.0", + "@aws-cdk/aws-stepfunctions": "^0.28.0", + "@aws-cdk/cdk": "^0.28.0", + "@aws-cdk/aws-iam": "^0.28.0", + "@aws-cdk/aws-lambda": "^0.28.0", + "@aws-cdk/aws-sns": "^0.28.0", + "@aws-cdk/aws-sqs": "^0.28.0" + }, + "engines": { + "node": ">= 8.10.0" + } +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts new file mode 100644 index 0000000000000..0272ad49d62ef --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts @@ -0,0 +1,64 @@ +import '@aws-cdk/assert/jest'; +import sfn = require('@aws-cdk/aws-stepfunctions'); +import { Stack } from '@aws-cdk/cdk'; +import tasks = require('../lib'); + +test('Activity can be used in a Task', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const activity = new sfn.Activity(stack, 'Activity'); + const task = new tasks.InvokeActivity(stack, 'Task', { activity }); + new sfn.StateMachine(stack, 'SM', { + definition: task + }); + + // THEN + expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + DefinitionString: { + "Fn::Join": ["", [ + "{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"", + { Ref: "Activity04690B0A" }, + "\"}}}" + ]] + }, + }); +}); + +test('Activity Task metrics and Activity metrics are the same', () => { + // GIVEN + const stack = new Stack(); + const activity = new sfn.Activity(stack, 'Activity'); + const task = new tasks.InvokeActivity(stack, 'Invoke', { activity }); + + // WHEN + const activityMetrics = [ + activity.metricFailed(), + activity.metricHeartbeatTimedOut(), + activity.metricRunTime(), + activity.metricScheduled(), + activity.metricScheduleTime(), + activity.metricStarted(), + activity.metricSucceeded(), + activity.metricTime(), + activity.metricTimedOut() + ]; + + const taskMetrics = [ + task.metricFailed(), + task.metricHeartbeatTimedOut(), + task.metricRunTime(), + task.metricScheduled(), + task.metricScheduleTime(), + task.metricStarted(), + task.metricSucceeded(), + task.metricTime(), + task.metricTimedOut(), + ]; + + // THEN + for (let i = 0; i < activityMetrics.length; i++) { + expect(activityMetrics[i]).toEqual(taskMetrics[i]); + } +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts new file mode 100644 index 0000000000000..1e4fc39fa1d1b --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts @@ -0,0 +1,26 @@ +import sns = require('@aws-cdk/aws-sns'); +import cdk = require('@aws-cdk/cdk'); +import tasks = require('../lib'); + +test('publish to SNS', () => { + // GIVEN + const stack = new cdk.Stack(); + const topic = new sns.Topic(stack, 'Topic'); + + // WHEN + const pub = new tasks.PublishTask(stack, 'Publish', { + topic, + message: 'Send this message' + }); + + // THEN + expect(stack.node.resolve(pub.toStateJson())).toEqual({ + Type: 'Task', + Resource: 'arn:aws:states:::sns:publish', + End: true, + Parameters: { + TopicArn: { Ref: 'TopicBFC7AF6E' }, + Message: 'Send this message' + }, + }); +}); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts new file mode 100644 index 0000000000000..035fe68efdd0a --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts @@ -0,0 +1,28 @@ +import sqs = require('@aws-cdk/aws-sqs'); +import cdk = require('@aws-cdk/cdk'); +import tasks = require('../lib'); + +test('publish to queue', () => { + // GIVEN + const stack = new cdk.Stack(); + const queue = new sqs.Queue(stack, 'Queue'); + + // WHEN + const pub = new tasks.SendMessageTask(stack, 'Send', { + queue, + messageBody: 'Send this message', + messageDeduplicationIdPath: '$.deduping', + }); + + // THEN + expect(stack.node.resolve(pub.toStateJson())).toEqual({ + Type: 'Task', + Resource: 'arn:aws:states:::sqs:sendMessage', + End: true, + Parameters: { + 'QueueUrl': { Ref: 'Queue4A7E3555' }, + 'MessageBody': 'Send this message', + 'MessageDeduplicationId.$': '$.deduping' + }, + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 98dfd9e3e1a10..03ba4bc1342bd 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -1,17 +1,22 @@ ## AWS Step Functions Construct Library The `@aws-cdk/aws-stepfunctions` package contains constructs for building -serverless workflows. Using objects. Defining a workflow looks like this -(for the [Step Functions Job Poller +serverless workflows using objects. Use this in conjunction with the +`@aws-cdk/aws-stepfunctions-tasks` package, which contains classes used +to call other AWS services. + +Defining a workflow looks like this (for the [Step Functions Job Poller example](https://docs.aws.amazon.com/step-functions/latest/dg/job-status-poller-sample.html)): ### TypeScript example ```ts +import tasks = require('@aws-cdk/aws-stepfunctions-tasks'); + const submitLambda = new lambda.Function(this, 'SubmitLambda', { ... }); const getStatusLambda = new lambda.Function(this, 'CheckLambda', { ... }); -const submitJob = new lambda.FunctionTask(this, 'Submit Job', { +const submitJob = new tasks.InvokeFunction(this, 'Submit Job', { function: submitLambda, // Put Lambda's result here in the execution's state object resultPath: '$.guid', @@ -19,7 +24,7 @@ const submitJob = new lambda.FunctionTask(this, 'Submit Job', { const waitX = new stepfunctions.Wait(this, 'Wait X Seconds', { secondsPath: '$.wait_time' }); -const getStatus = new lambda.FunctionTask(this, 'Get Job Status', { +const getStatus = new tasks.InvokeFunction(this, 'Get Job Status', { function: getStatusLambda, // Pass just the field named "guid" into the Lambda, put the // Lambda's result in a field called "status" @@ -32,7 +37,7 @@ const jobFailed = new stepfunctions.Fail(this, 'Job Failed', { error: 'DescribeJob returned FAILED', }); -const finalStatus = new lambda.FunctionTask(this, 'Get Final Job Status', { +const finalStatus = new tasks.InvokeFunction(this, 'Get Final Job Status', { function: getStatusLambda, // Use "guid" field as input, output of the Lambda becomes the // entire state machine output. @@ -67,7 +72,7 @@ var getStatusLambda = new Function(this, "CheckLambda", new FunctionProps // ... }); -var submitJob = new FunctionTask(this, "Submit Job", new TaskProps +var submitJob = new InvokeFunction(this, "Submit Job", new TaskProps { Function = submitLambda, ResultPath = "$.guid" @@ -78,7 +83,7 @@ var waitX = new Wait(this, "Wait X Seconds", new WaitProps SecondsPath = "$.wait_time" }); -var getStatus = new FunctionTask(this, "Get Job Status", new TaskProps +var getStatus = new InvokeFunction(this, "Get Job Status", new TaskProps { Function = getStatusLambda, InputPath = "$.guid", @@ -91,7 +96,7 @@ var jobFailed = new Fail(this, "Job Failed", new FailProps Error = "DescribeJob returned FAILED" }); -var finalStatus = new FunctionTask(this, "Get Final Job Status", new TaskProps +var finalStatus = new InvokeFunction(this, "Get Final Job Status", new TaskProps { Function = getStatusLambda, // Use "guid" field as input, output of the Lambda becomes the @@ -158,7 +163,7 @@ A `Task` represents some work that needs to be done. There are specific subclasses of `Task` which represent integrations with other AWS services that Step Functions supports. For example: -* `lambda.FunctionTask` -- call a Lambda Function +* `tasks.InvokeFunction` -- call a Lambda Function * `stepfunctions.ActivityTask` -- start an Activity (Activities represent a work queue that you poll on a compute fleet you manage yourself) * `sns.PublishTask` -- publish a message to an SNS topic @@ -168,7 +173,7 @@ that Step Functions supports. For example: #### Lambda example ```ts -const task = new lambda.FunctionTask(this, 'Invoke The Lambda', { +const task = new tasks.InvokeFunction(this, 'Invoke The Lambda', { resource: myLambda, inputPath: '$.input', timeoutSeconds: 300, @@ -496,4 +501,4 @@ Contributions welcome: - [ ] A single `LambdaTask` class that is both a `Lambda` and a `Task` in one might make for a nice API. - [ ] Expression parser for Conditions. -- [ ] Simulate state machines in unit tests. +- [ ] Simulate state machines in unit tests. \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity-task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity-task.ts deleted file mode 100644 index 23efebcf3776b..0000000000000 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity-task.ts +++ /dev/null @@ -1,40 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { IActivity } from './activity'; -import { BasicTaskProps, Task } from './states/task'; - -/** - * Properties for FunctionTask - */ -export interface ActivityTaskProps extends BasicTaskProps { - /** - * The function to run - */ - activity: IActivity; - - /** - * Maximum time between heart beats - * - * If the time between heart beats takes longer than this, a 'Timeout' error is raised. - * - * This is only relevant when using an Activity type as resource. - * - * @default No heart beat timeout - */ - heartbeatSeconds?: number; -} - -/** - * A StepFunctions Task to invoke a Lambda function. - * - * A Function can be used directly as a Resource, but this class mirrors - * integration with other AWS services via a specific class instance. - */ -export class ActivityTask extends Task { - constructor(scope: cdk.Construct, id: string, props: ActivityTaskProps) { - super(scope, id, { - ...props, - resource: props.activity, - heartbeatSeconds: props.heartbeatSeconds - }); - } -} diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index 04ce195a38d5e..010f892658f39 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -1,6 +1,5 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import { Construct, Resource } from '@aws-cdk/cdk'; -import { IStepFunctionsTaskResource, StepFunctionsTaskResourceProps, Task } from './states/task'; +import { Construct, IResource, Resource } from '@aws-cdk/cdk'; import { CfnActivity } from './stepfunctions.generated'; export interface ActivityProps { @@ -15,7 +14,7 @@ export interface ActivityProps { /** * Define a new StepFunctions activity */ -export class Activity extends Resource implements IActivity, IStepFunctionsTaskResource { +export class Activity extends Resource implements IActivity { public readonly activityArn: string; public readonly activityName: string; @@ -30,16 +29,6 @@ export class Activity extends Resource implements IActivity, IStepFunctionsTaskR this.activityName = resource.activityName; } - public asStepFunctionsTaskResource(_callingTask: Task): StepFunctionsTaskResourceProps { - // No IAM permissions necessary, execution role implicitly has Activity permissions. - return { - resourceArn: this.activityArn, - metricPrefixSingular: 'Activity', - metricPrefixPlural: 'Activities', - metricDimensions: { ActivityArn: this.activityArn }, - }; - } - /** * Return the given named metric for this Activity * @@ -145,7 +134,7 @@ export class Activity extends Resource implements IActivity, IStepFunctionsTaskR } } -export interface IActivity extends cdk.IConstruct, IStepFunctionsTaskResource { +export interface IActivity extends IResource { /** * The ARN of the activity */ diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions/lib/index.ts index 497758663dad4..8cf9837545852 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/index.ts @@ -1,5 +1,4 @@ export * from './activity'; -export * from './activity-task'; export * from './types'; export * from './condition'; export * from './state-machine'; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index a23f5a9159134..5a6fa4f2868ee 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -1,4 +1,3 @@ -import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); import { Chain } from '../chain'; @@ -64,9 +63,10 @@ export interface TaskProps extends BasicTaskProps { /** * The resource that represents the work to be executed * - * Can be either a Lambda Function or an Activity. + * Either the ARN of a Lambda Function or Activity, or a special + * ARN. */ - resource: IStepFunctionsTaskResource; + readonly resourceArn: string; /** * Parameters pass a collection of key-value pairs, either static values or JSONPath expressions that select from the input. @@ -80,7 +80,7 @@ export interface TaskProps extends BasicTaskProps { * * @default No parameters */ - parameters?: { [name: string]: any }; + readonly parameters?: { [name: string]: any }; /** * Maximum time between heart beats @@ -92,6 +92,13 @@ export interface TaskProps extends BasicTaskProps { * @default No heart beat timeout */ readonly heartbeatSeconds?: number; + + /** + * Additional policy statements to add to the execution role + * + * @default No policy roles + */ + readonly policyStatements?: iam.PolicyStatement[]; } /** @@ -106,16 +113,18 @@ export interface TaskProps extends BasicTaskProps { */ export class Task extends State implements INextable { public readonly endStates: INextable[]; - private readonly resourceProps: StepFunctionsTaskResourceProps; private readonly timeoutSeconds?: number; private readonly heartbeatSeconds?: number; + private readonly resourceArn: string; + private readonly policyStatements?: iam.PolicyStatement[]; constructor(scope: cdk.Construct, id: string, props: TaskProps) { super(scope, id, props); this.timeoutSeconds = props.timeoutSeconds; this.heartbeatSeconds = props.heartbeatSeconds; - this.resourceProps = props.resource.asStepFunctionsTaskResource(this); + this.resourceArn = props.resourceArn; + this.policyStatements = props.policyStatements; this.endStates = [this]; } @@ -159,186 +168,17 @@ export class Task extends State implements INextable { ...this.renderInputOutput(), Type: StateType.Task, Comment: this.comment, - Resource: this.resourceProps.resourceArn, + Resource: this.resourceArn, ResultPath: renderJsonPath(this.resultPath), TimeoutSeconds: this.timeoutSeconds, HeartbeatSeconds: this.heartbeatSeconds, }; } - /** - * Return the given named metric for this Task - * - * @default sum over 5 minutes - */ - public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return new cloudwatch.Metric({ - namespace: 'AWS/States', - metricName, - dimensions: this.resourceProps.metricDimensions, - statistic: 'sum', - ...props - }); - } - - /** - * The interval, in milliseconds, between the time the Task starts and the time it closes. - * - * @default average over 5 minutes - */ - public metricRunTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixSingular, 'RunTime', { statistic: 'avg', ...props }); - } - - /** - * The interval, in milliseconds, for which the activity stays in the schedule state. - * - * @default average over 5 minutes - */ - public metricScheduleTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixSingular, 'ScheduleTime', { statistic: 'avg', ...props }); - } - - /** - * The interval, in milliseconds, between the time the activity is scheduled and the time it closes. - * - * @default average over 5 minutes - */ - public metricTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixSingular, 'Time', { statistic: 'avg', ...props }); - } - - /** - * Metric for the number of times this activity is scheduled - * - * @default sum over 5 minutes - */ - public metricScheduled(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'Scheduled', props); - } - - /** - * Metric for the number of times this activity times out - * - * @default sum over 5 minutes - */ - public metricTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'TimedOut', props); - } - - /** - * Metric for the number of times this activity is started - * - * @default sum over 5 minutes - */ - public metricStarted(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'Started', props); - } - - /** - * Metric for the number of times this activity succeeds - * - * @default sum over 5 minutes - */ - public metricSucceeded(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'Succeeded', props); - } - - /** - * Metric for the number of times this activity fails - * - * @default sum over 5 minutes - */ - public metricFailed(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'Failed', props); - } - - /** - * Metric for the number of times the heartbeat times out for this activity - * - * @default sum over 5 minutes - */ - public metricHeartbeatTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'HeartbeatTimedOut', props); - } - protected onBindToGraph(graph: StateGraph) { super.onBindToGraph(graph); - for (const policyStatement of this.resourceProps.policyStatements || []) { + for (const policyStatement of this.policyStatements || []) { graph.registerPolicyStatement(policyStatement); } } - - private taskMetric(prefix: string | undefined, suffix: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { - if (prefix === undefined) { - throw new Error('This Task Resource does not expose metrics'); - } - return this.metric(prefix + suffix, props); - } -} - -/** - * Interface for objects that can be invoked in a Task state - */ -export interface IStepFunctionsTaskResource { - /** - * Return the properties required for using this object as a Task resource - */ - asStepFunctionsTaskResource(callingTask: Task): StepFunctionsTaskResourceProps; -} - -/** - * Properties that define how to refer to a TaskResource - */ -export interface StepFunctionsTaskResourceProps { - /** - * The ARN of the resource - */ - readonly resourceArn: string; - - /** - * Additional policy statements to add to the execution role - * - * @default No policy roles - */ - readonly policyStatements?: iam.PolicyStatement[]; - - /** - * Prefix for singular metric names of activity actions - * - * @default No such metrics - */ - readonly metricPrefixSingular?: string; - - /** - * Prefix for plural metric names of activity actions - * - * @default No such metrics - */ - readonly metricPrefixPlural?: string; - - /** - * The dimensions to attach to metrics - * - * @default No metrics - */ - readonly metricDimensions?: cloudwatch.DimensionHash; -} - -/** - * Generic Task Resource - * - * Can be used to integrate with resource types for which there is no - * specialized CDK class available yet. - */ -export class GenericTaskResource implements IStepFunctionsTaskResource { - constructor(private readonly arn: string, private readonly policyStatements?: iam.PolicyStatement[]) { - } - - public asStepFunctionsTaskResource(_callingTask: Task): StepFunctionsTaskResourceProps { - return { - resourceArn: this.arn, - policyStatements: this.policyStatements - }; - } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/integ.job-poller.ts b/packages/@aws-cdk/aws-stepfunctions/test/integ.job-poller.ts index 6de01ecd35db5..9a16e0f5845f6 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/integ.job-poller.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/integ.job-poller.ts @@ -9,12 +9,12 @@ class JobPollerStack extends cdk.Stack { const checkJobActivity = new stepfunctions.Activity(this, 'CheckJob'); const submitJob = new stepfunctions.Task(this, 'Submit Job', { - resource: submitJobActivity, + resourceArn: submitJobActivity.activityArn, resultPath: '$.guid', }); const waitX = new stepfunctions.Wait(this, 'Wait X Seconds', { duration: stepfunctions.WaitDuration.secondsPath('$.wait_time') }); const getStatus = new stepfunctions.Task(this, 'Get Job Status', { - resource: checkJobActivity, + resourceArn: checkJobActivity.activityArn, inputPath: '$.guid', resultPath: '$.status', }); @@ -24,7 +24,7 @@ class JobPollerStack extends cdk.Stack { error: 'DescribeJob returned FAILED', }); const finalStatus = new stepfunctions.Task(this, 'Get Final Job Status', { - resource: checkJobActivity, + resourceArn: checkJobActivity.activityArn, inputPath: '$.guid', }); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts index d4d15c8dec872..02cd600f42d21 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.activity.ts @@ -19,34 +19,6 @@ export = { test.done(); }, - 'Activity can be used in a Task'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const activity = new stepfunctions.Activity(stack, 'Activity'); - const task = new stepfunctions.Task(stack, 'Task', { - resource: activity - }); - new stepfunctions.StateMachine(stack, 'SM', { - definition: task - }); - - // THEN - expect(stack).to(haveResource('AWS::StepFunctions::StateMachine', { - DefinitionString: { - "Fn::Join": ["", [ - "{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"", - { Ref: "Activity04690B0A" }, - "\"}}}" - - ]] - }, - })); - - test.done(); - }, - 'Activity exposes metrics'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.metrics.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.metrics.ts deleted file mode 100644 index cd2752d396218..0000000000000 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.metrics.ts +++ /dev/null @@ -1,44 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { Test } from 'nodeunit'; -import stepfunctions = require('../lib'); - -export = { - 'Activity Task metrics and Activity metrics are the same'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - const activity = new stepfunctions.Activity(stack, 'Activity'); - const task = new stepfunctions.Task(stack, 'Task', { resource: activity }); - - // WHEN - const activityMetrics = [ - activity.metricFailed(), - activity.metricHeartbeatTimedOut(), - activity.metricRunTime(), - activity.metricScheduled(), - activity.metricScheduleTime(), - activity.metricStarted(), - activity.metricSucceeded(), - activity.metricTime(), - activity.metricTimedOut() - ]; - - const taskMetrics = [ - task.metricFailed(), - task.metricHeartbeatTimedOut(), - task.metricRunTime(), - task.metricScheduled(), - task.metricScheduleTime(), - task.metricStarted(), - task.metricSucceeded(), - task.metricTime(), - task.metricTimedOut(), - ]; - - // THEN - for (let i = 0; i < activityMetrics.length; i++) { - test.deepEqual(activityMetrics[i], taskMetrics[i]); - } - - test.done(); - } -}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts index cfcdec4f632f2..bccefe596603a 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts @@ -10,7 +10,11 @@ export = { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { - resource: new FakeResource(), + resourceArn: 'resource', + policyStatements: [new iam.PolicyStatement() + .addAction('resource:Everything') + .addResource('resource') + ], }); // WHEN @@ -39,7 +43,7 @@ export = { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { - resource: new FakeResource(), + resourceArn: 'resource' }); const para = new stepfunctions.Parallel(stack, 'Para'); @@ -67,39 +71,11 @@ export = { test.done(); }, - 'Task metrics use values returned from resource'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const task = new stepfunctions.Task(stack, 'Task', { resource: new FakeResource() }); - - // THEN - const sharedMetric = { - periodSec: 300, - namespace: 'AWS/States', - dimensions: { ResourceArn: 'resource' }, - }; - test.deepEqual(stack.node.resolve(task.metricRunTime()), { - ...sharedMetric, - metricName: 'FakeResourceRunTime', - statistic: 'Average' - }); - - test.deepEqual(stack.node.resolve(task.metricFailed()), { - ...sharedMetric, - metricName: 'FakeResourcesFailed', - statistic: 'Sum' - }); - - test.done(); - }, - 'Task should render InputPath / Parameters / OutputPath correctly'(test: Test) { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { - resource: new FakeResource(), + resourceArn: 'resource', inputPath: "$", outputPath: "$.state", parameters: { @@ -165,21 +141,4 @@ export = { test.done(); }, -}; - -class FakeResource implements stepfunctions.IStepFunctionsTaskResource { - public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { - const resourceArn = 'resource'; - - return { - resourceArn, - policyStatements: [new iam.PolicyStatement() - .addAction('resource:Everything') - .addResource('resource') - ], - metricPrefixSingular: 'FakeResource', - metricPrefixPlural: 'FakeResources', - metricDimensions: { ResourceArn: resourceArn }, - }; - } -} +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts index 57f624be2b5df..5c35cb7aec16a 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts @@ -397,7 +397,7 @@ export = { 'States can have error branches'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: new FakeResource() }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -429,7 +429,7 @@ export = { 'Retries and errors with a result path'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: new FakeResource() }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -462,8 +462,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: new FakeResource() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: new FakeResource() }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -508,8 +508,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: new FakeResource() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: new FakeResource() }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -539,9 +539,9 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: new FakeResource() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: new FakeResource() }); - const task3 = new stepfunctions.Task(stack, 'Task3', { resource: new FakeResource() }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); + const task3 = new stepfunctions.Task(stack, 'Task3', { resourceArn: 'resource' }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -568,8 +568,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: new FakeResource() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: new FakeResource() }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -584,8 +584,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: new FakeResource() }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: new FakeResource() }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -705,8 +705,8 @@ class SimpleChain extends stepfunctions.StateMachineFragment { constructor(scope: cdk.Construct, id: string) { super(scope, id); - const task1 = new stepfunctions.Task(this, 'Task1', { resource: new FakeResource() }); - this.task2 = new stepfunctions.Task(this, 'Task2', { resource: new FakeResource() }); + const task1 = new stepfunctions.Task(this, 'Task1', { resourceArn: 'resource' }); + this.task2 = new stepfunctions.Task(this, 'Task2', { resourceArn: 'resource' }); task1.next(this.task2); @@ -720,12 +720,6 @@ class SimpleChain extends stepfunctions.StateMachineFragment { } } -class FakeResource implements stepfunctions.IStepFunctionsTaskResource { - public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps { - return { resourceArn: 'resource' }; - } -} - function render(sm: stepfunctions.IChainable) { return new cdk.Stack().node.resolve(new stepfunctions.StateGraph(sm.startState, 'Test Graph').toGraphJson()); } From 55a15e16c3669df6a631e9424927f67176359c09 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Sat, 27 Apr 2019 09:38:38 +0200 Subject: [PATCH 07/21] Get ECS library to build --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 4 - .../aws-ecs/lib/base/task-definition.ts | 22 +- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 7 +- .../aws-ecs/lib/ec2/ec2-event-rule-target.ts | 3 +- .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 2 +- .../aws-ecs/lib/fargate/fargate-service.ts | 2 +- packages/@aws-cdk/aws-ecs/package.json | 12 +- .../integ.stepfunctions-task.expected.json | 937 ------------------ .../test/ec2/integ.stepfunctions-task.ts | 50 - .../integ.stepfunctions-task.expected.json | 619 ------------ .../test/fargate/integ.stepfunctions-task.ts | 50 - 11 files changed, 17 insertions(+), 1691 deletions(-) delete mode 100644 packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.expected.json delete mode 100644 packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.ts delete mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.expected.json delete mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.ts diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 7796485e8409e..32a6b80c8145a 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -231,10 +231,6 @@ export abstract class BaseService extends cdk.Construct } this.connections.addSecurityGroup(securityGroup); - if (assignPublicIp === undefined && subnets.every(s => vpc.isPublicSubnet(s))) { - assignPublicIp = true; - } - this.networkConfiguration = { awsvpcConfiguration: { assignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', 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 3814b989dd583..c31e973afa9c4 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -98,20 +98,6 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { * Base class for Ecs and Fargate task definitions */ export class TaskDefinition extends Resource { - /** - * Return true if the given task definition can be run on an EC2 cluster - */ - public static isEc2Compatible(taskDefinition: TaskDefinition): boolean { - return [Compatibility.Ec2, Compatibility.Ec2AndFargate].includes(taskDefinition.compatibility); - } - - /** - * Return true if the given task definition can be run on a Fargate cluster - */ - public static isFargateCompatible(taskDefinition: TaskDefinition): boolean { - return [Compatibility.Fargate, Compatibility.Ec2AndFargate].includes(taskDefinition.compatibility); - } - /** * The family name of this task definition */ @@ -179,16 +165,16 @@ export class TaskDefinition extends Resource { } this.networkMode = props.networkMode !== undefined ? props.networkMode : - TaskDefinition.isFargateCompatible(this) ? NetworkMode.AwsVpc : NetworkMode.Bridge; - if (TaskDefinition.isFargateCompatible(this) && this.networkMode !== NetworkMode.AwsVpc) { + this.isFargateCompatible ? NetworkMode.AwsVpc : NetworkMode.Bridge; + if (this.isFargateCompatible && this.networkMode !== NetworkMode.AwsVpc) { throw new Error(`Fargate tasks can only have AwsVpc network mode, got: ${this.networkMode}`); } - if (props.placementConstraints && props.placementConstraints.length > 0 && TaskDefinition.isFargateCompatible(this)) { + if (props.placementConstraints && props.placementConstraints.length > 0 && this.isFargateCompatible) { throw new Error('Cannot set placement constraints on tasks that run on Fargate'); } - if (TaskDefinition.isFargateCompatible(this) && (!props.cpu || !props.memoryMiB)) { + if (this.isFargateCompatible && (!props.cpu || !props.memoryMiB)) { throw new Error(`Fargate-compatible tasks require both CPU (${props.cpu}) and memory (${props.memoryMiB}) specifications`); } diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index e2d87c8791885..45edc038c6009 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -321,11 +321,6 @@ export interface ClusterImportProps { */ readonly clusterArn?: string; - /** - * ARN of the cluster - */ - clusterArn?: string; - /** * VPC that the cluster instances are running in */ @@ -398,7 +393,7 @@ class ImportedCluster extends Construct implements ICluster { resourceName: props.clusterName, }); - this.clusterArn = props.clusterArn !== undefined ? props.clusterArn : cdk.Stack.find(this).formatArn({ + this.clusterArn = props.clusterArn !== undefined ? props.clusterArn : this.node.stack.formatArn({ service: 'ecs', resource: 'cluster', resourceName: props.clusterName, diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts index 060ea4e3f840e..fc203ebbb5ebd 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts @@ -3,7 +3,6 @@ import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); import { TaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; -import { isEc2Compatible } from '../util'; /** * Properties to define an EC2 Event Task @@ -38,7 +37,7 @@ export class Ec2EventRuleTarget extends cdk.Construct implements events.IEventRu constructor(scope: cdk.Construct, id: string, props: Ec2EventRuleTargetProps) { super(scope, id); - if (!isEc2Compatible(props.taskDefinition.compatibility)) { + if (!props.taskDefinition.isEc2Compatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index 00401d819bbd2..a463596444d8f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -77,7 +77,7 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { throw new Error('Minimum healthy percent must be 0 for daemon mode.'); } - if (!TaskDefinition.isEc2Compatible(props.taskDefinition)) { + if (!props.taskDefinition.isEc2Compatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts index 53e39cd82c259..a6b396d1b19df 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -49,7 +49,7 @@ export interface FargateServiceProps extends BaseServiceProps { */ export class FargateService extends BaseService { constructor(scope: cdk.Construct, id: string, props: FargateServiceProps) { - if (!TaskDefinition.isFargateCompatible(props.taskDefinition)) { + if (!props.taskDefinition.isFargateCompatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with Fargate'); } diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 8cf15024840b5..0630d74da11f3 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -78,10 +78,13 @@ "@aws-cdk/aws-ecr": "^0.29.0", "@aws-cdk/aws-elasticloadbalancing": "^0.29.0", "@aws-cdk/aws-elasticloadbalancingv2": "^0.29.0", + "@aws-cdk/aws-events": "^0.29.0", "@aws-cdk/aws-iam": "^0.29.0", "@aws-cdk/aws-lambda": "^0.29.0", "@aws-cdk/aws-logs": "^0.29.0", "@aws-cdk/aws-route53": "^0.29.0", + "@aws-cdk/aws-secretsmanager": "^0.29.0", + "@aws-cdk/aws-servicediscovery": "^0.29.0", "@aws-cdk/aws-sns": "^0.29.0", "@aws-cdk/aws-stepfunctions": "^0.29.0", "@aws-cdk/cdk": "^0.29.0", @@ -93,19 +96,22 @@ "@aws-cdk/aws-applicationautoscaling": "^0.29.0", "@aws-cdk/aws-autoscaling": "^0.29.0", "@aws-cdk/aws-certificatemanager": "^0.29.0", + "@aws-cdk/aws-cloudformation": "^0.29.0", "@aws-cdk/aws-cloudwatch": "^0.29.0", "@aws-cdk/aws-ec2": "^0.29.0", "@aws-cdk/aws-ecr": "^0.29.0", "@aws-cdk/aws-elasticloadbalancing": "^0.29.0", "@aws-cdk/aws-elasticloadbalancingv2": "^0.29.0", + "@aws-cdk/aws-events": "^0.29.0", "@aws-cdk/aws-iam": "^0.29.0", + "@aws-cdk/aws-lambda": "^0.29.0", "@aws-cdk/aws-logs": "^0.29.0", "@aws-cdk/aws-route53": "^0.29.0", + "@aws-cdk/aws-secretsmanager": "^0.29.0", + "@aws-cdk/aws-servicediscovery": "^0.29.0", + "@aws-cdk/aws-sns": "^0.29.0", "@aws-cdk/aws-stepfunctions": "^0.29.0", "@aws-cdk/cdk": "^0.29.0", - "@aws-cdk/aws-cloudformation": "^0.29.0", - "@aws-cdk/aws-lambda": "^0.29.0", - "@aws-cdk/aws-sns": "^0.29.0", "@aws-cdk/cx-api": "^0.29.0" }, "engines": { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.expected.json deleted file mode 100644 index 630b75193813f..0000000000000 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.expected.json +++ /dev/null @@ -1,937 +0,0 @@ -{ - "Resources": { - "FargateCluster7CCD5F93": { - "Type": "AWS::ECS::Cluster" - }, - "FargateClusterDefaultAutoScalingGroupInstanceSecurityGroup42AF8A40": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup/InstanceSecurityGroup", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - } - ], - "SecurityGroupIngress": [], - "Tags": [ - { - "Key": "Name", - "Value": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup" - } - ], - "VpcId": "vpc-60900905" - } - }, - "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ec2.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecs:CreateCluster", - "ecs:DeregisterContainerInstance", - "ecs:DiscoverPollEndpoint", - "ecs:Poll", - "ecs:RegisterContainerInstance", - "ecs:StartTelemetrySession", - "ecs:Submit*", - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E", - "Roles": [ - { - "Ref": "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7" - } - ] - } - }, - "FargateClusterDefaultAutoScalingGroupInstanceProfile2C0FEF3B": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": { - "Roles": [ - { - "Ref": "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7" - } - ] - } - }, - "FargateClusterDefaultAutoScalingGroupLaunchConfig57306899": { - "Type": "AWS::AutoScaling::LaunchConfiguration", - "Properties": { - "ImageId": "ami-1234", - "InstanceType": "t2.micro", - "IamInstanceProfile": { - "Ref": "FargateClusterDefaultAutoScalingGroupInstanceProfile2C0FEF3B" - }, - "SecurityGroups": [ - { - "Fn::GetAtt": [ - "FargateClusterDefaultAutoScalingGroupInstanceSecurityGroup42AF8A40", - "GroupId" - ] - } - ], - "UserData": { - "Fn::Base64": { - "Fn::Join": [ - "", - [ - "#!/bin/bash\necho ECS_CLUSTER=", - { - "Ref": "FargateCluster7CCD5F93" - }, - " >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config" - ] - ] - } - } - }, - "DependsOn": [ - "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7", - "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E" - ] - }, - "FargateClusterDefaultAutoScalingGroupASG36A4948F": { - "Type": "AWS::AutoScaling::AutoScalingGroup", - "Properties": { - "MaxSize": "1", - "MinSize": "1", - "DesiredCapacity": "1", - "LaunchConfigurationName": { - "Ref": "FargateClusterDefaultAutoScalingGroupLaunchConfig57306899" - }, - "Tags": [ - { - "Key": "Name", - "PropagateAtLaunch": true, - "Value": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup" - } - ], - "VPCZoneIdentifier": [ - "subnet-e19455ca", - "subnet-e0c24797", - "subnet-ccd77395" - ] - }, - "UpdatePolicy": { - "AutoScalingReplacingUpdate": { - "WillReplace": true - }, - "AutoScalingScheduledAction": { - "IgnoreUnmodifiedGroupSizeProperties": true - } - } - }, - "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA": { - "Type": "AWS::SNS::Topic" - }, - "FargateClusterDefaultAutoScalingGroupDrainECSHookTopicFunctionSubscription129830E9": { - "Type": "AWS::SNS::Subscription", - "Properties": { - "Endpoint": { - "Fn::GetAtt": [ - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8", - "Arn" - ] - }, - "Protocol": "lambda", - "TopicArn": { - "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" - } - } - }, - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "autoscaling:CompleteLifecycleAction", - "ec2:DescribeInstances", - "ec2:DescribeInstanceAttribute", - "ec2:DescribeInstanceStatus", - "ec2:DescribeHosts" - ], - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": [ - "ecs:ListContainerInstances", - "ecs:SubmitContainerStateChange", - "ecs:SubmitTaskStateChange", - "ecs:DescribeContainerInstances", - "ecs:UpdateContainerInstancesState", - "ecs:ListTasks", - "ecs:DescribeTasks" - ], - "Effect": "Allow", - "Resource": "*" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343", - "Roles": [ - { - "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32" - } - ] - } - }, - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n while has_tasks(cluster, instance_arn):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\n\ndef has_tasks(cluster, instance_arn):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n tasks = instance['runningTasksCount'] + instance['pendingTasksCount']\n print('Instance %s has %s tasks' % (instance_arn, tasks))\n\n return tasks > 0\n\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n" - }, - "Handler": "index.lambda_handler", - "Role": { - "Fn::GetAtt": [ - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32", - "Arn" - ] - }, - "Runtime": "python3.6", - "Environment": { - "Variables": { - "CLUSTER": { - "Ref": "FargateCluster7CCD5F93" - } - } - }, - "Timeout": 310 - }, - "DependsOn": [ - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32", - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343" - ] - }, - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicA1F1F9E9": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8" - }, - "Principal": "sns.amazonaws.com", - "SourceArn": { - "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" - } - } - }, - "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "autoscaling.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Resource": { - "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D", - "Roles": [ - { - "Ref": "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D" - } - ] - } - }, - "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHook2AE13680": { - "Type": "AWS::AutoScaling::LifecycleHook", - "Properties": { - "AutoScalingGroupName": { - "Ref": "FargateClusterDefaultAutoScalingGroupASG36A4948F" - }, - "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", - "DefaultResult": "CONTINUE", - "HeartbeatTimeout": 300, - "NotificationTargetARN": { - "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" - }, - "RoleARN": { - "Fn::GetAtt": [ - "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D", - "Arn" - ] - } - }, - "DependsOn": [ - "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D", - "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D" - ] - }, - "TaskDefTaskRole1EDB4A67": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - "TaskDef54694570": { - "Type": "AWS::ECS::TaskDefinition", - "Properties": { - "ContainerDefinitions": [ - { - "Essential": true, - "Image": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 4, - { - "Fn::Split": [ - ":", - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::GetAtt": [ - "EventImageAdoptRepositoryDFAAC242", - "RepositoryName" - ] - } - ] - ] - } - ] - } - ] - }, - ".dkr.ecr.", - { - "Fn::Select": [ - 3, - { - "Fn::Split": [ - ":", - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::GetAtt": [ - "EventImageAdoptRepositoryDFAAC242", - "RepositoryName" - ] - } - ] - ] - } - ] - } - ] - }, - ".amazonaws.com/", - { - "Fn::GetAtt": [ - "EventImageAdoptRepositoryDFAAC242", - "RepositoryName" - ] - }, - ":", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Ref": "EventImageImageNameE972A8B1" - } - ] - } - ] - } - ] - ] - }, - "Links": [], - "LinuxParameters": { - "Capabilities": { - "Add": [], - "Drop": [] - }, - "Devices": [], - "Tmpfs": [] - }, - "LogConfiguration": { - "LogDriver": "awslogs", - "Options": { - "awslogs-group": { - "Ref": "TaskLoggingLogGroupC7E938D4" - }, - "awslogs-stream-prefix": "EventDemo", - "awslogs-region": { - "Ref": "AWS::Region" - } - } - }, - "Memory": 256, - "MountPoints": [], - "Name": "TheContainer", - "PortMappings": [], - "Ulimits": [], - "VolumesFrom": [] - } - ], - "ExecutionRoleArn": { - "Fn::GetAtt": [ - "TaskDefExecutionRoleB4775C97", - "Arn" - ] - }, - "Family": "awsecsinteg2TaskDef1F38909D", - "NetworkMode": "bridge", - "PlacementConstraints": [], - "RequiresCompatibilities": [ - "EC2" - ], - "TaskRoleArn": { - "Fn::GetAtt": [ - "TaskDefTaskRole1EDB4A67", - "Arn" - ] - }, - "Volumes": [] - } - }, - "TaskDefExecutionRoleB4775C97": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - "TaskDefExecutionRoleDefaultPolicy0DBB737A": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:BatchCheckLayerAvailability", - "ecr:GetDownloadUrlForLayer", - "ecr:BatchGetImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::GetAtt": [ - "EventImageAdoptRepositoryDFAAC242", - "RepositoryName" - ] - } - ] - ] - } - }, - { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": [ - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TaskLoggingLogGroupC7E938D4", - "Arn" - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "TaskDefExecutionRoleDefaultPolicy0DBB737A", - "Roles": [ - { - "Ref": "TaskDefExecutionRoleB4775C97" - } - ] - } - }, - "EventImageAdoptRepositoryDFAAC242": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Ref": "EventImageImageNameE972A8B1" - } - ] - } - ] - } - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Ref": "EventImageImageNameE972A8B1" - } - ] - } - ] - } - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs8.10", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C" - ] - }, - "TaskLoggingLogGroupC7E938D4": { - "Type": "AWS::Logs::LogGroup", - "Properties": { - "RetentionInDays": 365 - }, - "DeletionPolicy": "Retain" - }, - "StateMachineRoleB840431D": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "states.", - { - "Ref": "AWS::Region" - }, - ".amazonaws.com" - ] - ] - } - } - } - ], - "Version": "2012-10-17" - } - } - }, - "StateMachineRoleDefaultPolicyDF1E6607": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "ecs:RunTask", - "Effect": "Allow", - "Resource": { - "Ref": "TaskDef54694570" - } - }, - { - "Action": [ - "ecs:StopTask", - "ecs:DescribeTasks" - ], - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TaskDefTaskRole1EDB4A67", - "Arn" - ] - }, - { - "Fn::GetAtt": [ - "TaskDefExecutionRoleB4775C97", - "Arn" - ] - } - ] - }, - { - "Action": [ - "events:PutTargets", - "events:PutRule", - "events:DescribeRule" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":events:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":rule/StepFunctionsGetEventsForECSTaskRule" - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", - "Roles": [ - { - "Ref": "StateMachineRoleB840431D" - } - ] - } - }, - "StateMachine2E01A3A5": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"RunEc2\"},\"RunEc2\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", - { - "Fn::GetAtt": [ - "FargateCluster7CCD5F93", - "Arn" - ] - }, - "\",\"TaskDefinition\":\"", - { - "Ref": "TaskDef54694570" - }, - "\",\"Overrides\":{\"ContainerOverrides\":[{\"Name\":\"TheContainer\",\"Environment\":[{\"Name\":\"SOME_KEY\",\"Value.$\":\"$.SomeKey\"}]}]},\"LaunchType\":\"EC2\"},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::ecs:runTask.sync\"}}}" - ] - ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] - } - } - } - }, - "Parameters": { - "EventImageImageNameE972A8B1": { - "Type": "String", - "Description": "ECR repository name and tag asset \"aws-ecs-integ2/EventImage\"" - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6": { - "Type": "String", - "Description": "S3 bucket for asset \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276": { - "Type": "String", - "Description": "S3 key for asset version \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.ts deleted file mode 100644 index f9ccc90080595..0000000000000 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.stepfunctions-task.ts +++ /dev/null @@ -1,50 +0,0 @@ -import ec2 = require('@aws-cdk/aws-ec2'); -import sfn = require('@aws-cdk/aws-stepfunctions'); -import cdk = require('@aws-cdk/cdk'); -import path = require('path'); -import ecs = require('../../lib'); - -const app = new cdk.App(); -const stack = new cdk.Stack(app, 'aws-ecs-integ2'); - -const vpc = ec2.VpcNetwork.importFromContext(stack, 'Vpc', { - isDefault: true -}); - -const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); -cluster.addDefaultAutoScalingGroupCapacity({ - instanceType: new ec2.InstanceType('t2.micro'), - vpcPlacement: { subnetsToUse: ec2.SubnetType.Public }, -}); - -// Build task definition -const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); -taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, '..', 'eventhandler-image') }), - memoryLimitMiB: 256, - logging: new ecs.AwsLogDriver(stack, 'TaskLogging', { streamPrefix: 'EventDemo' }) -}); - -// Build state machine -const definition = new sfn.Pass(stack, 'Start', { - result: { SomeKey: 'SomeValue' } -}).next(new ecs.Ec2RunTask(stack, 'RunEc2', { - cluster, taskDefinition, - containerOverrides: [ - { - containerName: 'TheContainer', - environment: [ - { - name: 'SOME_KEY', - valuePath: '$.SomeKey' - } - ] - } - ] -})); - -new sfn.StateMachine(stack, 'StateMachine', { - definition, -}); - -app.run(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.expected.json deleted file mode 100644 index 61ca415d2f552..0000000000000 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.expected.json +++ /dev/null @@ -1,619 +0,0 @@ -{ - "Resources": { - "FargateCluster7CCD5F93": { - "Type": "AWS::ECS::Cluster" - }, - "TaskDefTaskRole1EDB4A67": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - "TaskDef54694570": { - "Type": "AWS::ECS::TaskDefinition", - "Properties": { - "ContainerDefinitions": [ - { - "Essential": true, - "Image": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 4, - { - "Fn::Split": [ - ":", - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::GetAtt": [ - "EventImageAdoptRepositoryDFAAC242", - "RepositoryName" - ] - } - ] - ] - } - ] - } - ] - }, - ".dkr.ecr.", - { - "Fn::Select": [ - 3, - { - "Fn::Split": [ - ":", - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::GetAtt": [ - "EventImageAdoptRepositoryDFAAC242", - "RepositoryName" - ] - } - ] - ] - } - ] - } - ] - }, - ".amazonaws.com/", - { - "Fn::GetAtt": [ - "EventImageAdoptRepositoryDFAAC242", - "RepositoryName" - ] - }, - ":", - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - ":", - { - "Ref": "EventImageImageNameE972A8B1" - } - ] - } - ] - } - ] - ] - }, - "Links": [], - "LinuxParameters": { - "Capabilities": { - "Add": [], - "Drop": [] - }, - "Devices": [], - "Tmpfs": [] - }, - "LogConfiguration": { - "LogDriver": "awslogs", - "Options": { - "awslogs-group": { - "Ref": "TaskLoggingLogGroupC7E938D4" - }, - "awslogs-stream-prefix": "EventDemo", - "awslogs-region": { - "Ref": "AWS::Region" - } - } - }, - "Memory": 256, - "MountPoints": [], - "Name": "TheContainer", - "PortMappings": [], - "Ulimits": [], - "VolumesFrom": [] - } - ], - "Cpu": "256", - "ExecutionRoleArn": { - "Fn::GetAtt": [ - "TaskDefExecutionRoleB4775C97", - "Arn" - ] - }, - "Family": "awsecsinteg2TaskDef1F38909D", - "Memory": "512", - "NetworkMode": "awsvpc", - "RequiresCompatibilities": [ - "FARGATE" - ], - "TaskRoleArn": { - "Fn::GetAtt": [ - "TaskDefTaskRole1EDB4A67", - "Arn" - ] - }, - "Volumes": [] - } - }, - "TaskDefExecutionRoleB4775C97": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - } - } - }, - "TaskDefExecutionRoleDefaultPolicy0DBB737A": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:BatchCheckLayerAvailability", - "ecr:GetDownloadUrlForLayer", - "ecr:BatchGetImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::GetAtt": [ - "EventImageAdoptRepositoryDFAAC242", - "RepositoryName" - ] - } - ] - ] - } - }, - { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": [ - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TaskLoggingLogGroupC7E938D4", - "Arn" - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "TaskDefExecutionRoleDefaultPolicy0DBB737A", - "Roles": [ - { - "Ref": "TaskDefExecutionRoleB4775C97" - } - ] - } - }, - "EventImageAdoptRepositoryDFAAC242": { - "Type": "Custom::ECRAdoptedRepository", - "Properties": { - "ServiceToken": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", - "Arn" - ] - }, - "RepositoryName": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Ref": "EventImageImageNameE972A8B1" - } - ] - } - ] - } - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ] - ] - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "ecr:GetRepositoryPolicy", - "ecr:SetRepositoryPolicy", - "ecr:DeleteRepository", - "ecr:ListImages", - "ecr:BatchDeleteImage" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":ecr:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":repository/", - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - ":", - { - "Ref": "EventImageImageNameE972A8B1" - } - ] - } - ] - } - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", - "Roles": [ - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" - } - ] - } - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6" - }, - "S3Key": { - "Fn::Join": [ - "", - [ - { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "||", - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" - } - ] - } - ] - }, - { - "Fn::Select": [ - 1, - { - "Fn::Split": [ - "||", - { - "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" - } - ] - } - ] - } - ] - ] - } - }, - "Handler": "handler.handler", - "Role": { - "Fn::GetAtt": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "Arn" - ] - }, - "Runtime": "nodejs8.10", - "Timeout": 300 - }, - "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C" - ] - }, - "TaskLoggingLogGroupC7E938D4": { - "Type": "AWS::Logs::LogGroup", - "Properties": { - "RetentionInDays": 365 - }, - "DeletionPolicy": "Retain" - }, - "RunFargateSecurityGroup709740F2": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "GroupDescription": "aws-ecs-integ2/RunFargate/SecurityGroup", - "SecurityGroupEgress": [ - { - "CidrIp": "0.0.0.0/0", - "Description": "Allow all outbound traffic by default", - "IpProtocol": "-1" - } - ], - "SecurityGroupIngress": [], - "VpcId": "vpc-60900905" - } - }, - "StateMachineRoleB840431D": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "states.", - { - "Ref": "AWS::Region" - }, - ".amazonaws.com" - ] - ] - } - } - } - ], - "Version": "2012-10-17" - } - } - }, - "StateMachineRoleDefaultPolicyDF1E6607": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "ecs:RunTask", - "Effect": "Allow", - "Resource": { - "Ref": "TaskDef54694570" - } - }, - { - "Action": [ - "ecs:StopTask", - "ecs:DescribeTasks" - ], - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": [ - { - "Fn::GetAtt": [ - "TaskDefTaskRole1EDB4A67", - "Arn" - ] - }, - { - "Fn::GetAtt": [ - "TaskDefExecutionRoleB4775C97", - "Arn" - ] - } - ] - }, - { - "Action": [ - "events:PutTargets", - "events:PutRule", - "events:DescribeRule" - ], - "Effect": "Allow", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":events:", - { - "Ref": "AWS::Region" - }, - ":", - { - "Ref": "AWS::AccountId" - }, - ":rule/StepFunctionsGetEventsForECSTaskRule" - ] - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", - "Roles": [ - { - "Ref": "StateMachineRoleB840431D" - } - ] - } - }, - "StateMachine2E01A3A5": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"RunFargate\"},\"RunFargate\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", - { - "Fn::GetAtt": [ - "FargateCluster7CCD5F93", - "Arn" - ] - }, - "\",\"TaskDefinition\":\"", - { - "Ref": "TaskDef54694570" - }, - "\",\"NetworkConfiguration\":{\"AwsvpcConfiguration\":{\"AssignPublicIp\":\"ENABLED\",\"Subnets\":[\"subnet-e19455ca\",\"subnet-e0c24797\",\"subnet-ccd77395\"],\"SecurityGroups\":[\"", - { - "Fn::GetAtt": [ - "RunFargateSecurityGroup709740F2", - "GroupId" - ] - }, - "\"]}},\"Overrides\":{\"ContainerOverrides\":[{\"Name\":\"TheContainer\",\"Environment\":[{\"Name\":\"SOME_KEY\",\"Value.$\":\"$.SomeKey\"}]}]},\"LaunchType\":\"FARGATE\"},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::ecs:runTask.sync\"}}}" - ] - ] - }, - "RoleArn": { - "Fn::GetAtt": [ - "StateMachineRoleB840431D", - "Arn" - ] - } - } - } - }, - "Parameters": { - "EventImageImageNameE972A8B1": { - "Type": "String", - "Description": "ECR repository name and tag asset \"aws-ecs-integ2/EventImage\"" - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6": { - "Type": "String", - "Description": "S3 bucket for asset \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" - }, - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276": { - "Type": "String", - "Description": "S3 key for asset version \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" - } - } -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.ts deleted file mode 100644 index 214a9394344f1..0000000000000 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.stepfunctions-task.ts +++ /dev/null @@ -1,50 +0,0 @@ -import ec2 = require('@aws-cdk/aws-ec2'); -import sfn = require('@aws-cdk/aws-stepfunctions'); -import cdk = require('@aws-cdk/cdk'); -import path = require('path'); -import ecs = require('../../lib'); - -const app = new cdk.App(); -const stack = new cdk.Stack(app, 'aws-ecs-integ2'); - -const vpc = ec2.VpcNetwork.importFromContext(stack, 'Vpc', { - isDefault: true -}); - -const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); - -// Build task definition -const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef', { - memoryMiB: '512', - cpu: '256' -}); -taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, '..', 'eventhandler-image') }), - memoryLimitMiB: 256, - logging: new ecs.AwsLogDriver(stack, 'TaskLogging', { streamPrefix: 'EventDemo' }) -}); - -// Build state machine -const definition = new sfn.Pass(stack, 'Start', { - result: { SomeKey: 'SomeValue' } -}).next(new ecs.FargateRunTask(stack, 'RunFargate', { - cluster, taskDefinition, - assignPublicIp: true, - containerOverrides: [ - { - containerName: 'TheContainer', - environment: [ - { - name: 'SOME_KEY', - valuePath: '$.SomeKey' - } - ] - } - ] -})); - -new sfn.StateMachine(stack, 'StateMachine', { - definition, -}); - -app.run(); \ No newline at end of file From b3cd0bc6b2bd09a8a45600b40de58bf6ca641bdb Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Sat, 27 Apr 2019 09:39:18 +0200 Subject: [PATCH 08/21] Move metrics away --- .../aws-stepfunctions-tasks/package.json | 46 +++--- .../aws-stepfunctions/lib/states/task.ts | 154 +----------------- tools/cdk-build-tools/package-lock.json | 41 ++--- 3 files changed, 35 insertions(+), 206 deletions(-) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index 96b551a330e66..9c75bed10733c 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-stepfunctions-tasks", - "version": "0.28.0", + "version": "0.29.0", "description": "Task integrations for AWS StepFunctions", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -70,34 +70,34 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.28.0", - "cdk-build-tools": "^0.28.0", - "cdk-integ-tools": "^0.28.0", - "pkglint": "^0.28.0", + "@aws-cdk/assert": "^0.29.0", + "cdk-build-tools": "^0.29.0", + "cdk-integ-tools": "^0.29.0", + "pkglint": "^0.29.0", "jest": "^24.7.1" }, "dependencies": { - "@aws-cdk/aws-cloudwatch": "^0.28.0", - "@aws-cdk/aws-ec2": "^0.28.0", - "@aws-cdk/aws-ecs": "^0.28.0", - "@aws-cdk/aws-stepfunctions": "^0.28.0", - "@aws-cdk/cdk": "^0.28.0", - "@aws-cdk/aws-iam": "^0.28.0", - "@aws-cdk/aws-lambda": "^0.28.0", - "@aws-cdk/aws-sns": "^0.28.0", - "@aws-cdk/aws-sqs": "^0.28.0" + "@aws-cdk/aws-cloudwatch": "^0.29.0", + "@aws-cdk/aws-ec2": "^0.29.0", + "@aws-cdk/aws-ecs": "^0.29.0", + "@aws-cdk/aws-stepfunctions": "^0.29.0", + "@aws-cdk/cdk": "^0.29.0", + "@aws-cdk/aws-iam": "^0.29.0", + "@aws-cdk/aws-lambda": "^0.29.0", + "@aws-cdk/aws-sns": "^0.29.0", + "@aws-cdk/aws-sqs": "^0.29.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.28.0", - "@aws-cdk/aws-ec2": "^0.28.0", - "@aws-cdk/aws-ecs": "^0.28.0", - "@aws-cdk/aws-stepfunctions": "^0.28.0", - "@aws-cdk/cdk": "^0.28.0", - "@aws-cdk/aws-iam": "^0.28.0", - "@aws-cdk/aws-lambda": "^0.28.0", - "@aws-cdk/aws-sns": "^0.28.0", - "@aws-cdk/aws-sqs": "^0.28.0" + "@aws-cdk/aws-cloudwatch": "^0.29.0", + "@aws-cdk/aws-ec2": "^0.29.0", + "@aws-cdk/aws-ecs": "^0.29.0", + "@aws-cdk/aws-stepfunctions": "^0.29.0", + "@aws-cdk/cdk": "^0.29.0", + "@aws-cdk/aws-iam": "^0.29.0", + "@aws-cdk/aws-lambda": "^0.29.0", + "@aws-cdk/aws-sns": "^0.29.0", + "@aws-cdk/aws-sqs": "^0.29.0" }, "engines": { "node": ">= 8.10.0" diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index b6cf266611a5a..5a6fa4f2868ee 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -175,162 +175,10 @@ export class Task extends State implements INextable { }; } - /** - * Return the given named metric for this Task - * - * @default sum over 5 minutes - */ - public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return new cloudwatch.Metric({ - namespace: 'AWS/States', - metricName, - dimensions: this.resourceProps.metricDimensions, - statistic: 'sum', - ...props - }); - } - - /** - * The interval, in milliseconds, between the time the Task starts and the time it closes. - * - * @default average over 5 minutes - */ - public metricRunTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixSingular, 'RunTime', { statistic: 'avg', ...props }); - } - - /** - * The interval, in milliseconds, for which the activity stays in the schedule state. - * - * @default average over 5 minutes - */ - public metricScheduleTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixSingular, 'ScheduleTime', { statistic: 'avg', ...props }); - } - - /** - * The interval, in milliseconds, between the time the activity is scheduled and the time it closes. - * - * @default average over 5 minutes - */ - public metricTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixSingular, 'Time', { statistic: 'avg', ...props }); - } - - /** - * Metric for the number of times this activity is scheduled - * - * @default sum over 5 minutes - */ - public metricScheduled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'Scheduled', props); - } - - /** - * Metric for the number of times this activity times out - * - * @default sum over 5 minutes - */ - public metricTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'TimedOut', props); - } - - /** - * Metric for the number of times this activity is started - * - * @default sum over 5 minutes - */ - public metricStarted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'Started', props); - } - - /** - * Metric for the number of times this activity succeeds - * - * @default sum over 5 minutes - */ - public metricSucceeded(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'Succeeded', props); - } - - /** - * Metric for the number of times this activity fails - * - * @default sum over 5 minutes - */ - public metricFailed(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'Failed', props); - } - - /** - * Metric for the number of times the heartbeat times out for this activity - * - * @default sum over 5 minutes - */ - public metricHeartbeatTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resourceProps.metricPrefixPlural, 'HeartbeatTimedOut', props); - } - protected onBindToGraph(graph: StateGraph) { super.onBindToGraph(graph); for (const policyStatement of this.policyStatements || []) { graph.registerPolicyStatement(policyStatement); } } - - private taskMetric(prefix: string | undefined, suffix: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { - if (prefix === undefined) { - throw new Error('This Task Resource does not expose metrics'); - } - return this.metric(prefix + suffix, props); - } -} - -/** - * Interface for objects that can be invoked in a Task state - */ -export interface IStepFunctionsTaskResource { - /** - * Return the properties required for using this object as a Task resource - */ - asStepFunctionsTaskResource(callingTask: Task): StepFunctionsTaskResourceProps; -} - -/** - * Properties that define how to refer to a TaskResource - */ -export interface StepFunctionsTaskResourceProps { - /** - * The ARN of the resource - */ - readonly resourceArn: string; - - /** - * Additional policy statements to add to the execution role - * - * @default No policy roles - */ - readonly policyStatements?: iam.PolicyStatement[]; - - /** - * Prefix for singular metric names of activity actions - * - * @default No such metrics - */ - readonly metricPrefixSingular?: string; - - /** - * Prefix for plural metric names of activity actions - * - * @default No such metrics - */ - readonly metricPrefixPlural?: string; - - /** - * The dimensions to attach to metrics - * - * @default No metrics - */ - readonly metricDimensions?: cloudwatch.DimensionHash; -} ->>>>>>> origin/master +} \ No newline at end of file diff --git a/tools/cdk-build-tools/package-lock.json b/tools/cdk-build-tools/package-lock.json index 0e2acbcb30750..dbd8ee185dbf6 100644 --- a/tools/cdk-build-tools/package-lock.json +++ b/tools/cdk-build-tools/package-lock.json @@ -1719,8 +1719,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -1738,13 +1737,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1757,18 +1754,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -1871,8 +1865,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -1882,7 +1875,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1895,20 +1887,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1925,7 +1914,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -1998,8 +1986,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -2009,7 +1996,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -2085,8 +2071,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -2116,7 +2101,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2134,7 +2118,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2173,13 +2156,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, From 7766d0edad89caec7af3b4eb57897fcb94075243 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Sat, 27 Apr 2019 13:48:25 +0200 Subject: [PATCH 09/21] Adding tests --- packages/@aws-cdk/assert/jest.ts | 62 +- .../assert/lib/assertions/have-resource.ts | 24 +- .../aws-stepfunctions-tasks/.npmignore | 2 +- .../lib/base-run-task.ts | 107 +- .../aws-stepfunctions-tasks/lib/index.ts | 5 +- .../lib/invoke-activity.ts | 22 +- .../lib/invoke-function.ts | 22 +- .../lib/publish-to-topic.ts | 14 +- .../{ec2-run-task.ts => run-ecs-ec2-task.ts} | 33 +- ...te-run-task.ts => run-ecs-fargate-task.ts} | 26 +- .../lib/send-to-queue.ts | 18 +- .../aws-stepfunctions-tasks/package.json | 2 +- .../test/ecs-tasks.test.ts | 209 ++++ .../test/integ.ec2-task.expected.json | 937 ++++++++++++++++++ .../test/integ.ec2-task.ts | 51 + .../test/integ.fargate-task.expected.json | 619 ++++++++++++ .../test/integ.fargate-task.ts | 51 + .../aws-stepfunctions/lib/states/task.ts | 1 + tools/cdk-build-tools/bin/cdk-test.ts | 2 +- 19 files changed, 2058 insertions(+), 149 deletions(-) rename packages/@aws-cdk/aws-stepfunctions-tasks/lib/{ec2-run-task.ts => run-ecs-ec2-task.ts} (82%) rename packages/@aws-cdk/aws-stepfunctions-tasks/lib/{fargate-run-task.ts => run-ecs-fargate-task.ts} (64%) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts diff --git a/packages/@aws-cdk/assert/jest.ts b/packages/@aws-cdk/assert/jest.ts index d848d6fb70d4f..03c5778750ad3 100644 --- a/packages/@aws-cdk/assert/jest.ts +++ b/packages/@aws-cdk/assert/jest.ts @@ -1,6 +1,6 @@ import { Stack } from "@aws-cdk/cdk"; import { SynthesizedStack } from "@aws-cdk/cx-api"; -import { haveResource, ResourcePart } from "./lib/assertions/have-resource"; +import { HaveResourceAssertion, ResourcePart } from "./lib/assertions/have-resource"; import { expect as ourExpect } from './lib/expect'; declare global { @@ -8,32 +8,48 @@ declare global { interface Matchers { toHaveResource(resourceType: string, properties?: any, - comparison?: ResourcePart, - allowValueExtension?: boolean): R; + comparison?: ResourcePart): R; + + toHaveResourceLike(resourceType: string, + properties?: any, + comparison?: ResourcePart): R; } } } expect.extend({ - toHaveResource(actual: SynthesizedStack | Stack, - resourceType: string, - properties?: any, - comparison?: ResourcePart, - allowValueExtension: boolean = false) { + toHaveResource( + actual: SynthesizedStack | Stack, + resourceType: string, + properties?: any, + comparison?: ResourcePart) { - const assertion = haveResource(resourceType, properties, comparison, allowValueExtension); - const inspector = ourExpect(actual); - const pass = assertion.assertUsing(inspector); - if (pass) { - return { - pass, - message: `Expected ${JSON.stringify(inspector.value, null, 2)} not to match ${assertion.description}`, - }; - } else { - return { - pass, - message: `Expected ${JSON.stringify(inspector.value, null, 2)} to match ${assertion.description}`, - }; - } + const assertion = new HaveResourceAssertion(resourceType, properties, comparison, false); + return assertHaveResource(assertion, actual); + }, + toHaveResourceLike( + actual: SynthesizedStack | Stack, + resourceType: string, + properties?: any, + comparison?: ResourcePart) { + + const assertion = new HaveResourceAssertion(resourceType, properties, comparison, true); + return assertHaveResource(assertion, actual); + } +}); + +function assertHaveResource(assertion: HaveResourceAssertion, actual: SynthesizedStack | Stack) { + const inspector = ourExpect(actual); + const pass = assertion.assertUsing(inspector); + if (pass) { + return { + pass, + message: () => `Not ` + assertion.generateErrorMessage(), + }; + } else { + return { + pass, + message: () => assertion.generateErrorMessage(), + }; } -}); \ No newline at end of file +} \ No newline at end of file diff --git a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts index 6b4c6d2ad294a..d6653f3b64ce6 100644 --- a/packages/@aws-cdk/assert/lib/assertions/have-resource.ts +++ b/packages/@aws-cdk/assert/lib/assertions/have-resource.ts @@ -30,7 +30,7 @@ export function haveResourceLike(resourceType: string, type PropertyPredicate = (props: any, inspection: InspectionFailure) => boolean; -class HaveResourceAssertion extends Assertion { +export class HaveResourceAssertion extends Assertion { private inspected: InspectionFailure[] = []; private readonly part: ResourcePart; private readonly predicate: PropertyPredicate; @@ -66,17 +66,21 @@ class HaveResourceAssertion extends Assertion { return false; } - public assertOrThrow(inspector: StackInspector) { - if (!this.assertUsing(inspector)) { - const lines: string[] = []; - lines.push(`None of ${this.inspected.length} resources matches ${this.description}.`); + public generateErrorMessage() { + const lines: string[] = []; + lines.push(`None of ${this.inspected.length} resources matches ${this.description}.`); - for (const inspected of this.inspected) { - lines.push(`- ${inspected.failureReason} in:`); - lines.push(indent(4, JSON.stringify(inspected.resource, null, 2))); - } + for (const inspected of this.inspected) { + lines.push(`- ${inspected.failureReason} in:`); + lines.push(indent(4, JSON.stringify(inspected.resource, null, 2))); + } - throw new Error(lines.join('\n')); + return lines.join('\n'); + } + + public assertOrThrow(inspector: StackInspector) { + if (!this.assertUsing(inspector)) { + throw new Error(this.generateErrorMessage()); } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore b/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore index 3f1f9c81eee9b..323f9e5fb74ed 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/.npmignore @@ -14,4 +14,4 @@ dist !.jsii *.snk -tsconfig.* \ No newline at end of file +*.tsbuildinfo diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts index bb8e4692c8d01..58d2ff76c4ab4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts @@ -7,16 +7,16 @@ import cdk = require('@aws-cdk/cdk'); /** * Properties for SendMessageTask */ -export interface BaseRunTaskProps extends stepfunctions.BasicTaskProps { +export interface CommonRunTaskProps extends stepfunctions.BasicTaskProps { /** * The topic to run the task on */ - cluster: ecs.ICluster; + readonly cluster: ecs.ICluster; /** * Task Definition used for running tasks in the service */ - taskDefinition: ecs.TaskDefinition; + readonly taskDefinition: ecs.TaskDefinition; /** * Container setting overrides @@ -24,14 +24,24 @@ export interface BaseRunTaskProps extends stepfunctions.BasicTaskProps { * Key is the name of the container to override, value is the * values you want to override. */ - containerOverrides?: ContainerOverride[]; + readonly containerOverrides?: ContainerOverride[]; /** * Whether to wait for the task to complete and return the response * * @default true */ - synchronous?: boolean; + readonly synchronous?: boolean; +} + +/** + * Construction properties for the BaseRunTaskProps + */ +export interface BaseRunTaskProps extends CommonRunTaskProps { + /** + * Additional parameters to pass to the base task + */ + readonly parameters?: {[key: string]: any}; } export interface ContainerOverride { @@ -40,75 +50,75 @@ export interface ContainerOverride { * * Exactly one of `containerName` and `containerNamePath` is required. */ - containerName?: string; + readonly containerName?: string; /** * JSONPath expression for the name of the container inside the task definition * * Exactly one of `containerName` and `containerNamePath` is required. */ - containerNamePath?: string; + readonly containerNamePath?: string; /** * Command to run inside the container * * @default Default command */ - command?: string[]; + readonly command?: string[]; /** * JSON expression for command to run inside the container * * @default Default command */ - commandPath?: string; + readonly commandPath?: string; /** * Variables to set in the container's environment */ - environment?: TaskEnvironmentVariable[]; + readonly environment?: TaskEnvironmentVariable[]; /** * The number of cpu units reserved for the container * * @Default The default value from the task definition. */ - cpu?: number; + readonly cpu?: number; /** * JSON expression for the number of CPU units * * @Default The default value from the task definition. */ - cpuPath?: string; + readonly cpuPath?: string; /** * Hard memory limit on the container * * @Default The default value from the task definition. */ - memoryLimit?: number; + readonly memoryLimit?: number; /** * JSON expression path for the hard memory limit * * @Default The default value from the task definition. */ - memoryLimitPath?: string; + readonly memoryLimitPath?: string; /** * Soft memory limit on the container * * @Default The default value from the task definition. */ - memoryReservation?: number; + readonly memoryReservation?: number; /** * JSONExpr path for memory limit on the container * * @Default The default value from the task definition. */ - memoryReservationPath?: number; + readonly memoryReservationPath?: number; } /** @@ -120,28 +130,28 @@ export interface TaskEnvironmentVariable { * * Exactly one of `name` and `namePath` must be specified. */ - name?: string; + readonly name?: string; /** * JSONExpr for the name of the variable * * Exactly one of `name` and `namePath` must be specified. */ - namePath?: string; + readonly namePath?: string; /** * Value of the environment variable * * Exactly one of `value` and `valuePath` must be specified. */ - value?: string; + readonly value?: string; /** * JSONPath expr for the environment variable * * Exactly one of `value` and `valuePath` must be specified. */ - valuePath?: string; + readonly valuePath?: string; } /** @@ -154,7 +164,6 @@ export class BaseRunTask extends stepfunctions.Task implements ec2.IConnectable public readonly connections: ec2.Connections = new ec2.Connections(); protected networkConfiguration?: any; - protected readonly _parameters: {[key: string]: any} = {}; protected readonly taskDefinition: ecs.TaskDefinition; private readonly sync: boolean; @@ -162,26 +171,26 @@ export class BaseRunTask extends stepfunctions.Task implements ec2.IConnectable super(scope, id, { ...props, resourceArn: 'arn:aws:states:::ecs:runTask' + (props.synchronous !== false ? '.sync' : ''), - parameters: new cdk.Token(() => ({ + parameters: { Cluster: props.cluster.clusterArn, TaskDefinition: props.taskDefinition.taskDefinitionArn, - NetworkConfiguration: this.networkConfiguration, - ...this._parameters - })) + NetworkConfiguration: new cdk.Token(() => this.networkConfiguration), + Overrides: renderOverrides(props.containerOverrides), + ...props.parameters, + } }); this.sync = props.synchronous !== false; - this._parameters.Overrides = this.renderOverrides(props.containerOverrides); this.taskDefinition = props.taskDefinition; } protected configureAwsVpcNetworking( vpc: ec2.IVpcNetwork, assignPublicIp?: boolean, - subnetSelection?: ec2.VpcSubnetSelection, + subnetSelection?: ec2.SubnetSelection, securityGroup?: ec2.ISecurityGroup) { if (subnetSelection === undefined) { - subnetSelection = { subnetsToUse: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; + subnetSelection = { subnetType: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; } if (securityGroup === undefined) { securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); @@ -225,27 +234,10 @@ export class BaseRunTask extends stepfunctions.Task implements ec2.IConnectable resourceName: 'StepFunctionsGetEventsForECSTaskRule' }))); } - } - private renderOverrides(containerOverrides?: ContainerOverride[]) { - if (!containerOverrides) { return undefined; } - - const ret = new Array(); - for (const override of containerOverrides) { - ret.push({ - ...extractRequired(override, 'containerName', 'Name'), - ...extractOptional(override, 'command', 'Command'), - ...extractOptional(override, 'cpu', 'Cpu'), - ...extractOptional(override, 'memoryLimit', 'Memory'), - ...extractOptional(override, 'memoryReservation', 'MemoryReservation'), - Environment: override.environment && override.environment.map(e => ({ - ...extractRequired(e, 'name', 'Name'), - ...extractRequired(e, 'value', 'Value'), - })) - }); + for (const policyStatement of policyStatements) { + graph.registerPolicyStatement(policyStatement); } - - return { ContainerOverrides: ret }; } private taskExecutionRoles(): iam.IRole[] { @@ -266,6 +258,27 @@ function extractRequired(obj: any, srcKey: string, dstKey: string) { return mapValue(obj, srcKey, dstKey); } +function renderOverrides(containerOverrides?: ContainerOverride[]) { + if (!containerOverrides) { return undefined; } + + const ret = new Array(); + for (const override of containerOverrides) { + ret.push({ + ...extractRequired(override, 'containerName', 'Name'), + ...extractOptional(override, 'command', 'Command'), + ...extractOptional(override, 'cpu', 'Cpu'), + ...extractOptional(override, 'memoryLimit', 'Memory'), + ...extractOptional(override, 'memoryReservation', 'MemoryReservation'), + Environment: override.environment && override.environment.map(e => ({ + ...extractRequired(e, 'name', 'Name'), + ...extractRequired(e, 'value', 'Value'), + })) + }); + } + + return { ContainerOverrides: ret }; +} + function extractOptional(obj: any, srcKey: string, dstKey: string) { if ((obj[srcKey] !== undefined) && (obj[srcKey + 'Path'] !== undefined)) { throw new Error(`Supply only one of '${srcKey}' or '${srcKey}Path'`); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index 665ddf6f26f11..02d3b691fc0aa 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -1,7 +1,8 @@ export * from './invoke-function'; export * from './invoke-activity'; export * from './nested-state-machine'; +export * from './base-run-task'; export * from './publish-to-topic'; export * from './send-to-queue'; -export * from './ec2-run-task'; -export * from './fargate-run-task'; +export * from './run-ecs-ec2-task'; +export * from './run-ecs-fargate-task'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts index 0594463680d2d..4fbe4671691ee 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts @@ -12,7 +12,7 @@ export interface InvokeActivityProps extends stepfunctions.BasicTaskProps { /** * The activity to invoke */ - activity: stepfunctions.IActivity; + readonly activity: stepfunctions.IActivity; /** * Maximum time between heart beats @@ -49,7 +49,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, @@ -64,7 +64,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default average over 5 minutes */ - public metricRunTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricRunTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_SINGULAR + 'RunTime', { statistic: 'avg', ...props }); } @@ -73,7 +73,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default average over 5 minutes */ - public metricScheduleTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricScheduleTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_SINGULAR + 'ScheduleTime', { statistic: 'avg', ...props }); } @@ -82,7 +82,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default average over 5 minutes */ - public metricTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_SINGULAR + 'Time', { statistic: 'avg', ...props }); } @@ -91,7 +91,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricScheduled(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricScheduled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'Scheduled', props); } @@ -100,7 +100,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'TimedOut', props); } @@ -109,7 +109,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricStarted(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricStarted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'Started', props); } @@ -118,7 +118,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricSucceeded(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricSucceeded(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'Succeeded', props); } @@ -127,7 +127,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricFailed(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricFailed(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'Failed', props); } @@ -136,7 +136,7 @@ export class InvokeActivity extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricHeartbeatTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricHeartbeatTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'HeartbeatTimedOut', props); } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts index 77e990b81dbe6..cc7f9a71da7ef 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts @@ -14,7 +14,7 @@ export interface InvokeFunctionProps extends stepfunctions.BasicTaskProps { /** * The function to run */ - function: lambda.IFunction; + readonly function: lambda.IFunction; } /** @@ -44,7 +44,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, @@ -59,7 +59,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default average over 5 minutes */ - public metricRunTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricRunTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_SINGULAR + 'RunTime', { statistic: 'avg', ...props }); } @@ -68,7 +68,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default average over 5 minutes */ - public metricScheduleTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricScheduleTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_SINGULAR + 'ScheduleTime', { statistic: 'avg', ...props }); } @@ -77,7 +77,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default average over 5 minutes */ - public metricTime(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_SINGULAR + 'Time', { statistic: 'avg', ...props }); } @@ -86,7 +86,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricScheduled(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricScheduled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'Scheduled', props); } @@ -95,7 +95,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'TimedOut', props); } @@ -104,7 +104,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricStarted(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricStarted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'Started', props); } @@ -113,7 +113,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricSucceeded(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricSucceeded(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'Succeeded', props); } @@ -122,7 +122,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricFailed(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricFailed(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'Failed', props); } @@ -131,7 +131,7 @@ export class InvokeFunction extends stepfunctions.Task { * * @default sum over 5 minutes */ - public metricHeartbeatTimedOut(props?: cloudwatch.MetricCustomization): cloudwatch.Metric { + public metricHeartbeatTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { return this.metric(METRIC_PREFIX_PLURAL + 'HeartbeatTimedOut', props); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts index 195e0659d1cd8..2d1c0ef6655fd 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts @@ -10,28 +10,28 @@ export interface PublishTaskProps extends stepfunctions.BasicTaskProps { /** * The topic to publish to */ - topic: sns.ITopic; + readonly topic: sns.ITopic; /** * The text message to send to the queue. * * Exactly one of `message`, `messageObject` and `messagePath` is required. */ - message?: string; + readonly message?: string; /** * JSONPath expression of the message to send to the queue * * Exactly one of `message`, `messageObject` and `messagePath` is required. */ - messagePath?: string; + readonly messagePath?: string; /** * Object to be JSON-encoded and used as message * * Exactly one of `message`, `messageObject` and `messagePath` is required. */ - messageObject?: string; + readonly messageObject?: string; /** * If true, send a different message to every subscription type @@ -43,17 +43,17 @@ export interface PublishTaskProps extends stepfunctions.BasicTaskProps { * * @see https://docs.aws.amazon.com/sns/latest/api/API_Publish.html#API_Publish_RequestParameters */ - messagePerSubscriptionType?: boolean; + readonly messagePerSubscriptionType?: boolean; /** * Message subject */ - subject?: string; + readonly subject?: string; /** * JSONPath expression of subject */ - subjectPath?: string; + readonly subjectPath?: string; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ec2-run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts similarity index 82% rename from packages/@aws-cdk/aws-stepfunctions-tasks/lib/ec2-run-task.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts index 8cd04b38ce8aa..ab1ec827be63d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ec2-run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts @@ -1,12 +1,12 @@ import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import cdk = require('@aws-cdk/cdk'); -import { BaseRunTask, BaseRunTaskProps } from './base-run-task'; +import { BaseRunTask, CommonRunTaskProps } from './base-run-task'; /** * Properties to run an ECS task on EC2 in StepFunctionsan ECS */ -export interface Ec2RunTaskProps extends BaseRunTaskProps { +export interface RunEcsEc2TaskProps extends CommonRunTaskProps { /** * In what subnets to place the task's ENIs * @@ -14,7 +14,7 @@ export interface Ec2RunTaskProps extends BaseRunTaskProps { * * @default Private subnets */ - vpcPlacement?: ec2.VpcPlacementStrategy; + readonly subnets?: ec2.SubnetSelection; /** * Existing security group to use for the task's ENIs @@ -23,25 +23,25 @@ export interface Ec2RunTaskProps extends BaseRunTaskProps { * * @default A new security group is created */ - securityGroup?: ec2.ISecurityGroup; + readonly securityGroup?: ec2.ISecurityGroup; /** * Whether to start services on distinct instances * * @default false */ - placeOnDistinctInstances?: boolean; + readonly placeOnDistinctInstances?: boolean; } /** * Run an ECS/EC2 Task in a StepFunctions workflow */ -export class Ec2RunTask extends BaseRunTask { +export class RunEcsEc2Task extends BaseRunTask { private readonly constraints: any[]; private readonly strategies: any[]; private readonly cluster: ecs.ICluster; - constructor(scope: cdk.Construct, id: string, props: Ec2RunTaskProps) { + constructor(scope: cdk.Construct, id: string, props: RunEcsEc2TaskProps) { if (!props.taskDefinition.isEc2Compatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } @@ -50,18 +50,21 @@ export class Ec2RunTask extends BaseRunTask { throw new Error('A TaskDefinition must have at least one essential container'); } - super(scope, id, props); + super(scope, id, { + ...props, + parameters: { + LaunchType: 'EC2', + PlacementConstraints: new cdk.Token(() => this.constraints.length > 0 ? this.constraints : undefined), + PlacementStrategy: new cdk.Token(() => this.constraints.length > 0 ? this.strategies : undefined), + } + }); this.cluster = props.cluster; this.constraints = []; this.strategies = []; - this._parameters.LaunchType = 'EC2'; - this._parameters.PlacementConstraints = new cdk.Token(() => this.constraints.length > 0 ? this.constraints : undefined); - this._parameters.PlacementStrategy = new cdk.Token(() => this.constraints.length > 0 ? this.strategies : undefined); - if (props.taskDefinition.networkMode === ecs.NetworkMode.AwsVpc) { - this.configureAwsVpcNetworking(props.cluster.vpc, false, props.vpcPlacement, props.securityGroup); + this.configureAwsVpcNetworking(props.cluster.vpc, false, props.subnets, props.securityGroup); } else { // Either None, Bridge or Host networking. Copy SecurityGroup from ASG. validateNoNetworkingProps(props); @@ -143,8 +146,8 @@ export class Ec2RunTask extends BaseRunTask { /** * Validate combinations of networking arguments */ -function validateNoNetworkingProps(props: Ec2RunTaskProps) { - if (props.vpcPlacement !== undefined || props.securityGroup !== undefined) { +function validateNoNetworkingProps(props: RunEcsEc2TaskProps) { + if (props.subnets !== undefined || props.securityGroup !== undefined) { throw new Error('vpcPlacement and securityGroup can only be used in AwsVpc networking mode'); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/fargate-run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts similarity index 64% rename from packages/@aws-cdk/aws-stepfunctions-tasks/lib/fargate-run-task.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts index 45126cd65bdc0..8a23870a2939d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/fargate-run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts @@ -1,32 +1,32 @@ import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import cdk = require('@aws-cdk/cdk'); -import { BaseRunTask, BaseRunTaskProps } from './base-run-task'; +import { BaseRunTask, CommonRunTaskProps } from './base-run-task'; /** * Properties to define an ECS service */ -export interface FargateRunTaskProps extends BaseRunTaskProps { +export interface RunEcsFargateTaskProps extends CommonRunTaskProps { /** * Assign public IP addresses to each task * * @default false */ - assignPublicIp?: boolean; + readonly assignPublicIp?: boolean; /** * In what subnets to place the task's ENIs * * @default Private subnet if assignPublicIp, public subnets otherwise */ - vpcPlacement?: ec2.VpcPlacementStrategy; + readonly subnets?: ec2.SubnetSelection; /** * Existing security group to use for the tasks * * @default A new security group is created */ - securityGroup?: ec2.ISecurityGroup; + readonly securityGroup?: ec2.ISecurityGroup; /** * Fargate platform version to run this service on @@ -36,14 +36,14 @@ export interface FargateRunTaskProps extends BaseRunTaskProps { * * @default Latest */ - platformVersion?: ecs.FargatePlatformVersion; + readonly platformVersion?: ecs.FargatePlatformVersion; } /** * Start a service on an ECS cluster */ -export class FargateRunTask extends BaseRunTask { - constructor(scope: cdk.Construct, id: string, props: FargateRunTaskProps) { +export class RunEcsFargateTask extends BaseRunTask { + constructor(scope: cdk.Construct, id: string, props: RunEcsFargateTaskProps) { if (!props.taskDefinition.isFargateCompatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } @@ -52,9 +52,13 @@ export class FargateRunTask extends BaseRunTask { throw new Error('A TaskDefinition must have at least one essential container'); } - super(scope, id, props); + super(scope, id, { + ...props, + parameters: { + LaunchType: 'FARGATE', + }, + }); - this._parameters.LaunchType = 'FARGATE'; - this.configureAwsVpcNetworking(props.cluster.vpc, props.assignPublicIp, props.vpcPlacement, props.securityGroup); + this.configureAwsVpcNetworking(props.cluster.vpc, props.assignPublicIp, props.subnets, props.securityGroup); } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts index ccb00e0f464fd..0a8168c1d9286 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts @@ -10,21 +10,21 @@ export interface SendMessageTaskProps extends stepfunctions.BasicTaskProps { /** * The topic to send a message to to */ - queue: sqs.IQueue; + readonly queue: sqs.IQueue; /** * The message body to send to the queue. * * Exactly one of `messageBody` and `messageBodyPath` is required. */ - messageBody?: string; + readonly messageBody?: string; /** * JSONPath for the message body to send to the queue. * * Exactly one of `messageBody` and `messageBodyPath` is required. */ - messageBodyPath?: string; + readonly messageBodyPath?: string; /** * The length of time, in seconds, for which to delay a specific message. @@ -33,28 +33,28 @@ export interface SendMessageTaskProps extends stepfunctions.BasicTaskProps { * * @default Default value of the queue is used */ - delaySeconds?: number; + readonly delaySeconds?: number; /** * JSONPath expression for delaySeconds setting * * @default Default value of the queue is used */ - delaySecondsPath?: string; + readonly delaySecondsPath?: string; /** * The token used for deduplication of sent messages. * * @default Use content-based deduplication */ - messageDeduplicationId?: string; + readonly messageDeduplicationId?: string; /** * JSONPath expression for deduplication ID * * @default Use content-based deduplication */ - messageDeduplicationIdPath?: string; + readonly messageDeduplicationIdPath?: string; /** * The tag that specifies that a message belongs to a specific message group. @@ -64,14 +64,14 @@ export interface SendMessageTaskProps extends stepfunctions.BasicTaskProps { * * @default No group ID */ - messageGroupId?: string; + readonly messageGroupId?: string; /** * JSONPath expression for message group ID * * @default No group ID */ - messageGroupIdPath?: string; + readonly messageGroupIdPath?: string; } /** diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index 9c75bed10733c..183f26bf1bd36 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -23,7 +23,7 @@ "sphinx": {}, "python": { "distName": "aws-cdk.aws-stepfunctions-tasks", - "module": "aws_cdk.aws_stepfunctions.tasks" + "module": "aws_cdk.aws_stepfunctions_tasks" } } }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts new file mode 100644 index 0000000000000..1ad5ccec506d6 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts @@ -0,0 +1,209 @@ +import '@aws-cdk/assert/jest'; +import ec2 = require('@aws-cdk/aws-ec2'); +import ecs = require('@aws-cdk/aws-ecs'); +import sfn = require('@aws-cdk/aws-stepfunctions'); +import { Stack } from '@aws-cdk/cdk'; +import tasks = require('../lib'); + +test('Running a Fargate Task', () => { + // GIVEN + const stack = new Stack(); + + const vpc = new ec2.VpcNetwork(stack, 'Vpc'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + memoryMiB: '512', + cpu: '256', + compatibility: ecs.Compatibility.Fargate + }); + taskDefinition.addContainer('henk', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); + + // WHEN + const runTask = new tasks.RunEcsFargateTask(stack, 'Run', { + cluster, + taskDefinition, + containerOverrides: [ + { + containerName: 'TheContainer', + environment: [ + {name: 'SOME_KEY', valuePath: '$.SomeKey'} + ] + } + ] + }); + + new sfn.StateMachine(stack, 'SM', { + definition: runTask + }); + + // THEN + expect(stack.node.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: {"Fn::GetAtt": ["ClusterEB0386A7", "Arn"]}, + LaunchType: "FARGATE", + NetworkConfiguration: { + AwsvpcConfiguration: { + AssignPublicIp: "DISABLED", + SecurityGroups: [{"Fn::GetAtt": ["RunSecurityGroupFBA8EDA8", "GroupId"]}], + Subnets: [ + {Ref: "VpcPrivateSubnet1Subnet536B997A"}, + {Ref: "VpcPrivateSubnet2Subnet3788AAA1"}, + {Ref: "VpcPrivateSubnet3SubnetF258B56E"}, + ] + }, + }, + TaskDefinition: {Ref: "TD49C78F36"}, + Overrides: { + ContainerOverrides: [ + { + Environment: [ + { + "Name": "SOME_KEY", + "Value.$": "$.SomeKey", + }, + ], + Name: "TheContainer", + }, + ], + }, + }, + Resource: "arn:aws:states:::ecs:runTask.sync", + Type: "Task", + }); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "ecs:RunTask", + Effect: "Allow", + Resource: {Ref: "TD49C78F36"} + }, + { + Action: ["ecs:StopTask", "ecs:DescribeTasks"], + Effect: "Allow", + Resource: "*" + }, + { + Action: "iam:PassRole", + Effect: "Allow", + Resource: [{"Fn::GetAtt": ["TDTaskRoleC497AFFC", "Arn"]}] + }, + { + Action: ["events:PutTargets", "events:PutRule", "events:DescribeRule"], + Effect: "Allow", + Resource: {"Fn::Join": ["", [ + "arn:", + {Ref: "AWS::Partition"}, + ":events:", + {Ref: "AWS::Region"}, + ":", + {Ref: "AWS::AccountId"}, + ":rule/StepFunctionsGetEventsForECSTaskRule" + ]]} + } + ], + }, + }); +}); + +test('Running an EC2 Task with bridge network', () => { + // GIVEN + const stack = new Stack(); + + const vpc = new ec2.VpcNetwork(stack, 'Vpc'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('Capacity', { + instanceType: new ec2.InstanceType('t3.medium') + }); + + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + compatibility: ecs.Compatibility.Ec2 + }); + taskDefinition.addContainer('henk', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); + + // WHEN + const runTask = new tasks.RunEcsEc2Task(stack, 'Run', { + cluster, + taskDefinition, + containerOverrides: [ + { + containerName: 'TheContainer', + environment: [ + {name: 'SOME_KEY', valuePath: '$.SomeKey'} + ] + } + ] + }); + + new sfn.StateMachine(stack, 'SM', { + definition: runTask + }); + + // THEN + expect(stack.node.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: {"Fn::GetAtt": ["ClusterEB0386A7", "Arn"]}, + LaunchType: "EC2", + TaskDefinition: {Ref: "TD49C78F36"}, + Overrides: { + ContainerOverrides: [ + { + Environment: [ + { + "Name": "SOME_KEY", + "Value.$": "$.SomeKey", + }, + ], + Name: "TheContainer", + }, + ], + }, + }, + Resource: "arn:aws:states:::ecs:runTask.sync", + Type: "Task", + }); + + expect(stack).toHaveResourceLike('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "ecs:RunTask", + Effect: "Allow", + Resource: {Ref: "TD49C78F36"} + }, + { + Action: ["ecs:StopTask", "ecs:DescribeTasks"], + Effect: "Allow", + Resource: "*" + }, + { + Action: "iam:PassRole", + Effect: "Allow", + Resource: [{"Fn::GetAtt": ["TDTaskRoleC497AFFC", "Arn"]}] + }, + { + Action: ["events:PutTargets", "events:PutRule", "events:DescribeRule"], + Effect: "Allow", + Resource: {"Fn::Join": ["", [ + "arn:", + {Ref: "AWS::Partition"}, + ":events:", + {Ref: "AWS::Region"}, + ":", + {Ref: "AWS::AccountId"}, + ":rule/StepFunctionsGetEventsForECSTaskRule" + ]]} + } + ], + }, + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json new file mode 100644 index 0000000000000..630b75193813f --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json @@ -0,0 +1,937 @@ +{ + "Resources": { + "FargateCluster7CCD5F93": { + "Type": "AWS::ECS::Cluster" + }, + "FargateClusterDefaultAutoScalingGroupInstanceSecurityGroup42AF8A40": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup" + } + ], + "VpcId": "vpc-60900905" + } + }, + "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ec2.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecs:CreateCluster", + "ecs:DeregisterContainerInstance", + "ecs:DiscoverPollEndpoint", + "ecs:Poll", + "ecs:RegisterContainerInstance", + "ecs:StartTelemetrySession", + "ecs:Submit*", + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E", + "Roles": [ + { + "Ref": "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7" + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupInstanceProfile2C0FEF3B": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7" + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupLaunchConfig57306899": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": "ami-1234", + "InstanceType": "t2.micro", + "IamInstanceProfile": { + "Ref": "FargateClusterDefaultAutoScalingGroupInstanceProfile2C0FEF3B" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupInstanceSecurityGroup42AF8A40", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\necho ECS_CLUSTER=", + { + "Ref": "FargateCluster7CCD5F93" + }, + " >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config" + ] + ] + } + } + }, + "DependsOn": [ + "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7", + "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E" + ] + }, + "FargateClusterDefaultAutoScalingGroupASG36A4948F": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "DesiredCapacity": "1", + "LaunchConfigurationName": { + "Ref": "FargateClusterDefaultAutoScalingGroupLaunchConfig57306899" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup" + } + ], + "VPCZoneIdentifier": [ + "subnet-e19455ca", + "subnet-e0c24797", + "subnet-ccd77395" + ] + }, + "UpdatePolicy": { + "AutoScalingReplacingUpdate": { + "WillReplace": true + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA": { + "Type": "AWS::SNS::Topic" + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookTopicFunctionSubscription129830E9": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8", + "Arn" + ] + }, + "Protocol": "lambda", + "TopicArn": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" + } + } + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "autoscaling:CompleteLifecycleAction", + "ec2:DescribeInstances", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceStatus", + "ec2:DescribeHosts" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecs:ListContainerInstances", + "ecs:SubmitContainerStateChange", + "ecs:SubmitTaskStateChange", + "ecs:DescribeContainerInstances", + "ecs:UpdateContainerInstancesState", + "ecs:ListTasks", + "ecs:DescribeTasks" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343", + "Roles": [ + { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32" + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n while has_tasks(cluster, instance_arn):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\n\ndef has_tasks(cluster, instance_arn):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n tasks = instance['runningTasksCount'] + instance['pendingTasksCount']\n print('Instance %s has %s tasks' % (instance_arn, tasks))\n\n return tasks > 0\n\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n" + }, + "Handler": "index.lambda_handler", + "Role": { + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32", + "Arn" + ] + }, + "Runtime": "python3.6", + "Environment": { + "Variables": { + "CLUSTER": { + "Ref": "FargateCluster7CCD5F93" + } + } + }, + "Timeout": 310 + }, + "DependsOn": [ + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32", + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343" + ] + }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicA1F1F9E9": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8" + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" + } + } + }, + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "autoscaling.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D", + "Roles": [ + { + "Ref": "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D" + } + ] + } + }, + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHook2AE13680": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "FargateClusterDefaultAutoScalingGroupASG36A4948F" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", + "DefaultResult": "CONTINUE", + "HeartbeatTimeout": 300, + "NotificationTargetARN": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" + }, + "RoleARN": { + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D", + "Arn" + ] + } + }, + "DependsOn": [ + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D", + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D" + ] + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 4, + { + "Fn::Split": [ + ":", + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + ] + } + ] + }, + ".dkr.ecr.", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + ] + } + ] + }, + ".amazonaws.com/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + }, + ":", + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + ] + ] + }, + "Links": [], + "LinuxParameters": { + "Capabilities": { + "Add": [], + "Drop": [] + }, + "Devices": [], + "Tmpfs": [] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "TaskLoggingLogGroupC7E938D4" + }, + "awslogs-stream-prefix": "EventDemo", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Memory": 256, + "MountPoints": [], + "Name": "TheContainer", + "PortMappings": [], + "Ulimits": [], + "VolumesFrom": [] + } + ], + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + }, + "Family": "awsecsinteg2TaskDef1F38909D", + "NetworkMode": "bridge", + "PlacementConstraints": [], + "RequiresCompatibilities": [ + "EC2" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "Volumes": [] + } + }, + "TaskDefExecutionRoleB4775C97": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDefExecutionRoleDefaultPolicy0DBB737A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + }, + { + "Action": [ + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TaskLoggingLogGroupC7E938D4", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TaskDefExecutionRoleDefaultPolicy0DBB737A", + "Roles": [ + { + "Ref": "TaskDefExecutionRoleB4775C97" + } + ] + } + }, + "EventImageAdoptRepositoryDFAAC242": { + "Type": "Custom::ECRAdoptedRepository", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", + "Arn" + ] + }, + "RepositoryName": { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:GetRepositoryPolicy", + "ecr:SetRepositoryPolicy", + "ecr:DeleteRepository", + "ecr:ListImages", + "ecr:BatchDeleteImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", + "Roles": [ + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" + } + ] + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "handler.handler", + "Role": { + "Fn::GetAtt": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Timeout": 300 + }, + "DependsOn": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C" + ] + }, + "TaskLoggingLogGroupC7E938D4": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 365 + }, + "DeletionPolicy": "Retain" + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ecs:RunTask", + "Effect": "Allow", + "Resource": { + "Ref": "TaskDef54694570" + } + }, + { + "Action": [ + "ecs:StopTask", + "ecs:DescribeTasks" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + } + ] + }, + { + "Action": [ + "events:PutTargets", + "events:PutRule", + "events:DescribeRule" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":rule/StepFunctionsGetEventsForECSTaskRule" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"RunEc2\"},\"RunEc2\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", + { + "Fn::GetAtt": [ + "FargateCluster7CCD5F93", + "Arn" + ] + }, + "\",\"TaskDefinition\":\"", + { + "Ref": "TaskDef54694570" + }, + "\",\"Overrides\":{\"ContainerOverrides\":[{\"Name\":\"TheContainer\",\"Environment\":[{\"Name\":\"SOME_KEY\",\"Value.$\":\"$.SomeKey\"}]}]},\"LaunchType\":\"EC2\"},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::ecs:runTask.sync\"}}}" + ] + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + } + } + } + }, + "Parameters": { + "EventImageImageNameE972A8B1": { + "Type": "String", + "Description": "ECR repository name and tag asset \"aws-ecs-integ2/EventImage\"" + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6": { + "Type": "String", + "Description": "S3 bucket for asset \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276": { + "Type": "String", + "Description": "S3 key for asset version \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts new file mode 100644 index 0000000000000..2502c7ae307df --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts @@ -0,0 +1,51 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import ecs = require('@aws-cdk/aws-ecs'); +import sfn = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); +import path = require('path'); +import tasks = require('../lib'); + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ2'); + +const vpc = ec2.VpcNetwork.importFromContext(stack, 'Vpc', { + isDefault: true +}); + +const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); +cluster.addCapacity('Capacity', { + instanceType: new ec2.InstanceType('t2.micro'), + vpcSubnets: { subnetType: ec2.SubnetType.Public }, +}); + +// Build task definition +const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); +taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, '..', 'eventhandler-image') }), + memoryLimitMiB: 256, + logging: new ecs.AwsLogDriver(stack, 'TaskLogging', { streamPrefix: 'EventDemo' }) +}); + +// Build state machine +const definition = new sfn.Pass(stack, 'Start', { + result: { SomeKey: 'SomeValue' } +}).next(new tasks.RunEcsEc2Task(stack, 'RunEc2', { + cluster, taskDefinition, + containerOverrides: [ + { + containerName: 'TheContainer', + environment: [ + { + name: 'SOME_KEY', + valuePath: '$.SomeKey' + } + ] + } + ] +})); + +new sfn.StateMachine(stack, 'StateMachine', { + definition, +}); + +app.run(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json new file mode 100644 index 0000000000000..61ca415d2f552 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json @@ -0,0 +1,619 @@ +{ + "Resources": { + "FargateCluster7CCD5F93": { + "Type": "AWS::ECS::Cluster" + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 4, + { + "Fn::Split": [ + ":", + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + ] + } + ] + }, + ".dkr.ecr.", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + ] + } + ] + }, + ".amazonaws.com/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + }, + ":", + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + ] + ] + }, + "Links": [], + "LinuxParameters": { + "Capabilities": { + "Add": [], + "Drop": [] + }, + "Devices": [], + "Tmpfs": [] + }, + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "TaskLoggingLogGroupC7E938D4" + }, + "awslogs-stream-prefix": "EventDemo", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Memory": 256, + "MountPoints": [], + "Name": "TheContainer", + "PortMappings": [], + "Ulimits": [], + "VolumesFrom": [] + } + ], + "Cpu": "256", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + }, + "Family": "awsecsinteg2TaskDef1F38909D", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "Volumes": [] + } + }, + "TaskDefExecutionRoleB4775C97": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDefExecutionRoleDefaultPolicy0DBB737A": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::GetAtt": [ + "EventImageAdoptRepositoryDFAAC242", + "RepositoryName" + ] + } + ] + ] + } + }, + { + "Action": [ + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TaskLoggingLogGroupC7E938D4", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TaskDefExecutionRoleDefaultPolicy0DBB737A", + "Roles": [ + { + "Ref": "TaskDefExecutionRoleB4775C97" + } + ] + } + }, + "EventImageAdoptRepositoryDFAAC242": { + "Type": "Custom::ECRAdoptedRepository", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9", + "Arn" + ] + }, + "RepositoryName": { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecr:GetRepositoryPolicy", + "ecr:SetRepositoryPolicy", + "ecr:DeleteRepository", + "ecr:ListImages", + "ecr:BatchDeleteImage" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":ecr:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":repository/", + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + ":", + { + "Ref": "EventImageImageNameE972A8B1" + } + ] + } + ] + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", + "Roles": [ + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" + } + ] + } + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c52BE89E9": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "handler.handler", + "Role": { + "Fn::GetAtt": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Timeout": 300 + }, + "DependsOn": [ + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C" + ] + }, + "TaskLoggingLogGroupC7E938D4": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 365 + }, + "DeletionPolicy": "Retain" + }, + "RunFargateSecurityGroup709740F2": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ2/RunFargate/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [], + "VpcId": "vpc-60900905" + } + }, + "StateMachineRoleB840431D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "states.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "StateMachineRoleDefaultPolicyDF1E6607": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ecs:RunTask", + "Effect": "Allow", + "Resource": { + "Ref": "TaskDef54694570" + } + }, + { + "Action": [ + "ecs:StopTask", + "ecs:DescribeTasks" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + } + ] + }, + { + "Action": [ + "events:PutTargets", + "events:PutRule", + "events:DescribeRule" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":events:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":rule/StepFunctionsGetEventsForECSTaskRule" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "StateMachineRoleDefaultPolicyDF1E6607", + "Roles": [ + { + "Ref": "StateMachineRoleB840431D" + } + ] + } + }, + "StateMachine2E01A3A5": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "DefinitionString": { + "Fn::Join": [ + "", + [ + "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"RunFargate\"},\"RunFargate\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", + { + "Fn::GetAtt": [ + "FargateCluster7CCD5F93", + "Arn" + ] + }, + "\",\"TaskDefinition\":\"", + { + "Ref": "TaskDef54694570" + }, + "\",\"NetworkConfiguration\":{\"AwsvpcConfiguration\":{\"AssignPublicIp\":\"ENABLED\",\"Subnets\":[\"subnet-e19455ca\",\"subnet-e0c24797\",\"subnet-ccd77395\"],\"SecurityGroups\":[\"", + { + "Fn::GetAtt": [ + "RunFargateSecurityGroup709740F2", + "GroupId" + ] + }, + "\"]}},\"Overrides\":{\"ContainerOverrides\":[{\"Name\":\"TheContainer\",\"Environment\":[{\"Name\":\"SOME_KEY\",\"Value.$\":\"$.SomeKey\"}]}]},\"LaunchType\":\"FARGATE\"},\"Type\":\"Task\",\"Resource\":\"arn:aws:states:::ecs:runTask.sync\"}}}" + ] + ] + }, + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineRoleB840431D", + "Arn" + ] + } + } + } + }, + "Parameters": { + "EventImageImageNameE972A8B1": { + "Type": "String", + "Description": "ECR repository name and tag asset \"aws-ecs-integ2/EventImage\"" + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3Bucket92AB06B6": { + "Type": "String", + "Description": "S3 bucket for asset \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" + }, + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cCodeS3VersionKey393B7276": { + "Type": "String", + "Description": "S3 key for asset version \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts new file mode 100644 index 0000000000000..747d977f7aee1 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts @@ -0,0 +1,51 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import ecs = require('@aws-cdk/aws-ecs'); +import sfn = require('@aws-cdk/aws-stepfunctions'); +import cdk = require('@aws-cdk/cdk'); +import path = require('path'); +import tasks = require('../lib'); + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ2'); + +const vpc = ec2.VpcNetwork.importFromContext(stack, 'Vpc', { + isDefault: true +}); + +const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); + +// Build task definition +const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef', { + memoryMiB: '512', + cpu: '256' +}); +taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, '..', 'eventhandler-image') }), + memoryLimitMiB: 256, + logging: new ecs.AwsLogDriver(stack, 'TaskLogging', { streamPrefix: 'EventDemo' }) +}); + +// Build state machine +const definition = new sfn.Pass(stack, 'Start', { + result: { SomeKey: 'SomeValue' } +}).next(new tasks.RunEcsFargateTask(stack, 'RunFargate', { + cluster, taskDefinition, + assignPublicIp: true, + containerOverrides: [ + { + containerName: 'TheContainer', + environment: [ + { + name: 'SOME_KEY', + valuePath: '$.SomeKey' + } + ] + } + ] +})); + +new sfn.StateMachine(stack, 'StateMachine', { + definition, +}); + +app.run(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index 5a6fa4f2868ee..a6c00e2dc8369 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -169,6 +169,7 @@ export class Task extends State implements INextable { Type: StateType.Task, Comment: this.comment, Resource: this.resourceArn, + Parameters: this.parameters, ResultPath: renderJsonPath(this.resultPath), TimeoutSeconds: this.timeoutSeconds, HeartbeatSeconds: this.heartbeatSeconds, diff --git a/tools/cdk-build-tools/bin/cdk-test.ts b/tools/cdk-build-tools/bin/cdk-test.ts index d9068029aa9f7..6bb08d668ff53 100644 --- a/tools/cdk-build-tools/bin/cdk-test.ts +++ b/tools/cdk-build-tools/bin/cdk-test.ts @@ -49,7 +49,7 @@ async function main() { if (testFiles.length > 0) { throw new Error(`Jest is enabled, but ${testFiles.length} nodeunit tests were found!`); } - await shell([args.jest, '--testEnvironment=node', '--coverage'], { timers }); + await shell([args.jest, '--testEnvironment=node', '--coverage', '--coverageReporters', 'html', 'lcov', 'text-summary'], { timers }); } else if (testFiles.length > 0) { const testCommand: string[] = []; From d0c52832ec4df8e49f9ddef4bc8b8032019b54df Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 6 May 2019 15:22:10 +0200 Subject: [PATCH 10/21] Don't inherit from task, do composition instead --- .../lib/invoke-activity.ts | 138 ++------------ .../lib/invoke-function.ts | 143 ++------------- .../lib/nested-state-machine.ts | 3 - .../lib/publish-to-topic.ts | 63 ++++--- .../lib/send-to-queue.ts | 66 ++++--- .../test/integ.job-poller.expected.json | 0 .../test/integ.job-poller.ts | 0 .../@aws-cdk/aws-stepfunctions/lib/index.ts | 1 + .../aws-stepfunctions/lib/states/task.ts | 172 ++++++++++++------ .../aws-stepfunctions/lib/task-resource.ts | 68 +++++++ .../test/test.state-machine-resources.ts | 32 ++-- .../test/test.states-language.ts | 30 +-- packages/decdk/package.json | 6 +- 13 files changed, 331 insertions(+), 391 deletions(-) delete mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/nested-state-machine.ts rename packages/@aws-cdk/{aws-stepfunctions => aws-stepfunctions-tasks}/test/integ.job-poller.expected.json (100%) rename packages/@aws-cdk/{aws-stepfunctions => aws-stepfunctions-tasks}/test/integ.job-poller.ts (100%) create mode 100644 packages/@aws-cdk/aws-stepfunctions/lib/task-resource.ts diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts index 4fbe4671691ee..29be6f95ef216 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts @@ -1,19 +1,11 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import stepfunctions = require('@aws-cdk/aws-stepfunctions'); -import cdk = require('@aws-cdk/cdk'); - -const METRIC_PREFIX_SINGULAR = 'Activity'; -const METRIC_PREFIX_PLURAL = 'Activities'; +import iam = require('@aws-cdk/aws-iam'); +import sfn = require('@aws-cdk/aws-stepfunctions'); /** * Properties for FunctionTask */ -export interface InvokeActivityProps extends stepfunctions.BasicTaskProps { - /** - * The activity to invoke - */ - readonly activity: stepfunctions.IActivity; - +export interface InvokeActivityProps { /** * Maximum time between heart beats * @@ -30,113 +22,21 @@ export interface InvokeActivityProps extends stepfunctions.BasicTaskProps { * A Function can be used directly as a Resource, but this class mirrors * integration with other AWS services via a specific class instance. */ -export class InvokeActivity extends stepfunctions.Task { - private readonly activityArn: string; - - constructor(scope: cdk.Construct, id: string, props: InvokeActivityProps) { - super(scope, id, { - ...props, - resourceArn: props.activity.activityArn, - heartbeatSeconds: props.heartbeatSeconds, - // No IAM permissions necessary, execution role implicitly has Activity permissions. - }); - - this.activityArn = props.activity.activityArn; - } - - /** - * Return the given named metric for this Task - * - * @default sum over 5 minutes - */ - public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return new cloudwatch.Metric({ - namespace: 'AWS/States', - metricName, - dimensions: { ActivityArn: this.activityArn }, - statistic: 'sum', - ...props - }); - } - - /** - * The interval, in milliseconds, between the time the Task starts and the time it closes. - * - * @default average over 5 minutes - */ - public metricRunTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_SINGULAR + 'RunTime', { statistic: 'avg', ...props }); - } - - /** - * The interval, in milliseconds, for which the activity stays in the schedule state. - * - * @default average over 5 minutes - */ - public metricScheduleTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_SINGULAR + 'ScheduleTime', { statistic: 'avg', ...props }); - } - - /** - * The interval, in milliseconds, between the time the activity is scheduled and the time it closes. - * - * @default average over 5 minutes - */ - public metricTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_SINGULAR + 'Time', { statistic: 'avg', ...props }); - } - - /** - * Metric for the number of times this activity is scheduled - * - * @default sum over 5 minutes - */ - public metricScheduled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'Scheduled', props); - } - - /** - * Metric for the number of times this activity times out - * - * @default sum over 5 minutes - */ - public metricTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'TimedOut', props); - } - - /** - * Metric for the number of times this activity is started - * - * @default sum over 5 minutes - */ - public metricStarted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'Started', props); - } - - /** - * Metric for the number of times this activity succeeds - * - * @default sum over 5 minutes - */ - public metricSucceeded(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'Succeeded', props); - } - - /** - * Metric for the number of times this activity fails - * - * @default sum over 5 minutes - */ - public metricFailed(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'Failed', props); - } - - /** - * Metric for the number of times the heartbeat times out for this activity - * - * @default sum over 5 minutes - */ - public metricHeartbeatTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'HeartbeatTimedOut', props); +export class InvokeActivity implements sfn.ITaskResource { + public readonly resourceArn: string; + public readonly policyStatements?: iam.PolicyStatement[] | undefined; + public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; + public readonly metricPrefixSingular?: string = 'Activity'; + public readonly metricPrefixPlural?: string = 'Activities'; + + public readonly heartbeatSeconds?: number | undefined; + public readonly parameters?: { [name: string]: any; } | undefined; + + constructor(activity: sfn.IActivity, props: InvokeActivityProps = {}) { + this.resourceArn = activity.activityArn; + this.metricDimensions = { ActivityArn: activity.activityArn }; + this.heartbeatSeconds = props.heartbeatSeconds; + + // No IAM permissions necessary, execution role implicitly has Activity permissions. } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts index cc7f9a71da7ef..428d4fce20805 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts @@ -1,21 +1,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); -import stepfunctions = require('@aws-cdk/aws-stepfunctions'); -import cdk = require('@aws-cdk/cdk'); - -const METRIC_PREFIX_SINGULAR = 'LambdaFunction'; -const METRIC_PREFIX_PLURAL = 'LambdaFunctions'; - -/** - * Properties for FunctionTask - */ -export interface InvokeFunctionProps extends stepfunctions.BasicTaskProps { - /** - * The function to run - */ - readonly function: lambda.IFunction; -} +import sfn = require('@aws-cdk/aws-stepfunctions'); /** * A StepFunctions Task to invoke a Lambda function. @@ -23,115 +9,22 @@ export interface InvokeFunctionProps extends stepfunctions.BasicTaskProps { * A Function can be used directly as a Resource, but this class mirrors * integration with other AWS services via a specific class instance. */ -export class InvokeFunction extends stepfunctions.Task { - private readonly functionArn: string; - - constructor(scope: cdk.Construct, id: string, props: InvokeFunctionProps) { - super(scope, id, { - ...props, - resourceArn: props.function.functionArn, - policyStatements: [new iam.PolicyStatement() - .addResource(props.function.functionArn) - .addActions("lambda:InvokeFunction") - ] - }); - - this.functionArn = props.function.functionArn; - } - - /** - * Return the given named metric for this Task - * - * @default sum over 5 minutes - */ - public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return new cloudwatch.Metric({ - namespace: 'AWS/States', - metricName, - dimensions: { LambdaFunctionArn: this.functionArn }, - statistic: 'sum', - ...props - }); - } - - /** - * The interval, in milliseconds, between the time the Task starts and the time it closes. - * - * @default average over 5 minutes - */ - public metricRunTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_SINGULAR + 'RunTime', { statistic: 'avg', ...props }); - } - - /** - * The interval, in milliseconds, for which the activity stays in the schedule state. - * - * @default average over 5 minutes - */ - public metricScheduleTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_SINGULAR + 'ScheduleTime', { statistic: 'avg', ...props }); - } - - /** - * The interval, in milliseconds, between the time the activity is scheduled and the time it closes. - * - * @default average over 5 minutes - */ - public metricTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_SINGULAR + 'Time', { statistic: 'avg', ...props }); - } - - /** - * Metric for the number of times this activity is scheduled - * - * @default sum over 5 minutes - */ - public metricScheduled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'Scheduled', props); - } - - /** - * Metric for the number of times this activity times out - * - * @default sum over 5 minutes - */ - public metricTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'TimedOut', props); - } - - /** - * Metric for the number of times this activity is started - * - * @default sum over 5 minutes - */ - public metricStarted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'Started', props); - } - - /** - * Metric for the number of times this activity succeeds - * - * @default sum over 5 minutes - */ - public metricSucceeded(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'Succeeded', props); - } - - /** - * Metric for the number of times this activity fails - * - * @default sum over 5 minutes - */ - public metricFailed(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'Failed', props); - } - - /** - * Metric for the number of times the heartbeat times out for this activity - * - * @default sum over 5 minutes - */ - public metricHeartbeatTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.metric(METRIC_PREFIX_PLURAL + 'HeartbeatTimedOut', props); +export class InvokeFunction implements sfn.ITaskResource { + public readonly resourceArn: string; + public readonly policyStatements?: iam.PolicyStatement[] | undefined; + public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; + public readonly metricPrefixSingular?: string = 'LambdaFunction'; + public readonly metricPrefixPlural?: string = 'LambdaFunctions'; + + public readonly heartbeatSeconds?: number | undefined; + public readonly parameters?: { [name: string]: any; } | undefined; + + constructor(lambdaFunction: lambda.IFunction) { + this.resourceArn = lambdaFunction.functionArn; + this.policyStatements = [new iam.PolicyStatement() + .addResource(lambdaFunction.functionArn) + .addActions("lambda:InvokeFunction") + ]; + this.metricDimensions = { LambdaFunctionArn: lambdaFunction.functionArn }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/nested-state-machine.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/nested-state-machine.ts deleted file mode 100644 index e87701835b966..0000000000000 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/nested-state-machine.ts +++ /dev/null @@ -1,3 +0,0 @@ - -export class NestedStateMachine { -} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts index 2d1c0ef6655fd..2da40ab3896cb 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts @@ -1,17 +1,13 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); import sns = require('@aws-cdk/aws-sns'); -import stepfunctions = require('@aws-cdk/aws-stepfunctions'); +import sfn = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); /** * Properties for PublishTask */ -export interface PublishTaskProps extends stepfunctions.BasicTaskProps { - /** - * The topic to publish to - */ - readonly topic: sns.ITopic; - +export interface PublishToTopicProps { /** * The text message to send to the queue. * @@ -57,10 +53,22 @@ export interface PublishTaskProps extends stepfunctions.BasicTaskProps { } /** - * A StepFunctions Task to publish to an SNS Topic + * A StepFunctions Task to invoke a Lambda function. + * + * A Function can be used directly as a Resource, but this class mirrors + * integration with other AWS services via a specific class instance. */ -export class PublishTask extends stepfunctions.Task { - constructor(scope: cdk.Construct, id: string, props: PublishTaskProps) { +export class PublishToTopic implements sfn.ITaskResource { + public readonly resourceArn: string; + public readonly policyStatements?: iam.PolicyStatement[] | undefined; + public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; + public readonly metricPrefixSingular?: string; + public readonly metricPrefixPlural?: string; + + public readonly heartbeatSeconds?: number | undefined; + public readonly parameters?: { [name: string]: any; } | undefined; + + constructor(topic: sns.ITopic, props: PublishToTopicProps) { if ((props.message !== undefined ? 1 : 0) + (props.messagePath !== undefined ? 1 : 0) + (props.messageObject !== undefined ? 1 : 0) !== 1) { @@ -71,23 +79,22 @@ export class PublishTask extends stepfunctions.Task { throw new Error(`Supply either 'subject' or 'subjectPath'`); } - super(scope, id, { - ...props, - resourceArn: 'arn:aws:states:::sns:publish', - policyStatements: [new iam.PolicyStatement() - .addAction('sns:Publish') - .addResource(props.topic.topicArn) - ], - parameters: { - "TopicArn": props.topic.topicArn, - "Message": props.messageObject - ? new cdk.Token(() => this.node.stringifyJson(props.messageObject)) - : props.message, - "Message.$": props.messagePath, - "MessageStructure": props.messagePerSubscriptionType ? "json" : undefined, - "Subject": props.subject, - "Subject.$": props.subjectPath - } - }); + this.resourceArn = 'arn:aws:states:::sns:publish'; + this.policyStatements = [new iam.PolicyStatement() + .addAction('sns:Publish') + .addResource(topic.topicArn) + ]; + this.parameters = { + "TopicArn": topic.topicArn, + "Message": props.messageObject + ? new cdk.Token(() => topic.node.stringifyJson(props.messageObject)) + : props.message, + "Message.$": props.messagePath, + "MessageStructure": props.messagePerSubscriptionType ? "json" : undefined, + "Subject": props.subject, + "Subject.$": props.subjectPath + }; + + // No IAM permissions necessary, execution role implicitly has Activity permissions. } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts index 0a8168c1d9286..b761ca9300f54 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts @@ -1,17 +1,12 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); import sqs = require('@aws-cdk/aws-sqs'); -import stepfunctions = require('@aws-cdk/aws-stepfunctions'); -import cdk = require('@aws-cdk/cdk'); +import sfn = require('@aws-cdk/aws-stepfunctions'); /** * Properties for SendMessageTask */ -export interface SendMessageTaskProps extends stepfunctions.BasicTaskProps { - /** - * The topic to send a message to to - */ - readonly queue: sqs.IQueue; - +export interface SendToQueueProps { /** * The message body to send to the queue. * @@ -75,10 +70,22 @@ export interface SendMessageTaskProps extends stepfunctions.BasicTaskProps { } /** - * A StepFunctions Task to send a message to an SQS Queue + * A StepFunctions Task to invoke a Lambda function. + * + * A Function can be used directly as a Resource, but this class mirrors + * integration with other AWS services via a specific class instance. */ -export class SendMessageTask extends stepfunctions.Task { - constructor(scope: cdk.Construct, id: string, props: SendMessageTaskProps) { +export class SendToQueue implements sfn.ITaskResource { + public readonly resourceArn: string; + public readonly policyStatements?: iam.PolicyStatement[] | undefined; + public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; + public readonly metricPrefixSingular?: string; + public readonly metricPrefixPlural?: string; + + public readonly heartbeatSeconds?: number | undefined; + public readonly parameters?: { [name: string]: any; } | undefined; + + constructor(queue: sqs.IQueue, props: SendToQueueProps) { if ((props.messageBody !== undefined) === (props.messageBodyPath !== undefined)) { throw new Error(`Supply exactly one of 'messageBody' and 'messageBodyPath'`); } @@ -95,24 +102,23 @@ export class SendMessageTask extends stepfunctions.Task { throw new Error(`Supply either of 'messageGroupId' or 'messageGroupIdPath'`); } - super(scope, id, { - ...props, - resourceArn: 'arn:aws:states:::sqs:sendMessage', - policyStatements: [new iam.PolicyStatement() - .addAction('sns:Publish') - .addResource(props.queue.queueArn) - ], - parameters: { - 'QueueUrl': props.queue.queueUrl, - 'MessageBody': props.messageBody, - 'MessageBody.$': props.messageBodyPath, - 'DelaySeconds': props.delaySeconds, - 'DelaySeconds.$': props.delaySecondsPath, - 'MessageDeduplicationId': props.messageDeduplicationId, - 'MessageDeduplicationId.$': props.messageDeduplicationIdPath, - 'MessageGroupId': props.messageGroupId, - 'MessageGroupId.$': props.messageGroupIdPath, - } - }); + this.resourceArn = 'arn:aws:states:::sqs:sendMessage'; + this.policyStatements = [new iam.PolicyStatement() + .addAction('sqs:SendMessage') + .addResource(queue.queueArn) + ]; + this.parameters = { + 'QueueUrl': queue.queueUrl, + 'MessageBody': props.messageBody, + 'MessageBody.$': props.messageBodyPath, + 'DelaySeconds': props.delaySeconds, + 'DelaySeconds.$': props.delaySecondsPath, + 'MessageDeduplicationId': props.messageDeduplicationId, + 'MessageDeduplicationId.$': props.messageDeduplicationIdPath, + 'MessageGroupId': props.messageGroupId, + 'MessageGroupId.$': props.messageGroupIdPath, + }; + + // No IAM permissions necessary, execution role implicitly has Activity permissions. } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/test/integ.job-poller.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.expected.json similarity index 100% rename from packages/@aws-cdk/aws-stepfunctions/test/integ.job-poller.expected.json rename to packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.expected.json diff --git a/packages/@aws-cdk/aws-stepfunctions/test/integ.job-poller.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts similarity index 100% rename from packages/@aws-cdk/aws-stepfunctions/test/integ.job-poller.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions/lib/index.ts index 8cf9837545852..ba3f11be6ba59 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/index.ts @@ -6,6 +6,7 @@ export * from './state-machine-fragment'; export * from './state-transition-metrics'; export * from './chain'; export * from './state-graph'; +export * from './task-resource'; export * from './states/choice'; export * from './states/fail'; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index a6c00e2dc8369..ceecfe9878826 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -1,14 +1,20 @@ -import iam = require('@aws-cdk/aws-iam'); +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import { Chain } from '../chain'; import { StateGraph } from '../state-graph'; +import { ITaskResource } from '../task-resource'; import { CatchProps, IChainable, INextable, RetryProps } from '../types'; import { renderJsonPath, State, StateType } from './state'; /** * Props that are common to all tasks */ -export interface BasicTaskProps { +export interface TaskProps { + /** + * Resource to be invoked in this workflow + */ + readonly resource: ITaskResource; + /** * An optional description for this state * @@ -56,51 +62,6 @@ export interface BasicTaskProps { readonly timeoutSeconds?: number; } -/** - * Properties for defining a Task state - */ -export interface TaskProps extends BasicTaskProps { - /** - * The resource that represents the work to be executed - * - * Either the ARN of a Lambda Function or Activity, or a special - * ARN. - */ - readonly resourceArn: string; - - /** - * Parameters pass a collection of key-value pairs, either static values or JSONPath expressions that select from the input. - * - * What is passed here will be merged with any default parameters - * configured by the `resource`. For example, a DynamoDB table target - * will - * - * @see - * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-parameters - * - * @default No parameters - */ - readonly parameters?: { [name: string]: any }; - - /** - * Maximum time between heart beats - * - * If the time between heart beats takes longer than this, a 'Timeout' error is raised. - * - * This is only relevant when using an Activity type as resource. - * - * @default No heart beat timeout - */ - readonly heartbeatSeconds?: number; - - /** - * Additional policy statements to add to the execution role - * - * @default No policy roles - */ - readonly policyStatements?: iam.PolicyStatement[]; -} - /** * Define a Task state in the state machine * @@ -114,17 +75,13 @@ export interface TaskProps extends BasicTaskProps { export class Task extends State implements INextable { public readonly endStates: INextable[]; private readonly timeoutSeconds?: number; - private readonly heartbeatSeconds?: number; - private readonly resourceArn: string; - private readonly policyStatements?: iam.PolicyStatement[]; + private readonly resource: ITaskResource; constructor(scope: cdk.Construct, id: string, props: TaskProps) { super(scope, id, props); this.timeoutSeconds = props.timeoutSeconds; - this.heartbeatSeconds = props.heartbeatSeconds; - this.resourceArn = props.resourceArn; - this.policyStatements = props.policyStatements; + this.resource = props.resource; this.endStates = [this]; } @@ -168,18 +125,121 @@ export class Task extends State implements INextable { ...this.renderInputOutput(), Type: StateType.Task, Comment: this.comment, - Resource: this.resourceArn, + Resource: this.resource.resourceArn, Parameters: this.parameters, ResultPath: renderJsonPath(this.resultPath), TimeoutSeconds: this.timeoutSeconds, - HeartbeatSeconds: this.heartbeatSeconds, + HeartbeatSeconds: this.resource.heartbeatSeconds, }; } + /** + * Return the given named metric for this Task + * + * @default sum over 5 minutes + */ + public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return new cloudwatch.Metric({ + namespace: 'AWS/States', + metricName, + dimensions: this.resource.metricDimensions, + statistic: 'sum', + ...props + }); + } + + /** + * The interval, in milliseconds, between the time the Task starts and the time it closes. + * + * @default average over 5 minutes + */ + public metricRunTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.taskMetric(this.resource.metricPrefixSingular, 'RunTime', { statistic: 'avg', ...props }); + } + + /** + * The interval, in milliseconds, for which the activity stays in the schedule state. + * + * @default average over 5 minutes + */ + public metricScheduleTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.taskMetric(this.resource.metricPrefixSingular, 'ScheduleTime', { statistic: 'avg', ...props }); + } + + /** + * The interval, in milliseconds, between the time the activity is scheduled and the time it closes. + * + * @default average over 5 minutes + */ + public metricTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.taskMetric(this.resource.metricPrefixSingular, 'Time', { statistic: 'avg', ...props }); + } + + /** + * Metric for the number of times this activity is scheduled + * + * @default sum over 5 minutes + */ + public metricScheduled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.taskMetric(this.resource.metricPrefixPlural, 'Scheduled', props); + } + + /** + * Metric for the number of times this activity times out + * + * @default sum over 5 minutes + */ + public metricTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.taskMetric(this.resource.metricPrefixPlural, 'TimedOut', props); + } + + /** + * Metric for the number of times this activity is started + * + * @default sum over 5 minutes + */ + public metricStarted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.taskMetric(this.resource.metricPrefixPlural, 'Started', props); + } + + /** + * Metric for the number of times this activity succeeds + * + * @default sum over 5 minutes + */ + public metricSucceeded(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.taskMetric(this.resource.metricPrefixPlural, 'Succeeded', props); + } + + /** + * Metric for the number of times this activity fails + * + * @default sum over 5 minutes + */ + public metricFailed(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.taskMetric(this.resource.metricPrefixPlural, 'Failed', props); + } + + /** + * Metric for the number of times the heartbeat times out for this activity + * + * @default sum over 5 minutes + */ + public metricHeartbeatTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { + return this.taskMetric(this.resource.metricPrefixPlural, 'HeartbeatTimedOut', props); + } + protected onBindToGraph(graph: StateGraph) { super.onBindToGraph(graph); - for (const policyStatement of this.policyStatements || []) { + for (const policyStatement of this.resource.policyStatements || []) { graph.registerPolicyStatement(policyStatement); } } + + private taskMetric(prefix: string | undefined, suffix: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric { + if (prefix === undefined) { + throw new Error('This Task Resource does not expose metrics'); + } + return this.metric(prefix + suffix, props); + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/task-resource.ts b/packages/@aws-cdk/aws-stepfunctions/lib/task-resource.ts new file mode 100644 index 0000000000000..ed1bdfae75e57 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions/lib/task-resource.ts @@ -0,0 +1,68 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); +import iam = require('@aws-cdk/aws-iam'); + +/** + * Interface for resources that can be used as tasks + */ +export interface ITaskResource { + /** + * The resource that represents the work to be executed + * + * Either the ARN of a Lambda Function or Activity, or a special + * ARN. + */ + readonly resourceArn: string; + + /** + * Parameters pass a collection of key-value pairs, either static values or JSONPath expressions that select from the input. + * + * What is passed here will be merged with any default parameters + * configured by the `resource`. For example, a DynamoDB table target + * will + * + * @see + * https://docs.aws.amazon.com/step-functions/latest/dg/input-output-inputpath-params.html#input-output-parameters + * + * @default No parameters + */ + readonly parameters?: { [name: string]: any }; + + /** + * Maximum time between heart beats + * + * If the time between heart beats takes longer than this, a 'Timeout' error is raised. + * + * This is only relevant when using an Activity type as resource. + * + * @default No heart beat timeout + */ + readonly heartbeatSeconds?: number; + + /** + * Additional policy statements to add to the execution role + * + * @default No policy roles + */ + readonly policyStatements?: iam.PolicyStatement[]; + + /** + * Prefix for singular metric names of activity actions + * + * @default No such metrics + */ + readonly metricPrefixSingular?: string; + + /** + * Prefix for plural metric names of activity actions + * + * @default No such metrics + */ + readonly metricPrefixPlural?: string; + + /** + * The dimensions to attach to metrics + * + * @default No metrics + */ + readonly metricDimensions?: cloudwatch.DimensionHash; +} diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts index bccefe596603a..a34085f553ab8 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts @@ -10,11 +10,13 @@ export = { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { - resourceArn: 'resource', - policyStatements: [new iam.PolicyStatement() - .addAction('resource:Everything') - .addResource('resource') - ], + resource: { + resourceArn: 'resource', + policyStatements: [new iam.PolicyStatement() + .addAction('resource:Everything') + .addResource('resource') + ], + } }); // WHEN @@ -43,7 +45,9 @@ export = { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { - resourceArn: 'resource' + resource: { + resourceArn: 'resource' + } }); const para = new stepfunctions.Parallel(stack, 'Para'); @@ -75,15 +79,17 @@ export = { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { - resourceArn: 'resource', inputPath: "$", outputPath: "$.state", - parameters: { - "input.$": "$", - "stringArgument": "inital-task", - "numberArgument": 123, - "booleanArgument": true, - "arrayArgument": ["a", "b", "c"] + resource: { + resourceArn: 'resource', + parameters: { + "input.$": "$", + "stringArgument": "inital-task", + "numberArgument": 123, + "booleanArgument": true, + "arrayArgument": ["a", "b", "c"] + } } }); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts index 5c35cb7aec16a..177dce8dc65e0 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts @@ -397,7 +397,7 @@ export = { 'States can have error branches'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' }}); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -429,7 +429,7 @@ export = { 'Retries and errors with a result path'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -462,8 +462,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -508,8 +508,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -539,9 +539,9 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); - const task3 = new stepfunctions.Task(stack, 'Task3', { resourceArn: 'resource' }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); + const task3 = new stepfunctions.Task(stack, 'Task3', { resource: { resourceArn: 'resource' } }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -568,8 +568,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -584,8 +584,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resourceArn: 'resource' }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resourceArn: 'resource' }); + const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); + const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -705,8 +705,8 @@ class SimpleChain extends stepfunctions.StateMachineFragment { constructor(scope: cdk.Construct, id: string) { super(scope, id); - const task1 = new stepfunctions.Task(this, 'Task1', { resourceArn: 'resource' }); - this.task2 = new stepfunctions.Task(this, 'Task2', { resourceArn: 'resource' }); + const task1 = new stepfunctions.Task(this, 'Task1', { resource: { resourceArn: 'resource' } }); + this.task2 = new stepfunctions.Task(this, 'Task2', { resource: { resourceArn: 'resource' } }); task1.next(this.task2); diff --git a/packages/decdk/package.json b/packages/decdk/package.json index b09f5f941bc78..2df81b548c131 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -118,6 +118,7 @@ "@aws-cdk/aws-sqs": "^0.29.0", "@aws-cdk/aws-ssm": "^0.29.0", "@aws-cdk/aws-stepfunctions": "^0.29.0", + "@aws-cdk/aws-stepfunctions-tasks": "^0.29.0", "@aws-cdk/aws-waf": "^0.29.0", "@aws-cdk/aws-wafregional": "^0.29.0", "@aws-cdk/aws-workspaces": "^0.29.0", @@ -130,7 +131,8 @@ "jsii-reflect": "^0.10.3", "jsonschema": "^1.2.4", "yaml": "1.5.0", - "yargs": "^13.2.2" + "yargs": "^13.2.2", + "@aws-cdk/aws-stepfunctions-tasks": "^0.29.0" }, "devDependencies": { "@types/fs-extra": "^5.0.5", @@ -148,4 +150,4 @@ "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} From 8db693ad670656e6c8cb388e0f656d50fbf85af4 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 7 May 2019 13:56:39 +0200 Subject: [PATCH 11/21] Refactor --- .../lib/base-run-task-types.ts | 109 ++++++++++++ .../lib/base-run-task.ts | 156 +++--------------- .../aws-stepfunctions-tasks/lib/index.ts | 4 +- .../lib/invoke-activity.ts | 2 +- .../lib/invoke-function.ts | 2 +- .../lib/publish-to-topic.ts | 2 +- .../lib/send-to-queue.ts | 2 +- .../test/ecs-tasks.test.ts | 65 +++++++- .../test/integ.ec2-task.ts | 4 +- .../test/integ.fargate-task.ts | 4 +- .../test/integ.job-poller.ts | 33 ++-- .../test/invoke-activity.test.ts | 4 +- .../test/invoke-function.test.ts | 32 ++++ .../test/publish-to-topic.test.ts | 6 +- .../test/send-to-queue.test.ts | 6 +- .../@aws-cdk/aws-stepfunctions/lib/index.ts | 2 +- .../aws-stepfunctions/lib/states/task.ts | 38 ++--- ...ask-resource.ts => step-functions-task.ts} | 2 +- .../aws-stepfunctions/test/test.condition.ts | 53 +++++- .../test/test.state-machine-resources.ts | 13 +- .../test/test.states-language.ts | 35 ++-- 21 files changed, 358 insertions(+), 216 deletions(-) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task-types.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-function.test.ts rename packages/@aws-cdk/aws-stepfunctions/lib/{task-resource.ts => step-functions-task.ts} (97%) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task-types.ts new file mode 100644 index 0000000000000..ae0eadc9bb657 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task-types.ts @@ -0,0 +1,109 @@ +export interface ContainerOverride { + /** + * Name of the container inside the task definition + * + * Exactly one of `containerName` and `containerNamePath` is required. + */ + readonly containerName?: string; + + /** + * JSONPath expression for the name of the container inside the task definition + * + * Exactly one of `containerName` and `containerNamePath` is required. + */ + readonly containerNamePath?: string; + + /** + * Command to run inside the container + * + * @default Default command + */ + readonly command?: string[]; + + /** + * JSON expression for command to run inside the container + * + * @default Default command + */ + readonly commandPath?: string; + + /** + * Variables to set in the container's environment + */ + readonly environment?: TaskEnvironmentVariable[]; + + /** + * The number of cpu units reserved for the container + * + * @Default The default value from the task definition. + */ + readonly cpu?: number; + + /** + * JSON expression for the number of CPU units + * + * @Default The default value from the task definition. + */ + readonly cpuPath?: string; + + /** + * Hard memory limit on the container + * + * @Default The default value from the task definition. + */ + readonly memoryLimit?: number; + + /** + * JSON expression path for the hard memory limit + * + * @Default The default value from the task definition. + */ + readonly memoryLimitPath?: string; + + /** + * Soft memory limit on the container + * + * @Default The default value from the task definition. + */ + readonly memoryReservation?: number; + + /** + * JSONExpr path for memory limit on the container + * + * @Default The default value from the task definition. + */ + readonly memoryReservationPath?: number; +} + +/** + * An environment variable to be set in the container run as a task + */ +export interface TaskEnvironmentVariable { + /** + * Name for the environment variable + * + * Exactly one of `name` and `namePath` must be specified. + */ + readonly name?: string; + + /** + * JSONExpr for the name of the variable + * + * Exactly one of `name` and `namePath` must be specified. + */ + readonly namePath?: string; + + /** + * Value of the environment variable + * + * Exactly one of `value` and `valuePath` must be specified. + */ + readonly value?: string; + + /** + * JSONPath expr for the environment variable + * + * Exactly one of `value` and `valuePath` must be specified. + */ + readonly valuePath?: string; +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts index 58d2ff76c4ab4..176944541ce5c 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts @@ -1,13 +1,14 @@ +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import iam = require('@aws-cdk/aws-iam'); -import stepfunctions = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); +import { ContainerOverride } from './base-run-task-types'; /** * Properties for SendMessageTask */ -export interface CommonRunTaskProps extends stepfunctions.BasicTaskProps { +export interface CommonRunTaskProps { /** * The topic to run the task on */ @@ -44,146 +45,43 @@ export interface BaseRunTaskProps extends CommonRunTaskProps { readonly parameters?: {[key: string]: any}; } -export interface ContainerOverride { - /** - * Name of the container inside the task definition - * - * Exactly one of `containerName` and `containerNamePath` is required. - */ - readonly containerName?: string; - - /** - * JSONPath expression for the name of the container inside the task definition - * - * Exactly one of `containerName` and `containerNamePath` is required. - */ - readonly containerNamePath?: string; - - /** - * Command to run inside the container - * - * @default Default command - */ - readonly command?: string[]; - - /** - * JSON expression for command to run inside the container - * - * @default Default command - */ - readonly commandPath?: string; - - /** - * Variables to set in the container's environment - */ - readonly environment?: TaskEnvironmentVariable[]; - - /** - * The number of cpu units reserved for the container - * - * @Default The default value from the task definition. - */ - readonly cpu?: number; - - /** - * JSON expression for the number of CPU units - * - * @Default The default value from the task definition. - */ - readonly cpuPath?: string; - - /** - * Hard memory limit on the container - * - * @Default The default value from the task definition. - */ - readonly memoryLimit?: number; - - /** - * JSON expression path for the hard memory limit - * - * @Default The default value from the task definition. - */ - readonly memoryLimitPath?: string; - - /** - * Soft memory limit on the container - * - * @Default The default value from the task definition. - */ - readonly memoryReservation?: number; - - /** - * JSONExpr path for memory limit on the container - * - * @Default The default value from the task definition. - */ - readonly memoryReservationPath?: number; -} - -/** - * An environment variable to be set in the container run as a task - */ -export interface TaskEnvironmentVariable { - /** - * Name for the environment variable - * - * Exactly one of `name` and `namePath` must be specified. - */ - readonly name?: string; - - /** - * JSONExpr for the name of the variable - * - * Exactly one of `name` and `namePath` must be specified. - */ - readonly namePath?: string; - - /** - * Value of the environment variable - * - * Exactly one of `value` and `valuePath` must be specified. - */ - readonly value?: string; - - /** - * JSONPath expr for the environment variable - * - * Exactly one of `value` and `valuePath` must be specified. - */ - readonly valuePath?: string; -} - /** * A StepFunctions Task to run a Task on ECS or Fargate */ -export class BaseRunTask extends stepfunctions.Task implements ec2.IConnectable { +export class BaseRunTask extends cdk.Construct implements ec2.IConnectable { /** * Manage allowed network traffic for this service */ public readonly connections: ec2.Connections = new ec2.Connections(); + public readonly resourceArn: string; + public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; + public readonly metricPrefixSingular?: string; + public readonly metricPrefixPlural?: string; + public readonly heartbeatSeconds?: number | undefined; + protected networkConfiguration?: any; protected readonly taskDefinition: ecs.TaskDefinition; private readonly sync: boolean; - constructor(scope: cdk.Construct, id: string, props: BaseRunTaskProps) { - super(scope, id, { - ...props, - resourceArn: 'arn:aws:states:::ecs:runTask' + (props.synchronous !== false ? '.sync' : ''), - parameters: { - Cluster: props.cluster.clusterArn, - TaskDefinition: props.taskDefinition.taskDefinitionArn, - NetworkConfiguration: new cdk.Token(() => this.networkConfiguration), - Overrides: renderOverrides(props.containerOverrides), - ...props.parameters, - } - }); + constructor(scope: cdk.Construct, id: string, private readonly props: BaseRunTaskProps) { + super(scope, id); + this.resourceArn = 'arn:aws:states:::ecs:runTask' + (props.synchronous !== false ? '.sync' : ''); this.sync = props.synchronous !== false; this.taskDefinition = props.taskDefinition; } + public get parameters(): { [name: string]: any; } | undefined { + return { + Cluster: this.props.cluster.clusterArn, + TaskDefinition: this.props.taskDefinition.taskDefinitionArn, + NetworkConfiguration: this.networkConfiguration, + Overrides: renderOverrides(this.props.containerOverrides), + ...this.props.parameters, + }; + } + protected configureAwsVpcNetworking( vpc: ec2.IVpcNetwork, assignPublicIp?: boolean, @@ -207,9 +105,7 @@ export class BaseRunTask extends stepfunctions.Task implements ec2.IConnectable }; } - protected onBindToGraph(graph: stepfunctions.StateGraph) { - super.onBindToGraph(graph); - + public get policyStatements(): iam.PolicyStatement[] | undefined { const stack = this.node.stack; // https://docs.aws.amazon.com/step-functions/latest/dg/ecs-iam.html @@ -235,9 +131,7 @@ export class BaseRunTask extends stepfunctions.Task implements ec2.IConnectable }))); } - for (const policyStatement of policyStatements) { - graph.registerPolicyStatement(policyStatement); - } + return policyStatements; } private taskExecutionRoles(): iam.IRole[] { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index 02d3b691fc0aa..67a40324e3fcd 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -1,7 +1,7 @@ export * from './invoke-function'; export * from './invoke-activity'; -export * from './nested-state-machine'; -export * from './base-run-task'; +export * from './base-run-task'; // Remove this once we can +export * from './base-run-task-types'; export * from './publish-to-topic'; export * from './send-to-queue'; export * from './run-ecs-ec2-task'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts index 29be6f95ef216..2662ac15e9b33 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts @@ -22,7 +22,7 @@ export interface InvokeActivityProps { * A Function can be used directly as a Resource, but this class mirrors * integration with other AWS services via a specific class instance. */ -export class InvokeActivity implements sfn.ITaskResource { +export class InvokeActivity implements sfn.IStepFunctionsTask { public readonly resourceArn: string; public readonly policyStatements?: iam.PolicyStatement[] | undefined; public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts index 428d4fce20805..d8db31776fd80 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts @@ -9,7 +9,7 @@ import sfn = require('@aws-cdk/aws-stepfunctions'); * A Function can be used directly as a Resource, but this class mirrors * integration with other AWS services via a specific class instance. */ -export class InvokeFunction implements sfn.ITaskResource { +export class InvokeFunction implements sfn.IStepFunctionsTask { public readonly resourceArn: string; public readonly policyStatements?: iam.PolicyStatement[] | undefined; public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts index 2da40ab3896cb..04e78e30fe971 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts @@ -58,7 +58,7 @@ export interface PublishToTopicProps { * A Function can be used directly as a Resource, but this class mirrors * integration with other AWS services via a specific class instance. */ -export class PublishToTopic implements sfn.ITaskResource { +export class PublishToTopic implements sfn.IStepFunctionsTask { public readonly resourceArn: string; public readonly policyStatements?: iam.PolicyStatement[] | undefined; public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts index b761ca9300f54..9283370c2182d 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts @@ -75,7 +75,7 @@ export interface SendToQueueProps { * A Function can be used directly as a Resource, but this class mirrors * integration with other AWS services via a specific class instance. */ -export class SendToQueue implements sfn.ITaskResource { +export class SendToQueue implements sfn.IStepFunctionsTask { public readonly resourceArn: string; public readonly policyStatements?: iam.PolicyStatement[] | undefined; public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts index 1ad5ccec506d6..cd2cd85e7ba98 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts @@ -22,7 +22,7 @@ test('Running a Fargate Task', () => { }); // WHEN - const runTask = new tasks.RunEcsFargateTask(stack, 'Run', { + const runTask = new sfn.Task(stack, 'Run', { task: new tasks.RunEcsFargateTask(stack, 'RunFargate', { cluster, taskDefinition, containerOverrides: [ @@ -33,7 +33,7 @@ test('Running a Fargate Task', () => { ] } ] - }); + }) }); new sfn.StateMachine(stack, 'SM', { definition: runTask @@ -48,7 +48,7 @@ test('Running a Fargate Task', () => { NetworkConfiguration: { AwsvpcConfiguration: { AssignPublicIp: "DISABLED", - SecurityGroups: [{"Fn::GetAtt": ["RunSecurityGroupFBA8EDA8", "GroupId"]}], + SecurityGroups: [{"Fn::GetAtt": ["RunFargateSecurityGroup709740F2", "GroupId"]}], Subnets: [ {Ref: "VpcPrivateSubnet1Subnet536B997A"}, {Ref: "VpcPrivateSubnet2Subnet3788AAA1"}, @@ -130,7 +130,7 @@ test('Running an EC2 Task with bridge network', () => { }); // WHEN - const runTask = new tasks.RunEcsEc2Task(stack, 'Run', { + const runTask = new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task(stack, 'RunEc2', { cluster, taskDefinition, containerOverrides: [ @@ -141,7 +141,7 @@ test('Running an EC2 Task with bridge network', () => { ] } ] - }); + }) }); new sfn.StateMachine(stack, 'SM', { definition: runTask @@ -206,4 +206,59 @@ test('Running an EC2 Task with bridge network', () => { ], }, }); +}); + +test('Running an EC2 Task with placement strategies', () => { + // GIVEN + const stack = new Stack(); + + const vpc = new ec2.VpcNetwork(stack, 'Vpc'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('Capacity', { + instanceType: new ec2.InstanceType('t3.medium') + }); + + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + compatibility: ecs.Compatibility.Ec2 + }); + taskDefinition.addContainer('henk', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); + + const ec2Task = new tasks.RunEcsEc2Task(stack, 'RunEc2', { + cluster, + taskDefinition, + }); + ec2Task.placeSpreadAcross(); + ec2Task.placePackedBy(ecs.BinPackResource.Cpu); + ec2Task.placeRandomly(); + ec2Task.placeOnMemberOf('blieptuut'); + + // WHEN + const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); + + new sfn.StateMachine(stack, 'SM', { + definition: runTask + }); + + // THEN + expect(stack.node.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: {"Fn::GetAtt": ["ClusterEB0386A7", "Arn"]}, + LaunchType: "EC2", + TaskDefinition: {Ref: "TD49C78F36"}, + PlacementConstraints: [ + { Type: "memberOf", expression: "blieptuut", }, + ], + PlacementStrategy: [ + { Field: "instanceId", Type: "spread", }, + { Field: "cpu", Type: "binpack", }, + { Type: "random", }, + ], + }, + Resource: "arn:aws:states:::ecs:runTask.sync", + Type: "Task", + }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts index 2502c7ae307df..bbe9875963561 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts @@ -29,7 +29,7 @@ taskDefinition.addContainer('TheContainer', { // Build state machine const definition = new sfn.Pass(stack, 'Start', { result: { SomeKey: 'SomeValue' } -}).next(new tasks.RunEcsEc2Task(stack, 'RunEc2', { +}).next(new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task(stack, 'RunEc2', { cluster, taskDefinition, containerOverrides: [ { @@ -42,7 +42,7 @@ const definition = new sfn.Pass(stack, 'Start', { ] } ] -})); +})})); new sfn.StateMachine(stack, 'StateMachine', { definition, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts index 747d977f7aee1..4909b78578554 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts @@ -28,7 +28,7 @@ taskDefinition.addContainer('TheContainer', { // Build state machine const definition = new sfn.Pass(stack, 'Start', { result: { SomeKey: 'SomeValue' } -}).next(new tasks.RunEcsFargateTask(stack, 'RunFargate', { +}).next(new sfn.Task(stack, 'Run', { task: new tasks.RunEcsFargateTask(stack, 'RunFargate', { cluster, taskDefinition, assignPublicIp: true, containerOverrides: [ @@ -42,7 +42,7 @@ const definition = new sfn.Pass(stack, 'Start', { ] } ] -})); +})})); new sfn.StateMachine(stack, 'StateMachine', { definition, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts index 9a16e0f5845f6..3352ca14255a6 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts @@ -1,43 +1,44 @@ +import sfn = require('@aws-cdk/aws-stepfunctions'); import cdk = require( '@aws-cdk/cdk'); -import stepfunctions = require('../lib'); +import tasks = require('../lib'); class JobPollerStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props: cdk.StackProps = {}) { super(scope, id, props); - const submitJobActivity = new stepfunctions.Activity(this, 'SubmitJob'); - const checkJobActivity = new stepfunctions.Activity(this, 'CheckJob'); + const submitJobActivity = new sfn.Activity(this, 'SubmitJob'); + const checkJobActivity = new sfn.Activity(this, 'CheckJob'); - const submitJob = new stepfunctions.Task(this, 'Submit Job', { - resourceArn: submitJobActivity.activityArn, + const submitJob = new sfn.Task(this, 'Submit Job', { + task: new tasks.InvokeActivity(submitJobActivity), resultPath: '$.guid', }); - const waitX = new stepfunctions.Wait(this, 'Wait X Seconds', { duration: stepfunctions.WaitDuration.secondsPath('$.wait_time') }); - const getStatus = new stepfunctions.Task(this, 'Get Job Status', { - resourceArn: checkJobActivity.activityArn, + const waitX = new sfn.Wait(this, 'Wait X Seconds', { duration: sfn.WaitDuration.secondsPath('$.wait_time') }); + const getStatus = new sfn.Task(this, 'Get Job Status', { + task: new tasks.InvokeActivity(submitJobActivity), inputPath: '$.guid', resultPath: '$.status', }); - const isComplete = new stepfunctions.Choice(this, 'Job Complete?'); - const jobFailed = new stepfunctions.Fail(this, 'Job Failed', { + const isComplete = new sfn.Choice(this, 'Job Complete?'); + const jobFailed = new sfn.Fail(this, 'Job Failed', { cause: 'AWS Batch Job Failed', error: 'DescribeJob returned FAILED', }); - const finalStatus = new stepfunctions.Task(this, 'Get Final Job Status', { - resourceArn: checkJobActivity.activityArn, + const finalStatus = new sfn.Task(this, 'Get Final Job Status', { + task: new tasks.InvokeActivity(checkJobActivity), inputPath: '$.guid', }); - const chain = stepfunctions.Chain + const chain = sfn.Chain .start(submitJob) .next(waitX) .next(getStatus) .next(isComplete - .when(stepfunctions.Condition.stringEquals('$.status', 'FAILED'), jobFailed) - .when(stepfunctions.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus) + .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed) + .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus) .otherwise(waitX)); - new stepfunctions.StateMachine(this, 'StateMachine', { + new sfn.StateMachine(this, 'StateMachine', { definition: chain, timeoutSec: 30 }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts index 0272ad49d62ef..89976aad4ec92 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-activity.test.ts @@ -9,7 +9,7 @@ test('Activity can be used in a Task', () => { // WHEN const activity = new sfn.Activity(stack, 'Activity'); - const task = new tasks.InvokeActivity(stack, 'Task', { activity }); + const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeActivity(activity) }); new sfn.StateMachine(stack, 'SM', { definition: task }); @@ -30,7 +30,7 @@ test('Activity Task metrics and Activity metrics are the same', () => { // GIVEN const stack = new Stack(); const activity = new sfn.Activity(stack, 'Activity'); - const task = new tasks.InvokeActivity(stack, 'Invoke', { activity }); + const task = new sfn.Task(stack, 'Invoke', { task: new tasks.InvokeActivity(activity) }); // WHEN const activityMetrics = [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-function.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-function.test.ts new file mode 100644 index 0000000000000..e0fab6553ac17 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/invoke-function.test.ts @@ -0,0 +1,32 @@ +import '@aws-cdk/assert/jest'; +import lambda = require('@aws-cdk/aws-lambda'); +import sfn = require('@aws-cdk/aws-stepfunctions'); +import { Stack } from '@aws-cdk/cdk'; +import tasks = require('../lib'); + +test('Lambda function can be used in a Task', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const fn = new lambda.Function(stack, 'Fn', { + code: lambda.Code.inline('hello'), + handler: 'index.hello', + runtime: lambda.Runtime.Python27, + }); + const task = new sfn.Task(stack, 'Task', { task: new tasks.InvokeFunction(fn) }); + new sfn.StateMachine(stack, 'SM', { + definition: task + }); + + // THEN + expect(stack).toHaveResource('AWS::StepFunctions::StateMachine', { + DefinitionString: { + "Fn::Join": ["", [ + "{\"StartAt\":\"Task\",\"States\":{\"Task\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"", + { "Fn::GetAtt": ["Fn9270CBC0", "Arn"] }, + "\"}}}" + ]] + }, + }); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts index 1e4fc39fa1d1b..aa2840864c07a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/publish-to-topic.test.ts @@ -1,4 +1,5 @@ import sns = require('@aws-cdk/aws-sns'); +import sfn = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); import tasks = require('../lib'); @@ -8,10 +9,9 @@ test('publish to SNS', () => { const topic = new sns.Topic(stack, 'Topic'); // WHEN - const pub = new tasks.PublishTask(stack, 'Publish', { - topic, + const pub = new sfn.Task(stack, 'Publish', { task: new tasks.PublishToTopic(topic, { message: 'Send this message' - }); + }) }); // THEN expect(stack.node.resolve(pub.toStateJson())).toEqual({ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts index 035fe68efdd0a..4dbb94fb195d9 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts @@ -1,4 +1,5 @@ import sqs = require('@aws-cdk/aws-sqs'); +import sfn = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); import tasks = require('../lib'); @@ -8,11 +9,10 @@ test('publish to queue', () => { const queue = new sqs.Queue(stack, 'Queue'); // WHEN - const pub = new tasks.SendMessageTask(stack, 'Send', { - queue, + const pub = new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { messageBody: 'Send this message', messageDeduplicationIdPath: '$.deduping', - }); + }) }); // THEN expect(stack.node.resolve(pub.toStateJson())).toEqual({ diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions/lib/index.ts index ba3f11be6ba59..a550a54412b66 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/index.ts @@ -6,7 +6,7 @@ export * from './state-machine-fragment'; export * from './state-transition-metrics'; export * from './chain'; export * from './state-graph'; -export * from './task-resource'; +export * from './step-functions-task'; export * from './states/choice'; export * from './states/fail'; diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index ceecfe9878826..21fa75b92e002 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -2,7 +2,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import { Chain } from '../chain'; import { StateGraph } from '../state-graph'; -import { ITaskResource } from '../task-resource'; +import { IStepFunctionsTask } from '../step-functions-task'; import { CatchProps, IChainable, INextable, RetryProps } from '../types'; import { renderJsonPath, State, StateType } from './state'; @@ -11,9 +11,9 @@ import { renderJsonPath, State, StateType } from './state'; */ export interface TaskProps { /** - * Resource to be invoked in this workflow + * Actual task to be invoked in this workflow */ - readonly resource: ITaskResource; + readonly task: IStepFunctionsTask; /** * An optional description for this state @@ -75,13 +75,13 @@ export interface TaskProps { export class Task extends State implements INextable { public readonly endStates: INextable[]; private readonly timeoutSeconds?: number; - private readonly resource: ITaskResource; + private readonly task: IStepFunctionsTask; constructor(scope: cdk.Construct, id: string, props: TaskProps) { super(scope, id, props); this.timeoutSeconds = props.timeoutSeconds; - this.resource = props.resource; + this.task = props.task; this.endStates = [this]; } @@ -125,11 +125,11 @@ export class Task extends State implements INextable { ...this.renderInputOutput(), Type: StateType.Task, Comment: this.comment, - Resource: this.resource.resourceArn, - Parameters: this.parameters, + Resource: this.task.resourceArn, + Parameters: this.task.parameters, ResultPath: renderJsonPath(this.resultPath), TimeoutSeconds: this.timeoutSeconds, - HeartbeatSeconds: this.resource.heartbeatSeconds, + HeartbeatSeconds: this.task.heartbeatSeconds, }; } @@ -142,7 +142,7 @@ export class Task extends State implements INextable { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, - dimensions: this.resource.metricDimensions, + dimensions: this.task.metricDimensions, statistic: 'sum', ...props }); @@ -154,7 +154,7 @@ export class Task extends State implements INextable { * @default average over 5 minutes */ public metricRunTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resource.metricPrefixSingular, 'RunTime', { statistic: 'avg', ...props }); + return this.taskMetric(this.task.metricPrefixSingular, 'RunTime', { statistic: 'avg', ...props }); } /** @@ -163,7 +163,7 @@ export class Task extends State implements INextable { * @default average over 5 minutes */ public metricScheduleTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resource.metricPrefixSingular, 'ScheduleTime', { statistic: 'avg', ...props }); + return this.taskMetric(this.task.metricPrefixSingular, 'ScheduleTime', { statistic: 'avg', ...props }); } /** @@ -172,7 +172,7 @@ export class Task extends State implements INextable { * @default average over 5 minutes */ public metricTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resource.metricPrefixSingular, 'Time', { statistic: 'avg', ...props }); + return this.taskMetric(this.task.metricPrefixSingular, 'Time', { statistic: 'avg', ...props }); } /** @@ -181,7 +181,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricScheduled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resource.metricPrefixPlural, 'Scheduled', props); + return this.taskMetric(this.task.metricPrefixPlural, 'Scheduled', props); } /** @@ -190,7 +190,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resource.metricPrefixPlural, 'TimedOut', props); + return this.taskMetric(this.task.metricPrefixPlural, 'TimedOut', props); } /** @@ -199,7 +199,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricStarted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resource.metricPrefixPlural, 'Started', props); + return this.taskMetric(this.task.metricPrefixPlural, 'Started', props); } /** @@ -208,7 +208,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricSucceeded(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resource.metricPrefixPlural, 'Succeeded', props); + return this.taskMetric(this.task.metricPrefixPlural, 'Succeeded', props); } /** @@ -217,7 +217,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricFailed(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resource.metricPrefixPlural, 'Failed', props); + return this.taskMetric(this.task.metricPrefixPlural, 'Failed', props); } /** @@ -226,12 +226,12 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricHeartbeatTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.resource.metricPrefixPlural, 'HeartbeatTimedOut', props); + return this.taskMetric(this.task.metricPrefixPlural, 'HeartbeatTimedOut', props); } protected onBindToGraph(graph: StateGraph) { super.onBindToGraph(graph); - for (const policyStatement of this.resource.policyStatements || []) { + for (const policyStatement of this.task.policyStatements || []) { graph.registerPolicyStatement(policyStatement); } } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/task-resource.ts b/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts similarity index 97% rename from packages/@aws-cdk/aws-stepfunctions/lib/task-resource.ts rename to packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts index ed1bdfae75e57..b180fe1a4984b 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/task-resource.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts @@ -4,7 +4,7 @@ import iam = require('@aws-cdk/aws-iam'); /** * Interface for resources that can be used as tasks */ -export interface ITaskResource { +export interface IStepFunctionsTask { /** * The resource that represents the work to be executed * diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.condition.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.condition.ts index 118504b910f74..19b166b74437a 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.condition.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.condition.ts @@ -10,15 +10,56 @@ export = { test.done(); }, 'NotConditon must render properly'(test: Test) { - // GIVEN - const condition = stepfunctions.Condition.not(stepfunctions.Condition.stringEquals('$.a', 'b')); + assertRendersTo(test, + stepfunctions.Condition.not(stepfunctions.Condition.stringEquals('$.a', 'b')), + {Not: {Variable: '$.a', StringEquals: 'b'}} + ); - // WHEN - const render = condition.renderCondition(); + test.done(); + }, + 'CompoundCondition must render properly'(test: Test) { + assertRendersTo(test, + stepfunctions.Condition.and( + stepfunctions.Condition.booleanEquals('$.a', true), + stepfunctions.Condition.numberGreaterThan('$.b', 3) + ), + { And: [ { Variable: '$.a', BooleanEquals: true }, { Variable: '$.b', NumericGreaterThan: 3 } ] } + ); + + test.done(); + }, + 'Exercise a number of other conditions'(test: Test) { + const cases: Array<[stepfunctions.Condition, object]> = [ + [ + stepfunctions.Condition.stringLessThan('$.a', 'foo'), + { Variable: '$.a', StringLessThan: 'foo' }, + ], + [ + stepfunctions.Condition.stringLessThanEquals('$.a', 'foo'), + { Variable: '$.a', StringLessThanEquals: 'foo' }, + ], + [ + stepfunctions.Condition.stringGreaterThan('$.a', 'foo'), + { Variable: '$.a', StringGreaterThan: 'foo' }, + ], + [ + stepfunctions.Condition.stringGreaterThanEquals('$.a', 'foo'), + { Variable: '$.a', StringGreaterThanEquals: 'foo' }, + ], + [ + stepfunctions.Condition.numberEquals('$.a', 5), + { Variable: '$.a', NumericEquals: 5 } + ], + ]; - // THEN - test.deepEqual(render, {Not: {Variable: '$.a', StringEquals: 'b'}}); + for (const [cond, expected] of cases) { + assertRendersTo(test, cond, expected); + } test.done(); }, }; + +function assertRendersTo(test: Test, cond: stepfunctions.Condition, expected: any) { + test.deepEqual(cond.renderCondition(), expected); +} diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts index a34085f553ab8..7faa51972a7a5 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts @@ -10,7 +10,7 @@ export = { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { - resource: { + task: { resourceArn: 'resource', policyStatements: [new iam.PolicyStatement() .addAction('resource:Everything') @@ -45,8 +45,13 @@ export = { // GIVEN const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { - resource: { - resourceArn: 'resource' + task: { + resourceArn: 'resource', + policyStatements: [ + new iam.PolicyStatement() + .addAction('resource:Everything') + .addResource('resource') + ] } }); @@ -81,7 +86,7 @@ export = { const task = new stepfunctions.Task(stack, 'Task', { inputPath: "$", outputPath: "$.state", - resource: { + task: { resourceArn: 'resource', parameters: { "input.$": "$", diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts index 177dce8dc65e0..c559e173c4bb5 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts @@ -1,6 +1,7 @@ import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import stepfunctions = require('../lib'); +import { IStepFunctionsTask } from '../lib'; export = { 'Basic composition': { @@ -397,7 +398,7 @@ export = { 'States can have error branches'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' }}); + const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask()}); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -429,7 +430,7 @@ export = { 'Retries and errors with a result path'(test: Test) { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); + const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -462,8 +463,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); + const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); + const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -508,8 +509,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); + const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); + const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -539,9 +540,9 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); - const task3 = new stepfunctions.Task(stack, 'Task3', { resource: { resourceArn: 'resource' } }); + const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); + const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); + const task3 = new stepfunctions.Task(stack, 'Task3', { task: new FakeTask() }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -568,8 +569,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); + const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); + const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); const errorHandler = new stepfunctions.Pass(stack, 'ErrorHandler'); // WHEN @@ -584,8 +585,8 @@ export = { // GIVEN const stack = new cdk.Stack(); - const task1 = new stepfunctions.Task(stack, 'Task1', { resource: { resourceArn: 'resource' } }); - const task2 = new stepfunctions.Task(stack, 'Task2', { resource: { resourceArn: 'resource' } }); + const task1 = new stepfunctions.Task(stack, 'Task1', { task: new FakeTask() }); + const task2 = new stepfunctions.Task(stack, 'Task2', { task: new FakeTask() }); const failure = new stepfunctions.Fail(stack, 'Failed', { error: 'DidNotWork', cause: 'We got stuck' }); // WHEN @@ -705,8 +706,8 @@ class SimpleChain extends stepfunctions.StateMachineFragment { constructor(scope: cdk.Construct, id: string) { super(scope, id); - const task1 = new stepfunctions.Task(this, 'Task1', { resource: { resourceArn: 'resource' } }); - this.task2 = new stepfunctions.Task(this, 'Task2', { resource: { resourceArn: 'resource' } }); + const task1 = new stepfunctions.Task(this, 'Task1', { task: new FakeTask() }); + this.task2 = new stepfunctions.Task(this, 'Task2', { task: new FakeTask() }); task1.next(this.task2); @@ -723,3 +724,7 @@ class SimpleChain extends stepfunctions.StateMachineFragment { function render(sm: stepfunctions.IChainable) { return new cdk.Stack().node.resolve(new stepfunctions.StateGraph(sm.startState, 'Test Graph').toGraphJson()); } + +class FakeTask implements IStepFunctionsTask { + public readonly resourceArn = 'resource'; +} \ No newline at end of file From 3d2bf84aee1d60a8569a5106d0bdcc47cd193a76 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 7 May 2019 17:24:17 +0200 Subject: [PATCH 12/21] Get rid of 'xxx' and 'xxxPath' arguments, use Tokens to embed the distinction. --- packages/@aws-cdk/aws-ecs/package.json | 1 - .../@aws-cdk/aws-iam/lib/policy-document.ts | 2 +- .../aws-stepfunctions-tasks/.gitignore | 3 +- .../lib/base-run-task-types.ts | 109 ------------- .../lib/ecs-run-task-base-types.ts | 60 +++++++ ...{base-run-task.ts => ecs-run-task-base.ts} | 74 ++++----- .../aws-stepfunctions-tasks/lib/index.ts | 6 +- .../aws-stepfunctions-tasks/lib/json-path.ts | 119 ++++++++++++++ .../lib/number-value.ts | 53 ++++++ .../lib/publish-to-topic.ts | 39 ++--- .../lib/run-ecs-ec2-task.ts | 10 +- .../lib/run-ecs-fargate-task.ts | 8 +- .../lib/send-to-queue.ts | 66 +------- .../aws-stepfunctions-tasks/package.json | 2 +- .../test/ecs-tasks.test.ts | 100 ++++++++---- .../test/eventhandler-image/Dockerfile | 5 + .../test/eventhandler-image/index.py | 6 + .../test/integ.ec2-task.expected.json | 137 ++++++++++++---- .../test/integ.ec2-task.ts | 8 +- .../test/integ.fargate-task.expected.json | 52 ++++-- .../test/integ.fargate-task.ts | 6 +- .../test/integ.job-poller.ts | 2 +- .../test/send-to-queue.test.ts | 2 +- packages/@aws-cdk/aws-stepfunctions/README.md | 151 +++++++---------- .../@aws-cdk/aws-stepfunctions/lib/types.ts | 5 + packages/@aws-cdk/cdk/lib/arn.ts | 4 +- packages/@aws-cdk/cdk/lib/construct.ts | 3 +- packages/@aws-cdk/cdk/lib/encoding.ts | 154 +++++------------- packages/@aws-cdk/cdk/lib/fn.ts | 3 +- packages/@aws-cdk/cdk/lib/index.ts | 2 +- packages/@aws-cdk/cdk/lib/instrinsics.ts | 6 +- packages/@aws-cdk/cdk/lib/resolve.ts | 40 ++++- packages/@aws-cdk/cdk/lib/token-map.ts | 99 +++++++++++ packages/@aws-cdk/cdk/lib/token.ts | 8 +- packages/@aws-cdk/cdk/lib/uniqueid.ts | 2 +- packages/@aws-cdk/cdk/lib/unresolved.ts | 19 --- packages/@aws-cdk/cdk/test/test.condition.ts | 2 +- packages/@aws-cdk/cdk/test/test.tokens.ts | 30 +++- 38 files changed, 806 insertions(+), 592 deletions(-) delete mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task-types.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base-types.ts rename packages/@aws-cdk/aws-stepfunctions-tasks/lib/{base-run-task.ts => ecs-run-task-base.ts} (74%) create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/json-path.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/lib/number-value.ts create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/eventhandler-image/Dockerfile create mode 100644 packages/@aws-cdk/aws-stepfunctions-tasks/test/eventhandler-image/index.py create mode 100644 packages/@aws-cdk/cdk/lib/token-map.ts delete mode 100644 packages/@aws-cdk/cdk/lib/unresolved.ts diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 0630d74da11f3..c2f80deb70f1a 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -86,7 +86,6 @@ "@aws-cdk/aws-secretsmanager": "^0.29.0", "@aws-cdk/aws-servicediscovery": "^0.29.0", "@aws-cdk/aws-sns": "^0.29.0", - "@aws-cdk/aws-stepfunctions": "^0.29.0", "@aws-cdk/cdk": "^0.29.0", "@aws-cdk/cx-api": "^0.29.0" }, diff --git a/packages/@aws-cdk/aws-iam/lib/policy-document.ts b/packages/@aws-cdk/aws-iam/lib/policy-document.ts index 8678f280718cd..1bd5851efe6a5 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-document.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-document.ts @@ -533,7 +533,7 @@ export class PolicyStatement extends cdk.Token { return undefined; } - if (cdk.unresolved(values)) { + if (cdk.Token.unresolved(values)) { return values; } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore b/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore index 205e21fe7353b..0eb18b3fdfc39 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/.gitignore @@ -13,4 +13,5 @@ lib/generated/resources.ts coverage .nycrc .LAST_PACKAGE -*.snk \ No newline at end of file +*.snk +.cdk.staging diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task-types.ts deleted file mode 100644 index ae0eadc9bb657..0000000000000 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task-types.ts +++ /dev/null @@ -1,109 +0,0 @@ -export interface ContainerOverride { - /** - * Name of the container inside the task definition - * - * Exactly one of `containerName` and `containerNamePath` is required. - */ - readonly containerName?: string; - - /** - * JSONPath expression for the name of the container inside the task definition - * - * Exactly one of `containerName` and `containerNamePath` is required. - */ - readonly containerNamePath?: string; - - /** - * Command to run inside the container - * - * @default Default command - */ - readonly command?: string[]; - - /** - * JSON expression for command to run inside the container - * - * @default Default command - */ - readonly commandPath?: string; - - /** - * Variables to set in the container's environment - */ - readonly environment?: TaskEnvironmentVariable[]; - - /** - * The number of cpu units reserved for the container - * - * @Default The default value from the task definition. - */ - readonly cpu?: number; - - /** - * JSON expression for the number of CPU units - * - * @Default The default value from the task definition. - */ - readonly cpuPath?: string; - - /** - * Hard memory limit on the container - * - * @Default The default value from the task definition. - */ - readonly memoryLimit?: number; - - /** - * JSON expression path for the hard memory limit - * - * @Default The default value from the task definition. - */ - readonly memoryLimitPath?: string; - - /** - * Soft memory limit on the container - * - * @Default The default value from the task definition. - */ - readonly memoryReservation?: number; - - /** - * JSONExpr path for memory limit on the container - * - * @Default The default value from the task definition. - */ - readonly memoryReservationPath?: number; -} - -/** - * An environment variable to be set in the container run as a task - */ -export interface TaskEnvironmentVariable { - /** - * Name for the environment variable - * - * Exactly one of `name` and `namePath` must be specified. - */ - readonly name?: string; - - /** - * JSONExpr for the name of the variable - * - * Exactly one of `name` and `namePath` must be specified. - */ - readonly namePath?: string; - - /** - * Value of the environment variable - * - * Exactly one of `value` and `valuePath` must be specified. - */ - readonly value?: string; - - /** - * JSONPath expr for the environment variable - * - * Exactly one of `value` and `valuePath` must be specified. - */ - readonly valuePath?: string; -} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base-types.ts new file mode 100644 index 0000000000000..7e1ca0e1e21be --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base-types.ts @@ -0,0 +1,60 @@ +import { NumberValue } from "./number-value"; + +export interface ContainerOverride { + /** + * Name of the container inside the task definition + */ + readonly containerName: string; + + /** + * Command to run inside the container + * + * @default Default command + */ + readonly command?: string[]; + + /** + * Variables to set in the container's environment + */ + readonly environment?: TaskEnvironmentVariable[]; + + /** + * The number of cpu units reserved for the container + * + * @Default The default value from the task definition. + */ + readonly cpu?: NumberValue; + + /** + * Hard memory limit on the container + * + * @Default The default value from the task definition. + */ + readonly memoryLimit?: NumberValue; + + /** + * Soft memory limit on the container + * + * @Default The default value from the task definition. + */ + readonly memoryReservation?: NumberValue; +} + +/** + * An environment variable to be set in the container run as a task + */ +export interface TaskEnvironmentVariable { + /** + * Name for the environment variable + * + * Exactly one of `name` and `namePath` must be specified. + */ + readonly name: string; + + /** + * Value of the environment variable + * + * Exactly one of `value` and `valuePath` must be specified. + */ + readonly value: string; +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts similarity index 74% rename from packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts index 176944541ce5c..94ea58b5b132b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/base-run-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts @@ -3,12 +3,13 @@ import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import { ContainerOverride } from './base-run-task-types'; +import { ContainerOverride } from './ecs-run-task-base-types'; +import { renderNumber, renderString, renderStringList } from './json-path'; /** - * Properties for SendMessageTask + * Basic properties for ECS Tasks */ -export interface CommonRunTaskProps { +export interface CommonEcsRunTaskProps { /** * The topic to run the task on */ @@ -38,7 +39,7 @@ export interface CommonRunTaskProps { /** * Construction properties for the BaseRunTaskProps */ -export interface BaseRunTaskProps extends CommonRunTaskProps { +export interface EcsRunTaskBaseProps extends CommonEcsRunTaskProps { /** * Additional parameters to pass to the base task */ @@ -48,7 +49,7 @@ export interface BaseRunTaskProps extends CommonRunTaskProps { /** * A StepFunctions Task to run a Task on ECS or Fargate */ -export class BaseRunTask extends cdk.Construct implements ec2.IConnectable { +export class EcsRunTaskBase extends cdk.Construct implements ec2.IConnectable { /** * Manage allowed network traffic for this service */ @@ -64,7 +65,7 @@ export class BaseRunTask extends cdk.Construct implements ec2.IConnectable { protected readonly taskDefinition: ecs.TaskDefinition; private readonly sync: boolean; - constructor(scope: cdk.Construct, id: string, private readonly props: BaseRunTaskProps) { + constructor(scope: cdk.Construct, id: string, private readonly props: EcsRunTaskBaseProps) { super(scope, id); this.resourceArn = 'arn:aws:states:::ecs:runTask' + (props.synchronous !== false ? '.sync' : ''); @@ -134,6 +135,28 @@ export class BaseRunTask extends cdk.Construct implements ec2.IConnectable { return policyStatements; } + /** + * Validate this service + * + * Check that all mentioned container overrides exist in the task definition + * (but only if the name is not a token, in which case we can't tell). + */ + protected validate(): string[] { + const ret = super.validate(); + + for (const override of this.props.containerOverrides || []) { + const name = override.containerName; + if (!cdk.Token.unresolved(name)) { + const cont = this.props.taskDefinition.node.tryFindChild(name); + if (!cont) { + ret.push(`Overrides mention container with name '${name}', but no such container in task definition`); + } + } + } + + return ret; + } + private taskExecutionRoles(): iam.IRole[] { // Need to be able to pass both Task and Execution role, apparently const ret = new Array(); @@ -145,48 +168,23 @@ export class BaseRunTask extends cdk.Construct implements ec2.IConnectable { } } -function extractRequired(obj: any, srcKey: string, dstKey: string) { - if ((obj[srcKey] !== undefined) === (obj[srcKey + 'Path'] !== undefined)) { - throw new Error(`Supply exactly one of '${srcKey}' or '${srcKey}Path'`); - } - return mapValue(obj, srcKey, dstKey); -} - function renderOverrides(containerOverrides?: ContainerOverride[]) { if (!containerOverrides) { return undefined; } const ret = new Array(); for (const override of containerOverrides) { ret.push({ - ...extractRequired(override, 'containerName', 'Name'), - ...extractOptional(override, 'command', 'Command'), - ...extractOptional(override, 'cpu', 'Cpu'), - ...extractOptional(override, 'memoryLimit', 'Memory'), - ...extractOptional(override, 'memoryReservation', 'MemoryReservation'), + ...renderString('Name', override.containerName), + ...renderStringList('Command', override.command), + ...renderNumber('Cpu', override.cpu), + ...renderNumber('Memory', override.memoryLimit), + ...renderNumber('MemoryReservation', override.memoryReservation), Environment: override.environment && override.environment.map(e => ({ - ...extractRequired(e, 'name', 'Name'), - ...extractRequired(e, 'value', 'Value'), + ...renderString('Name', e.name), + ...renderString('Value', e.value), })) }); } return { ContainerOverrides: ret }; -} - -function extractOptional(obj: any, srcKey: string, dstKey: string) { - if ((obj[srcKey] !== undefined) && (obj[srcKey + 'Path'] !== undefined)) { - throw new Error(`Supply only one of '${srcKey}' or '${srcKey}Path'`); - } - return mapValue(obj, srcKey, dstKey); -} - -function mapValue(obj: any, srcKey: string, dstKey: string) { - if (obj[srcKey + 'Path'] !== undefined && !obj[srcKey + 'Path'].startsWith('$.')) { - throw new Error(`Value for '${srcKey}Path' must start with '$.', got: '${obj[srcKey + 'Path']}'`); - } - - return { - [dstKey]: obj[srcKey], - [dstKey + '.$']: obj[srcKey + 'Path'] - }; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index 67a40324e3fcd..517febc29c455 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -1,8 +1,10 @@ export * from './invoke-function'; export * from './invoke-activity'; -export * from './base-run-task'; // Remove this once we can -export * from './base-run-task-types'; +export * from './ecs-run-task-base'; // Remove this once we can +export * from './ecs-run-task-base-types'; export * from './publish-to-topic'; export * from './send-to-queue'; export * from './run-ecs-ec2-task'; export * from './run-ecs-fargate-task'; +export * from './number-value'; +export * from './json-path'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/json-path.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/json-path.ts new file mode 100644 index 0000000000000..9b80f9e37a9d5 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/json-path.ts @@ -0,0 +1,119 @@ +import { Token, TokenMap } from '@aws-cdk/cdk'; +import { NumberValue } from './number-value'; + +/** + * Class to create special parameters for state machine states + */ +export class JsonPath { + /** + * Instead of using a literal string, get the value from a JSON path + */ + public static stringFromPath(path: string): string { + if (!path.startsWith('$.')) { + throw new Error("JSONPath values must start with '$.'"); + } + return new JsonPathToken(path).toString(); + } + + /** + * Instead of using a literal string list, get the value from a JSON path + */ + public static listFromPath(path: string): string[] { + if (!path.startsWith('$.')) { + throw new Error("JSONPath values must start with '$.'"); + } + return new JsonPathToken(path).toList(); + } + + /** + * Get a number from a JSON path + */ + public static numberFromPath(path: string): NumberValue { + return NumberValue.fromJsonPath(path); + } + + private constructor() { + } +} + +const JSON_PATH_TOKEN_SYMBOL = Symbol.for('JsonPathToken'); + +class JsonPathToken extends Token { + public static isJsonPathToken(x: object): x is JsonPathToken { + return (x as any)[JSON_PATH_TOKEN_SYMBOL] === true; + } + + constructor(public readonly path: string) { + super(() => path); // Make function to prevent eager evaluation in superclass + Object.defineProperty(this, JSON_PATH_TOKEN_SYMBOL, { value: true }); + } +} + +/** + * Render a parameter string + * + * If the string value starts with '$.', render it as a path string, otherwise as a direct string. + */ +export function renderString(key: string, value: string | undefined): {[key: string]: string} { + if (value === undefined) { return {}; } + + const path = jsonPathString(value); + if (path !== undefined) { + return { [key + '.$']: path }; + } else { + return { [key]: value }; + } +} + +/** + * Render a parameter string + * + * If the string value starts with '$.', render it as a path string, otherwise as a direct string. + */ +export function renderStringList(key: string, value: string[] | undefined): {[key: string]: string[] | string} { + if (value === undefined) { return {}; } + + const path = jsonPathStringList(value); + if (path !== undefined) { + return { [key + '.$']: path }; + } else { + return { [key]: value }; + } +} + +/** + * Render a parameter string + * + * If the string value starts with '$.', render it as a path string, otherwise as a direct string. + */ +export function renderNumber(key: string, value: NumberValue | undefined): {[key: string]: number | string} { + if (value === undefined) { return {}; } + + if (!value.isLiteralNumber) { + return { [key + '.$']: value.jsonPath }; + } else { + return { [key]: value.numberValue }; + } +} + +/** + * If the indicated string is an encoded JSON path, return the path + * + * Otherwise return undefined. + */ +function jsonPathString(x: string): string | undefined { + return pathFromToken(TokenMap.instance().lookupString(x)); +} + +/** + * If the indicated string list is an encoded JSON path, return the path + * + * Otherwise return undefined. + */ +function jsonPathStringList(x: string[]): string | undefined { + return pathFromToken(TokenMap.instance().lookupList(x)); +} + +function pathFromToken(token: Token | undefined) { + return token && (JsonPathToken.isJsonPathToken(token) ? token.path : undefined); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/number-value.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/number-value.ts new file mode 100644 index 0000000000000..4d01b302ab504 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/number-value.ts @@ -0,0 +1,53 @@ +/** + * A number value argument to a Task + * + * Either obtained from the current state, or from a literal number. + * + * This class is only necessary until https://github.com/awslabs/aws-cdk/issues/1455 is solved, + * after which time we'll be able to use actual numbers to encode Tokens. + */ +export class NumberValue { + /** + * Use a literal number + */ + public static fromNumber(n: number): NumberValue { + return new NumberValue(n); + } + + /** + * Obtain a number from the current state + */ + public static fromJsonPath(path: string): NumberValue { + return new NumberValue(undefined, path); + } + + private constructor(private readonly n?: number, private readonly path?: string) { + } + + /** + * Return whether the NumberValue contains a literal number + */ + public get isLiteralNumber(): boolean { + return this.n !== undefined; + } + + /** + * Get the literal number from the NumberValue + */ + public get numberValue(): number { + if (this.n === undefined) { + throw new Error('NumberValue does not have a number'); + } + return this.n; + } + + /** + * Get the JSON Path from the NumberValue + */ + public get jsonPath(): string { + if (this.path === undefined) { + throw new Error('NumberValue does not have a JSONPath'); + } + return this.path; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts index 04e78e30fe971..ed61153472504 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts @@ -3,6 +3,7 @@ import iam = require('@aws-cdk/aws-iam'); import sns = require('@aws-cdk/aws-sns'); import sfn = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); +import { renderString } from './json-path'; /** * Properties for PublishTask @@ -11,17 +12,10 @@ export interface PublishToTopicProps { /** * The text message to send to the queue. * - * Exactly one of `message`, `messageObject` and `messagePath` is required. + * Exactly one of `message` and `messageObject` is required. */ readonly message?: string; - /** - * JSONPath expression of the message to send to the queue - * - * Exactly one of `message`, `messageObject` and `messagePath` is required. - */ - readonly messagePath?: string; - /** * Object to be JSON-encoded and used as message * @@ -45,11 +39,6 @@ export interface PublishToTopicProps { * Message subject */ readonly subject?: string; - - /** - * JSONPath expression of subject - */ - readonly subjectPath?: string; } /** @@ -69,14 +58,8 @@ export class PublishToTopic implements sfn.IStepFunctionsTask { public readonly parameters?: { [name: string]: any; } | undefined; constructor(topic: sns.ITopic, props: PublishToTopicProps) { - if ((props.message !== undefined ? 1 : 0) - + (props.messagePath !== undefined ? 1 : 0) - + (props.messageObject !== undefined ? 1 : 0) !== 1) { - throw new Error(`Supply exactly one of 'message', 'messageObject' or 'messagePath'`); - } - - if (props.subject !== undefined && props.subjectPath !== undefined) { - throw new Error(`Supply either 'subject' or 'subjectPath'`); + if ((props.message === undefined) === (props.messageObject === undefined)) { + throw new Error(`Supply exactly one of 'message' or 'messageObject'`); } this.resourceArn = 'arn:aws:states:::sns:publish'; @@ -85,14 +68,12 @@ export class PublishToTopic implements sfn.IStepFunctionsTask { .addResource(topic.topicArn) ]; this.parameters = { - "TopicArn": topic.topicArn, - "Message": props.messageObject - ? new cdk.Token(() => topic.node.stringifyJson(props.messageObject)) - : props.message, - "Message.$": props.messagePath, - "MessageStructure": props.messagePerSubscriptionType ? "json" : undefined, - "Subject": props.subject, - "Subject.$": props.subjectPath + TopicArn: topic.topicArn, + ...(props.messageObject + ? { Message: new cdk.Token(() => topic.node.stringifyJson(props.messageObject)) } + : renderString('Message', props.message)), + MessageStructure: props.messagePerSubscriptionType ? "json" : undefined, + ...renderString('Subject', props.subject), }; // No IAM permissions necessary, execution role implicitly has Activity permissions. diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts index ab1ec827be63d..e943e7081be63 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts @@ -1,12 +1,12 @@ import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import cdk = require('@aws-cdk/cdk'); -import { BaseRunTask, CommonRunTaskProps } from './base-run-task'; +import { CommonEcsRunTaskProps, EcsRunTaskBase } from './ecs-run-task-base'; /** * Properties to run an ECS task on EC2 in StepFunctionsan ECS */ -export interface RunEcsEc2TaskProps extends CommonRunTaskProps { +export interface EcsRunEc2TaskProps extends CommonEcsRunTaskProps { /** * In what subnets to place the task's ENIs * @@ -36,12 +36,12 @@ export interface RunEcsEc2TaskProps extends CommonRunTaskProps { /** * Run an ECS/EC2 Task in a StepFunctions workflow */ -export class RunEcsEc2Task extends BaseRunTask { +export class EcsRunEc2Task extends EcsRunTaskBase { private readonly constraints: any[]; private readonly strategies: any[]; private readonly cluster: ecs.ICluster; - constructor(scope: cdk.Construct, id: string, props: RunEcsEc2TaskProps) { + constructor(scope: cdk.Construct, id: string, props: EcsRunEc2TaskProps) { if (!props.taskDefinition.isEc2Compatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } @@ -146,7 +146,7 @@ export class RunEcsEc2Task extends BaseRunTask { /** * Validate combinations of networking arguments */ -function validateNoNetworkingProps(props: RunEcsEc2TaskProps) { +function validateNoNetworkingProps(props: EcsRunEc2TaskProps) { if (props.subnets !== undefined || props.securityGroup !== undefined) { throw new Error('vpcPlacement and securityGroup can only be used in AwsVpc networking mode'); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts index 8a23870a2939d..d464e25353edf 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts @@ -1,12 +1,12 @@ import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import cdk = require('@aws-cdk/cdk'); -import { BaseRunTask, CommonRunTaskProps } from './base-run-task'; +import { CommonEcsRunTaskProps, EcsRunTaskBase } from './ecs-run-task-base'; /** * Properties to define an ECS service */ -export interface RunEcsFargateTaskProps extends CommonRunTaskProps { +export interface EcsRunFargateTaskProps extends CommonEcsRunTaskProps { /** * Assign public IP addresses to each task * @@ -42,8 +42,8 @@ export interface RunEcsFargateTaskProps extends CommonRunTaskProps { /** * Start a service on an ECS cluster */ -export class RunEcsFargateTask extends BaseRunTask { - constructor(scope: cdk.Construct, id: string, props: RunEcsFargateTaskProps) { +export class EcsRunFargateTask extends EcsRunTaskBase { + constructor(scope: cdk.Construct, id: string, props: EcsRunFargateTaskProps) { if (!props.taskDefinition.isFargateCompatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts index 9283370c2182d..25912202f7c27 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts @@ -2,6 +2,8 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); import sqs = require('@aws-cdk/aws-sqs'); import sfn = require('@aws-cdk/aws-stepfunctions'); +import { renderNumber, renderString } from './json-path'; +import { NumberValue } from './number-value'; /** * Properties for SendMessageTask @@ -9,17 +11,8 @@ import sfn = require('@aws-cdk/aws-stepfunctions'); export interface SendToQueueProps { /** * The message body to send to the queue. - * - * Exactly one of `messageBody` and `messageBodyPath` is required. - */ - readonly messageBody?: string; - - /** - * JSONPath for the message body to send to the queue. - * - * Exactly one of `messageBody` and `messageBodyPath` is required. */ - readonly messageBodyPath?: string; + readonly messageBody: string; /** * The length of time, in seconds, for which to delay a specific message. @@ -28,14 +21,7 @@ export interface SendToQueueProps { * * @default Default value of the queue is used */ - readonly delaySeconds?: number; - - /** - * JSONPath expression for delaySeconds setting - * - * @default Default value of the queue is used - */ - readonly delaySecondsPath?: string; + readonly delaySeconds?: NumberValue; /** * The token used for deduplication of sent messages. @@ -44,13 +30,6 @@ export interface SendToQueueProps { */ readonly messageDeduplicationId?: string; - /** - * JSONPath expression for deduplication ID - * - * @default Use content-based deduplication - */ - readonly messageDeduplicationIdPath?: string; - /** * The tag that specifies that a message belongs to a specific message group. * @@ -60,13 +39,6 @@ export interface SendToQueueProps { * @default No group ID */ readonly messageGroupId?: string; - - /** - * JSONPath expression for message group ID - * - * @default No group ID - */ - readonly messageGroupIdPath?: string; } /** @@ -86,37 +58,17 @@ export class SendToQueue implements sfn.IStepFunctionsTask { public readonly parameters?: { [name: string]: any; } | undefined; constructor(queue: sqs.IQueue, props: SendToQueueProps) { - if ((props.messageBody !== undefined) === (props.messageBodyPath !== undefined)) { - throw new Error(`Supply exactly one of 'messageBody' and 'messageBodyPath'`); - } - - if ((props.delaySeconds !== undefined) && (props.delaySecondsPath !== undefined)) { - throw new Error(`Supply either of 'delaySeconds' or 'delaySecondsPath'`); - } - - if ((props.messageDeduplicationId !== undefined) && (props.messageDeduplicationIdPath !== undefined)) { - throw new Error(`Supply either of 'messageDeduplicationId' or 'messageDeduplicationIdPath'`); - } - - if ((props.messageGroupId !== undefined) && (props.messageGroupIdPath !== undefined)) { - throw new Error(`Supply either of 'messageGroupId' or 'messageGroupIdPath'`); - } - this.resourceArn = 'arn:aws:states:::sqs:sendMessage'; this.policyStatements = [new iam.PolicyStatement() .addAction('sqs:SendMessage') .addResource(queue.queueArn) ]; this.parameters = { - 'QueueUrl': queue.queueUrl, - 'MessageBody': props.messageBody, - 'MessageBody.$': props.messageBodyPath, - 'DelaySeconds': props.delaySeconds, - 'DelaySeconds.$': props.delaySecondsPath, - 'MessageDeduplicationId': props.messageDeduplicationId, - 'MessageDeduplicationId.$': props.messageDeduplicationIdPath, - 'MessageGroupId': props.messageGroupId, - 'MessageGroupId.$': props.messageGroupIdPath, + QueueUrl: queue.queueUrl, + ...renderString('MessageBody', props.messageBody), + ...renderNumber('DelaySeconds', props.delaySeconds), + ...renderString('MessageDeduplicationId', props.messageDeduplicationId), + ...renderString('MessageGroupId', props.messageGroupId), }; // No IAM permissions necessary, execution role implicitly has Activity permissions. diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index 183f26bf1bd36..0a411f7ed99f2 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -49,7 +49,7 @@ ], "coverageThreshold": { "global": { - "branches": 80, + "branches": 70, "statements": 80 } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts index cd2cd85e7ba98..61b6af3e686a9 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts @@ -4,32 +4,42 @@ import ecs = require('@aws-cdk/aws-ecs'); import sfn = require('@aws-cdk/aws-stepfunctions'); import { Stack } from '@aws-cdk/cdk'; import tasks = require('../lib'); +import { JsonPath, NumberValue } from '../lib'; -test('Running a Fargate Task', () => { +let stack: Stack; +let vpc: ec2.VpcNetwork; +let cluster: ecs.Cluster; + +beforeEach(() => { // GIVEN - const stack = new Stack(); + stack = new Stack(); + vpc = new ec2.VpcNetwork(stack, 'Vpc'); + cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addCapacity('Capacity', { + instanceType: new ec2.InstanceType('t3.medium') + }); +}); - const vpc = new ec2.VpcNetwork(stack, 'Vpc'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); +test('Running a Fargate Task', () => { const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { memoryMiB: '512', cpu: '256', compatibility: ecs.Compatibility.Fargate }); - taskDefinition.addContainer('henk', { + taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('foo/bar'), memoryLimitMiB: 256, }); // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: new tasks.RunEcsFargateTask(stack, 'RunFargate', { + const runTask = new sfn.Task(stack, 'Run', { task: new tasks.EcsRunFargateTask(stack, 'RunFargate', { cluster, taskDefinition, containerOverrides: [ { containerName: 'TheContainer', environment: [ - {name: 'SOME_KEY', valuePath: '$.SomeKey'} + {name: 'SOME_KEY', value: JsonPath.stringFromPath('$.SomeKey')} ] } ] @@ -112,32 +122,23 @@ test('Running a Fargate Task', () => { }); test('Running an EC2 Task with bridge network', () => { - // GIVEN - const stack = new Stack(); - - const vpc = new ec2.VpcNetwork(stack, 'Vpc'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('Capacity', { - instanceType: new ec2.InstanceType('t3.medium') - }); - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { compatibility: ecs.Compatibility.Ec2 }); - taskDefinition.addContainer('henk', { + taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('foo/bar'), memoryLimitMiB: 256, }); // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task(stack, 'RunEc2', { + const runTask = new sfn.Task(stack, 'Run', { task: new tasks.EcsRunEc2Task(stack, 'RunEc2', { cluster, taskDefinition, containerOverrides: [ { containerName: 'TheContainer', environment: [ - {name: 'SOME_KEY', valuePath: '$.SomeKey'} + {name: 'SOME_KEY', value: JsonPath.stringFromPath('$.SomeKey')} ] } ] @@ -209,24 +210,15 @@ test('Running an EC2 Task with bridge network', () => { }); test('Running an EC2 Task with placement strategies', () => { - // GIVEN - const stack = new Stack(); - - const vpc = new ec2.VpcNetwork(stack, 'Vpc'); - const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); - cluster.addCapacity('Capacity', { - instanceType: new ec2.InstanceType('t3.medium') - }); - const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { compatibility: ecs.Compatibility.Ec2 }); - taskDefinition.addContainer('henk', { + taskDefinition.addContainer('TheContainer', { image: ecs.ContainerImage.fromRegistry('foo/bar'), memoryLimitMiB: 256, }); - const ec2Task = new tasks.RunEcsEc2Task(stack, 'RunEc2', { + const ec2Task = new tasks.EcsRunEc2Task(stack, 'RunEc2', { cluster, taskDefinition, }); @@ -261,4 +253,52 @@ test('Running an EC2 Task with placement strategies', () => { Resource: "arn:aws:states:::ecs:runTask.sync", Type: "Task", }); +}); + +test('Running an EC2 Task with overridden number values', () => { + const taskDefinition = new ecs.TaskDefinition(stack, 'TD', { + compatibility: ecs.Compatibility.Ec2 + }); + taskDefinition.addContainer('TheContainer', { + image: ecs.ContainerImage.fromRegistry('foo/bar'), + memoryLimitMiB: 256, + }); + + const ec2Task = new tasks.EcsRunEc2Task(stack, 'RunEc2', { + cluster, + taskDefinition, + containerOverrides: [ + { + containerName: 'TheContainer', + command: JsonPath.listFromPath('$.TheCommand'), + cpu: NumberValue.fromNumber(5), + memoryLimit: JsonPath.numberFromPath('$.MemoryLimit'), + } + ] + }); + + // WHEN + const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); + + // THEN + expect(stack.node.resolve(runTask.toStateJson())).toEqual({ + End: true, + Parameters: { + Cluster: {"Fn::GetAtt": ["ClusterEB0386A7", "Arn"]}, + LaunchType: "EC2", + TaskDefinition: {Ref: "TD49C78F36"}, + Overrides: { + ContainerOverrides: [ + { + "Command.$": "$.TheCommand", + "Cpu": 5, + "Memory.$": "$.MemoryLimit", + "Name": "TheContainer", + }, + ], + }, + }, + Resource: "arn:aws:states:::ecs:runTask.sync", + Type: "Task", + }); }); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventhandler-image/Dockerfile b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventhandler-image/Dockerfile new file mode 100644 index 0000000000000..123b5670febc8 --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventhandler-image/Dockerfile @@ -0,0 +1,5 @@ +FROM python:3.6 +EXPOSE 8000 +WORKDIR /src +ADD . /src +CMD python3 index.py diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventhandler-image/index.py b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventhandler-image/index.py new file mode 100644 index 0000000000000..c4cab119afc2d --- /dev/null +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/eventhandler-image/index.py @@ -0,0 +1,6 @@ +#!/usr/bin/python +import os +import pprint + +print('Hello from ECS!') +pprint.pprint(dict(os.environ)) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json index 630b75193813f..bb459bf9218f4 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json @@ -33,7 +33,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "ec2.amazonaws.com" + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } } } ], @@ -115,8 +125,8 @@ } }, "DependsOn": [ - "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7", - "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E" + "FargateClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy3BD78F3E", + "FargateClusterDefaultAutoScalingGroupInstanceRole0C1F7FF7" ] }, "FargateClusterDefaultAutoScalingGroupASG36A4948F": { @@ -153,21 +163,6 @@ "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA": { "Type": "AWS::SNS::Topic" }, - "FargateClusterDefaultAutoScalingGroupDrainECSHookTopicFunctionSubscription129830E9": { - "Type": "AWS::SNS::Subscription", - "Properties": { - "Endpoint": { - "Fn::GetAtt": [ - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8", - "Arn" - ] - }, - "Protocol": "lambda", - "TopicArn": { - "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" - } - } - }, "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32": { "Type": "AWS::IAM::Role", "Properties": { @@ -177,7 +172,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "lambda.amazonaws.com" + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } } } ], @@ -260,19 +265,43 @@ } } }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ2/FargateCluster/DefaultAutoScalingGroup" + } + ], "Timeout": 310 }, "DependsOn": [ - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32", - "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343" + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicyB91C5343", + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole7FEDCD32" ] }, + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicSubscriptionA4A8D57E": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookTopic92C2F1DA" + }, + "Endpoint": { + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8", + "Arn" + ] + } + } + }, "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionTopicA1F1F9E9": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { - "Ref": "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8" + "Fn::GetAtt": [ + "FargateClusterDefaultAutoScalingGroupDrainECSHookFunctionE3D5BEE8", + "Arn" + ] }, "Principal": "sns.amazonaws.com", "SourceArn": { @@ -289,7 +318,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "autoscaling.amazonaws.com" + "Service": { + "Fn::Join": [ + "", + [ + "autoscaling.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } } } ], @@ -340,8 +379,8 @@ } }, "DependsOn": [ - "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D", - "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D" + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicy4958D19D", + "FargateClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole410D556D" ] }, "TaskDefTaskRole1EDB4A67": { @@ -353,7 +392,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "ecs-tasks.amazonaws.com" + "Service": { + "Fn::Join": [ + "", + [ + "ecs-tasks.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } } } ], @@ -527,7 +576,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "ecs-tasks.amazonaws.com" + "Service": { + "Fn::Join": [ + "", + [ + "ecs-tasks.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } } } ], @@ -575,11 +634,7 @@ } }, { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], + "Action": "ecr:GetAuthorizationToken", "Effect": "Allow", "Resource": "*" }, @@ -640,7 +695,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "lambda.amazonaws.com" + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } } } ], @@ -773,8 +838,8 @@ "Timeout": 300 }, "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C" + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" ] }, "TaskLoggingLogGroupC7E938D4": { @@ -896,7 +961,7 @@ "Fn::Join": [ "", [ - "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"RunEc2\"},\"RunEc2\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", + "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"Run\"},\"Run\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", { "Fn::GetAtt": [ "FargateCluster7CCD5F93", diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts index bbe9875963561..08991deba12e9 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts @@ -13,7 +13,7 @@ const vpc = ec2.VpcNetwork.importFromContext(stack, 'Vpc', { }); const cluster = new ecs.Cluster(stack, 'FargateCluster', { vpc }); -cluster.addCapacity('Capacity', { +cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro'), vpcSubnets: { subnetType: ec2.SubnetType.Public }, }); @@ -21,7 +21,7 @@ cluster.addCapacity('Capacity', { // Build task definition const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, '..', 'eventhandler-image') }), + image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, 'eventhandler-image') }), memoryLimitMiB: 256, logging: new ecs.AwsLogDriver(stack, 'TaskLogging', { streamPrefix: 'EventDemo' }) }); @@ -29,7 +29,7 @@ taskDefinition.addContainer('TheContainer', { // Build state machine const definition = new sfn.Pass(stack, 'Start', { result: { SomeKey: 'SomeValue' } -}).next(new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task(stack, 'RunEc2', { +}).next(new sfn.Task(stack, 'Run', { task: new tasks.EcsRunEc2Task(stack, 'RunEc2', { cluster, taskDefinition, containerOverrides: [ { @@ -37,7 +37,7 @@ const definition = new sfn.Pass(stack, 'Start', { environment: [ { name: 'SOME_KEY', - valuePath: '$.SomeKey' + value: tasks.JsonPath.stringFromPath('$.SomeKey') } ] } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json index 61ca415d2f552..ffde4bbd5153a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json @@ -12,7 +12,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "ecs-tasks.amazonaws.com" + "Service": { + "Fn::Join": [ + "", + [ + "ecs-tasks.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } } } ], @@ -187,7 +197,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "ecs-tasks.amazonaws.com" + "Service": { + "Fn::Join": [ + "", + [ + "ecs-tasks.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } } } ], @@ -235,11 +255,7 @@ } }, { - "Action": [ - "ecr:GetAuthorizationToken", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], + "Action": "ecr:GetAuthorizationToken", "Effect": "Allow", "Resource": "*" }, @@ -300,7 +316,17 @@ "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { - "Service": "lambda.amazonaws.com" + "Service": { + "Fn::Join": [ + "", + [ + "lambda.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } } } ], @@ -433,8 +459,8 @@ "Timeout": 300 }, "DependsOn": [ - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17", - "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C" + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleDefaultPolicy6BC8737C", + "AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62cServiceRoleD788AA17" ] }, "TaskLoggingLogGroupC7E938D4": { @@ -444,10 +470,10 @@ }, "DeletionPolicy": "Retain" }, - "RunFargateSecurityGroup709740F2": { + "FargateTaskSecurityGroup0BBB27CB": { "Type": "AWS::EC2::SecurityGroup", "Properties": { - "GroupDescription": "aws-ecs-integ2/RunFargate/SecurityGroup", + "GroupDescription": "aws-ecs-integ2/FargateTask/SecurityGroup", "SecurityGroupEgress": [ { "CidrIp": "0.0.0.0/0", @@ -585,7 +611,7 @@ "\",\"NetworkConfiguration\":{\"AwsvpcConfiguration\":{\"AssignPublicIp\":\"ENABLED\",\"Subnets\":[\"subnet-e19455ca\",\"subnet-e0c24797\",\"subnet-ccd77395\"],\"SecurityGroups\":[\"", { "Fn::GetAtt": [ - "RunFargateSecurityGroup709740F2", + "FargateTaskSecurityGroup0BBB27CB", "GroupId" ] }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts index 4909b78578554..f9c5da60a27de 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts @@ -20,7 +20,7 @@ const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef', { cpu: '256' }); taskDefinition.addContainer('TheContainer', { - image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, '..', 'eventhandler-image') }), + image: ecs.ContainerImage.fromAsset(stack, 'EventImage', { directory: path.resolve(__dirname, 'eventhandler-image') }), memoryLimitMiB: 256, logging: new ecs.AwsLogDriver(stack, 'TaskLogging', { streamPrefix: 'EventDemo' }) }); @@ -28,7 +28,7 @@ taskDefinition.addContainer('TheContainer', { // Build state machine const definition = new sfn.Pass(stack, 'Start', { result: { SomeKey: 'SomeValue' } -}).next(new sfn.Task(stack, 'Run', { task: new tasks.RunEcsFargateTask(stack, 'RunFargate', { +}).next(new sfn.Task(stack, 'RunFargate', { task: new tasks.EcsRunFargateTask(stack, 'FargateTask', { cluster, taskDefinition, assignPublicIp: true, containerOverrides: [ @@ -37,7 +37,7 @@ const definition = new sfn.Pass(stack, 'Start', { environment: [ { name: 'SOME_KEY', - valuePath: '$.SomeKey' + value: tasks.JsonPath.stringFromPath('$.SomeKey') } ] } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts index 3352ca14255a6..db6aa70f55440 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.job-poller.ts @@ -15,7 +15,7 @@ class JobPollerStack extends cdk.Stack { }); const waitX = new sfn.Wait(this, 'Wait X Seconds', { duration: sfn.WaitDuration.secondsPath('$.wait_time') }); const getStatus = new sfn.Task(this, 'Get Job Status', { - task: new tasks.InvokeActivity(submitJobActivity), + task: new tasks.InvokeActivity(checkJobActivity), inputPath: '$.guid', resultPath: '$.status', }); diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts index 4dbb94fb195d9..6af5300dba9aa 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/send-to-queue.test.ts @@ -11,7 +11,7 @@ test('publish to queue', () => { // WHEN const pub = new sfn.Task(stack, 'Send', { task: new tasks.SendToQueue(queue, { messageBody: 'Send this message', - messageDeduplicationIdPath: '$.deduping', + messageDeduplicationId: tasks.JsonPath.stringFromPath('$.deduping'), }) }); // THEN diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index 03ba4bc1342bd..ec583c4204188 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -11,34 +11,37 @@ example](https://docs.aws.amazon.com/step-functions/latest/dg/job-status-poller- ### TypeScript example ```ts +import sfn = require('@aws-cdk/aws-stepfunctions'); import tasks = require('@aws-cdk/aws-stepfunctions-tasks'); const submitLambda = new lambda.Function(this, 'SubmitLambda', { ... }); const getStatusLambda = new lambda.Function(this, 'CheckLambda', { ... }); -const submitJob = new tasks.InvokeFunction(this, 'Submit Job', { - function: submitLambda, +const submitJob = new sfn.Task(this, 'Submit Job', { + task: new tasks.InvokeFunction(submitLambda), // Put Lambda's result here in the execution's state object resultPath: '$.guid', }); -const waitX = new stepfunctions.Wait(this, 'Wait X Seconds', { secondsPath: '$.wait_time' }); +const waitX = new sfn.Wait(this, 'Wait X Seconds', { + duration: sfn.WaitDuration.secondsPath('$.wait_time'), +}); -const getStatus = new tasks.InvokeFunction(this, 'Get Job Status', { - function: getStatusLambda, +const getStatus = new sfn.Task(this, 'Get Job Status', { + task: new tasks.InvokeFunction(getStatusLambda), // Pass just the field named "guid" into the Lambda, put the // Lambda's result in a field called "status" inputPath: '$.guid', resultPath: '$.status', }); -const jobFailed = new stepfunctions.Fail(this, 'Job Failed', { +const jobFailed = new sfn.Fail(this, 'Job Failed', { cause: 'AWS Batch Job Failed', error: 'DescribeJob returned FAILED', }); -const finalStatus = new tasks.InvokeFunction(this, 'Get Final Job Status', { - function: getStatusLambda, +const finalStatus = new sfn.Task(this, 'Get Final Job Status', { + task: new tasks.InvokeFunction(getStatusLambda), // Use "guid" field as input, output of the Lambda becomes the // entire state machine output. inputPath: '$.guid', @@ -47,78 +50,18 @@ const finalStatus = new tasks.InvokeFunction(this, 'Get Final Job Status', { const definition = submitJob .next(waitX) .next(getStatus) - .next(new stepfunctions.Choice(this, 'Job Complete?') + .next(new sfn.Choice(this, 'Job Complete?') // Look at the "status" field - .when(stepfunctions.Condition.stringEquals('$.status', 'FAILED'), jobFailed) - .when(stepfunctions.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus) + .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed) + .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus) .otherwise(waitX)); -new stepfunctions.StateMachine(this, 'StateMachine', { +new sfn.StateMachine(this, 'StateMachine', { definition, timeoutSec: 300 }); ``` -### .NET Example - -```csharp -var submitLambda = new Function(this, "SubmitLambda", new FunctionProps -{ - // ... -}); - -var getStatusLambda = new Function(this, "CheckLambda", new FunctionProps -{ - // ... -}); - -var submitJob = new InvokeFunction(this, "Submit Job", new TaskProps -{ - Function = submitLambda, - ResultPath = "$.guid" -}); - -var waitX = new Wait(this, "Wait X Seconds", new WaitProps -{ - SecondsPath = "$.wait_time" -}); - -var getStatus = new InvokeFunction(this, "Get Job Status", new TaskProps -{ - Function = getStatusLambda, - InputPath = "$.guid", - ResultPath = "$.status" -}); - -var jobFailed = new Fail(this, "Job Failed", new FailProps -{ - Cause = "AWS Batch Job Failed", - Error = "DescribeJob returned FAILED" -}); - -var finalStatus = new InvokeFunction(this, "Get Final Job Status", new TaskProps -{ - Function = getStatusLambda, - // Use "guid" field as input, output of the Lambda becomes the - // entire state machine output. - InputPath = "$.guid" -}); - -var definition = submitJob - .Next(waitX) - .Next(getStatus) - .Next(new Choice(this, "Job Complete?", new ChoiceProps()) - .When(Amazon.CDK.AWS.StepFunctions.Condition.StringEquals("$.status", "FAILED"), jobFailed) - .When(Amazon.CDK.AWS.StepFunctions.Condition.StringEquals("$.status", "SUCCEEDED"), finalStatus) - .Otherwise(waitX)); - -new StateMachine(this, "StateMachine", new StateMachineProps -{ - Definition = definition, - TimeoutSec = 300 -}); -``` - ## State Machine A `stepfunctions.StateMachine` is a resource that takes a state machine @@ -145,7 +88,7 @@ This library comes with a set of classes that model the [Amazon States Language](https://states-language.net/spec.html). The following State classes are supported: -* `Task` (with subclasses for every specific task, see below) +* `Task` * `Pass` * `Wait` * `Choice` @@ -159,22 +102,34 @@ information, see the States Language spec. ### Task -A `Task` represents some work that needs to be done. There are specific -subclasses of `Task` which represent integrations with other AWS services -that Step Functions supports. For example: +A `Task` represents some work that needs to be done. The exact work to be +done is determine by a class that implements `IStepFunctionsTask`, a collection +of which can be found in the `@aws-cdk/aws-stepfunctions-tasks` package. A +couple of the tasks available are: * `tasks.InvokeFunction` -- call a Lambda Function -* `stepfunctions.ActivityTask` -- start an Activity (Activities represent a work +* `tasks.InvokeActivity` -- start an Activity (Activities represent a work queue that you poll on a compute fleet you manage yourself) -* `sns.PublishTask` -- publish a message to an SNS topic -* `sqs.SendMessageTask` -- send a message to an SQS queue -* `ecs.FargateRunTask`/`ecs.Ec2RunTask` -- run a container task +* `tasks.PublishToTopic` -- publish a message to an SNS topic +* `tasks.SendToQueue` -- send a message to an SQS queue +* `tasks.EcsRunFargateTask`/`ecs.EcsRunEc2Task` -- run a container task, + depending on the type of capacity. + +#### Task parameters from the state json + +Many tasks take parameters. The values for those can either be supplied +directly in the workflow definition (by specifying their values), or at +runtime by passing a value obtained from the static functions on `JsonPath`, +such as `JsonPath.stringFromPath()`. + +If so, the value is taken from the indicated location in the state JSON, +similar to (for example) `inputPath`. #### Lambda example ```ts -const task = new tasks.InvokeFunction(this, 'Invoke The Lambda', { - resource: myLambda, +const task = new sfn.Task(this, 'Invoke The Lambda', { + task: new tasks.InvokeFunction(myLambda), inputPath: '$.input', timeoutSeconds: 300, }); @@ -200,9 +155,10 @@ import sns = require('@aws-cdk/aws-sns'); // ... const topic = new sns.Topic(this, 'Topic'); -const task = new sns.PublishTask(this, 'Publish', { - topic, - message: 'A message to send to the queue' +const task = new sfn.Task(this, 'Publish', { + task: new tasks.PublishToTopic(topic, { + message: JsonPath.stringFromPath('$.state.message'), + }) }); ``` @@ -214,11 +170,12 @@ import sqs = require('@aws-cdk/aws-sqs'); // ... const queue = new sns.Queue(this, 'Queue'); -const task = new sns.SendMessageTask(this, 'Send', { - queue, - messageBodyPath: '$.message', - // Only for FIFO queues - messageGroupId: '$.messageGroupId', +const task = new sfn.Task(this, 'Send', { + task: new tasks.SendToQueue(queue, { + messageBody: JsonPath.stringFromPath('$.message'), + // Only for FIFO queues + messageGroupId: JsonPath.stringFromPath('$.messageGroupId'), + }) }); ``` @@ -229,7 +186,7 @@ import ecs = require('@aws-cdk/aws-ecs'); // See examples in ECS library for initialization of 'cluster' and 'taskDefinition' -const task = new ecs.FargateRunTask(stack, 'RunFargate', { +const fargateTask = new ecs.EcsRunFargateTask(task, 'FargateTask', { cluster, taskDefinition, containerOverrides: [ @@ -238,12 +195,18 @@ const task = new ecs.FargateRunTask(stack, 'RunFargate', { environment: [ { name: 'CONTAINER_INPUT', - valuePath: '$.valueFromStateData' + value: JsonPath.stringFromPath('$.valueFromStateData') } ] } ] -})); +}); + +fargateTask.connections.allowToDefaultPort(rdsCluster, 'Read the database'); + +const task = new sfn.Task(this, 'CallFargate', { + task: fargateTask +}); ``` ### Pass @@ -272,7 +235,7 @@ state. // Wait until it's the time mentioned in the the state object's "triggerTime" // field. const wait = new stepfunctions.Wait(this, 'Wait For Trigger Time', { - timestampPath: '$.triggerTime', + duration: stepfunctions.WaitDuration.timestampPath('$.triggerTime'), }); // Set the next state diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/types.ts b/packages/@aws-cdk/aws-stepfunctions/lib/types.ts index 1efd71f983144..b6e19a127ba98 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/types.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/types.ts @@ -5,6 +5,11 @@ import { State } from './states/state'; * Interface for states that can have 'next' states */ export interface INextable { + /** + * Go to the indicated state after this state + * + * @returns The chain of states built up + */ next(state: IChainable): Chain; } diff --git a/packages/@aws-cdk/cdk/lib/arn.ts b/packages/@aws-cdk/cdk/lib/arn.ts index 8cbdbbe822f77..09f31fb95038b 100644 --- a/packages/@aws-cdk/cdk/lib/arn.ts +++ b/packages/@aws-cdk/cdk/lib/arn.ts @@ -1,6 +1,6 @@ import { Fn } from './fn'; import { Stack } from './stack'; -import { unresolved } from './unresolved'; +import { Token } from './token'; import { filterUndefined } from './util'; /** @@ -78,7 +78,7 @@ export function arnFromComponents(components: ArnComponents, stack: Stack): stri * components of the ARN. */ export function parseArn(arn: string, sepIfToken: string = '/', hasName: boolean = true): ArnComponents { - if (unresolved(arn)) { + if (Token.unresolved(arn)) { return parseToken(arn, sepIfToken, hasName); } diff --git a/packages/@aws-cdk/cdk/lib/construct.ts b/packages/@aws-cdk/cdk/lib/construct.ts index 4c98cabc21c44..08d2ec796d9d4 100644 --- a/packages/@aws-cdk/cdk/lib/construct.ts +++ b/packages/@aws-cdk/cdk/lib/construct.ts @@ -5,7 +5,6 @@ import { IDependable } from './dependency'; import { resolve } from './resolve'; import { Token } from './token'; import { makeUniqueId } from './uniqueid'; -import { unresolved } from './unresolved'; export const PATH_SEP = '/'; @@ -83,7 +82,7 @@ export class ConstructNode { // escape any path separators so they don't wreck havoc this.id = this._escapePathSeparator(this.id); - if (unresolved(id)) { + if (Token.unresolved(id)) { throw new Error(`Cannot use tokens in construct ID: ${id}`); } } diff --git a/packages/@aws-cdk/cdk/lib/encoding.ts b/packages/@aws-cdk/cdk/lib/encoding.ts index fc3eb8ca7355b..f556adb75e230 100644 --- a/packages/@aws-cdk/cdk/lib/encoding.ts +++ b/packages/@aws-cdk/cdk/lib/encoding.ts @@ -1,117 +1,17 @@ -import { resolve } from "./resolve"; -import { ResolveContext, Token } from "./token"; -import { unresolved } from "./unresolved"; +import { RESOLVE_METHOD, Token } from "./token"; -// Encoding Tokens into native types; should not be exported +// Details for encoding and decoding Tokens into native types; should not be exported -/** - * Central place where we keep a mapping from Tokens to their String representation - * - * The string representation is used to embed token into strings, - * and stored to be able to - * - * All instances of TokenStringMap share the same storage, so that this process - * works even when different copies of the library are loaded. - */ -export class TokenMap { - private readonly tokenMap = new Map(); - - /** - * Generate a unique string for this Token, returning a key - * - * Every call for the same Token will produce a new unique string, no - * attempt is made to deduplicate. Token objects should cache the - * value themselves, if required. - * - * The token can choose (part of) its own representation string with a - * hint. This may be used to produce aesthetically pleasing and - * recognizable token representations for humans. - */ - public registerString(token: Token, representationHint?: string): string { - const key = this.register(token, representationHint); - return `${BEGIN_STRING_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`; - } - - /** - * Generate a unique string for this Token, returning a key - */ - public registerList(token: Token, representationHint?: string): string[] { - const key = this.register(token, representationHint); - return [`${BEGIN_LIST_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`]; - } - - /** - * Returns a `TokenString` for this string. - */ - public createStringTokenString(s: string) { - return new TokenString(s, QUOTED_BEGIN_STRING_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, QUOTED_END_TOKEN_MARKER); - } - - /** - * Returns a `TokenString` for this string. - */ - public createListTokenString(s: string) { - return new TokenString(s, QUOTED_BEGIN_LIST_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, QUOTED_END_TOKEN_MARKER); - } +export const BEGIN_STRING_TOKEN_MARKER = '${Token['; +export const BEGIN_LIST_TOKEN_MARKER = '#{Token['; +export const END_TOKEN_MARKER = ']}'; - /** - * Replace any Token markers in this string with their resolved values - */ - public resolveStringTokens(s: string, context: ResolveContext): any { - const str = this.createStringTokenString(s); - const fragments = str.split(this.lookupToken.bind(this)); - // require() here to break cyclic dependencies - const ret = fragments.mapUnresolved(x => resolve(x, context)).join(require('./cfn-concat').cloudFormationConcat); - if (unresolved(ret)) { - return resolve(ret, context); - } - return ret; - } - - public resolveListTokens(xs: string[], context: ResolveContext): any { - // Must be a singleton list token, because concatenation is not allowed. - if (xs.length !== 1) { - throw new Error(`Cannot add elements to list token, got: ${xs}`); - } - - const str = this.createListTokenString(xs[0]); - const fragments = str.split(this.lookupToken.bind(this)); - if (fragments.length !== 1) { - throw new Error(`Cannot concatenate strings in a tokenized string array, got: ${xs[0]}`); - } - return fragments.mapUnresolved(x => resolve(x, context)).values[0]; - } - - /** - * Find a Token by key - */ - public lookupToken(key: string): Token { - const token = this.tokenMap.get(key); - if (!token) { - throw new Error(`Unrecognized token key: ${key}`); - } - return token; - } - - private register(token: Token, representationHint?: string): string { - const counter = this.tokenMap.size; - const representation = (representationHint || `TOKEN`).replace(new RegExp(`[^${VALID_KEY_CHARS}]`, 'g'), '.'); - const key = `${representation}.${counter}`; - this.tokenMap.set(key, token); - return key; - } -} - -const BEGIN_STRING_TOKEN_MARKER = '${Token['; -const BEGIN_LIST_TOKEN_MARKER = '#{Token['; -const END_TOKEN_MARKER = ']}'; +export const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; const QUOTED_BEGIN_STRING_TOKEN_MARKER = regexQuote(BEGIN_STRING_TOKEN_MARKER); const QUOTED_BEGIN_LIST_TOKEN_MARKER = regexQuote(BEGIN_LIST_TOKEN_MARKER); const QUOTED_END_TOKEN_MARKER = regexQuote(END_TOKEN_MARKER); -const VALID_KEY_CHARS = 'a-zA-Z0-9:._-'; - /** * Interface that Token joiners implement */ @@ -133,7 +33,21 @@ export interface ITokenJoiner { /** * A string with markers in it that can be resolved to external values */ -class TokenString { +export class TokenString { + /** + * Returns a `TokenString` for this string. + */ + public static forStringToken(s: string) { + return new TokenString(s, QUOTED_BEGIN_STRING_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, QUOTED_END_TOKEN_MARKER); + } + + /** + * Returns a `TokenString` for this string (must be the first string element of the list) + */ + public static forListToken(s: string) { + return new TokenString(s, QUOTED_BEGIN_LIST_TOKEN_MARKER, `[${VALID_KEY_CHARS}]+`, QUOTED_END_TOKEN_MARKER); + } + private pattern: string; constructor( @@ -272,17 +186,23 @@ function regexQuote(s: string) { */ export type ConcatFunc = (left: any | undefined, right: any | undefined) => any; -const glob = global as any; +export function containsListTokenElement(xs: any[]) { + return xs.some(x => typeof(x) === 'string' && TokenString.forListToken(x).test()); +} /** - * Singleton instance of the token string map + * Returns true if obj is a token (i.e. has the resolve() method or is a string + * that includes token markers), or it's a listifictaion of a Token string. + * + * @param obj The object to test. + * @deprecated use `Token.unresolved` */ -export const TOKEN_MAP: TokenMap = glob.__cdkTokenMap = glob.__cdkTokenMap || new TokenMap(); - -export function isListToken(x: any) { - return typeof(x) === 'string' && TOKEN_MAP.createListTokenString(x).test(); -} - -export function containsListToken(xs: any[]) { - return xs.some(isListToken); +export function unresolved(obj: any): boolean { + if (typeof(obj) === 'string') { + return TokenString.forStringToken(obj).test(); + } else if (Array.isArray(obj) && obj.length === 1) { + return typeof(obj[0]) === 'string' && TokenString.forListToken(obj[0]).test(); + } else { + return obj && typeof(obj[RESOLVE_METHOD]) === 'function'; + } } \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/fn.ts b/packages/@aws-cdk/cdk/lib/fn.ts index 807ec36b2b01d..0cb40c97936aa 100644 --- a/packages/@aws-cdk/cdk/lib/fn.ts +++ b/packages/@aws-cdk/cdk/lib/fn.ts @@ -2,7 +2,6 @@ import { ICfnConditionExpression } from './cfn-condition'; import { minimalCloudFormationJoin } from './instrinsics'; import { resolve } from './resolve'; import { ResolveContext, Token } from './token'; -import { unresolved } from './unresolved'; // tslint:disable:max-line-length @@ -639,7 +638,7 @@ class FnJoin extends Token { } public resolve(context: ResolveContext): any { - if (unresolved(this.listOfValues)) { + if (Token.unresolved(this.listOfValues)) { // This is a list token, don't try to do smart things with it. return { 'Fn::Join': [ this.delimiter, this.listOfValues ] }; } diff --git a/packages/@aws-cdk/cdk/lib/index.ts b/packages/@aws-cdk/cdk/lib/index.ts index 5c7a51fb302c7..328863209ef19 100644 --- a/packages/@aws-cdk/cdk/lib/index.ts +++ b/packages/@aws-cdk/cdk/lib/index.ts @@ -3,7 +3,7 @@ export * from './tag-aspect'; export * from './construct'; export * from './token'; -export * from './unresolved'; +export * from './token-map'; export * from './tag-manager'; export * from './dependency'; diff --git a/packages/@aws-cdk/cdk/lib/instrinsics.ts b/packages/@aws-cdk/cdk/lib/instrinsics.ts index 99cd5ca73ae82..c0a3b0f1803b5 100644 --- a/packages/@aws-cdk/cdk/lib/instrinsics.ts +++ b/packages/@aws-cdk/cdk/lib/instrinsics.ts @@ -18,7 +18,7 @@ export function minimalCloudFormationJoin(delimiter: string, values: any[]): any return values; function isPlainString(obj: any): boolean { - return typeof obj === 'string' && !unresolved(obj); + return typeof obj === 'string' && !require('./token').Token.unresolved(obj); } function isSplicableFnJoinInstrinsic(obj: any): boolean { @@ -53,6 +53,4 @@ export function isNameOfCloudFormationIntrinsic(name: string): boolean { */ export function canEvaluateToList(x: any) { return isIntrinsic(x) && ['Ref', 'Fn::GetAtt', 'Fn::GetAZs', 'Fn::Split', 'Fn::FindInMap', 'Fn::ImportValue'].includes(Object.keys(x)[0]); -} - -import { unresolved } from "./unresolved"; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/resolve.ts b/packages/@aws-cdk/cdk/lib/resolve.ts index a8a4582274a7e..beb21cb17b6b4 100644 --- a/packages/@aws-cdk/cdk/lib/resolve.ts +++ b/packages/@aws-cdk/cdk/lib/resolve.ts @@ -1,11 +1,13 @@ import { IConstruct } from './construct'; -import { containsListToken, TOKEN_MAP } from "./encoding"; +import { containsListTokenElement, TokenString, unresolved } from "./encoding"; import { RESOLVE_OPTIONS } from "./options"; import { isResolvedValuePostProcessor, RESOLVE_METHOD, ResolveContext, Token } from "./token"; -import { unresolved } from "./unresolved"; +import { TokenMap } from './token-map'; // This file should not be exported to consumers, resolving should happen through Construct.resolve() +const tokenMap = TokenMap.instance(); + /** * Resolves an object by evaluating all tokens and removing any undefined or empty objects or arrays. * Values can only be primitives, arrays or tokens. Other objects (i.e. with methods) will be rejected. @@ -49,7 +51,7 @@ export function resolve(obj: any, context: ResolveContext): any { // string - potentially replace all stringified Tokens // if (typeof(obj) === 'string') { - return TOKEN_MAP.resolveStringTokens(obj, context); + return resolveStringTokens(obj, context); } // @@ -65,8 +67,8 @@ export function resolve(obj: any, context: ResolveContext): any { // if (Array.isArray(obj)) { - if (containsListToken(obj)) { - return TOKEN_MAP.resolveListTokens(obj, context); + if (containsListTokenElement(obj)) { + return resolveListTokens(obj, context); } const arr = obj @@ -154,3 +156,31 @@ export function findTokens(scope: IConstruct, fn: () => any): Token[] { function isConstruct(x: any): boolean { return x._children !== undefined && x._metadata !== undefined; } + +/** + * Replace any Token markers in this string with their resolved values + */ +function resolveStringTokens(s: string, context: ResolveContext): any { + const str = TokenString.forStringToken(s); + const fragments = str.split(tokenMap.lookupToken.bind(tokenMap)); + // require() here to break cyclic dependencies + const ret = fragments.mapUnresolved(x => resolve(x, context)).join(require('./cfn-concat').cloudFormationConcat); + if (unresolved(ret)) { + return resolve(ret, context); + } + return ret; +} + +function resolveListTokens(xs: string[], context: ResolveContext): any { + // Must be a singleton list token, because concatenation is not allowed. + if (xs.length !== 1) { + throw new Error(`Cannot add elements to list token, got: ${xs}`); + } + + const str = TokenString.forListToken(xs[0]); + const fragments = str.split(tokenMap.lookupToken.bind(tokenMap)); + if (fragments.length !== 1) { + throw new Error(`Cannot concatenate strings in a tokenized string array, got: ${xs[0]}`); + } + return fragments.mapUnresolved(x => resolve(x, context)).values[0]; +} diff --git a/packages/@aws-cdk/cdk/lib/token-map.ts b/packages/@aws-cdk/cdk/lib/token-map.ts new file mode 100644 index 0000000000000..3dd31ad0d8481 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/token-map.ts @@ -0,0 +1,99 @@ +import { BEGIN_LIST_TOKEN_MARKER, BEGIN_STRING_TOKEN_MARKER, END_TOKEN_MARKER, TokenString, VALID_KEY_CHARS } from "./encoding"; +import { Token } from "./token"; + +const glob = global as any; + +/** + * Central place where we keep a mapping from Tokens to their String representation + * + * The string representation is used to embed token into strings, + * and stored to be able to reverse that mapping. + * + * All instances of TokenStringMap share the same storage, so that this process + * works even when different copies of the library are loaded. + */ +export class TokenMap { + /** + * Singleton instance of the token string map + */ + public static instance(): TokenMap { + if (!glob.__cdkTokenMap) { + glob.__cdkTokenMap = new TokenMap(); + } + return glob.__cdkTokenMap; + } + + private readonly tokenMap = new Map(); + + /** + * Generate a unique string for this Token, returning a key + * + * Every call for the same Token will produce a new unique string, no + * attempt is made to deduplicate. Token objects should cache the + * value themselves, if required. + * + * The token can choose (part of) its own representation string with a + * hint. This may be used to produce aesthetically pleasing and + * recognizable token representations for humans. + */ + public registerString(token: Token, representationHint?: string): string { + const key = this.register(token, representationHint); + return `${BEGIN_STRING_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`; + } + + /** + * Generate a unique string for this Token, returning a key + */ + public registerList(token: Token, representationHint?: string): string[] { + const key = this.register(token, representationHint); + return [`${BEGIN_LIST_TOKEN_MARKER}${key}${END_TOKEN_MARKER}`]; + } + + /** + * Reverse a string representation into a Token object + */ + public lookupString(s: string): Token | undefined { + const str = TokenString.forStringToken(s); + const fragments = str.split(this.lookupToken.bind(this)); + if (fragments.length === 1) { + const v = fragments.values[0]; + if (typeof v !== 'string') { return v as Token; } + } + return undefined; + } + + /** + * Reverse a string representation into a Token object + */ + public lookupList(xs: string[]): Token | undefined { + if (xs.length !== 1) { return undefined; } + const str = TokenString.forListToken(xs[0]); + const fragments = str.split(this.lookupToken.bind(this)); + if (fragments.length === 1) { + const v = fragments.values[0]; + if (typeof v !== 'string') { return v as Token; } + } + return undefined; + } + + /** + * Find a Token by key. + * + * This excludes the token markers. + */ + public lookupToken(key: string): Token { + const token = this.tokenMap.get(key); + if (!token) { + throw new Error(`Unrecognized token key: ${key}`); + } + return token; + } + + private register(token: Token, representationHint?: string): string { + const counter = this.tokenMap.size; + const representation = (representationHint || `TOKEN`).replace(new RegExp(`[^${VALID_KEY_CHARS}]`, 'g'), '.'); + const key = `${representation}.${counter}`; + this.tokenMap.set(key, token); + return key; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/lib/token.ts b/packages/@aws-cdk/cdk/lib/token.ts index 4ce9792079931..484eafd841b90 100644 --- a/packages/@aws-cdk/cdk/lib/token.ts +++ b/packages/@aws-cdk/cdk/lib/token.ts @@ -1,6 +1,6 @@ import { IConstruct } from "./construct"; -import { TOKEN_MAP } from "./encoding"; -import { unresolved } from './unresolved'; +import { unresolved } from "./encoding"; +import { TokenMap } from "./token-map"; /** * If objects has a function property by this name, they will be considered tokens, and this @@ -86,7 +86,7 @@ export class Token { } if (this.tokenStringification === undefined) { - this.tokenStringification = TOKEN_MAP.registerString(this, this.displayName); + this.tokenStringification = TokenMap.instance().registerString(this, this.displayName); } return this.tokenStringification; } @@ -121,7 +121,7 @@ export class Token { } if (this.tokenListification === undefined) { - this.tokenListification = TOKEN_MAP.registerList(this, this.displayName); + this.tokenListification = TokenMap.instance().registerList(this, this.displayName); } return this.tokenListification; } diff --git a/packages/@aws-cdk/cdk/lib/uniqueid.ts b/packages/@aws-cdk/cdk/lib/uniqueid.ts index 13942f4817b8b..00a46f9e50379 100644 --- a/packages/@aws-cdk/cdk/lib/uniqueid.ts +++ b/packages/@aws-cdk/cdk/lib/uniqueid.ts @@ -36,7 +36,7 @@ export function makeUniqueId(components: string[]) { } // Lazy require in order to break a module dependency cycle - const unresolvedTokens = components.filter(c => require('./unresolved').unresolved(c)); + const unresolvedTokens = components.filter(c => require('./encoding').unresolved(c)); if (unresolvedTokens.length > 0) { throw new Error(`ID components may not include unresolved tokens: ${unresolvedTokens.join(',')}`); } diff --git a/packages/@aws-cdk/cdk/lib/unresolved.ts b/packages/@aws-cdk/cdk/lib/unresolved.ts deleted file mode 100644 index 06cb599149351..0000000000000 --- a/packages/@aws-cdk/cdk/lib/unresolved.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { isListToken, TOKEN_MAP } from "./encoding"; -import { RESOLVE_METHOD } from "./token"; - -/** - * Returns true if obj is a token (i.e. has the resolve() method or is a string - * that includes token markers), or it's a listifictaion of a Token string. - * - * @param obj The object to test. - * @deprecated use `Token.unresolved` - */ -export function unresolved(obj: any): boolean { - if (typeof(obj) === 'string') { - return TOKEN_MAP.createStringTokenString(obj).test(); - } else if (Array.isArray(obj) && obj.length === 1) { - return isListToken(obj[0]); - } else { - return obj && typeof(obj[RESOLVE_METHOD]) === 'function'; - } -} diff --git a/packages/@aws-cdk/cdk/test/test.condition.ts b/packages/@aws-cdk/cdk/test/test.condition.ts index 86a6517d8e73b..0dd49450ecc93 100644 --- a/packages/@aws-cdk/cdk/test/test.condition.ts +++ b/packages/@aws-cdk/cdk/test/test.condition.ts @@ -44,7 +44,7 @@ export = { }); // THEN - test.ok(cdk.unresolved(propValue)); + test.ok(cdk.Token.unresolved(propValue)); test.deepEqual(stack._toCloudFormation(), { Resources: { MyResource: { diff --git a/packages/@aws-cdk/cdk/test/test.tokens.ts b/packages/@aws-cdk/cdk/test/test.tokens.ts index a385756ca48dd..86e100a208342 100644 --- a/packages/@aws-cdk/cdk/test/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/test.tokens.ts @@ -1,5 +1,6 @@ import { Test } from 'nodeunit'; -import { App as Root, Fn, Token, unresolved } from '../lib'; +import { App as Root, Fn, Token } from '../lib'; +import { TokenMap } from '../lib/token-map'; import { evaluateCFN } from './evaluate-cfn'; export = { @@ -123,9 +124,9 @@ export = { }, 'isToken(obj) can be used to determine if an object is a token'(test: Test) { - test.ok(unresolved({ resolve: () => 123 })); - test.ok(unresolved({ a: 1, b: 2, resolve: () => 'hello' })); - test.ok(!unresolved({ a: 1, b: 2, resolve: 3 })); + test.ok(Token.unresolved({ resolve: () => 123 })); + test.ok(Token.unresolved({ a: 1, b: 2, resolve: () => 'hello' })); + test.ok(!Token.unresolved({ a: 1, b: 2, resolve: 3 })); test.done(); }, @@ -157,6 +158,27 @@ export = { test.done(); }, + 'tokens stringification can be reversed'(test: Test) { + // GIVEN + const token = new Token(() => 'woof woof'); + + // THEN + test.equal(token, TokenMap.instance().lookupString(`${token}`)); + test.done(); + }, + + 'concatenated tokens are undefined'(test: Test) { + // GIVEN + const token = new Token(() => 'woof woof'); + + // WHEN + test.equal(undefined, TokenMap.instance().lookupString(`${token}bla`)); + test.equal(undefined, TokenMap.instance().lookupString(`bla${token}`)); + test.equal(undefined, TokenMap.instance().lookupString(`bla`)); + + test.done(); + }, + 'Tokens stringification and reversing of CloudFormation Tokens is implemented using Fn::Join'(test: Test) { // GIVEN const token = new Token(() => ({ woof: 'woof' })); From c0e5b87110e5762924458c34e88648c4dfd10a37 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Tue, 7 May 2019 18:50:36 +0200 Subject: [PATCH 13/21] Fix conflicts --- .../aws-codepipeline/lib/validation.ts | 2 +- .../aws-stepfunctions-tasks/package.json | 46 +++++++++---------- packages/decdk/package-lock.json | 2 +- tools/cdk-build-tools/package-lock.json | 45 ++++++++++++------ 4 files changed, 57 insertions(+), 38 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline/lib/validation.ts b/packages/@aws-cdk/aws-codepipeline/lib/validation.ts index 1caee6f9fc073..55ed7a5f9ed91 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/validation.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/validation.ts @@ -55,7 +55,7 @@ export function validateArtifactName(artifactName: string | undefined): void { function validateAgainstRegex(regex: RegExp, thing: string, name: string | undefined) { // name could be a Token - in that case, skip validation altogether - if (cdk.unresolved(name)) { + if (cdk.Token.unresolved(name)) { return; } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json index 0a411f7ed99f2..326ce7e61b9bf 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/package.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/package.json @@ -1,6 +1,6 @@ { "name": "@aws-cdk/aws-stepfunctions-tasks", - "version": "0.29.0", + "version": "0.31.0", "description": "Task integrations for AWS StepFunctions", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -70,34 +70,34 @@ }, "license": "Apache-2.0", "devDependencies": { - "@aws-cdk/assert": "^0.29.0", - "cdk-build-tools": "^0.29.0", - "cdk-integ-tools": "^0.29.0", - "pkglint": "^0.29.0", + "@aws-cdk/assert": "^0.31.0", + "cdk-build-tools": "^0.31.0", + "cdk-integ-tools": "^0.31.0", + "pkglint": "^0.31.0", "jest": "^24.7.1" }, "dependencies": { - "@aws-cdk/aws-cloudwatch": "^0.29.0", - "@aws-cdk/aws-ec2": "^0.29.0", - "@aws-cdk/aws-ecs": "^0.29.0", - "@aws-cdk/aws-stepfunctions": "^0.29.0", - "@aws-cdk/cdk": "^0.29.0", - "@aws-cdk/aws-iam": "^0.29.0", - "@aws-cdk/aws-lambda": "^0.29.0", - "@aws-cdk/aws-sns": "^0.29.0", - "@aws-cdk/aws-sqs": "^0.29.0" + "@aws-cdk/aws-cloudwatch": "^0.31.0", + "@aws-cdk/aws-ec2": "^0.31.0", + "@aws-cdk/aws-ecs": "^0.31.0", + "@aws-cdk/aws-stepfunctions": "^0.31.0", + "@aws-cdk/cdk": "^0.31.0", + "@aws-cdk/aws-iam": "^0.31.0", + "@aws-cdk/aws-lambda": "^0.31.0", + "@aws-cdk/aws-sns": "^0.31.0", + "@aws-cdk/aws-sqs": "^0.31.0" }, "homepage": "https://github.com/awslabs/aws-cdk", "peerDependencies": { - "@aws-cdk/aws-cloudwatch": "^0.29.0", - "@aws-cdk/aws-ec2": "^0.29.0", - "@aws-cdk/aws-ecs": "^0.29.0", - "@aws-cdk/aws-stepfunctions": "^0.29.0", - "@aws-cdk/cdk": "^0.29.0", - "@aws-cdk/aws-iam": "^0.29.0", - "@aws-cdk/aws-lambda": "^0.29.0", - "@aws-cdk/aws-sns": "^0.29.0", - "@aws-cdk/aws-sqs": "^0.29.0" + "@aws-cdk/aws-cloudwatch": "^0.31.0", + "@aws-cdk/aws-ec2": "^0.31.0", + "@aws-cdk/aws-ecs": "^0.31.0", + "@aws-cdk/aws-stepfunctions": "^0.31.0", + "@aws-cdk/cdk": "^0.31.0", + "@aws-cdk/aws-iam": "^0.31.0", + "@aws-cdk/aws-lambda": "^0.31.0", + "@aws-cdk/aws-sns": "^0.31.0", + "@aws-cdk/aws-sqs": "^0.31.0" }, "engines": { "node": ">= 8.10.0" diff --git a/packages/decdk/package-lock.json b/packages/decdk/package-lock.json index d10b94a3f3859..ac19069fa77a7 100644 --- a/packages/decdk/package-lock.json +++ b/packages/decdk/package-lock.json @@ -1,6 +1,6 @@ { "name": "decdk", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tools/cdk-build-tools/package-lock.json b/tools/cdk-build-tools/package-lock.json index 035d86397bc32..764ea6f6f243e 100644 --- a/tools/cdk-build-tools/package-lock.json +++ b/tools/cdk-build-tools/package-lock.json @@ -1,6 +1,6 @@ { "name": "cdk-build-tools", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1719,7 +1719,8 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1737,11 +1738,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "bundled": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1754,15 +1757,18 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "bundled": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1865,7 +1871,8 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "bundled": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1875,6 +1882,7 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1887,17 +1895,20 @@ "minimatch": { "version": "3.0.4", "bundled": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "bundled": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1914,6 +1925,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1986,7 +1998,8 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "bundled": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1996,6 +2009,7 @@ "once": { "version": "1.4.0", "bundled": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2071,7 +2085,8 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "bundled": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2101,6 +2116,7 @@ "string-width": { "version": "1.0.2", "bundled": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2118,6 +2134,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2156,11 +2173,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true + "bundled": true, + "optional": true } } }, @@ -3768,7 +3787,7 @@ "dependencies": { "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "resolved": "", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "yargs-parser": { From cc980364dafa3dc97c8649df6a95022a85ff0dc0 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 May 2019 10:10:06 +0200 Subject: [PATCH 14/21] Get rid of duplicate definitions in vpc-ref --- .../@aws-cdk/aws-ec2/lib/security-group.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/util.ts | 3 +- packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts | 2 +- .../aws-ec2/lib/vpc-network-provider.ts | 2 +- packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts | 480 ------------------ packages/@aws-cdk/aws-ec2/lib/vpc.ts | 6 +- packages/@aws-cdk/aws-ec2/lib/vpn.ts | 2 +- 7 files changed, 8 insertions(+), 489 deletions(-) delete mode 100644 packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts diff --git a/packages/@aws-cdk/aws-ec2/lib/security-group.ts b/packages/@aws-cdk/aws-ec2/lib/security-group.ts index c915026df65ae..15245f5af3a05 100644 --- a/packages/@aws-cdk/aws-ec2/lib/security-group.ts +++ b/packages/@aws-cdk/aws-ec2/lib/security-group.ts @@ -2,7 +2,7 @@ import { CfnOutput, Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; import { Connections, IConnectable } from './connections'; import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupIngress } from './ec2.generated'; import { IPortRange, ISecurityGroupRule } from './security-group-rule'; -import { IVpcNetwork } from './vpc-ref'; +import { IVpcNetwork } from './vpc'; const isSecurityGroupSymbol = Symbol.for('aws-cdk:isSecurityGroup'); diff --git a/packages/@aws-cdk/aws-ec2/lib/util.ts b/packages/@aws-cdk/aws-ec2/lib/util.ts index 873d453d418d7..d0c3cf44ee9eb 100644 --- a/packages/@aws-cdk/aws-ec2/lib/util.ts +++ b/packages/@aws-cdk/aws-ec2/lib/util.ts @@ -1,6 +1,5 @@ import cdk = require('@aws-cdk/cdk'); -import { VpcSubnet } from './vpc'; -import { IVpcSubnet, SubnetType } from "./vpc-ref"; +import { IVpcSubnet, SubnetType, VpcSubnet } from './vpc'; /** * Turn an arbitrary string into one that can be used as a CloudFormation identifier by stripping special characters diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts index c9ead17185aa7..e07837225a4b7 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts @@ -4,7 +4,7 @@ import { Connections, IConnectable } from './connections'; import { CfnVPCEndpoint } from './ec2.generated'; import { SecurityGroup } from './security-group'; import { TcpPort, TcpPortFromAttribute } from './security-group-rule'; -import { IVpcNetwork, SubnetSelection, SubnetType } from './vpc-ref'; +import { IVpcNetwork, SubnetSelection, SubnetType } from './vpc'; /** * A VPC endpoint. diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts index 2207287fad670..f8d65f185eec7 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc-network-provider.ts @@ -1,6 +1,6 @@ import cdk = require('@aws-cdk/cdk'); import cxapi = require('@aws-cdk/cx-api'); -import { VpcNetworkImportProps } from './vpc-ref'; +import { VpcNetworkImportProps } from './vpc'; /** * Properties for looking up an existing VPC. diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts b/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts deleted file mode 100644 index b8b68e65ae763..0000000000000 --- a/packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts +++ /dev/null @@ -1,480 +0,0 @@ -import { Construct, IConstruct, IDependable } from '@aws-cdk/cdk'; -import { DEFAULT_SUBNET_NAME, subnetName } from './util'; -import { InterfaceVpcEndpoint, InterfaceVpcEndpointOptions } from './vpc-endpoint'; -import { VpnConnection, VpnConnectionOptions } from './vpn'; - -export interface IVpcSubnet extends IConstruct { - /** - * The Availability Zone the subnet is located in - */ - readonly availabilityZone: string; - - /** - * The subnetId for this particular subnet - */ - readonly subnetId: string; - - /** - * Dependable that can be depended upon to force internet connectivity established on the VPC - */ - readonly internetConnectivityEstablished: IDependable; - - /** - * Route table ID - */ - readonly routeTableId?: string; - - /** - * Exports this subnet to another stack. - */ - export(): VpcSubnetImportProps; -} - -export interface IVpcNetwork extends IConstruct { - /** - * Identifier for this VPC - */ - readonly vpcId: string; - - /** - * List of public subnets in this VPC - */ - readonly publicSubnets: IVpcSubnet[]; - - /** - * List of private subnets in this VPC - */ - readonly privateSubnets: IVpcSubnet[]; - - /** - * List of isolated subnets in this VPC - */ - readonly isolatedSubnets: IVpcSubnet[]; - - /** - * AZs for this VPC - */ - readonly availabilityZones: string[]; - - /** - * Region where this VPC is located - */ - readonly vpcRegion: string; - - /** - * Identifier for the VPN gateway - */ - readonly vpnGatewayId?: string; - - /** - * Return IDs of the subnets appropriate for the given selection strategy - * - * Requires that at least one subnet is matched, throws a descriptive - * error message otherwise. - * - * @deprecated Use selectSubnets() instead. - */ - selectSubnetIds(selection?: SubnetSelection): string[]; - - /** - * Return information on the subnets appropriate for the given selection strategy - * - * Requires that at least one subnet is matched, throws a descriptive - * error message otherwise. - */ - selectSubnets(selection?: SubnetSelection): SelectedSubnets; - - /** - * Return whether all of the given subnets are from the VPC's public subnets. - */ - isPublicSubnets(subnetIds: string[]): boolean; - - /** - * Adds a new VPN connection to this VPC - */ - addVpnConnection(id: string, options: VpnConnectionOptions): VpnConnection; - - /** - * Adds a new interface endpoint to this VPC - */ - addInterfaceEndpoint(id: string, options: InterfaceVpcEndpointOptions): InterfaceVpcEndpoint - - /** - * Exports this VPC so it can be consumed by another stack. - */ - export(): VpcNetworkImportProps; -} - -/** - * The type of Subnet - */ -export enum SubnetType { - /** - * Isolated Subnets do not route Outbound traffic - * - * This can be good for subnets with RDS or - * Elasticache endpoints - */ - Isolated = 'Isolated', - - /** - * Subnet that routes to the internet, but not vice versa. - * - * Instances in a private subnet can connect to the Internet, but will not - * allow connections to be initiated from the Internet. - * - * Outbound traffic will be routed via a NAT Gateway. Preference being in - * the same AZ, but if not available will use another AZ (control by - * specifing `maxGateways` on VpcNetwork). This might be used for - * experimental cost conscious accounts or accounts where HA outbound - * traffic is not needed. - */ - Private = 'Private', - - /** - * Subnet connected to the Internet - * - * Instances in a Public subnet can connect to the Internet and can be - * connected to from the Internet as long as they are launched with public - * IPs (controlled on the AutoScalingGroup or other constructs that launch - * instances). - * - * Public subnets route outbound traffic via an Internet Gateway. - */ - Public = 'Public' -} - -/** - * Customize subnets that are selected for placement of ENIs - * - * Constructs that allow customization of VPC placement use parameters of this - * type to provide placement settings. - * - * By default, the instances are placed in the private subnets. - */ -export interface SubnetSelection { - /** - * Place the instances in the subnets of the given type - * - * At most one of `subnetType` and `subnetName` can be supplied. - * - * @default SubnetType.Private - */ - readonly subnetType?: SubnetType; - - /** - * Place the instances in the subnets with the given name - * - * (This is the name supplied in subnetConfiguration). - * - * At most one of `subnetType` and `subnetName` can be supplied. - * - * @default name - */ - readonly subnetName?: string; - - /** - * If true, return at most one subnet per AZ - * - * @defautl false - */ - readonly onePerAz?: boolean; -} - -/** - * Result of selecting a subset of subnets from a VPC - */ -export interface SelectedSubnets { - /** - * The subnet IDs - */ - readonly subnetIds: string[]; - - /** - * The respective AZs of each subnet - */ - readonly availabilityZones: string[]; - - /** - * Route table IDs of each respective subnet - */ - readonly routeTableIds: string[]; - - /** - * Dependency representing internet connectivity for these subnets - */ - readonly internetConnectedDependency: IDependable; -} - -/** - * A new or imported VPC - */ -export abstract class VpcNetworkBase extends Construct implements IVpcNetwork { - - /** - * Identifier for this VPC - */ - public abstract readonly vpcId: string; - - /** - * List of public subnets in this VPC - */ - public abstract readonly publicSubnets: IVpcSubnet[]; - - /** - * List of private subnets in this VPC - */ - public abstract readonly privateSubnets: IVpcSubnet[]; - - /** - * List of isolated subnets in this VPC - */ - public abstract readonly isolatedSubnets: IVpcSubnet[]; - - /** - * AZs for this VPC - */ - public abstract readonly availabilityZones: string[]; - - /** - * Identifier for the VPN gateway - */ - public abstract readonly vpnGatewayId?: string; - - /** - * Dependencies for internet connectivity - */ - public readonly internetDependencies = new Array(); - - /** - * Dependencies for NAT connectivity - */ - public readonly natDependencies = new Array(); - - public selectSubnetIds(selection?: SubnetSelection): string[] { - return this.selectSubnets(selection).subnetIds; - } - - /** - * Returns IDs of selected subnets - */ - public selectSubnets(selection: SubnetSelection = {}): SelectedSubnets { - const subnets = this.selectSubnetObjects(selection); - - return { - subnetIds: subnets.map(s => s.subnetId), - availabilityZones: subnets.map(s => s.availabilityZone), - routeTableIds: subnets.map(s => s.routeTableId).filter(notUndefined), // Possibly don't have this information - internetConnectedDependency: tap(new CompositeDependable(), d => subnets.forEach(s => d.add(s.internetConnectivityEstablished))), - }; - } - - /** - * Adds a new VPN connection to this VPC - */ - public addVpnConnection(id: string, options: VpnConnectionOptions): VpnConnection { - return new VpnConnection(this, id, { - vpc: this, - ...options - }); - } - - /** - * Adds a new interface endpoint to this VPC - */ - public addInterfaceEndpoint(id: string, options: InterfaceVpcEndpointOptions): InterfaceVpcEndpoint { - return new InterfaceVpcEndpoint(this, id, { - vpc: this, - ...options - }); - } - - /** - * Export this VPC from the stack - */ - public abstract export(): VpcNetworkImportProps; - - /** - * Return whether all of the given subnets are from the VPC's public subnets. - */ - public isPublicSubnets(subnetIds: string[]): boolean { - const pubIds = new Set(this.publicSubnets.map(n => n.subnetId)); - return subnetIds.every(pubIds.has.bind(pubIds)); - } - - /** - * The region where this VPC is defined - */ - public get vpcRegion(): string { - return this.node.stack.region; - } - - /** - * Return the subnets appropriate for the placement strategy - */ - protected selectSubnetObjects(selection: SubnetSelection = {}): IVpcSubnet[] { - selection = reifySelectionDefaults(selection); - let subnets: IVpcSubnet[] = []; - - if (selection.subnetName !== undefined) { // Select by name - const allSubnets = [...this.publicSubnets, ...this.privateSubnets, ...this.isolatedSubnets]; - subnets = allSubnets.filter(s => subnetName(s) === selection.subnetName); - } else { // Select by type - subnets = { - [SubnetType.Isolated]: this.isolatedSubnets, - [SubnetType.Private]: this.privateSubnets, - [SubnetType.Public]: this.publicSubnets, - }[selection.subnetType || SubnetType.Private]; - - if (selection.onePerAz && subnets.length > 0) { - // Restrict to at most one subnet group - subnets = subnets.filter(s => subnetName(s) === subnetName(subnets[0])); - } - } - - if (subnets.length === 0) { - throw new Error(`There are no ${describeSelection(selection)} in this VPC. Use a different VPC subnet selection.`); - } - - return subnets; - } -} - -/** - * Properties that reference an external VpcNetwork - */ -export interface VpcNetworkImportProps { - /** - * VPC's identifier - */ - readonly vpcId: string; - - /** - * List of availability zones for the subnets in this VPC. - */ - readonly availabilityZones: string[]; - - /** - * List of public subnet IDs - * - * Must be undefined or match the availability zones in length and order. - */ - readonly publicSubnetIds?: string[]; - - /** - * List of names for the public subnets - * - * Must be undefined or have a name for every public subnet group. - */ - readonly publicSubnetNames?: string[]; - - /** - * List of private subnet IDs - * - * Must be undefined or match the availability zones in length and order. - */ - readonly privateSubnetIds?: string[]; - - /** - * List of names for the private subnets - * - * Must be undefined or have a name for every private subnet group. - */ - readonly privateSubnetNames?: string[]; - - /** - * List of isolated subnet IDs - * - * Must be undefined or match the availability zones in length and order. - */ - readonly isolatedSubnetIds?: string[]; - - /** - * List of names for the isolated subnets - * - * Must be undefined or have a name for every isolated subnet group. - */ - readonly isolatedSubnetNames?: string[]; - - /** - * VPN gateway's identifier - */ - readonly vpnGatewayId?: string; -} - -export interface VpcSubnetImportProps { - /** - * The Availability Zone the subnet is located in - */ - readonly availabilityZone: string; - - /** - * The subnetId for this particular subnet - */ - readonly subnetId: string; -} - -/** - * If the placement strategy is completely "default", reify the defaults so - * consuming code doesn't have to reimplement the same analysis every time. - * - * Returns "private subnets" by default. - */ -function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { - if (placement.subnetType !== undefined && placement.subnetName !== undefined) { - throw new Error('Only one of subnetType and subnetName can be supplied'); - } - - if (placement.subnetType === undefined && placement.subnetName === undefined) { - return { subnetType: SubnetType.Private, onePerAz: placement.onePerAz }; - } - - return placement; -} - -/** - * Describe the given placement strategy - */ -function describeSelection(placement: SubnetSelection): string { - if (placement.subnetType !== undefined) { - return `'${DEFAULT_SUBNET_NAME[placement.subnetType]}' subnets`; - } - if (placement.subnetName !== undefined) { - return `subnets named '${placement.subnetName}'`; - } - return JSON.stringify(placement); -} - -class CompositeDependable implements IDependable { - private readonly dependables = new Array(); - - /** - * Add a construct to the dependency roots - */ - public add(dep: IDependable) { - this.dependables.push(dep); - } - - /** - * Retrieve the current set of dependency roots - */ - public get dependencyRoots(): IConstruct[] { - const ret = []; - for (const dep of this.dependables) { - ret.push(...dep.dependencyRoots); - } - return ret; - } -} - -/** - * Invoke a function on a value (for its side effect) and return the value - */ -function tap(x: T, fn: (x: T) => void): T { - fn(x); - return x; -} - -function notUndefined(x: T | undefined): x is T { - return x !== undefined; -} diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 85fb2955ecc5b..b912e243e6d18 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -121,7 +121,7 @@ export enum SubnetType { * This can be good for subnets with RDS or * Elasticache endpoints */ - Isolated = 1, + Isolated = 'Isolated', /** * Subnet that routes to the internet, but not vice versa. @@ -135,7 +135,7 @@ export enum SubnetType { * experimental cost conscious accounts or accounts where HA outbound * traffic is not needed. */ - Private = 2, + Private = 'Private', /** * Subnet connected to the Internet @@ -147,7 +147,7 @@ export enum SubnetType { * * Public subnets route outbound traffic via an Internet Gateway. */ - Public = 3 + Public = 'Public' } /** diff --git a/packages/@aws-cdk/aws-ec2/lib/vpn.ts b/packages/@aws-cdk/aws-ec2/lib/vpn.ts index 592dc64cab534..ace419446ba45 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpn.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpn.ts @@ -2,7 +2,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import net = require('net'); import { CfnCustomerGateway, CfnVPNConnection, CfnVPNConnectionRoute } from './ec2.generated'; -import { IVpcNetwork } from './vpc-ref'; +import { IVpcNetwork } from './vpc'; export interface IVpnConnection extends cdk.IConstruct { /** From 349e32d4d89de984bd3136fea1ad8bf81e9a3f7f Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 May 2019 10:45:02 +0200 Subject: [PATCH 15/21] Fix build --- .../lib/alb/application-target-group.ts | 2 +- .../lib/records/zone-delegation.ts | 4 +-- .../aws-stepfunctions/lib/activity.ts | 30 +++++++++++++++++++ packages/decdk/package.json | 2 +- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts index 24920a7c89186..d580a1cf8c709 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts @@ -120,7 +120,7 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat */ public registerConnectable(connectable: ec2.IConnectable, portRange?: ec2.IPortRange) { if (portRange === undefined) { - if (cdk.unresolved(this.defaultPort)) { + if (cdk.Token.isToken(this.defaultPort)) { portRange = new ec2.TcpPortFromAttribute(this.defaultPort); } else { portRange = new ec2.TcpPort(parseInt(this.defaultPort, 10)); diff --git a/packages/@aws-cdk/aws-route53/lib/records/zone-delegation.ts b/packages/@aws-cdk/aws-route53/lib/records/zone-delegation.ts index d67716756f430..175e1c921aceb 100644 --- a/packages/@aws-cdk/aws-route53/lib/records/zone-delegation.ts +++ b/packages/@aws-cdk/aws-route53/lib/records/zone-delegation.ts @@ -28,9 +28,9 @@ export class ZoneDelegationRecord extends cdk.Construct { super(scope, id); const ttl = props.ttl === undefined ? 172_800 : props.ttl; - const resourceRecords = cdk.unresolved(props.nameServers) + const resourceRecords = cdk.Token.isToken(props.nameServers) ? props.nameServers // Can't map a string-array token! - : props.nameServers.map(ns => (cdk.unresolved(ns) || ns.endsWith('.')) ? ns : `${ns}.`); + : props.nameServers.map(ns => (cdk.Token.isToken(ns) || ns.endsWith('.')) ? ns : `${ns}.`); new CfnRecordSet(this, 'Resource', { hostedZoneId: props.zone.hostedZoneId, diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts index 3994685f537f6..8ab47f29e9223 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/activity.ts @@ -15,6 +15,32 @@ export interface ActivityProps { * Define a new StepFunctions activity */ export class Activity extends Resource implements IActivity { + /** + * Construct an Activity from an existing Activity ARN + */ + public static fromActivityArn(scope: Construct, id: string, activityArn: string): IActivity { + class Imported extends Construct implements IActivity { + public get activityArn() { return activityArn; } + public get activityName() { + return this.node.stack.parseArn(activityArn, ':').resourceName || ''; + } + } + + return new Imported(scope, id); + } + + /** + * Construct an Activity from an existing Activity Name + */ + public static fromActivityName(scope: Construct, id: string, activityName: string): IActivity { + return Activity.fromActivityArn(scope, id, scope.node.stack.formatArn({ + service: 'states', + resource: 'activity', + resourceName: activityName, + sep: ':', + })); + } + /** * @attribute */ @@ -144,11 +170,15 @@ export class Activity extends Resource implements IActivity { export interface IActivity extends IResource { /** * The ARN of the activity + * + * @attribute */ readonly activityArn: string; /** * The name of the activity + * + * @attribute */ readonly activityName: string; } \ No newline at end of file diff --git a/packages/decdk/package.json b/packages/decdk/package.json index 3be1352f7ce5e..26f763ee21f84 100644 --- a/packages/decdk/package.json +++ b/packages/decdk/package.json @@ -149,4 +149,4 @@ "engines": { "node": ">= 8.10.0" } -} +} \ No newline at end of file From 8d6df56d4629206845cc24964cf2eaf8300fcc29 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 May 2019 11:41:11 +0200 Subject: [PATCH 16/21] Mass rename "unresolved" => "isToken" --- design/aws-guidelines.md | 2 +- packages/@aws-cdk/aws-codepipeline/lib/validation.ts | 2 +- packages/@aws-cdk/aws-ecr/lib/repository.ts | 2 +- packages/@aws-cdk/aws-iam/lib/policy-document.ts | 2 +- packages/@aws-cdk/aws-s3/lib/bucket.ts | 2 +- packages/@aws-cdk/aws-ssm/lib/parameter.ts | 6 +++--- .../aws-stepfunctions-tasks/lib/ecs-run-task-base.ts | 4 ++-- packages/@aws-cdk/cdk/lib/arn.ts | 2 +- packages/@aws-cdk/cdk/lib/cfn-mapping.ts | 4 ++-- packages/@aws-cdk/cdk/lib/construct.ts | 4 ++-- packages/@aws-cdk/cdk/lib/fn.ts | 2 +- packages/@aws-cdk/cdk/lib/instrinsics.ts | 4 ++-- packages/@aws-cdk/cdk/test/test.condition.ts | 2 +- packages/@aws-cdk/cdk/test/test.tokens.ts | 6 +++--- 14 files changed, 22 insertions(+), 22 deletions(-) diff --git a/design/aws-guidelines.md b/design/aws-guidelines.md index 7566d86f8d6bd..183a4789c6665 100644 --- a/design/aws-guidelines.md +++ b/design/aws-guidelines.md @@ -143,7 +143,7 @@ into an `{ "Fn::Join" }` expression which includes the relevant intrinsic functions. If needed, you can query whether an object includes unresolved tokens by using -the `cdk.unresolved(x)` function. +the `cdk.isToken(x)` function. Resource attributes should use a type that corresponds to the __resolved__ AWS CloudFormation type (e.g. `string`, `string[]`). diff --git a/packages/@aws-cdk/aws-codepipeline/lib/validation.ts b/packages/@aws-cdk/aws-codepipeline/lib/validation.ts index 55ed7a5f9ed91..f3b90d3c72ede 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/validation.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/validation.ts @@ -55,7 +55,7 @@ export function validateArtifactName(artifactName: string | undefined): void { function validateAgainstRegex(regex: RegExp, thing: string, name: string | undefined) { // name could be a Token - in that case, skip validation altogether - if (cdk.Token.unresolved(name)) { + if (cdk.Token.isToken(name)) { return; } diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 7f2e3d5efd531..42e1f35cf659b 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -242,7 +242,7 @@ export class Repository extends RepositoryBase { // if repositoryArn is a token, the repository name is also required. this is because // repository names can include "/" (e.g. foo/bar/myrepo) and it is impossible to // parse the name from an ARN using CloudFormation's split/select. - if (Token.unresolved(repositoryArn)) { + if (Token.isToken(repositoryArn)) { throw new Error('"repositoryArn" is a late-bound value, and therefore "repositoryName" is required. Use `fromRepositoryAttributes` instead'); } diff --git a/packages/@aws-cdk/aws-iam/lib/policy-document.ts b/packages/@aws-cdk/aws-iam/lib/policy-document.ts index 1bd5851efe6a5..7ebf7565dc6a6 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy-document.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy-document.ts @@ -533,7 +533,7 @@ export class PolicyStatement extends cdk.Token { return undefined; } - if (cdk.Token.unresolved(values)) { + if (cdk.Token.isToken(values)) { return values; } diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index df3eaacf40ba6..d0c034b0881dd 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -725,7 +725,7 @@ export class Bucket extends BucketBase { super(scope, id); const { bucketEncryption, encryptionKey } = this.parseEncryption(props); - if (props.bucketName && !Token.unresolved(props.bucketName)) { + if (props.bucketName && !Token.isToken(props.bucketName)) { this.validateBucketName(props.bucketName); } diff --git a/packages/@aws-cdk/aws-ssm/lib/parameter.ts b/packages/@aws-cdk/aws-ssm/lib/parameter.ts index a3bad500669b0..bfb38b7a118fc 100644 --- a/packages/@aws-cdk/aws-ssm/lib/parameter.ts +++ b/packages/@aws-cdk/aws-ssm/lib/parameter.ts @@ -217,11 +217,11 @@ export class StringListParameter extends ParameterBase implements IStringListPar constructor(scope: Construct, id: string, props: StringListParameterProps) { super(scope, id); - if (props.stringListValue.find(str => !Token.unresolved(str) && str.indexOf(',') !== -1)) { + if (props.stringListValue.find(str => !Token.isToken(str) && str.indexOf(',') !== -1)) { throw new Error('Values of a StringList SSM Parameter cannot contain the \',\' character. Use a string parameter instead.'); } - if (props.allowedPattern && !Token.unresolved(props.stringListValue)) { + if (props.allowedPattern && !Token.isToken(props.stringListValue)) { props.stringListValue.forEach(str => _assertValidValue(str, props.allowedPattern!)); } @@ -249,7 +249,7 @@ export class StringListParameter extends ParameterBase implements IStringListPar * ``cdk.unresolved``). */ function _assertValidValue(value: string, allowedPattern: string): void { - if (Token.unresolved(value) || Token.unresolved(allowedPattern)) { + if (Token.isToken(value) || Token.isToken(allowedPattern)) { // Unable to perform validations against unresolved tokens return; } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts index 94ea58b5b132b..7fab62cdd5c26 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts @@ -146,7 +146,7 @@ export class EcsRunTaskBase extends cdk.Construct implements ec2.IConnectable { for (const override of this.props.containerOverrides || []) { const name = override.containerName; - if (!cdk.Token.unresolved(name)) { + if (!cdk.Token.isToken(name)) { const cont = this.props.taskDefinition.node.tryFindChild(name); if (!cont) { ret.push(`Overrides mention container with name '${name}', but no such container in task definition`); @@ -187,4 +187,4 @@ function renderOverrides(containerOverrides?: ContainerOverride[]) { } return { ContainerOverrides: ret }; -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cdk/lib/arn.ts b/packages/@aws-cdk/cdk/lib/arn.ts index 09f31fb95038b..338c35fd894b8 100644 --- a/packages/@aws-cdk/cdk/lib/arn.ts +++ b/packages/@aws-cdk/cdk/lib/arn.ts @@ -78,7 +78,7 @@ export function arnFromComponents(components: ArnComponents, stack: Stack): stri * components of the ARN. */ export function parseArn(arn: string, sepIfToken: string = '/', hasName: boolean = true): ArnComponents { - if (Token.unresolved(arn)) { + if (Token.isToken(arn)) { return parseToken(arn, sepIfToken, hasName); } diff --git a/packages/@aws-cdk/cdk/lib/cfn-mapping.ts b/packages/@aws-cdk/cdk/lib/cfn-mapping.ts index 07140f37ed05c..ac2fd0cfe7827 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-mapping.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-mapping.ts @@ -34,12 +34,12 @@ export class CfnMapping extends CfnRefElement { */ public findInMap(key1: string, key2: string): string { // opportunistically check that the key exists (if the key does not contain tokens) - if (!Token.unresolved(key1) && !(key1 in this.mapping)) { + if (!Token.isToken(key1) && !(key1 in this.mapping)) { throw new Error(`Mapping doesn't contain top-level key '${key1}'`); } // opportunistically check that the key exists (if the key does not contain tokens) - if (!Token.unresolved(key2) && !(key2 in this.mapping[key1])) { + if (!Token.isToken(key2) && !(key2 in this.mapping[key1])) { throw new Error(`Mapping doesn't contain second-level key '${key2}'`); } diff --git a/packages/@aws-cdk/cdk/lib/construct.ts b/packages/@aws-cdk/cdk/lib/construct.ts index 08d2ec796d9d4..b553d8c337e9f 100644 --- a/packages/@aws-cdk/cdk/lib/construct.ts +++ b/packages/@aws-cdk/cdk/lib/construct.ts @@ -82,7 +82,7 @@ export class ConstructNode { // escape any path separators so they don't wreck havoc this.id = this._escapePathSeparator(this.id); - if (Token.unresolved(id)) { + if (Token.isToken(id)) { throw new Error(`Cannot use tokens in construct ID: ${id}`); } } @@ -725,4 +725,4 @@ export interface OutgoingReference { } // Import this _after_ everything else to help node work the classes out in the correct order... -import { Reference } from './reference'; \ No newline at end of file +import { Reference } from './reference'; diff --git a/packages/@aws-cdk/cdk/lib/fn.ts b/packages/@aws-cdk/cdk/lib/fn.ts index 2b63250e9e201..4bd33656fe3ac 100644 --- a/packages/@aws-cdk/cdk/lib/fn.ts +++ b/packages/@aws-cdk/cdk/lib/fn.ts @@ -651,7 +651,7 @@ class FnJoin extends Token { } public resolve(context: ResolveContext): any { - if (Token.unresolved(this.listOfValues)) { + if (Token.isToken(this.listOfValues)) { // This is a list token, don't try to do smart things with it. return { 'Fn::Join': [ this.delimiter, this.listOfValues ] }; } diff --git a/packages/@aws-cdk/cdk/lib/instrinsics.ts b/packages/@aws-cdk/cdk/lib/instrinsics.ts index c0a3b0f1803b5..d81926ea49393 100644 --- a/packages/@aws-cdk/cdk/lib/instrinsics.ts +++ b/packages/@aws-cdk/cdk/lib/instrinsics.ts @@ -18,7 +18,7 @@ export function minimalCloudFormationJoin(delimiter: string, values: any[]): any return values; function isPlainString(obj: any): boolean { - return typeof obj === 'string' && !require('./token').Token.unresolved(obj); + return typeof obj === 'string' && !require('./token').Token.isToken(obj); } function isSplicableFnJoinInstrinsic(obj: any): boolean { @@ -53,4 +53,4 @@ export function isNameOfCloudFormationIntrinsic(name: string): boolean { */ export function canEvaluateToList(x: any) { return isIntrinsic(x) && ['Ref', 'Fn::GetAtt', 'Fn::GetAZs', 'Fn::Split', 'Fn::FindInMap', 'Fn::ImportValue'].includes(Object.keys(x)[0]); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/cdk/test/test.condition.ts b/packages/@aws-cdk/cdk/test/test.condition.ts index 0dd49450ecc93..18733eaa05574 100644 --- a/packages/@aws-cdk/cdk/test/test.condition.ts +++ b/packages/@aws-cdk/cdk/test/test.condition.ts @@ -44,7 +44,7 @@ export = { }); // THEN - test.ok(cdk.Token.unresolved(propValue)); + test.ok(cdk.Token.isToken(propValue)); test.deepEqual(stack._toCloudFormation(), { Resources: { MyResource: { diff --git a/packages/@aws-cdk/cdk/test/test.tokens.ts b/packages/@aws-cdk/cdk/test/test.tokens.ts index 86e100a208342..259a31974eabb 100644 --- a/packages/@aws-cdk/cdk/test/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/test.tokens.ts @@ -124,9 +124,9 @@ export = { }, 'isToken(obj) can be used to determine if an object is a token'(test: Test) { - test.ok(Token.unresolved({ resolve: () => 123 })); - test.ok(Token.unresolved({ a: 1, b: 2, resolve: () => 'hello' })); - test.ok(!Token.unresolved({ a: 1, b: 2, resolve: 3 })); + test.ok(Token.isToken({ resolve: () => 123 })); + test.ok(Token.isToken({ a: 1, b: 2, resolve: () => 'hello' })); + test.ok(!Token.isToken({ a: 1, b: 2, resolve: 3 })); test.done(); }, From d22091420054aceacbe251c6bb3f3d862fcb857a Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 May 2019 11:44:12 +0200 Subject: [PATCH 17/21] Rename Ecs classes --- .../aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts | 8 ++++---- .../aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts | 6 +++--- .../aws-stepfunctions-tasks/test/ecs-tasks.test.ts | 8 ++++---- .../aws-stepfunctions-tasks/test/integ.ec2-task.ts | 2 +- .../aws-stepfunctions-tasks/test/integ.fargate-task.ts | 2 +- packages/@aws-cdk/aws-stepfunctions/README.md | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts index e943e7081be63..613e93eb904ec 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts @@ -6,7 +6,7 @@ import { CommonEcsRunTaskProps, EcsRunTaskBase } from './ecs-run-task-base'; /** * Properties to run an ECS task on EC2 in StepFunctionsan ECS */ -export interface EcsRunEc2TaskProps extends CommonEcsRunTaskProps { +export interface RunEcsEc2TaskProps extends CommonEcsRunTaskProps { /** * In what subnets to place the task's ENIs * @@ -36,12 +36,12 @@ export interface EcsRunEc2TaskProps extends CommonEcsRunTaskProps { /** * Run an ECS/EC2 Task in a StepFunctions workflow */ -export class EcsRunEc2Task extends EcsRunTaskBase { +export class RunEcsEc2Task extends EcsRunTaskBase { private readonly constraints: any[]; private readonly strategies: any[]; private readonly cluster: ecs.ICluster; - constructor(scope: cdk.Construct, id: string, props: EcsRunEc2TaskProps) { + constructor(scope: cdk.Construct, id: string, props: RunEcsEc2TaskProps) { if (!props.taskDefinition.isEc2Compatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } @@ -146,7 +146,7 @@ export class EcsRunEc2Task extends EcsRunTaskBase { /** * Validate combinations of networking arguments */ -function validateNoNetworkingProps(props: EcsRunEc2TaskProps) { +function validateNoNetworkingProps(props: RunEcsEc2TaskProps) { if (props.subnets !== undefined || props.securityGroup !== undefined) { throw new Error('vpcPlacement and securityGroup can only be used in AwsVpc networking mode'); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts index d464e25353edf..422539f19504a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts @@ -6,7 +6,7 @@ import { CommonEcsRunTaskProps, EcsRunTaskBase } from './ecs-run-task-base'; /** * Properties to define an ECS service */ -export interface EcsRunFargateTaskProps extends CommonEcsRunTaskProps { +export interface RunEcsFargateTaskProps extends CommonEcsRunTaskProps { /** * Assign public IP addresses to each task * @@ -42,8 +42,8 @@ export interface EcsRunFargateTaskProps extends CommonEcsRunTaskProps { /** * Start a service on an ECS cluster */ -export class EcsRunFargateTask extends EcsRunTaskBase { - constructor(scope: cdk.Construct, id: string, props: EcsRunFargateTaskProps) { +export class RunEcsFargateTask extends EcsRunTaskBase { + constructor(scope: cdk.Construct, id: string, props: RunEcsFargateTaskProps) { if (!props.taskDefinition.isFargateCompatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts index 61b6af3e686a9..8af823f27ab10 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts @@ -32,7 +32,7 @@ test('Running a Fargate Task', () => { }); // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: new tasks.EcsRunFargateTask(stack, 'RunFargate', { + const runTask = new sfn.Task(stack, 'Run', { task: new tasks.RunEcsFargateTask(stack, 'RunFargate', { cluster, taskDefinition, containerOverrides: [ @@ -131,7 +131,7 @@ test('Running an EC2 Task with bridge network', () => { }); // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: new tasks.EcsRunEc2Task(stack, 'RunEc2', { + const runTask = new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task(stack, 'RunEc2', { cluster, taskDefinition, containerOverrides: [ @@ -218,7 +218,7 @@ test('Running an EC2 Task with placement strategies', () => { memoryLimitMiB: 256, }); - const ec2Task = new tasks.EcsRunEc2Task(stack, 'RunEc2', { + const ec2Task = new tasks.RunEcsEc2Task(stack, 'RunEc2', { cluster, taskDefinition, }); @@ -264,7 +264,7 @@ test('Running an EC2 Task with overridden number values', () => { memoryLimitMiB: 256, }); - const ec2Task = new tasks.EcsRunEc2Task(stack, 'RunEc2', { + const ec2Task = new tasks.RunEcsEc2Task(stack, 'RunEc2', { cluster, taskDefinition, containerOverrides: [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts index 08991deba12e9..37586b8baabc7 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts @@ -29,7 +29,7 @@ taskDefinition.addContainer('TheContainer', { // Build state machine const definition = new sfn.Pass(stack, 'Start', { result: { SomeKey: 'SomeValue' } -}).next(new sfn.Task(stack, 'Run', { task: new tasks.EcsRunEc2Task(stack, 'RunEc2', { +}).next(new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task(stack, 'RunEc2', { cluster, taskDefinition, containerOverrides: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts index f9c5da60a27de..6bb8bdf1f6f96 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts @@ -28,7 +28,7 @@ taskDefinition.addContainer('TheContainer', { // Build state machine const definition = new sfn.Pass(stack, 'Start', { result: { SomeKey: 'SomeValue' } -}).next(new sfn.Task(stack, 'RunFargate', { task: new tasks.EcsRunFargateTask(stack, 'FargateTask', { +}).next(new sfn.Task(stack, 'RunFargate', { task: new tasks.RunEcsFargateTask(stack, 'FargateTask', { cluster, taskDefinition, assignPublicIp: true, containerOverrides: [ diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index ec583c4204188..ed86fb34ca811 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -112,7 +112,7 @@ couple of the tasks available are: queue that you poll on a compute fleet you manage yourself) * `tasks.PublishToTopic` -- publish a message to an SNS topic * `tasks.SendToQueue` -- send a message to an SQS queue -* `tasks.EcsRunFargateTask`/`ecs.EcsRunEc2Task` -- run a container task, +* `tasks.RunEcsFargateTask`/`ecs.RunEcsEc2Task` -- run a container task, depending on the type of capacity. #### Task parameters from the state json @@ -186,7 +186,7 @@ import ecs = require('@aws-cdk/aws-ecs'); // See examples in ECS library for initialization of 'cluster' and 'taskDefinition' -const fargateTask = new ecs.EcsRunFargateTask(task, 'FargateTask', { +const fargateTask = new ecs.RunEcsFargateTask(task, 'FargateTask', { cluster, taskDefinition, containerOverrides: [ From 8d41855050b488c3c3b646aa34df088588be6e8d Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 May 2019 13:18:48 +0200 Subject: [PATCH 18/21] Nothing is a construct and everything is bound through bind() --- packages/@aws-cdk/aws-ec2/lib/util.ts | 16 +-- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 8 +- .../aws-stepfunctions-tasks/lib/index.ts | 4 +- .../lib/invoke-activity.ts | 27 ++--- .../lib/invoke-function.ts | 29 +++-- .../lib/publish-to-topic.ts | 42 +++---- .../lib/run-ecs-ec2-task.ts | 23 ++-- .../lib/run-ecs-fargate-task.ts | 7 +- ...se-types.ts => run-ecs-task-base-types.ts} | 0 ...-run-task-base.ts => run-ecs-task-base.ts} | 103 ++++++++---------- .../lib/send-to-queue.ts | 39 +++---- .../test/ecs-tasks.test.ts | 8 +- .../test/integ.ec2-task.expected.json | 10 +- .../test/integ.ec2-task.ts | 2 +- .../test/integ.fargate-task.expected.json | 12 +- .../test/integ.fargate-task.ts | 2 +- packages/@aws-cdk/aws-stepfunctions/README.md | 2 +- .../aws-stepfunctions/lib/states/task.ts | 34 +++--- .../lib/step-functions-task.ts | 11 ++ .../test/test.state-machine-resources.ts | 44 ++++---- .../test/test.states-language.ts | 6 +- 21 files changed, 196 insertions(+), 233 deletions(-) rename packages/@aws-cdk/aws-stepfunctions-tasks/lib/{ecs-run-task-base-types.ts => run-ecs-task-base-types.ts} (100%) rename packages/@aws-cdk/aws-stepfunctions-tasks/lib/{ecs-run-task-base.ts => run-ecs-task-base.ts} (63%) diff --git a/packages/@aws-cdk/aws-ec2/lib/util.ts b/packages/@aws-cdk/aws-ec2/lib/util.ts index d0c3cf44ee9eb..b52c460aba921 100644 --- a/packages/@aws-cdk/aws-ec2/lib/util.ts +++ b/packages/@aws-cdk/aws-ec2/lib/util.ts @@ -13,11 +13,13 @@ export function slugify(x: string): string { /** * The default names for every subnet type */ -export const DEFAULT_SUBNET_NAME = { - [SubnetType.Public]: 'Public', - [SubnetType.Private]: 'Private', - [SubnetType.Isolated]: 'Isolated', -}; +export function defaultSubnetName(type: SubnetType) { + switch (type) { + case SubnetType.Public: return 'Public'; + case SubnetType.Private: return 'Private'; + case SubnetType.Isolated: return 'Isolated'; + } +} /** * Return a subnet name from its construct ID @@ -86,7 +88,7 @@ export class ExportSubnetGroup { // Splat down to [ INGRESS, EGRESS, ... ] const groupNames = range(this.groups).map(i => netNames[i * this.azs]); - if (groupNames.length === 1 && groupNames[0] === DEFAULT_SUBNET_NAME[this.type]) { return undefined; } + if (groupNames.length === 1 && groupNames[0] === defaultSubnetName(this.type)) { return undefined; } return groupNames; } @@ -113,7 +115,7 @@ export class ImportSubnetGroup { throw new Error(`Amount of ${idField} (${this.subnetIds.length}) must be a multiple of availability zones (${this.availabilityZones.length}).`); } - this.names = this.normalizeNames(names, DEFAULT_SUBNET_NAME[type], nameField); + this.names = this.normalizeNames(names, defaultSubnetName(type), nameField); } public import(scope: cdk.Construct): IVpcSubnet[] { diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index b912e243e6d18..4b669d5a9c00c 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -3,7 +3,7 @@ import { ConcreteDependable, Construct, IConstruct, IDependable } from '@aws-cdk import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnVPNGateway, CfnVPNGatewayRoutePropagation } from './ec2.generated'; import { CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCGatewayAttachment } from './ec2.generated'; import { NetworkBuilder } from './network-util'; -import { DEFAULT_SUBNET_NAME, ExportSubnetGroup, ImportSubnetGroup, subnetId, subnetName } from './util'; +import { defaultSubnetName, ExportSubnetGroup, ImportSubnetGroup, subnetId, subnetName } from './util'; import { GatewayVpcEndpoint, GatewayVpcEndpointAwsService, GatewayVpcEndpointOptions } from './vpc-endpoint'; import { InterfaceVpcEndpoint, InterfaceVpcEndpointOptions } from './vpc-endpoint'; import { VpcNetworkProvider, VpcNetworkProviderProps } from './vpc-network-provider'; @@ -661,11 +661,11 @@ export class VpcNetwork extends VpcNetworkBase { public static readonly DEFAULT_SUBNETS: SubnetConfiguration[] = [ { subnetType: SubnetType.Public, - name: DEFAULT_SUBNET_NAME[SubnetType.Public], + name: defaultSubnetName(SubnetType.Public), }, { subnetType: SubnetType.Private, - name: DEFAULT_SUBNET_NAME[SubnetType.Private], + name: defaultSubnetName(SubnetType.Private), } ]; @@ -1281,7 +1281,7 @@ function reifySelectionDefaults(placement: SubnetSelection): SubnetSelection { */ function describeSelection(placement: SubnetSelection): string { if (placement.subnetType !== undefined) { - return `'${DEFAULT_SUBNET_NAME[placement.subnetType]}' subnets`; + return `'${defaultSubnetName(placement.subnetType)}' subnets`; } if (placement.subnetName !== undefined) { return `subnets named '${placement.subnetName}'`; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts index 517febc29c455..3405a459919d1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/index.ts @@ -1,7 +1,7 @@ export * from './invoke-function'; export * from './invoke-activity'; -export * from './ecs-run-task-base'; // Remove this once we can -export * from './ecs-run-task-base-types'; +export * from './run-ecs-task-base'; // Remove this once we can +export * from './run-ecs-task-base-types'; export * from './publish-to-topic'; export * from './send-to-queue'; export * from './run-ecs-ec2-task'; diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts index 2662ac15e9b33..aef21f12ba100 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-activity.ts @@ -1,5 +1,3 @@ -import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import iam = require('@aws-cdk/aws-iam'); import sfn = require('@aws-cdk/aws-stepfunctions'); /** @@ -23,20 +21,17 @@ export interface InvokeActivityProps { * integration with other AWS services via a specific class instance. */ export class InvokeActivity implements sfn.IStepFunctionsTask { - public readonly resourceArn: string; - public readonly policyStatements?: iam.PolicyStatement[] | undefined; - public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; - public readonly metricPrefixSingular?: string = 'Activity'; - public readonly metricPrefixPlural?: string = 'Activities'; - - public readonly heartbeatSeconds?: number | undefined; - public readonly parameters?: { [name: string]: any; } | undefined; - - constructor(activity: sfn.IActivity, props: InvokeActivityProps = {}) { - this.resourceArn = activity.activityArn; - this.metricDimensions = { ActivityArn: activity.activityArn }; - this.heartbeatSeconds = props.heartbeatSeconds; + constructor(private readonly activity: sfn.IActivity, private readonly props: InvokeActivityProps = {}) { + } - // No IAM permissions necessary, execution role implicitly has Activity permissions. + public bind(_task: sfn.Task): sfn.StepFunctionsTaskProperties { + return { + resourceArn: this.activity.activityArn, + metricDimensions: { ActivityArn: this.activity.activityArn }, + heartbeatSeconds: this.props.heartbeatSeconds, + // No IAM permissions necessary, execution role implicitly has Activity permissions. + metricPrefixSingular: 'Activity', + metricPrefixPlural: 'Activities', + }; } } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts index d8db31776fd80..a57d0e1580409 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/invoke-function.ts @@ -1,4 +1,3 @@ -import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); import lambda = require('@aws-cdk/aws-lambda'); import sfn = require('@aws-cdk/aws-stepfunctions'); @@ -10,21 +9,19 @@ import sfn = require('@aws-cdk/aws-stepfunctions'); * integration with other AWS services via a specific class instance. */ export class InvokeFunction implements sfn.IStepFunctionsTask { - public readonly resourceArn: string; - public readonly policyStatements?: iam.PolicyStatement[] | undefined; - public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; - public readonly metricPrefixSingular?: string = 'LambdaFunction'; - public readonly metricPrefixPlural?: string = 'LambdaFunctions'; - - public readonly heartbeatSeconds?: number | undefined; - public readonly parameters?: { [name: string]: any; } | undefined; + constructor(private readonly lambdaFunction: lambda.IFunction) { + } - constructor(lambdaFunction: lambda.IFunction) { - this.resourceArn = lambdaFunction.functionArn; - this.policyStatements = [new iam.PolicyStatement() - .addResource(lambdaFunction.functionArn) - .addActions("lambda:InvokeFunction") - ]; - this.metricDimensions = { LambdaFunctionArn: lambdaFunction.functionArn }; + public bind(_task: sfn.Task): sfn.StepFunctionsTaskProperties { + return { + resourceArn: this.lambdaFunction.functionArn, + policyStatements: [new iam.PolicyStatement() + .addResource(this.lambdaFunction.functionArn) + .addActions("lambda:InvokeFunction") + ], + metricPrefixSingular: 'LambdaFunction', + metricPrefixPlural: 'LambdaFunctions', + metricDimensions: { LambdaFunctionArn: this.lambdaFunction.functionArn }, + }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts index ed61153472504..6bcda1589857f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/publish-to-topic.ts @@ -1,4 +1,3 @@ -import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); import sns = require('@aws-cdk/aws-sns'); import sfn = require('@aws-cdk/aws-stepfunctions'); @@ -48,34 +47,27 @@ export interface PublishToTopicProps { * integration with other AWS services via a specific class instance. */ export class PublishToTopic implements sfn.IStepFunctionsTask { - public readonly resourceArn: string; - public readonly policyStatements?: iam.PolicyStatement[] | undefined; - public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; - public readonly metricPrefixSingular?: string; - public readonly metricPrefixPlural?: string; - - public readonly heartbeatSeconds?: number | undefined; - public readonly parameters?: { [name: string]: any; } | undefined; - - constructor(topic: sns.ITopic, props: PublishToTopicProps) { + constructor(private readonly topic: sns.ITopic, private readonly props: PublishToTopicProps) { if ((props.message === undefined) === (props.messageObject === undefined)) { throw new Error(`Supply exactly one of 'message' or 'messageObject'`); } + } - this.resourceArn = 'arn:aws:states:::sns:publish'; - this.policyStatements = [new iam.PolicyStatement() - .addAction('sns:Publish') - .addResource(topic.topicArn) - ]; - this.parameters = { - TopicArn: topic.topicArn, - ...(props.messageObject - ? { Message: new cdk.Token(() => topic.node.stringifyJson(props.messageObject)) } - : renderString('Message', props.message)), - MessageStructure: props.messagePerSubscriptionType ? "json" : undefined, - ...renderString('Subject', props.subject), + public bind(task: sfn.Task): sfn.StepFunctionsTaskProperties { + return { + resourceArn: 'arn:aws:states:::sns:publish', + policyStatements: [new iam.PolicyStatement() + .addAction('sns:Publish') + .addResource(this.topic.topicArn) + ], + parameters: { + TopicArn: this.topic.topicArn, + ...(this.props.messageObject + ? { Message: new cdk.Token(() => task.node.stringifyJson(this.props.messageObject)) } + : renderString('Message', this.props.message)), + MessageStructure: this.props.messagePerSubscriptionType ? "json" : undefined, + ...renderString('Subject', this.props.subject), + } }; - - // No IAM permissions necessary, execution role implicitly has Activity permissions. } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts index 613e93eb904ec..e7c28f0d52f39 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts @@ -1,7 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import cdk = require('@aws-cdk/cdk'); -import { CommonEcsRunTaskProps, EcsRunTaskBase } from './ecs-run-task-base'; +import { CommonEcsRunTaskProps, EcsRunTaskBase } from './run-ecs-task-base'; /** * Properties to run an ECS task on EC2 in StepFunctionsan ECS @@ -39,18 +39,21 @@ export interface RunEcsEc2TaskProps extends CommonEcsRunTaskProps { export class RunEcsEc2Task extends EcsRunTaskBase { private readonly constraints: any[]; private readonly strategies: any[]; - private readonly cluster: ecs.ICluster; - constructor(scope: cdk.Construct, id: string, props: RunEcsEc2TaskProps) { + constructor(props: RunEcsEc2TaskProps) { if (!props.taskDefinition.isEc2Compatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } + if (!props.cluster.hasEc2Capacity) { + throw new Error('Cluster for this service needs Ec2 capacity. Call addXxxCapacity() on the cluster.'); + } + if (!props.taskDefinition.defaultContainer) { throw new Error('A TaskDefinition must have at least one essential container'); } - super(scope, id, { + super({ ...props, parameters: { LaunchType: 'EC2', @@ -59,7 +62,6 @@ export class RunEcsEc2Task extends EcsRunTaskBase { } }); - this.cluster = props.cluster; this.constraints = []; this.strategies = []; @@ -130,17 +132,6 @@ export class RunEcsEc2Task extends EcsRunTaskBase { public placeRandomly() { this.strategies.push({ Type: 'random' }); } - - /** - * Validate this Ec2Service - */ - protected validate(): string[] { - const ret = super.validate(); - if (!this.cluster.hasEc2Capacity) { - ret.push('Cluster for this service needs Ec2 capacity. Call addXxxCapacity() on the cluster.'); - } - return ret; - } } /** diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts index 422539f19504a..ff06a9ceab03b 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-fargate-task.ts @@ -1,7 +1,6 @@ import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); -import cdk = require('@aws-cdk/cdk'); -import { CommonEcsRunTaskProps, EcsRunTaskBase } from './ecs-run-task-base'; +import { CommonEcsRunTaskProps, EcsRunTaskBase } from './run-ecs-task-base'; /** * Properties to define an ECS service @@ -43,7 +42,7 @@ export interface RunEcsFargateTaskProps extends CommonEcsRunTaskProps { * Start a service on an ECS cluster */ export class RunEcsFargateTask extends EcsRunTaskBase { - constructor(scope: cdk.Construct, id: string, props: RunEcsFargateTaskProps) { + constructor(props: RunEcsFargateTaskProps) { if (!props.taskDefinition.isFargateCompatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } @@ -52,7 +51,7 @@ export class RunEcsFargateTask extends EcsRunTaskBase { throw new Error('A TaskDefinition must have at least one essential container'); } - super(scope, id, { + super({ ...props, parameters: { LaunchType: 'FARGATE', diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base-types.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base-types.ts similarity index 100% rename from packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base-types.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base-types.ts diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts similarity index 63% rename from packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts rename to packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts index 7fab62cdd5c26..9a5c773f86ce3 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/ecs-run-task-base.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-task-base.ts @@ -1,10 +1,10 @@ -import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import iam = require('@aws-cdk/aws-iam'); +import sfn = require('@aws-cdk/aws-stepfunctions'); import cdk = require('@aws-cdk/cdk'); -import { ContainerOverride } from './ecs-run-task-base-types'; import { renderNumber, renderString, renderStringList } from './json-path'; +import { ContainerOverride } from './run-ecs-task-base-types'; /** * Basic properties for ECS Tasks @@ -49,37 +49,49 @@ export interface EcsRunTaskBaseProps extends CommonEcsRunTaskProps { /** * A StepFunctions Task to run a Task on ECS or Fargate */ -export class EcsRunTaskBase extends cdk.Construct implements ec2.IConnectable { +export class EcsRunTaskBase implements ec2.IConnectable, sfn.IStepFunctionsTask { /** * Manage allowed network traffic for this service */ public readonly connections: ec2.Connections = new ec2.Connections(); - public readonly resourceArn: string; - public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; - public readonly metricPrefixSingular?: string; - public readonly metricPrefixPlural?: string; - public readonly heartbeatSeconds?: number | undefined; - - protected networkConfiguration?: any; - protected readonly taskDefinition: ecs.TaskDefinition; + private securityGroup?: ec2.ISecurityGroup; + private networkConfiguration?: any; private readonly sync: boolean; - constructor(scope: cdk.Construct, id: string, private readonly props: EcsRunTaskBaseProps) { - super(scope, id); - - this.resourceArn = 'arn:aws:states:::ecs:runTask' + (props.synchronous !== false ? '.sync' : ''); + constructor(private readonly props: EcsRunTaskBaseProps) { this.sync = props.synchronous !== false; - this.taskDefinition = props.taskDefinition; + + for (const override of this.props.containerOverrides || []) { + const name = override.containerName; + if (!cdk.Token.isToken(name)) { + const cont = this.props.taskDefinition.node.tryFindChild(name); + if (!cont) { + throw new Error(`Overrides mention container with name '${name}', but no such container in task definition`); + } + } + } } - public get parameters(): { [name: string]: any; } | undefined { + public bind(task: sfn.Task): sfn.StepFunctionsTaskProperties { + if (this.networkConfiguration !== undefined) { + // Make sure we have a security group if we're using AWSVPC networking + if (this.securityGroup === undefined) { + this.securityGroup = new ec2.SecurityGroup(task, 'SecurityGroup', { vpc: this.props.cluster.vpc }); + } + this.connections.addSecurityGroup(this.securityGroup); + } + return { - Cluster: this.props.cluster.clusterArn, - TaskDefinition: this.props.taskDefinition.taskDefinitionArn, - NetworkConfiguration: this.networkConfiguration, - Overrides: renderOverrides(this.props.containerOverrides), - ...this.props.parameters, + resourceArn: 'arn:aws:states:::ecs:runTask' + (this.sync ? '.sync' : ''), + parameters: { + Cluster: this.props.cluster.clusterArn, + TaskDefinition: this.props.taskDefinition.taskDefinitionArn, + NetworkConfiguration: this.networkConfiguration, + Overrides: renderOverrides(this.props.containerOverrides), + ...this.props.parameters, + }, + policyStatements: this.makePolicyStatements(task), }; } @@ -88,32 +100,31 @@ export class EcsRunTaskBase extends cdk.Construct implements ec2.IConnectable { assignPublicIp?: boolean, subnetSelection?: ec2.SubnetSelection, securityGroup?: ec2.ISecurityGroup) { + if (subnetSelection === undefined) { subnetSelection = { subnetType: assignPublicIp ? ec2.SubnetType.Public : ec2.SubnetType.Private }; } - if (securityGroup === undefined) { - securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc }); - } - const subnets = vpc.selectSubnets(subnetSelection); - this.connections.addSecurityGroup(securityGroup); + + // If none is given here, one will be created later on during bind() + this.securityGroup = securityGroup; this.networkConfiguration = { AwsvpcConfiguration: { AssignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED', - Subnets: subnets.subnetIds, - SecurityGroups: new cdk.Token(() => [securityGroup!.securityGroupId]), + Subnets: vpc.selectSubnets(subnetSelection).subnetIds, + SecurityGroups: new cdk.Token(() => [this.securityGroup!.securityGroupId]), } }; } - public get policyStatements(): iam.PolicyStatement[] | undefined { - const stack = this.node.stack; + private makePolicyStatements(task: sfn.Task): iam.PolicyStatement[] { + const stack = task.node.stack; // https://docs.aws.amazon.com/step-functions/latest/dg/ecs-iam.html const policyStatements = [ new iam.PolicyStatement() .addAction('ecs:RunTask') - .addResource(this.taskDefinition.taskDefinitionArn), + .addResource(this.props.taskDefinition.taskDefinitionArn), new iam.PolicyStatement() .addActions('ecs:StopTask', 'ecs:DescribeTasks') .addAllResources(), @@ -135,34 +146,12 @@ export class EcsRunTaskBase extends cdk.Construct implements ec2.IConnectable { return policyStatements; } - /** - * Validate this service - * - * Check that all mentioned container overrides exist in the task definition - * (but only if the name is not a token, in which case we can't tell). - */ - protected validate(): string[] { - const ret = super.validate(); - - for (const override of this.props.containerOverrides || []) { - const name = override.containerName; - if (!cdk.Token.isToken(name)) { - const cont = this.props.taskDefinition.node.tryFindChild(name); - if (!cont) { - ret.push(`Overrides mention container with name '${name}', but no such container in task definition`); - } - } - } - - return ret; - } - private taskExecutionRoles(): iam.IRole[] { // Need to be able to pass both Task and Execution role, apparently const ret = new Array(); - ret.push(this.taskDefinition.taskRole); - if ((this.taskDefinition as any).executionRole) { - ret.push((this.taskDefinition as any).executionRole); + ret.push(this.props.taskDefinition.taskRole); + if ((this.props.taskDefinition as any).executionRole) { + ret.push((this.props.taskDefinition as any).executionRole); } return ret; } diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts index 25912202f7c27..820af3e1e83cf 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/send-to-queue.ts @@ -1,4 +1,3 @@ -import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); import sqs = require('@aws-cdk/aws-sqs'); import sfn = require('@aws-cdk/aws-stepfunctions'); @@ -48,29 +47,23 @@ export interface SendToQueueProps { * integration with other AWS services via a specific class instance. */ export class SendToQueue implements sfn.IStepFunctionsTask { - public readonly resourceArn: string; - public readonly policyStatements?: iam.PolicyStatement[] | undefined; - public readonly metricDimensions?: cloudwatch.DimensionHash | undefined; - public readonly metricPrefixSingular?: string; - public readonly metricPrefixPlural?: string; - - public readonly heartbeatSeconds?: number | undefined; - public readonly parameters?: { [name: string]: any; } | undefined; + constructor(private readonly queue: sqs.IQueue, private readonly props: SendToQueueProps) { + } - constructor(queue: sqs.IQueue, props: SendToQueueProps) { - this.resourceArn = 'arn:aws:states:::sqs:sendMessage'; - this.policyStatements = [new iam.PolicyStatement() - .addAction('sqs:SendMessage') - .addResource(queue.queueArn) - ]; - this.parameters = { - QueueUrl: queue.queueUrl, - ...renderString('MessageBody', props.messageBody), - ...renderNumber('DelaySeconds', props.delaySeconds), - ...renderString('MessageDeduplicationId', props.messageDeduplicationId), - ...renderString('MessageGroupId', props.messageGroupId), + public bind(_task: sfn.Task): sfn.StepFunctionsTaskProperties { + return { + resourceArn: 'arn:aws:states:::sqs:sendMessage', + policyStatements: [new iam.PolicyStatement() + .addAction('sqs:SendMessage') + .addResource(this.queue.queueArn) + ], + parameters: { + QueueUrl: this.queue.queueUrl, + ...renderString('MessageBody', this.props.messageBody), + ...renderNumber('DelaySeconds', this.props.delaySeconds), + ...renderString('MessageDeduplicationId', this.props.messageDeduplicationId), + ...renderString('MessageGroupId', this.props.messageGroupId), + } }; - - // No IAM permissions necessary, execution role implicitly has Activity permissions. } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts index 8af823f27ab10..c18f1764f6ba8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts @@ -32,7 +32,7 @@ test('Running a Fargate Task', () => { }); // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: new tasks.RunEcsFargateTask(stack, 'RunFargate', { + const runTask = new sfn.Task(stack, 'RunFargate', { task: new tasks.RunEcsFargateTask({ cluster, taskDefinition, containerOverrides: [ @@ -131,7 +131,7 @@ test('Running an EC2 Task with bridge network', () => { }); // WHEN - const runTask = new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task(stack, 'RunEc2', { + const runTask = new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task({ cluster, taskDefinition, containerOverrides: [ @@ -218,7 +218,7 @@ test('Running an EC2 Task with placement strategies', () => { memoryLimitMiB: 256, }); - const ec2Task = new tasks.RunEcsEc2Task(stack, 'RunEc2', { + const ec2Task = new tasks.RunEcsEc2Task({ cluster, taskDefinition, }); @@ -264,7 +264,7 @@ test('Running an EC2 Task with overridden number values', () => { memoryLimitMiB: 256, }); - const ec2Task = new tasks.RunEcsEc2Task(stack, 'RunEc2', { + const ec2Task = new tasks.RunEcsEc2Task({ cluster, taskDefinition, containerOverrides: [ diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json index bb459bf9218f4..d9dc5f70fe9d1 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json @@ -518,14 +518,6 @@ ] }, "Links": [], - "LinuxParameters": { - "Capabilities": { - "Add": [], - "Drop": [] - }, - "Devices": [], - "Tmpfs": [] - }, "LogConfiguration": { "LogDriver": "awslogs", "Options": { @@ -999,4 +991,4 @@ "Description": "S3 key for asset version \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts index 37586b8baabc7..4b1b07bb65f52 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.ts @@ -29,7 +29,7 @@ taskDefinition.addContainer('TheContainer', { // Build state machine const definition = new sfn.Pass(stack, 'Start', { result: { SomeKey: 'SomeValue' } -}).next(new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task(stack, 'RunEc2', { +}).next(new sfn.Task(stack, 'Run', { task: new tasks.RunEcsEc2Task({ cluster, taskDefinition, containerOverrides: [ { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json index ffde4bbd5153a..286d975ab72ed 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.expected.json @@ -138,14 +138,6 @@ ] }, "Links": [], - "LinuxParameters": { - "Capabilities": { - "Add": [], - "Drop": [] - }, - "Devices": [], - "Tmpfs": [] - }, "LogConfiguration": { "LogDriver": "awslogs", "Options": { @@ -597,7 +589,7 @@ "Fn::Join": [ "", [ - "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"RunFargate\"},\"RunFargate\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", + "{\"StartAt\":\"Start\",\"States\":{\"Start\":{\"Type\":\"Pass\",\"Result\":{\"SomeKey\":\"SomeValue\"},\"Next\":\"FargateTask\"},\"FargateTask\":{\"End\":true,\"Parameters\":{\"Cluster\":\"", { "Fn::GetAtt": [ "FargateCluster7CCD5F93", @@ -642,4 +634,4 @@ "Description": "S3 key for asset version \"aws-ecs-integ2/AdoptEcrRepositorydbc60defc59544bcaa5c28c95d68f62c/Code\"" } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts index 6bb8bdf1f6f96..aa1f24764f604 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.fargate-task.ts @@ -28,7 +28,7 @@ taskDefinition.addContainer('TheContainer', { // Build state machine const definition = new sfn.Pass(stack, 'Start', { result: { SomeKey: 'SomeValue' } -}).next(new sfn.Task(stack, 'RunFargate', { task: new tasks.RunEcsFargateTask(stack, 'FargateTask', { +}).next(new sfn.Task(stack, 'FargateTask', { task: new tasks.RunEcsFargateTask({ cluster, taskDefinition, assignPublicIp: true, containerOverrides: [ diff --git a/packages/@aws-cdk/aws-stepfunctions/README.md b/packages/@aws-cdk/aws-stepfunctions/README.md index ed86fb34ca811..3640e5d6d09ee 100644 --- a/packages/@aws-cdk/aws-stepfunctions/README.md +++ b/packages/@aws-cdk/aws-stepfunctions/README.md @@ -186,7 +186,7 @@ import ecs = require('@aws-cdk/aws-ecs'); // See examples in ECS library for initialization of 'cluster' and 'taskDefinition' -const fargateTask = new ecs.RunEcsFargateTask(task, 'FargateTask', { +const fargateTask = new ecs.RunEcsFargateTask({ cluster, taskDefinition, containerOverrides: [ diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts index 21fa75b92e002..54a3d00e113b1 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -2,7 +2,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import { Chain } from '../chain'; import { StateGraph } from '../state-graph'; -import { IStepFunctionsTask } from '../step-functions-task'; +import { IStepFunctionsTask, StepFunctionsTaskProperties } from '../step-functions-task'; import { CatchProps, IChainable, INextable, RetryProps } from '../types'; import { renderJsonPath, State, StateType } from './state'; @@ -75,13 +75,13 @@ export interface TaskProps { export class Task extends State implements INextable { public readonly endStates: INextable[]; private readonly timeoutSeconds?: number; - private readonly task: IStepFunctionsTask; + private readonly taskProps: StepFunctionsTaskProperties; constructor(scope: cdk.Construct, id: string, props: TaskProps) { super(scope, id, props); this.timeoutSeconds = props.timeoutSeconds; - this.task = props.task; + this.taskProps = props.task.bind(this); this.endStates = [this]; } @@ -125,11 +125,11 @@ export class Task extends State implements INextable { ...this.renderInputOutput(), Type: StateType.Task, Comment: this.comment, - Resource: this.task.resourceArn, - Parameters: this.task.parameters, + Resource: this.taskProps.resourceArn, + Parameters: this.taskProps.parameters, ResultPath: renderJsonPath(this.resultPath), TimeoutSeconds: this.timeoutSeconds, - HeartbeatSeconds: this.task.heartbeatSeconds, + HeartbeatSeconds: this.taskProps.heartbeatSeconds, }; } @@ -142,7 +142,7 @@ export class Task extends State implements INextable { return new cloudwatch.Metric({ namespace: 'AWS/States', metricName, - dimensions: this.task.metricDimensions, + dimensions: this.taskProps.metricDimensions, statistic: 'sum', ...props }); @@ -154,7 +154,7 @@ export class Task extends State implements INextable { * @default average over 5 minutes */ public metricRunTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.task.metricPrefixSingular, 'RunTime', { statistic: 'avg', ...props }); + return this.taskMetric(this.taskProps.metricPrefixSingular, 'RunTime', { statistic: 'avg', ...props }); } /** @@ -163,7 +163,7 @@ export class Task extends State implements INextable { * @default average over 5 minutes */ public metricScheduleTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.task.metricPrefixSingular, 'ScheduleTime', { statistic: 'avg', ...props }); + return this.taskMetric(this.taskProps.metricPrefixSingular, 'ScheduleTime', { statistic: 'avg', ...props }); } /** @@ -172,7 +172,7 @@ export class Task extends State implements INextable { * @default average over 5 minutes */ public metricTime(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.task.metricPrefixSingular, 'Time', { statistic: 'avg', ...props }); + return this.taskMetric(this.taskProps.metricPrefixSingular, 'Time', { statistic: 'avg', ...props }); } /** @@ -181,7 +181,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricScheduled(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.task.metricPrefixPlural, 'Scheduled', props); + return this.taskMetric(this.taskProps.metricPrefixPlural, 'Scheduled', props); } /** @@ -190,7 +190,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.task.metricPrefixPlural, 'TimedOut', props); + return this.taskMetric(this.taskProps.metricPrefixPlural, 'TimedOut', props); } /** @@ -199,7 +199,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricStarted(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.task.metricPrefixPlural, 'Started', props); + return this.taskMetric(this.taskProps.metricPrefixPlural, 'Started', props); } /** @@ -208,7 +208,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricSucceeded(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.task.metricPrefixPlural, 'Succeeded', props); + return this.taskMetric(this.taskProps.metricPrefixPlural, 'Succeeded', props); } /** @@ -217,7 +217,7 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricFailed(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.task.metricPrefixPlural, 'Failed', props); + return this.taskMetric(this.taskProps.metricPrefixPlural, 'Failed', props); } /** @@ -226,12 +226,12 @@ export class Task extends State implements INextable { * @default sum over 5 minutes */ public metricHeartbeatTimedOut(props?: cloudwatch.MetricOptions): cloudwatch.Metric { - return this.taskMetric(this.task.metricPrefixPlural, 'HeartbeatTimedOut', props); + return this.taskMetric(this.taskProps.metricPrefixPlural, 'HeartbeatTimedOut', props); } protected onBindToGraph(graph: StateGraph) { super.onBindToGraph(graph); - for (const policyStatement of this.task.policyStatements || []) { + for (const policyStatement of this.taskProps.policyStatements || []) { graph.registerPolicyStatement(policyStatement); } } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts b/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts index b180fe1a4984b..bbea68d8263c2 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/step-functions-task.ts @@ -1,10 +1,21 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import iam = require('@aws-cdk/aws-iam'); +import { Task } from './states/task'; /** * Interface for resources that can be used as tasks */ export interface IStepFunctionsTask { + /** + * Called when the task object is used in a workflow + */ + bind(task: Task): StepFunctionsTaskProperties; +} + +/** + * Properties that define what kind of task should be created + */ +export interface StepFunctionsTaskProperties { /** * The resource that represents the work to be executed * diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts index 7faa51972a7a5..998428b9f7d02 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.state-machine-resources.ts @@ -11,11 +11,13 @@ export = { const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { task: { - resourceArn: 'resource', - policyStatements: [new iam.PolicyStatement() - .addAction('resource:Everything') - .addResource('resource') - ], + bind: () => ({ + resourceArn: 'resource', + policyStatements: [new iam.PolicyStatement() + .addAction('resource:Everything') + .addResource('resource') + ], + }) } }); @@ -46,12 +48,14 @@ export = { const stack = new cdk.Stack(); const task = new stepfunctions.Task(stack, 'Task', { task: { - resourceArn: 'resource', - policyStatements: [ - new iam.PolicyStatement() - .addAction('resource:Everything') - .addResource('resource') - ] + bind: () => ({ + resourceArn: 'resource', + policyStatements: [ + new iam.PolicyStatement() + .addAction('resource:Everything') + .addResource('resource') + ] + }) } }); @@ -87,14 +91,16 @@ export = { inputPath: "$", outputPath: "$.state", task: { - resourceArn: 'resource', - parameters: { - "input.$": "$", - "stringArgument": "inital-task", - "numberArgument": 123, - "booleanArgument": true, - "arrayArgument": ["a", "b", "c"] - } + bind: () => ({ + resourceArn: 'resource', + parameters: { + "input.$": "$", + "stringArgument": "inital-task", + "numberArgument": 123, + "booleanArgument": true, + "arrayArgument": ["a", "b", "c"] + } + }) } }); diff --git a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts index c559e173c4bb5..24cea8db4d79f 100644 --- a/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts +++ b/packages/@aws-cdk/aws-stepfunctions/test/test.states-language.ts @@ -726,5 +726,9 @@ function render(sm: stepfunctions.IChainable) { } class FakeTask implements IStepFunctionsTask { - public readonly resourceArn = 'resource'; + public bind(_task: stepfunctions.Task): stepfunctions.StepFunctionsTaskProperties { + return { + resourceArn: 'resource' + }; + } } \ No newline at end of file From cf090d8041ebfdaea91e7a0db348971441a9f14a Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 May 2019 14:10:47 +0200 Subject: [PATCH 19/21] Placement is now configured in constructor through proper union types --- .../aws-ecs/lib/base/task-definition.ts | 47 +------ .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 91 +++++++------ .../aws-ecs/lib/ec2/ec2-task-definition.ts | 3 +- packages/@aws-cdk/aws-ecs/lib/index.ts | 1 + packages/@aws-cdk/aws-ecs/lib/placement.ts | 127 ++++++++++++++++++ .../ec2/integ.event-task.lit.expected.json | 1 - .../test/ec2/integ.lb-awsvpc-nw.expected.json | 7 - .../test/ec2/integ.lb-bridge-nw.expected.json | 7 - .../test/ec2/integ.sd-awsvpc-nw.expected.json | 3 - .../test/ec2/integ.sd-bridge-nw.expected.json | 3 - .../aws-ecs/test/ec2/test.ec2-service.ts | 4 +- .../test/ec2/test.ec2-task-definition.ts | 8 +- .../lib/run-ecs-ec2-task.ts | 88 +++--------- .../test/ecs-tasks.test.ts | 10 +- 14 files changed, 209 insertions(+), 191 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecs/lib/placement.ts 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 60bf9feafcd44..a8b33d977fd4d 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -2,6 +2,7 @@ import iam = require('@aws-cdk/aws-iam'); import { Construct, Resource, Token } from '@aws-cdk/cdk'; import { ContainerDefinition, ContainerDefinitionOptions } from '../container-definition'; import { CfnTaskDefinition } from '../ecs.generated'; +import { PlacementConstraint } from '../placement'; /** * Properties common to all Task definitions @@ -196,7 +197,8 @@ export class TaskDefinition extends Resource { ...(isFargateCompatible(props.compatibility) ? ["FARGATE"] : []), ], networkMode: this.networkMode, - placementConstraints: !isFargateCompatible(this.compatibility) ? new Token(this.placementConstraints) : undefined, + placementConstraints: new Token( + () => !isFargateCompatible(this.compatibility) && this.placementConstraints.length > 0 ? this.placementConstraints : undefined), cpu: props.cpu, memory: props.memoryMiB, }); @@ -254,8 +256,7 @@ export class TaskDefinition extends Resource { if (isFargateCompatible(this.compatibility)) { throw new Error('Cannot set placement constraints on tasks that run on Fargate'); } - const pc = this.renderPlacementConstraint(constraint); - this.placementConstraints.push(pc); + this.placementConstraints.push(...constraint._json); } /** @@ -312,16 +313,6 @@ export class TaskDefinition extends Resource { } return ret; } - - /** - * Render the placement constraints - */ - private renderPlacementConstraint(pc: PlacementConstraint): CfnTaskDefinition.TaskDefinitionPlacementConstraintProperty { - return { - type: pc.type, - expression: pc.expression - }; - } } /** @@ -426,36 +417,6 @@ export enum Scope { Shared = "shared" } -/** - * A constraint on how instances should be placed - */ -export interface PlacementConstraint { - /** - * The type of constraint - */ - readonly type: PlacementConstraintType; - - /** - * Additional information for the constraint - */ - readonly expression?: string; -} - -/** - * A placement constraint type - */ -export enum PlacementConstraintType { - /** - * Place each task on a different instance - */ - DistinctInstance = "distinctInstance", - - /** - * Place tasks only on instances matching the expression in 'expression' - */ - MemberOf = "memberOf" -} - /** * Task compatibility */ diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index a463596444d8f..a9720f37b6cff 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -5,6 +5,7 @@ import cdk = require('@aws-cdk/cdk'); import { BaseService, BaseServiceProps } from '../base/base-service'; import { NetworkMode, TaskDefinition } from '../base/task-definition'; import { CfnService } from '../ecs.generated'; +import { BinPackResource, PlacementConstraint, PlacementStrategy } from '../placement'; /** * Properties to define an ECS service @@ -34,11 +35,18 @@ export interface Ec2ServiceProps extends BaseServiceProps { readonly securityGroup?: ec2.ISecurityGroup; /** - * Whether to start tasks on distinct instances + * Placement constraints * - * @default true + * @default No constraints */ - readonly placeOnDistinctInstances?: boolean; + readonly placementConstraints?: PlacementConstraint[]; + + /** + * Placement strategies + * + * @default No strategies + */ + readonly placementStrategies?: PlacementStrategy[]; /** * Deploy exactly one task on each instance in your cluster. @@ -92,8 +100,8 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { cluster: props.cluster.clusterName, taskDefinition: props.taskDefinition.taskDefinitionArn, launchType: 'EC2', - placementConstraints: new cdk.Token(() => this.constraints), - placementStrategies: new cdk.Token(() => this.strategies), + placementConstraints: new cdk.Token(() => this.constraints.length > 0 ? this.constraints : undefined), + placementStrategies: new cdk.Token(() => this.strategies.length > 0 ? this.strategies : undefined), schedulingStrategy: props.daemon ? 'DAEMON' : 'REPLICA', }, props.cluster.clusterName, props.taskDefinition); @@ -110,9 +118,8 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { this.connections.addSecurityGroup(...props.cluster.connections.securityGroups); } - if (props.placeOnDistinctInstances !== false) { - this.constraints.push({ type: 'distinctInstance' }); - } + this.addPlacementConstraints(...props.placementConstraints || []); + this.addPlacementStrategies(...props.placementStrategies || []); if (!this.taskDefinition.defaultContainer) { throw new Error('A TaskDefinition must have at least one essential container'); @@ -126,11 +133,10 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { * be placed on instances matching all expressions. * * @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-query-language.html + * @deprecated Use addPlacementConstraints() instead. */ public placeOnMemberOf(...expressions: string[]) { - for (const expression of expressions) { - this.constraints.push({ type: 'memberOf', expression }); - } + this.addPlacementConstraints(PlacementConstraint.memberOf(...expressions)); } /** @@ -141,17 +147,13 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { * is supplied, spreading is done in order. * * @default attributes instanceId + * @deprecated Use addPlacementStrategies() instead. */ public placeSpreadAcross(...fields: string[]) { - if (this.daemon) { - throw new Error("Can't configure spreading placement for a service with daemon=true"); - } - if (fields.length === 0) { - fields = [BuiltInAttributes.InstanceId]; - } - for (const field of fields) { - this.strategies.push({ type: 'spread', field }); + this.addPlacementStrategies(PlacementStrategy.spreadAcrossInstances()); + } else { + this.addPlacementStrategies(PlacementStrategy.spreadAcross(...fields)); } } @@ -159,24 +161,42 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { * Try to place tasks on instances with the least amount of indicated resource available * * This ensures the total consumption of this resource is lowest. + * + * @deprecated Use addPlacementStrategies() instead. */ public placePackedBy(resource: BinPackResource) { - if (this.daemon) { - throw new Error("Can't configure packing placement for a service with daemon=true"); - } - - this.strategies.push({ type: 'binpack', field: resource }); + this.addPlacementStrategies(PlacementStrategy.packedBy(resource)); } /** * Place tasks randomly across the available instances. + * + * @deprecated Use addPlacementStrategies() instead. */ public placeRandomly() { - if (this.daemon) { - throw new Error("Can't configure random placement for a service with daemon=true"); + this.addPlacementStrategies(PlacementStrategy.randomly()); + } + + /** + * Add one or more placement strategies + */ + public addPlacementStrategies(...strategies: PlacementStrategy[]) { + if (strategies.length > 0 && this.daemon) { + throw new Error("Can't configure placement strategies when daemon=true"); } - this.strategies.push({ type: 'random' }); + for (const strategy of strategies) { + this.strategies.push(...strategy._json); + } + } + + /** + * Add one or more placement strategies + */ + public addPlacementConstraints(...constraints: PlacementConstraint[]) { + for (const constraint of constraints) { + this.constraints.push(...constraint._json); + } } /** @@ -280,19 +300,4 @@ export class BuiltInAttributes { * Either 'linux' or 'windows'. */ public static readonly OsType = 'attribute:ecs.os-type'; -} - -/** - * Instance resource used for bin packing - */ -export enum BinPackResource { - /** - * Fill up hosts' CPU allocations first - */ - Cpu = 'cpu', - - /** - * Fill up hosts' memory allocations first - */ - Memory = 'memory', -} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts index fac05588e1e71..75a4c62583d07 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts @@ -1,5 +1,6 @@ import cdk = require('@aws-cdk/cdk'); -import { CommonTaskDefinitionProps, Compatibility, NetworkMode, PlacementConstraint, TaskDefinition } from '../base/task-definition'; +import { CommonTaskDefinitionProps, Compatibility, NetworkMode, TaskDefinition } from '../base/task-definition'; +import { PlacementConstraint } from '../placement'; /** * Properties to define an ECS task definition diff --git a/packages/@aws-cdk/aws-ecs/lib/index.ts b/packages/@aws-cdk/aws-ecs/lib/index.ts index 2e575685d7bff..a2a19ba7712ed 100644 --- a/packages/@aws-cdk/aws-ecs/lib/index.ts +++ b/packages/@aws-cdk/aws-ecs/lib/index.ts @@ -5,6 +5,7 @@ export * from './base/task-definition'; export * from './container-definition'; export * from './container-image'; export * from './cluster'; +export * from './placement'; export * from './ec2/ec2-service'; export * from './ec2/ec2-task-definition'; diff --git a/packages/@aws-cdk/aws-ecs/lib/placement.ts b/packages/@aws-cdk/aws-ecs/lib/placement.ts new file mode 100644 index 0000000000000..5d29ac92790e4 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/placement.ts @@ -0,0 +1,127 @@ +import { BuiltInAttributes } from "./ec2/ec2-service"; +import { CfnService } from "./ecs.generated"; + +/** + * Instance resource used for bin packing + */ +export enum BinPackResource { + /** + * Fill up hosts' CPU allocations first + */ + Cpu = 'cpu', + + /** + * Fill up hosts' memory allocations first + */ + Memory = 'memory', +} + +/** + * An ECS placement strategy + * + * Tasks will preferentially be placed on instances that match these rules. + */ +export class PlacementStrategy { + /** + * Try to place tasks spread across instances + */ + public static spreadAcrossInstances() { + return new PlacementStrategy([{ type: 'spread', field: BuiltInAttributes.InstanceId }]); + } + + /** + * Try to place tasks spread across instances based on given attributes + * + * You can use one of the built-in attributes found on `BuiltInAttributes` + * or supply your own custom instance attributes. If more than one attribute + * is supplied, spreading is done in order. + * + * @default attributes instanceId + */ + public static spreadAcross(...fields: string[]) { + if (fields.length === 0) { + throw new Error('spreadAcross: give at least one field to spread by'); + } + return new PlacementStrategy(fields.map(field => ({ type: 'spread', field }))); + } + + /** + * Try to place tasks on instances with the least amount of CPU + * + * This ensures the total consumption of CPU is lowest + */ + public static packedByCpu() { + return PlacementStrategy.packedBy(BinPackResource.Cpu); + } + + /** + * Try to place tasks on instances with the least amount of memory + * + * This ensures the total consumption of memory is lowest + */ + public static packedByMemory() { + return PlacementStrategy.packedBy(BinPackResource.Memory); + } + + /** + * Try to place tasks on instances with the least amount of indicated resource available + * + * This ensures the total consumption of this resource is lowest. + */ + public static packedBy(resource: BinPackResource) { + return new PlacementStrategy([{ type: 'binpack', field: resource }]); + } + + /** + * Place tasks randomly across the available instances. + */ + public static randomly() { + return new PlacementStrategy([{ type: 'random' }]); + } + + private constructor(private readonly json: CfnService.PlacementStrategyProperty[]) { + } + + /** + * @internal + */ + public get _json() { + return this.json; + } +} + +/** + * An ECS placement constraint + * + * Tasks will only be placed on instances that match these rules. + */ +export class PlacementConstraint { + /** + * Place every task on a different instance + */ + public static distinctInstances() { + return new PlacementConstraint([{ type: 'distinctInstance' }]); + } + + /** + * Place tasks only on instances matching the given query expression + * + * You can specify multiple expressions in one call. The tasks will only + * be placed on instances matching all expressions. + * + * @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-query-language.html + */ + public static memberOf(...expressions: string[]) { + return new PlacementConstraint(expressions.map(expression => ({ type: 'memberOf', expression }))); + } + + private constructor(private readonly json: CfnService.PlacementConstraintProperty[]) { + } + + /** + * @internal + */ + public get _json() { + return this.json; + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json index 502085b90f7ce..ed575becc83d6 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.event-task.lit.expected.json @@ -738,7 +738,6 @@ }, "Family": "awsecsintegecsTaskDef8DD0C801", "NetworkMode": "bridge", - "PlacementConstraints": [], "RequiresCompatibilities": [ "EC2" ], diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json index 9d3fe97380080..cbd2eb50eed2d 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-awsvpc-nw.expected.json @@ -781,7 +781,6 @@ ], "Family": "awsecsintegTaskDef6FDFB69A", "NetworkMode": "awsvpc", - "PlacementConstraints": [], "RequiresCompatibilities": [ "EC2" ], @@ -839,12 +838,6 @@ ] } }, - "PlacementConstraints": [ - { - "Type": "distinctInstance" - } - ], - "PlacementStrategies": [], "SchedulingStrategy": "REPLICA", "ServiceRegistries": [] }, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json index ab306e2109a66..e3b11aa4727f9 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.lb-bridge-nw.expected.json @@ -803,7 +803,6 @@ ], "Family": "awsecsintegecsTaskDef8DD0C801", "NetworkMode": "bridge", - "PlacementConstraints": [], "RequiresCompatibilities": [ "EC2" ], @@ -840,12 +839,6 @@ } } ], - "PlacementConstraints": [ - { - "Type": "distinctInstance" - } - ], - "PlacementStrategies": [], "SchedulingStrategy": "REPLICA", "ServiceRegistries": [] }, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json index 770f6c475d76a..e6cc62dfd2fac 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-awsvpc-nw.expected.json @@ -791,7 +791,6 @@ ], "Family": "awsecsintegecsTaskDef8DD0C801", "NetworkMode": "awsvpc", - "PlacementConstraints": [], "RequiresCompatibilities": [ "EC2" ], @@ -841,8 +840,6 @@ ] } }, - "PlacementConstraints": [], - "PlacementStrategies": [], "SchedulingStrategy": "REPLICA", "ServiceRegistries": [ { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json index ee4a100f8cf81..6d9188792f1f3 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.sd-bridge-nw.expected.json @@ -791,7 +791,6 @@ ], "Family": "awsecsintegecsfrontendTD16AB905D", "NetworkMode": "bridge", - "PlacementConstraints": [], "RequiresCompatibilities": [ "EC2" ], @@ -820,8 +819,6 @@ "DesiredCount": 1, "LaunchType": "EC2", "LoadBalancers": [], - "PlacementConstraints": [], - "PlacementStrategies": [], "SchedulingStrategy": "REPLICA", "ServiceRegistries": [ { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts index 3e81ea33c5b5d..179f22c7341ee 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts @@ -42,8 +42,6 @@ export = { DesiredCount: 1, LaunchType: "EC2", LoadBalancers: [], - PlacementConstraints: [], - PlacementStrategies: [], SchedulingStrategy: "REPLICA" })); @@ -329,7 +327,7 @@ export = { new ecs.Ec2Service(stack, "Ec2Service", { cluster, taskDefinition, - placeOnDistinctInstances: true + placementConstraints: [ecs.PlacementConstraint.distinctInstances()] }); // THEN 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 0ba65423fac86..d16d915e7adb3 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 @@ -16,7 +16,6 @@ export = { expect(stack).to(haveResource("AWS::ECS::TaskDefinition", { Family: "Ec2TaskDef", ContainerDefinitions: [], - PlacementConstraints: [], Volumes: [], NetworkMode: ecs.NetworkMode.Bridge, RequiresCompatibilities: ["EC2"] @@ -184,10 +183,9 @@ export = { // GIVEN const stack = new cdk.Stack(); const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', { - placementConstraints: [{ - expression: "attribute:ecs.instance-type =~ t2.*", - type: ecs.PlacementConstraintType.MemberOf - }] + placementConstraints: [ + ecs.PlacementConstraint.memberOf("attribute:ecs.instance-type =~ t2.*"), + ] }); taskDefinition.addContainer("web", { diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts index e7c28f0d52f39..e61ab66d88ed8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts @@ -1,6 +1,5 @@ import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); -import cdk = require('@aws-cdk/cdk'); import { CommonEcsRunTaskProps, EcsRunTaskBase } from './run-ecs-task-base'; /** @@ -26,20 +25,24 @@ export interface RunEcsEc2TaskProps extends CommonEcsRunTaskProps { readonly securityGroup?: ec2.ISecurityGroup; /** - * Whether to start services on distinct instances + * Placement constraints * - * @default false + * @default No constraints */ - readonly placeOnDistinctInstances?: boolean; + readonly placementConstraints?: ecs.PlacementConstraint[]; + + /** + * Placement strategies + * + * @default No strategies + */ + readonly placementStrategies?: ecs.PlacementStrategy[]; } /** * Run an ECS/EC2 Task in a StepFunctions workflow */ export class RunEcsEc2Task extends EcsRunTaskBase { - private readonly constraints: any[]; - private readonly strategies: any[]; - constructor(props: RunEcsEc2TaskProps) { if (!props.taskDefinition.isEc2Compatible) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); @@ -57,14 +60,11 @@ export class RunEcsEc2Task extends EcsRunTaskBase { ...props, parameters: { LaunchType: 'EC2', - PlacementConstraints: new cdk.Token(() => this.constraints.length > 0 ? this.constraints : undefined), - PlacementStrategy: new cdk.Token(() => this.constraints.length > 0 ? this.strategies : undefined), + PlacementConstraints: mapArray(props.placementConstraints, c => c._json), + PlacementStrategy: mapArray(props.placementStrategies, c => c._json), } }); - this.constraints = []; - this.strategies = []; - if (props.taskDefinition.networkMode === ecs.NetworkMode.AwsVpc) { this.configureAwsVpcNetworking(props.cluster.vpc, false, props.subnets, props.securityGroup); } else { @@ -72,65 +72,6 @@ export class RunEcsEc2Task extends EcsRunTaskBase { validateNoNetworkingProps(props); this.connections.addSecurityGroup(...props.cluster.connections.securityGroups); } - - // False for now because I'm getting the error - // StateMachine (StateMachine2E01A3A5) Invalid State Machine Definition: - // 'SCHEMA_VALIDATION_FAILED: The value for 'PlacementConstraintType' must - // be one of the values: [distinctInstance, memberOf] but was - // 'distinctInstance' at /States/RunEc2/Parameters' (Service: - // AWSStepFunctions; Status Code: 400; Error Code: InvalidDefinition; - // Request ID: ad672a90-2558-11e9-ae36-d99a98e3de72) - if (props.placeOnDistinctInstances) { - this.constraints.push({ Type: 'distinctInstance' }); - } - } - - /** - * Place task only on instances matching the given query expression - * - * You can specify multiple expressions in one call. The tasks will only - * be placed on instances matching all expressions. - * - * @see https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-query-language.html - */ - public placeOnMemberOf(...expressions: string[]) { - for (const expression of expressions) { - this.constraints.push({ Type: 'memberOf', expression }); - } - } - - /** - * Try to place tasks spread across instance attributes. - * - * You can use one of the built-in attributes found on `BuiltInAttributes` - * or supply your own custom instance attributes. If more than one attribute - * is supplied, spreading is done in order. - * - * @default attributes instanceId - */ - public placeSpreadAcross(...fields: string[]) { - if (fields.length === 0) { - fields = [ecs.BuiltInAttributes.InstanceId]; - } - for (const field of fields) { - this.strategies.push({ Type: 'spread', Field: field }); - } - } - - /** - * Try to place tasks on instances with the least amount of indicated resource available - * - * This ensures the total consumption of this resource is lowest. - */ - public placePackedBy(resource: ecs.BinPackResource) { - this.strategies.push({ Type: 'binpack', Field: resource }); - } - - /** - * Place tasks randomly across the available instances. - */ - public placeRandomly() { - this.strategies.push({ Type: 'random' }); } } @@ -141,4 +82,9 @@ function validateNoNetworkingProps(props: RunEcsEc2TaskProps) { if (props.subnets !== undefined || props.securityGroup !== undefined) { throw new Error('vpcPlacement and securityGroup can only be used in AwsVpc networking mode'); } +} + +function mapArray(xs: A[] | undefined, fn: (x: A) => B): B[] | undefined { + if (xs === undefined || xs.length === 0) { return undefined; } + return xs.map(fn); } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts index c18f1764f6ba8..178e11e81cc7a 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts @@ -221,11 +221,13 @@ test('Running an EC2 Task with placement strategies', () => { const ec2Task = new tasks.RunEcsEc2Task({ cluster, taskDefinition, + placementStrategies: [ + ecs.PlacementStrategy.spreadAcross(), + ecs.PlacementStrategy.packedByCpu(), + ecs.PlacementStrategy.randomly(), + ], + placementConstraints: [ecs.PlacementConstraint.memberOf('blieptuut')], }); - ec2Task.placeSpreadAcross(); - ec2Task.placePackedBy(ecs.BinPackResource.Cpu); - ec2Task.placeRandomly(); - ec2Task.placeOnMemberOf('blieptuut'); // WHEN const runTask = new sfn.Task(stack, 'Run', { task: ec2Task }); From f2a78a5c7148bbff1a2403368003b8a0752b0c25 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 May 2019 14:24:17 +0200 Subject: [PATCH 20/21] Fix casing in stepfunctions task --- .../aws-ecs/lib/base/task-definition.ts | 2 +- .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 4 ++-- packages/@aws-cdk/aws-ecs/lib/placement.ts | 8 +++---- .../lib/run-ecs-ec2-task.ts | 22 ++++++++++++++----- .../test/ecs-tasks.test.ts | 4 ++-- .../test/integ.ec2-task.expected.json | 1 - 6 files changed, 26 insertions(+), 15 deletions(-) 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 a8b33d977fd4d..719da8487ce6f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -256,7 +256,7 @@ export class TaskDefinition extends Resource { if (isFargateCompatible(this.compatibility)) { throw new Error('Cannot set placement constraints on tasks that run on Fargate'); } - this.placementConstraints.push(...constraint._json); + this.placementConstraints.push(...constraint.toJson()); } /** diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index a9720f37b6cff..d4e9cf33d8b6f 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -186,7 +186,7 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { } for (const strategy of strategies) { - this.strategies.push(...strategy._json); + this.strategies.push(...strategy.toJson()); } } @@ -195,7 +195,7 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { */ public addPlacementConstraints(...constraints: PlacementConstraint[]) { for (const constraint of constraints) { - this.constraints.push(...constraint._json); + this.constraints.push(...constraint.toJson()); } } diff --git a/packages/@aws-cdk/aws-ecs/lib/placement.ts b/packages/@aws-cdk/aws-ecs/lib/placement.ts index 5d29ac92790e4..cacc8530de8fc 100644 --- a/packages/@aws-cdk/aws-ecs/lib/placement.ts +++ b/packages/@aws-cdk/aws-ecs/lib/placement.ts @@ -83,9 +83,9 @@ export class PlacementStrategy { } /** - * @internal + * Return the placement JSON */ - public get _json() { + public toJson(): CfnService.PlacementStrategyProperty[] { return this.json; } } @@ -119,9 +119,9 @@ export class PlacementConstraint { } /** - * @internal + * Return the placement JSON */ - public get _json() { + public toJson(): CfnService.PlacementConstraintProperty[] { return this.json; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts index e61ab66d88ed8..0c07cfc636601 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/run-ecs-ec2-task.ts @@ -60,8 +60,8 @@ export class RunEcsEc2Task extends EcsRunTaskBase { ...props, parameters: { LaunchType: 'EC2', - PlacementConstraints: mapArray(props.placementConstraints, c => c._json), - PlacementStrategy: mapArray(props.placementStrategies, c => c._json), + PlacementConstraints: noEmpty(flatten((props.placementConstraints || []).map(c => c.toJson().map(uppercaseKeys)))), + PlacementStrategy: noEmpty(flatten((props.placementStrategies || []).map(c => c.toJson().map(uppercaseKeys)))), } }); @@ -84,7 +84,19 @@ function validateNoNetworkingProps(props: RunEcsEc2TaskProps) { } } -function mapArray(xs: A[] | undefined, fn: (x: A) => B): B[] | undefined { - if (xs === undefined || xs.length === 0) { return undefined; } - return xs.map(fn); +function uppercaseKeys(obj: {[key: string]: any}): {[key: string]: any} { + const ret: {[key: string]: any} = {}; + for (const key of Object.keys(obj)) { + ret[key.slice(0, 1).toUpperCase() + key.slice(1)] = obj[key]; + } + return ret; +} + +function flatten(xs: A[][]): A[] { + return Array.prototype.concat([], ...xs); +} + +function noEmpty(xs: A[]): A[] | undefined { + if (xs.length === 0) { return undefined; } + return xs; } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts index 178e11e81cc7a..4f02c420913e8 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/ecs-tasks.test.ts @@ -222,7 +222,7 @@ test('Running an EC2 Task with placement strategies', () => { cluster, taskDefinition, placementStrategies: [ - ecs.PlacementStrategy.spreadAcross(), + ecs.PlacementStrategy.spreadAcrossInstances(), ecs.PlacementStrategy.packedByCpu(), ecs.PlacementStrategy.randomly(), ], @@ -244,7 +244,7 @@ test('Running an EC2 Task with placement strategies', () => { LaunchType: "EC2", TaskDefinition: {Ref: "TD49C78F36"}, PlacementConstraints: [ - { Type: "memberOf", expression: "blieptuut", }, + { Type: "memberOf", Expression: "blieptuut", }, ], PlacementStrategy: [ { Field: "instanceId", Type: "spread", }, diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json index d9dc5f70fe9d1..f0b73d35299ca 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/integ.ec2-task.expected.json @@ -546,7 +546,6 @@ }, "Family": "awsecsinteg2TaskDef1F38909D", "NetworkMode": "bridge", - "PlacementConstraints": [], "RequiresCompatibilities": [ "EC2" ], From 755ef40f9936b75eaed73deb393a59bd0d2a2786 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Wed, 8 May 2019 14:53:31 +0200 Subject: [PATCH 21/21] Remove peerDependency --- packages/@aws-cdk/aws-ecs/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 7874667b8914c..b2ee60994b16e 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -108,7 +108,6 @@ "@aws-cdk/aws-secretsmanager": "^0.31.0", "@aws-cdk/aws-servicediscovery": "^0.31.0", "@aws-cdk/aws-sns": "^0.31.0", - "@aws-cdk/aws-stepfunctions": "^0.31.0", "@aws-cdk/cdk": "^0.31.0", "@aws-cdk/cx-api": "^0.31.0" },