From 1ff8a42d7ef17cf30efcdcf3a72215a70b53f34d Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 10:38:42 +0300 Subject: [PATCH 01/11] refactor: use interface references in public api (awslint:ref-via-interface) Adds a new awslint:ref-via-interface rule which validates that all input APIs (e.g. props, method arguments) use construct interface (IBucket) and not concrete classes (Bucket). This is in order to enable passing in unowned resources and in accordance with the aws construct library guidelines. There are situations where an owned resource is required. In those, the rule can be disabled by adding [disable-awslint:ref-via-interface] to the element's inline documentation. To enable this, the following new construct interfaces were added, along with `fromXxx` import methods: * `applicationautoscaling.IScalableTarget` * `cloudwatch.IAlarm` * `ecs.IService` * `ecs.IEc2Service` * `ec2.IFargateService` * `ecs.ITaskDefinition` * `iam.IGroup` * `iam.IUser` * `iam.IPolicy` * `lambda.IVersion` BREAKING CHANGE: `apigateway.ResourceBase.trackChild` is now internal. * `cloudfront.S3OriginConfig.originAccessIdentity` is now `originAccessIdentityId` * `codedeploy.LambdaDeploymentGroup.alarms` is now `cloudwatch.IAlarm[]` (previously `cloudwatch.Alarm[]`) * `codepipeline.crossRegionScaffoldingStacks` renamed to `crossRegionScaffolding` * `codepipeline.CrossRegionScaffoldingStack` renamed to `codepipeline.CrossRegionScaffolding` and cannot be instantiated (abstract) * `ec2.VpcSubnet.addDefaultRouteToNAT` renamed to `addDefaultNatRoute` and made public * `ec2.VpcSubnet.addDefaultRouteToIGW` renamed to `addDefaultInternetRoute`, made public and first argument is the gateway ID (string) and not the CFN L1 class * `ecs.Ec2EventRuleTarget.taskDefinition` is now `ITaskDefinition` (previously `TaskDefinition`) * `lambda.IEventSource.bind` now accepts `IFunction` instead of `FunctionBase`. Use `IFunction.addEventSourceMapping` to add an event source mapping under the function. * `lambda.Layer.grantUsage` renamed to `lambda.layer.addPermission` and returns void * `stepfunctions.StateMachine.role` is now `iam.IRole` (previously `iam.Role`) --- packages/@aws-cdk/assets/lib/asset.ts | 2 +- .../@aws-cdk/aws-apigateway/lib/resource.ts | 7 +- packages/@aws-cdk/aws-apigateway/lib/stage.ts | 2 +- packages/@aws-cdk/aws-apigateway/package.json | 4 +- .../lib/scalable-target.ts | 19 ++- .../lib/step-scaling-action.ts | 4 +- .../lib/step-scaling-policy.ts | 4 +- .../lib/target-tracking-scaling-policy.ts | 4 +- .../aws-cloudfront/lib/web_distribution.ts | 12 +- packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts | 25 ++- .../lib/lambda/deployment-group.ts | 8 +- .../lib/server/deployment-group.ts | 24 +-- packages/@aws-cdk/aws-codedeploy/lib/utils.ts | 4 +- .../test/test.pipeline.ts | 6 +- .../lib/cross-region-scaffold-stack.ts | 8 +- .../@aws-cdk/aws-codepipeline/lib/index.ts | 1 - .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 16 +- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 50 +++--- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 16 +- .../aws-ecs/lib/base/task-definition.ts | 62 ++++++-- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 3 + .../aws-ecs/lib/container-definition.ts | 2 + .../aws-ecs/lib/ec2/ec2-event-rule-target.ts | 12 +- .../@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 28 +++- .../aws-ecs/lib/ec2/ec2-task-definition.ts | 21 ++- .../aws-ecs/lib/fargate/fargate-service.ts | 21 ++- .../lib/fargate/fargate-task-definition.ts | 22 ++- .../load-balanced-fargate-service-applet.ts | 2 +- packages/@aws-cdk/aws-ecs/package.json | 7 - packages/@aws-cdk/aws-eks/lib/cluster.ts | 1 + .../lib/load-balancer.ts | 1 + packages/@aws-cdk/aws-iam/lib/group.ts | 109 ++++++++----- .../@aws-cdk/aws-iam/lib/identity-base.ts | 2 +- packages/@aws-cdk/aws-iam/lib/policy.ts | 36 +++-- packages/@aws-cdk/aws-iam/lib/user.ts | 11 +- packages/@aws-cdk/aws-iam/lib/util.ts | 6 +- .../aws-lambda-event-sources/lib/api.ts | 9 +- .../aws-lambda-event-sources/lib/dynamodb.ts | 5 +- .../aws-lambda-event-sources/lib/kinesis.ts | 5 +- .../aws-lambda-event-sources/lib/s3.ts | 2 +- .../aws-lambda-event-sources/lib/sns.ts | 2 +- .../aws-lambda-event-sources/lib/sqs.ts | 5 +- packages/@aws-cdk/aws-lambda/lib/alias.ts | 6 +- .../aws-lambda/lib/event-source-mapping.ts | 14 +- .../@aws-cdk/aws-lambda/lib/event-source.ts | 4 +- .../@aws-cdk/aws-lambda/lib/function-base.ts | 15 ++ .../@aws-cdk/aws-lambda/lib/lambda-version.ts | 39 +++-- packages/@aws-cdk/aws-lambda/lib/layers.ts | 22 +-- packages/@aws-cdk/aws-lambda/package.json | 10 -- .../test/integ.layer-version.lit.ts | 2 +- .../@aws-cdk/aws-lambda/test/test.layers.ts | 4 +- .../aws-logs/lib/cross-account-destination.ts | 2 +- .../@aws-cdk/aws-route53/lib/hosted-zone.ts | 2 +- packages/@aws-cdk/aws-s3/package.json | 9 -- .../aws-stepfunctions/lib/state-machine.ts | 4 +- .../aws-stepfunctions/lib/states/task.ts | 2 + tools/awslint/bin/awslint.ts | 14 +- tools/awslint/lib/linter.ts | 22 ++- tools/awslint/lib/rules/api.ts | 146 ++++++++++++++++++ tools/awslint/lib/rules/cfn-resource.ts | 54 ++++--- tools/awslint/lib/rules/construct.ts | 18 ++- tools/awslint/lib/rules/index.ts | 1 + tools/awslint/lib/rules/resource.ts | 17 +- 63 files changed, 698 insertions(+), 299 deletions(-) create mode 100644 tools/awslint/lib/rules/api.ts diff --git a/packages/@aws-cdk/assets/lib/asset.ts b/packages/@aws-cdk/assets/lib/asset.ts index 5132ca86d47df..f725e857cd84c 100644 --- a/packages/@aws-cdk/assets/lib/asset.ts +++ b/packages/@aws-cdk/assets/lib/asset.ts @@ -158,7 +158,7 @@ export class Asset extends cdk.Construct { * * @see https://github.com/awslabs/aws-cdk/issues/1432 * - * @param resource The CloudFormation resource which is using this asset. + * @param resource The CloudFormation resource which is using this asset [disable-awslint:ref-via-interface] * @param resourceProperty The property name where this asset is referenced * (e.g. "Code" for AWS::Lambda::Function) */ diff --git a/packages/@aws-cdk/aws-apigateway/lib/resource.ts b/packages/@aws-cdk/aws-apigateway/lib/resource.ts index f93edcf74ab89..20a9ab49b97cc 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/resource.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/resource.ts @@ -144,7 +144,10 @@ export abstract class ResourceBase extends ResourceConstruct implements IResourc return this.children[pathPart]; } - public trackChild(pathPart: string, resource: Resource) { + /** + * @internal + */ + public _trackChild(pathPart: string, resource: Resource) { this.children[pathPart] = resource; } @@ -194,7 +197,7 @@ export class Resource extends ResourceBase { this.parentResource = props.parent; if (props.parent instanceof ResourceBase) { - props.parent.trackChild(props.pathPart, this); + props.parent._trackChild(props.pathPart, this); } const resourceProps: CfnResourceProps = { diff --git a/packages/@aws-cdk/aws-apigateway/lib/stage.ts b/packages/@aws-cdk/aws-apigateway/lib/stage.ts index c6cc7e3ac5bf0..8825bb866bbba 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/stage.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/stage.ts @@ -69,7 +69,7 @@ export interface StageOptions extends MethodDeploymentOptions { export interface StageProps extends StageOptions { /** - * The deployment that this stage points to. + * The deployment that this stage points to [disable-awslint:ref-via-interface]. */ readonly deployment: Deployment; } diff --git a/packages/@aws-cdk/aws-apigateway/package.json b/packages/@aws-cdk/aws-apigateway/package.json index 8e31614317dcd..2fd32a7451dae 100644 --- a/packages/@aws-cdk/aws-apigateway/package.json +++ b/packages/@aws-cdk/aws-apigateway/package.json @@ -86,9 +86,7 @@ }, "awslint": { "exclude": [ - "resource-attribute:@aws-cdk/aws-apigateway.IRestApi.restApiRootResourceId", - "from-method:@aws-cdk/aws-apigateway.Resource", - "construct-base-is-private:@aws-cdk/aws-apigateway.ResourceBase" + "from-method:@aws-cdk/aws-apigateway.Resource" ] } } diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts index 12cb04bde5d92..f14695a236043 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/scalable-target.ts @@ -1,9 +1,16 @@ import iam = require('@aws-cdk/aws-iam'); -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource } from '@aws-cdk/cdk'; import { CfnScalableTarget } from './applicationautoscaling.generated'; import { BasicStepScalingPolicyProps, StepScalingPolicy } from './step-scaling-policy'; import { BasicTargetTrackingScalingPolicyProps, TargetTrackingScalingPolicy } from './target-tracking-scaling-policy'; +export interface IScalableTarget extends IResource { + /** + * @attribute + */ + readonly scalableTargetId: string; +} + /** * Properties for a scalable target */ @@ -61,7 +68,15 @@ export interface ScalableTargetProps { /** * Define a scalable target */ -export class ScalableTarget extends Resource { +export class ScalableTarget extends Resource implements IScalableTarget { + + public static fromScalableTargetId(scope: Construct, id: string, scalableTargetId: string): IScalableTarget { + class Import extends Resource implements IScalableTarget { + public readonly scalableTargetId = scalableTargetId; + } + return new Import(scope, id); + } + /** * ID of the Scalable Target * diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts index 5499ddcda4b41..d889522658977 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-action.ts @@ -1,7 +1,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import { CfnScalingPolicy } from './applicationautoscaling.generated'; -import { ScalableTarget } from './scalable-target'; +import { IScalableTarget } from './scalable-target'; /** * Properties for a scaling policy @@ -10,7 +10,7 @@ export interface StepScalingActionProps { /** * The scalable target */ - readonly scalingTarget: ScalableTarget; + readonly scalingTarget: IScalableTarget; /** * A name for the scaling policy diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts index 5cbe98b95e0d2..55180dc345aec 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/step-scaling-policy.ts @@ -1,7 +1,7 @@ import { findAlarmThresholds, normalizeIntervals } from '@aws-cdk/aws-autoscaling-common'; import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); -import { ScalableTarget } from './scalable-target'; +import { IScalableTarget } from './scalable-target'; import { AdjustmentType, MetricAggregationType, StepScalingAction } from './step-scaling-action'; export interface BasicStepScalingPolicyProps { @@ -52,7 +52,7 @@ export interface StepScalingPolicyProps extends BasicStepScalingPolicyProps { /** * The scaling target */ - readonly scalingTarget: ScalableTarget; + readonly scalingTarget: IScalableTarget; } /** diff --git a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts index bea6f15f0d7a9..0026bd6b833fb 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts @@ -1,7 +1,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import cdk = require('@aws-cdk/cdk'); import { CfnScalingPolicy } from './applicationautoscaling.generated'; -import { ScalableTarget } from './scalable-target'; +import { IScalableTarget } from './scalable-target'; /** * Base interface for target tracking props @@ -95,7 +95,7 @@ export interface TargetTrackingScalingPolicyProps extends BasicTargetTrackingSca /* * The scalable target */ - readonly scalingTarget: ScalableTarget; + readonly scalingTarget: IScalableTarget; } export class TargetTrackingScalingPolicy extends cdk.Construct { diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index da76fc22b2611..051c81f7e229f 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -1,7 +1,7 @@ import route53 = require('@aws-cdk/aws-route53'); import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); -import { CfnCloudFrontOriginAccessIdentity, CfnDistribution } from './cloudfront.generated'; +import { CfnDistribution } from './cloudfront.generated'; export enum HttpVersion { HTTP1_1 = "http1.1", @@ -237,12 +237,12 @@ export interface S3OriginConfig { /** * The source bucket to serve content from */ - readonly s3BucketSource: s3.IBucket, + readonly s3BucketSource: s3.IBucket; /** - * The optional origin identity cloudfront will use when calling your s3 bucket. + * The optional ID of the origin identity cloudfront will use when calling your s3 bucket. */ - readonly originAccessIdentity?: CfnCloudFrontOriginAccessIdentity + readonly originAccessIdentityId?: string; } /** @@ -570,8 +570,8 @@ export class CloudFrontWebDistribution extends cdk.Construct implements route53. : originConfig.customOriginSource!.domainName, originPath: originConfig.originPath, originCustomHeaders: originHeaders.length > 0 ? originHeaders : undefined, - s3OriginConfig: originConfig.s3OriginSource && originConfig.s3OriginSource.originAccessIdentity - ? { originAccessIdentity: `origin-access-identity/cloudfront/${originConfig.s3OriginSource.originAccessIdentity.ref}` } + s3OriginConfig: originConfig.s3OriginSource && originConfig.s3OriginSource.originAccessIdentityId + ? { originAccessIdentity: `origin-access-identity/cloudfront/${originConfig.s3OriginSource.originAccessIdentityId}` } : originConfig.s3OriginSource ? { } : undefined, diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index a09e2d2dde5d0..2f5929fc853d3 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -1,9 +1,21 @@ -import { Construct, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; import { CfnAlarm } from './cloudwatch.generated'; import { HorizontalAnnotation } from './graph'; import { Dimension, Metric, MetricAlarmProps, Statistic, Unit } from './metric'; import { parseStatistic } from './util.statistic'; +export interface IAlarm extends IResource { + /** + * @attribute + */ + readonly alarmArn: string; + + /** + * @attribute + */ + readonly alarmName: string; +} + /** * Properties for Alarms */ @@ -62,7 +74,16 @@ export enum TreatMissingData { /** * An alarm on a CloudWatch metric */ -export class Alarm extends Resource { +export class Alarm extends Resource implements IAlarm { + + public static fromAlarmArn(scope: Construct, id: string, alarmArn: string): IAlarm { + class Import extends Resource implements IAlarm { + public readonly alarmArn = alarmArn; + public readonly alarmName = scope.node.stack.parseArn(alarmArn, ':').resourceName!; + } + return new Import(scope, id); + } + /** * ARN of this alarm * diff --git a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts index 05a4535907833..df869a854abcb 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/lambda/deployment-group.ts @@ -71,7 +71,7 @@ export interface LambdaDeploymentGroupProps { * @default [] * @see https://docs.aws.amazon.com/codedeploy/latest/userguide/monitoring-create-alarms.html */ - readonly alarms?: cloudwatch.Alarm[]; + readonly alarms?: cloudwatch.IAlarm[]; /** * The service Role of this Deployment Group. @@ -83,6 +83,8 @@ export interface LambdaDeploymentGroupProps { /** * Lambda Alias to shift traffic. Updating the version * of the alias will trigger a CodeDeploy deployment. + * + * [disable-awslint:ref-via-interface] since we need to modify the alias CFN resource update policy */ readonly alias: lambda.Alias; @@ -134,7 +136,7 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy public readonly deploymentGroupArn: string; public readonly role: iam.IRole; - private readonly alarms: cloudwatch.Alarm[]; + private readonly alarms: cloudwatch.IAlarm[]; private preHook?: lambda.IFunction; private postHook?: lambda.IFunction; @@ -188,7 +190,7 @@ export class LambdaDeploymentGroup extends cdk.Resource implements ILambdaDeploy * * @param alarm the alarm to associate with this Deployment Group */ - public addAlarm(alarm: cloudwatch.Alarm): void { + public addAlarm(alarm: cloudwatch.IAlarm): void { this.alarms.push(alarm); } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index 9250f9d16560f..e90aa98ef4b13 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts @@ -13,7 +13,7 @@ import { IServerDeploymentConfig, ServerDeploymentConfig } from './deployment-co export interface IServerDeploymentGroup extends cdk.IResource { readonly application: IServerApplication; - readonly role?: iam.Role; + readonly role?: iam.IRole; /** * @attribute */ @@ -67,7 +67,7 @@ export interface ServerDeploymentGroupAttributes { */ abstract class ServerDeploymentGroupBase extends cdk.Resource implements IServerDeploymentGroup { public abstract readonly application: IServerApplication; - public abstract readonly role?: iam.Role; + public abstract readonly role?: iam.IRole; public abstract readonly deploymentGroupName: string; public abstract readonly deploymentGroupArn: string; public readonly deploymentConfig: IServerDeploymentConfig; @@ -150,7 +150,7 @@ export interface ServerDeploymentGroupProps { * The service Role of this Deployment Group. * If you don't provide one, a new Role will be created. */ - readonly role?: iam.Role; + readonly role?: iam.IRole; /** * The physical, human-readable name of the CodeDeploy Deployment Group. @@ -169,7 +169,11 @@ export interface ServerDeploymentGroupProps { /** * The auto-scaling groups belonging to this Deployment Group. * - * Auto-scaling groups can also be added after the Deployment Group is created using the {@link #addAutoScalingGroup} method. + * Auto-scaling groups can also be added after the Deployment Group is created + * using the {@link #addAutoScalingGroup} method. + * + * [disable-awslint:ref-via-interface] is needed because we update userdata + * for ASGs to install the codedeploy agent. * * @default [] */ @@ -217,7 +221,7 @@ export interface ServerDeploymentGroupProps { * @default [] * @see https://docs.aws.amazon.com/codedeploy/latest/userguide/monitoring-create-alarms.html */ - readonly alarms?: cloudwatch.Alarm[]; + readonly alarms?: cloudwatch.IAlarm[]; /** * Whether to continue a deployment even if fetching the alarm status from CloudWatch failed. @@ -254,14 +258,14 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { } public readonly application: IServerApplication; - public readonly role?: iam.Role; + public readonly role?: iam.IRole; public readonly deploymentGroupArn: string; public readonly deploymentGroupName: string; private readonly _autoScalingGroups: autoscaling.AutoScalingGroup[]; private readonly installAgent: boolean; private readonly codeDeployBucket: s3.IBucket; - private readonly alarms: cloudwatch.Alarm[]; + private readonly alarms: cloudwatch.IAlarm[]; constructor(scope: cdk.Construct, id: string, props: ServerDeploymentGroupProps = {}) { super(scope, id, props.deploymentConfig); @@ -321,7 +325,9 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { /** * Adds an additional auto-scaling group to this Deployment Group. * - * @param asg the auto-scaling group to add to this Deployment Group + * @param asg the auto-scaling group to add to this Deployment Group. + * [disable-awslint:ref-via-interface] is needed in order to install the code + * deploy agent by updating the ASGs user data. */ public addAutoScalingGroup(asg: autoscaling.AutoScalingGroup): void { this._autoScalingGroups.push(asg); @@ -333,7 +339,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupBase { * * @param alarm the alarm to associate with this Deployment Group */ - public addAlarm(alarm: cloudwatch.Alarm): void { + public addAlarm(alarm: cloudwatch.IAlarm): void { this.alarms.push(alarm); } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/utils.ts b/packages/@aws-cdk/aws-codedeploy/lib/utils.ts index 9eca2c51327ff..717b3cfd1b679 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/utils.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/utils.ts @@ -15,7 +15,7 @@ export function arnForDeploymentConfig(name: string): string { return `arn:${Aws.partition}:codedeploy:${Aws.region}:${Aws.accountId}:deploymentconfig:${name}`; } -export function renderAlarmConfiguration(alarms: cloudwatch.Alarm[], ignorePollAlarmFailure?: boolean): +export function renderAlarmConfiguration(alarms: cloudwatch.IAlarm[], ignorePollAlarmFailure?: boolean): CfnDeploymentGroup.AlarmConfigurationProperty | undefined { return alarms.length === 0 ? undefined @@ -32,7 +32,7 @@ enum AutoRollbackEvent { DeploymentStopOnRequest = 'DEPLOYMENT_STOP_ON_REQUEST' } -export function renderAutoRollbackConfiguration(alarms: cloudwatch.Alarm[], autoRollbackConfig: AutoRollbackConfig = {}): +export function renderAutoRollbackConfiguration(alarms: cloudwatch.IAlarm[], autoRollbackConfig: AutoRollbackConfig = {}): CfnDeploymentGroup.AutoRollbackConfigurationProperty | undefined { const events = new Array(); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts index 85d7eab484dd1..1bba1fd738244 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/test.pipeline.ts @@ -692,10 +692,10 @@ export = { ] })); - test.equal(pipeline.crossRegionScaffoldStacks[pipelineRegion], undefined); - test.equal(pipeline.crossRegionScaffoldStacks['us-west-1'], undefined); + test.equal(pipeline.crossRegionScaffolding[pipelineRegion], undefined); + test.equal(pipeline.crossRegionScaffolding['us-west-1'], undefined); - const usEast1ScaffoldStack = pipeline.crossRegionScaffoldStacks['us-east-1']; + const usEast1ScaffoldStack = pipeline.crossRegionScaffolding['us-east-1']; test.notEqual(usEast1ScaffoldStack, undefined); test.equal(usEast1ScaffoldStack.env.region, 'us-east-1'); test.equal(usEast1ScaffoldStack.env.account, pipelineAccount); diff --git a/packages/@aws-cdk/aws-codepipeline/lib/cross-region-scaffold-stack.ts b/packages/@aws-cdk/aws-codepipeline/lib/cross-region-scaffold-stack.ts index 7b7ffdf084d81..a8fcddd49e791 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/cross-region-scaffold-stack.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/cross-region-scaffold-stack.ts @@ -1,6 +1,7 @@ import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import crypto = require('crypto'); +import { CrossRegionScaffolding } from './pipeline'; /** * Construction properties for {@link CrossRegionScaffoldStack}. @@ -22,14 +23,15 @@ export interface CrossRegionScaffoldStackProps { /** * A Stack containing resources required for the cross-region CodePipeline functionality to work. */ -export class CrossRegionScaffoldStack extends cdk.Stack { +export class CrossRegionScaffoldStack extends CrossRegionScaffolding { /** * The name of the S3 Bucket used for replicating the Pipeline's artifacts into the region. */ public readonly replicationBucketName: string; - constructor(scope: cdk.App, props: CrossRegionScaffoldStackProps) { - super(scope, generateStackName(props), { + constructor(scope: cdk.Construct, id: string, props: CrossRegionScaffoldStackProps) { + super(scope, id, { + stackName: generateStackName(props), env: { region: props.region, account: props.account, diff --git a/packages/@aws-cdk/aws-codepipeline/lib/index.ts b/packages/@aws-cdk/aws-codepipeline/lib/index.ts index 5976da7d21b5e..c62d8dda6b33f 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/index.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/index.ts @@ -1,6 +1,5 @@ export * from './action'; export * from './artifact'; -export * from './cross-region-scaffold-stack'; export * from './pipeline'; // AWS::CodePipeline CloudFormation Resources: diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 5805597bc5a31..9108a1fd51e35 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -2,7 +2,7 @@ import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import s3 = require('@aws-cdk/aws-s3'); -import { Construct, RemovalPolicy, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/cdk'; import { Action, IPipeline, IStage } from "./action"; import { CfnPipeline } from './codepipeline.generated'; import { CrossRegionScaffoldStack } from './cross-region-scaffold-stack'; @@ -342,7 +342,7 @@ export class Pipeline extends PipelineBase { * Returns all of the {@link CrossRegionScaffoldStack}s that were generated automatically * when dealing with Actions that reside in a different region than the Pipeline itself. */ - public get crossRegionScaffoldStacks(): { [region: string]: CrossRegionScaffoldStack } { + public get crossRegionScaffolding(): { [region: string]: CrossRegionScaffolding } { const ret: { [region: string]: CrossRegionScaffoldStack } = {}; Object.keys(this._crossRegionScaffoldStacks).forEach((key) => { ret[key] = this._crossRegionScaffoldStacks[key]; @@ -400,7 +400,7 @@ export class Pipeline extends PipelineBase { if (!app) { throw new Error(`Pipeline stack which uses cross region actions must be part of an application`); } - const crossRegionScaffoldStack = new CrossRegionScaffoldStack(app, { + const crossRegionScaffoldStack = new CrossRegionScaffoldStack(this, `cross-region-stack-${pipelineAccount}:${region}`, { region, account: pipelineAccount, }); @@ -588,3 +588,13 @@ export class Pipeline extends PipelineBase { return this.stages.map(stage => stage.render()); } } + +/** + * A Stack containing resources required for the cross-region CodePipeline functionality to work. + */ +export abstract class CrossRegionScaffolding extends Stack { + /** + * The name of the S3 Bucket used for replicating the Pipeline's artifacts into the region. + */ + public abstract readonly replicationBucketName: string; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 85fb2955ecc5b..abc5951cd3366 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -790,7 +790,7 @@ export class VpcNetwork extends VpcNetworkBase { }); (this.publicSubnets as VpcPublicSubnet[]).forEach(publicSubnet => { - publicSubnet.addDefaultIGWRouteEntry(igw, att); + publicSubnet.addDefaultInternetRoute(igw.ref, att); }); // if gateways are needed create them @@ -803,7 +803,7 @@ export class VpcNetwork extends VpcNetworkBase { // round robin the available NatGW since one is not in your AZ ngwId = ngwArray[i % ngwArray.length]; } - privateSubnet.addDefaultNatRouteEntry(ngwId); + privateSubnet.addDefaultNatRoute(ngwId); }); } @@ -1114,31 +1114,36 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet { return this.internetDependencies; } - protected addDefaultRouteToNAT(natGatewayId: string) { + /** + * Create a default route that points to a passed IGW, with a dependency + * on the IGW's attachment to the VPC. + * + * @param gatewayId the logical ID (ref) of the gateway attached to your VPC + * @param gatewayAttachment the gateway attachment construct to be added as a dependency + */ + public addDefaultInternetRoute(gatewayId: string, gatewayAttachment: cdk.IDependable) { const route = new CfnRoute(this, `DefaultRoute`, { routeTableId: this.routeTableId!, destinationCidrBlock: '0.0.0.0/0', - natGatewayId + gatewayId }); + route.node.addDependency(gatewayAttachment); + + // Since the 'route' depends on the gateway attachment, just + // depending on the route is enough. this.internetDependencies.add(route); } /** - * Create a default route that points to a passed IGW, with a dependency - * on the IGW's attachment to the VPC. + * Adds an entry to this subnets route table that points to the passed NATGatwayId + * @param natGatewayId The ID of the NAT gateway */ - protected addDefaultRouteToIGW( - gateway: CfnInternetGateway, - gatewayAttachment: CfnVPCGatewayAttachment) { + public addDefaultNatRoute(natGatewayId: string) { const route = new CfnRoute(this, `DefaultRoute`, { routeTableId: this.routeTableId!, destinationCidrBlock: '0.0.0.0/0', - gatewayId: gateway.ref + natGatewayId }); - route.node.addDependency(gatewayAttachment); - - // Since the 'route' depends on the gateway attachment, just - // depending on the route is enough. this.internetDependencies.add(route); } } @@ -1157,16 +1162,6 @@ export class VpcPublicSubnet extends VpcSubnet { super(scope, id, props); } - /** - * Create a default route that points to a passed IGW, with a dependency - * on the IGW's attachment to the VPC. - */ - public addDefaultIGWRouteEntry( - gateway: CfnInternetGateway, - gatewayAttachment: CfnVPCGatewayAttachment) { - this.addDefaultRouteToIGW(gateway, gatewayAttachment); - } - /** * Creates a new managed NAT gateway attached to this public subnet. * Also adds the EIP for the managed NAT. @@ -1196,13 +1191,6 @@ export class VpcPrivateSubnet extends VpcSubnet { constructor(scope: cdk.Construct, id: string, props: VpcPrivateSubnetProps) { super(scope, id, props); } - - /** - * Adds an entry to this subnets route table that points to the passed NATGatwayId - */ - public addDefaultNatRouteEntry(natGatewayId: string) { - this.addDefaultRouteToNAT(natGatewayId); - } } function ifUndefined(value: T | undefined, defaultValue: T): T { 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 19066aaafcea7..1c0f652e9b630 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -4,12 +4,22 @@ import ec2 = require('@aws-cdk/aws-ec2'); import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import iam = require('@aws-cdk/aws-iam'); import cloudmap = require('@aws-cdk/aws-servicediscovery'); +import { IResource, Resource } from '@aws-cdk/cdk'; import cdk = require('@aws-cdk/cdk'); import { NetworkMode, TaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; import { CfnService } from '../ecs.generated'; import { ScalableTaskCount } from './scalable-task-count'; +export interface IService extends IResource { + /** + * ARN of this service + * + * @attribute + */ + readonly serviceArn: string; +} + /** * Basic service properties */ @@ -82,8 +92,8 @@ export interface BaseServiceProps { /** * Base class for Ecs and Fargate services */ -export abstract class BaseService extends cdk.Construct - implements elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget { +export abstract class BaseService extends Resource + implements IService, elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget { /** * Manage allowed network traffic for this service @@ -97,6 +107,8 @@ export abstract class BaseService extends cdk.Construct /** * Name of this service + * + * @attribute */ public readonly serviceName: string; 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 af54b1333b9f2..5399764e8678c 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -1,9 +1,27 @@ import iam = require('@aws-cdk/aws-iam'); -import { Construct, Resource, Token } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; import { ContainerDefinition, ContainerDefinitionOptions } from '../container-definition'; import { CfnTaskDefinition } from '../ecs.generated'; import { isEc2Compatible, isFargateCompatible } from '../util'; +export interface ITaskDefinition extends IResource { + /** + * ARN of this task definition + * @attribute + */ + readonly taskDefinitionArn: string; + + /** + * Execution role for this task definition + */ + readonly executionRole?: iam.IRole; + + /** + * What launch types this task definition should be compatible with. + */ + readonly compatibility: Compatibility; +} + /** * Properties common to all Task definitions */ @@ -98,7 +116,22 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { /** * Base class for Ecs and Fargate task definitions */ -export class TaskDefinition extends Resource { +export class TaskDefinition extends Resource implements ITaskDefinition { + + /** + * Imports a task definition by ARN. + * + * The task will have a compatibility of EC2+Fargate. + */ + public static fromTaskDefinitionArn(scope: Construct, id: string, taskDefinitionArn: string): ITaskDefinition { + class Import extends Resource implements ITaskDefinition { + public readonly taskDefinitionArn = taskDefinitionArn; + public readonly compatibility = Compatibility.Ec2AndFargate; + } + + return new Import(scope, id); + } + /** * The family name of this task definition */ @@ -132,14 +165,7 @@ export class TaskDefinition extends Resource { /** * What launching modes this task is compatible with */ - public compatibility: Compatibility; - - /** - * Execution role for this task definition - * - * May not exist, will be created as needed. - */ - public executionRole?: iam.IRole; + public readonly compatibility: Compatibility; /** * All containers @@ -156,6 +182,8 @@ export class TaskDefinition extends Resource { */ private readonly placementConstraints = new Array(); + private _executionRole?: iam.IRole; + constructor(scope: Construct, id: string, props: TaskDefinitionProps) { super(scope, id); @@ -180,7 +208,7 @@ export class TaskDefinition extends Resource { throw new Error(`Fargate-compatible tasks require both CPU (${props.cpu}) and memory (${props.memoryMiB}) specifications`); } - this.executionRole = props.executionRole; + this._executionRole = props.executionRole; this.taskRole = props.taskRole || new iam.Role(this, 'TaskRole', { assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), @@ -209,6 +237,10 @@ export class TaskDefinition extends Resource { this.taskDefinitionArn = taskDef.taskDefinitionArn; } + public get executionRole(): iam.IRole | undefined { + return this._executionRole; + } + /** * Add a policy statement to the Task Role */ @@ -273,12 +305,12 @@ export class TaskDefinition extends Resource { * Create the execution role if it doesn't exist */ public obtainExecutionRole(): iam.IRole { - if (!this.executionRole) { - this.executionRole = new iam.Role(this, 'ExecutionRole', { + if (!this._executionRole) { + this._executionRole = new iam.Role(this, 'ExecutionRole', { assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), }); } - return this.executionRole; + return this._executionRole; } /** @@ -475,6 +507,8 @@ export enum Compatibility { export interface ITaskDefinitionExtension { /** * Apply the extension to the given TaskDefinition + * + * @param taskDefinition [disable-awslint:ref-via-interface] */ extend(taskDefinition: TaskDefinition): void; } diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 54a0d8d2ecf1b..8d336292b36df 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -131,6 +131,9 @@ export class Cluster extends Resource implements ICluster { /** * Add compute capacity to this ECS cluster in the form of an AutoScalingGroup + * @param autoScalingGroup the ASG to add to this cluster. + * [disable-awslint:ref-via-interface] is needed in order to install the ECS + * agent by updating the ASGs user data. */ public addAutoScalingGroup(autoScalingGroup: autoscaling.AutoScalingGroup, options: AddAutoScalingGroupCapacityOptions = {}) { this._hasEc2Capacity = true; diff --git a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts index 3d1403beb949a..ee75bb0ec668e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/container-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/container-definition.ts @@ -176,6 +176,8 @@ export interface ContainerDefinitionOptions { export interface ContainerDefinitionProps extends ContainerDefinitionOptions { /** * The task this container definition belongs to. + * + * [disable-awslint:ref-via-interface] */ readonly taskDefinition: TaskDefinition; } 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..e65d9974a83de 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 @@ -1,7 +1,7 @@ import events = require ('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import { TaskDefinition } from '../base/task-definition'; +import { ITaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; import { isEc2Compatible } from '../util'; @@ -17,7 +17,7 @@ export interface Ec2EventRuleTargetProps { /** * Task Definition of the task that should be started */ - readonly taskDefinition: TaskDefinition; + readonly taskDefinition: ITaskDefinition; /** * How many tasks should be started when this event is triggered @@ -32,7 +32,7 @@ export interface Ec2EventRuleTargetProps { */ export class Ec2EventRuleTarget extends cdk.Construct implements events.IEventRuleTarget { private readonly cluster: ICluster; - private readonly taskDefinition: TaskDefinition; + private readonly taskDefinition: ITaskDefinition; private readonly taskCount: number; constructor(scope: cdk.Construct, id: string, props: Ec2EventRuleTargetProps) { @@ -76,9 +76,11 @@ export class Ec2EventRuleTarget extends cdk.Construct implements events.IEventRu * they can reuse the same role. */ public get eventsRole(): iam.IRole { - let role = this.taskDefinition.node.tryFindChild('EventsRole') as iam.IRole; + const stack = this.node.stack; + const id = `${this.taskDefinition.node.uniqueId}-EventsRole`; + let role = stack.node.tryFindChild(id) as iam.IRole; if (role === undefined) { - role = new iam.Role(this.taskDefinition, 'EventsRole', { + role = new iam.Role(stack, id, { assumedBy: new iam.ServicePrincipal('events.amazonaws.com') }); } 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 864c84bcf5781..c8623cb0b9422 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -1,8 +1,8 @@ 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 { Construct, Resource, Token } from '@aws-cdk/cdk'; +import { BaseService, BaseServiceProps, IService } from '../base/base-service'; import { NetworkMode, TaskDefinition } from '../base/task-definition'; import { CfnService } from '../ecs.generated'; import { isEc2Compatible } from '../util'; @@ -13,6 +13,8 @@ import { isEc2Compatible } from '../util'; export interface Ec2ServiceProps extends BaseServiceProps { /** * Task Definition used for running tasks in the service + * + * [disable-awslint:ref-via-interface] */ readonly taskDefinition: TaskDefinition; @@ -52,10 +54,24 @@ export interface Ec2ServiceProps extends BaseServiceProps { readonly daemon?: boolean; } +export interface IEc2Service extends IService { + +} + /** * Start a service on an ECS cluster + * + * @resource AWS::ECS::Service */ -export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { +export class Ec2Service extends BaseService implements IEc2Service, elb.ILoadBalancerTarget { + + public static fromEc2ServiceArn(scope: Construct, id: string, ec2ServiceArn: string): IEc2Service { + class Import extends Resource implements IEc2Service { + public readonly serviceArn = ec2ServiceArn; + } + return new Import(scope, id); + } + /** * Name of the cluster */ @@ -65,7 +81,7 @@ export class Ec2Service extends BaseService implements elb.ILoadBalancerTarget { private readonly strategies: CfnService.PlacementStrategyProperty[]; private readonly daemon: boolean; - constructor(scope: cdk.Construct, id: string, props: Ec2ServiceProps) { + constructor(scope: 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.'); } @@ -93,8 +109,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 Token(() => this.constraints), + placementStrategies: new Token(() => this.strategies), schedulingStrategy: props.daemon ? 'DAEMON' : 'REPLICA', }, props.cluster.clusterName, props.taskDefinition); 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..e6fa7b38d38e9 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,5 @@ -import cdk = require('@aws-cdk/cdk'); -import { CommonTaskDefinitionProps, Compatibility, NetworkMode, PlacementConstraint, TaskDefinition } from '../base/task-definition'; +import { Construct, Resource } from '@aws-cdk/cdk'; +import { CommonTaskDefinitionProps, Compatibility, ITaskDefinition, NetworkMode, PlacementConstraint, TaskDefinition } from '../base/task-definition'; /** * Properties to define an ECS task definition @@ -24,13 +24,26 @@ export interface Ec2TaskDefinitionProps extends CommonTaskDefinitionProps { readonly placementConstraints?: PlacementConstraint[]; } +export interface IEc2TaskDefinition extends ITaskDefinition { + +} + /** * Define Tasks to run on an ECS cluster * * @resource AWS::ECS::TaskDefinition */ -export class Ec2TaskDefinition extends TaskDefinition { - constructor(scope: cdk.Construct, id: string, props: Ec2TaskDefinitionProps = {}) { +export class Ec2TaskDefinition extends TaskDefinition implements IEc2TaskDefinition { + + public static fromEc2TaskDefinitionArn(scope: Construct, id: string, ec2TaskDefinitionArn: string): IEc2TaskDefinition { + class Import extends Resource implements IEc2TaskDefinition { + public readonly taskDefinitionArn = ec2TaskDefinitionArn; + public readonly compatibility = Compatibility.Ec2; + } + return new Import(scope, id); + } + + constructor(scope: Construct, id: string, props: Ec2TaskDefinitionProps = {}) { super(scope, id, { ...props, compatibility: Compatibility.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 f50ad650baadc..9da3b89ae772b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -1,6 +1,7 @@ import ec2 = require('@aws-cdk/aws-ec2'); import cdk = require('@aws-cdk/cdk'); -import { BaseService, BaseServiceProps } from '../base/base-service'; +import { Construct, Resource } from '@aws-cdk/cdk'; +import { BaseService, BaseServiceProps, IService } from '../base/base-service'; import { TaskDefinition } from '../base/task-definition'; import { isFargateCompatible } from '../util'; @@ -10,6 +11,8 @@ import { isFargateCompatible } from '../util'; export interface FargateServiceProps extends BaseServiceProps { /** * Task Definition used for running tasks in the service + * + * [disable-awslint:ref-via-interface] */ readonly taskDefinition: TaskDefinition; @@ -45,10 +48,24 @@ export interface FargateServiceProps extends BaseServiceProps { readonly platformVersion?: FargatePlatformVersion; } +export interface IFargateService extends IService { + +} + /** * Start a service on an ECS cluster + * + * @resource AWS::ECS::Service */ -export class FargateService extends BaseService { +export class FargateService extends BaseService implements IFargateService { + + public static fromFargateServiceArn(scope: Construct, id: string, fargateServiceArn: string): IFargateService { + class Import extends Resource implements IFargateService { + public readonly serviceArn = fargateServiceArn; + } + return new Import(scope, id); + } + constructor(scope: cdk.Construct, id: string, props: FargateServiceProps) { if (!isFargateCompatible(props.taskDefinition.compatibility)) { throw new Error('Supplied TaskDefinition is not configured for compatibility with Fargate'); diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts index 49731e6d26ec5..212102b660500 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts @@ -1,5 +1,5 @@ -import cdk = require('@aws-cdk/cdk'); -import { CommonTaskDefinitionProps, Compatibility, NetworkMode, TaskDefinition } from '../base/task-definition'; +import { Construct, Resource } from '@aws-cdk/cdk'; +import { CommonTaskDefinitionProps, Compatibility, ITaskDefinition, NetworkMode, TaskDefinition } from '../base/task-definition'; /** * Properties to define a Fargate Task @@ -39,11 +39,25 @@ export interface FargateTaskDefinitionProps extends CommonTaskDefinitionProps { readonly memoryMiB?: string; } +export interface IFargateTaskDefinition extends ITaskDefinition { + +} + /** * A definition for Tasks on a Fargate cluster * @resource AWS::ECS::TaskDefinition */ -export class FargateTaskDefinition extends TaskDefinition { +export class FargateTaskDefinition extends TaskDefinition implements IFargateTaskDefinition { + + public static fromFargateTaskDefinitionArn(scope: Construct, id: string, fargateTaskDefinitionArn: string): IFargateTaskDefinition { + class Import extends Resource implements IFargateTaskDefinition { + public readonly taskDefinitionArn = fargateTaskDefinitionArn; + public readonly compatibility = Compatibility.Fargate; + } + + return new Import(scope, id); + } + /** * The configured network mode */ @@ -52,7 +66,7 @@ export class FargateTaskDefinition extends TaskDefinition { // we need to explicitly write the type here, as type deduction for enums won't lead to // the import being generated in the .d.ts file. - constructor(scope: cdk.Construct, id: string, props: FargateTaskDefinitionProps = {}) { + constructor(scope: Construct, id: string, props: FargateTaskDefinitionProps = {}) { super(scope, id, { ...props, cpu: props.cpu || '256', diff --git a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts index 6ea1fa21d6c38..4a20c1287ebbe 100644 --- a/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts +++ b/packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service-applet.ts @@ -109,7 +109,7 @@ export interface LoadBalancedFargateServiceAppletProps extends cdk.StackProps { * load balancer, ECS cluster, VPC, and (optionally) Route53 alias record. */ export class LoadBalancedFargateServiceApplet extends cdk.Stack { - constructor(scope: cdk.App, id: string, props: LoadBalancedFargateServiceAppletProps) { + constructor(scope: cdk.Construct, id: string, props: LoadBalancedFargateServiceAppletProps) { super(scope, id, props); const vpc = new VpcNetwork(this, 'MyVpc', { maxAZs: 2 }); diff --git a/packages/@aws-cdk/aws-ecs/package.json b/packages/@aws-cdk/aws-ecs/package.json index 7407279f74fad..1f034b71ccebb 100644 --- a/packages/@aws-cdk/aws-ecs/package.json +++ b/packages/@aws-cdk/aws-ecs/package.json @@ -113,12 +113,5 @@ }, "engines": { "node": ">= 8.10.0" - }, - "awslint": { - "exclude": [ - "resource-attribute:@aws-cdk/aws-ecs.ICluster.clusterArn", - "construct-ctor:@aws-cdk/aws-ecs.BaseService.", - "construct-ctor:@aws-cdk/aws-ecs.LoadBalancedFargateServiceApplet..params[0]" - ] } } diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index 8f2c1da02cf1c..ad8fb05c02dbe 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -317,6 +317,7 @@ export class Cluster extends ClusterBase { * the right AMI and the `maxPods` number based on your instance type. * * @see https://docs.aws.amazon.com/eks/latest/userguide/launch-workers.html + * @param autoScalingGroup [disable-awslint:ref-via-interface] */ public addAutoScalingGroup(autoScalingGroup: autoscaling.AutoScalingGroup, options: AddAutoScalingGroupOptions) { // self rules diff --git a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts index 515f37e4b3a4b..72ad94c36a209 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancing/lib/load-balancer.ts @@ -109,6 +109,7 @@ export interface HealthCheck { export interface ILoadBalancerTarget extends IConnectable { /** * Attach load-balanced target to a classic ELB + * @param loadBalancer [disable-awslint:ref-via-interface] The load balancer to attach the target to */ attachToClassicLB(loadBalancer: LoadBalancer): void; } diff --git a/packages/@aws-cdk/aws-iam/lib/group.ts b/packages/@aws-cdk/aws-iam/lib/group.ts index 228321690e3c5..96a3e8404b7a8 100644 --- a/packages/@aws-cdk/aws-iam/lib/group.ts +++ b/packages/@aws-cdk/aws-iam/lib/group.ts @@ -4,9 +4,21 @@ import { IIdentity } from './identity-base'; import { Policy } from './policy'; import { ArnPrincipal, PolicyStatement, PrincipalPolicyFragment } from './policy-document'; import { IPrincipal } from './principals'; -import { User } from './user'; +import { IUser } from './user'; import { AttachedPolicies, undefinedIfEmpty } from './util'; +export interface IGroup extends IIdentity { + /** + * @attribute + */ + readonly groupName: string; + + /** + * @attribute + */ + readonly groupArn: string; +} + export interface GroupProps { /** * A name for the IAM group. For valid values, see the GroupName parameter @@ -36,50 +48,18 @@ export interface GroupProps { readonly path?: string; } -export class Group extends Resource implements IIdentity { +abstract class GroupBase extends Resource implements IGroup { + public abstract readonly groupName: string; + public abstract readonly groupArn: string; + public readonly grantPrincipal: IPrincipal = this; public readonly assumeRoleAction: string = 'sts:AssumeRole'; - /** - * The runtime name of this group. - * @attribute - */ - public readonly groupName: string; - - /** - * The ARN of this group. - * @attribute - */ - public readonly groupArn: string; - - public readonly policyFragment: PrincipalPolicyFragment; - - private readonly managedPolicies: string[]; private readonly attachedPolicies = new AttachedPolicies(); private defaultPolicy?: Policy; - constructor(scope: Construct, id: string, props: GroupProps = {}) { - super(scope, id); - - this.managedPolicies = props.managedPolicyArns || []; - - const group = new CfnGroup(this, 'Resource', { - groupName: props.groupName, - managedPolicyArns: undefinedIfEmpty(() => this.managedPolicies), - path: props.path, - }); - - this.groupName = group.groupName; - this.groupArn = group.groupArn; - this.policyFragment = new ArnPrincipal(this.groupArn).policyFragment; - } - - /** - * Attaches a managed policy to this group. - * @param arn The ARN of the managed policy to attach. - */ - public attachManagedPolicy(arn: string) { - this.managedPolicies.push(arn); + public get policyFragment(): PrincipalPolicyFragment { + return new ArnPrincipal(this.groupArn).policyFragment; } /** @@ -91,10 +71,14 @@ export class Group extends Resource implements IIdentity { policy.attachToGroup(this); } + public attachManagedPolicy(_arn: string) { + // drop + } + /** * Adds a user to this group. */ - public addUser(user: User) { + public addUser(user: IUser) { user.addToGroup(this); } @@ -111,3 +95,48 @@ export class Group extends Resource implements IIdentity { return true; } } + +export class Group extends GroupBase { + + /** + * Imports a group from ARN + * @param groupArn (e.g. `arn:aws:iam::account-id:group/group-name`) + */ + public static fromGroupArn(scope: Construct, id: string, groupArn: string): IGroup { + const groupName = scope.node.stack.parseArn(groupArn).resourceName!; + class Import extends GroupBase { + public groupName = groupName; + public groupArn = groupArn; + } + + return new Import(scope, id); + } + + public readonly groupName: string; + public readonly groupArn: string; + + private readonly managedPolicies: string[]; + + constructor(scope: Construct, id: string, props: GroupProps = {}) { + super(scope, id); + + this.managedPolicies = props.managedPolicyArns || []; + + const group = new CfnGroup(this, 'Resource', { + groupName: props.groupName, + managedPolicyArns: undefinedIfEmpty(() => this.managedPolicies), + path: props.path, + }); + + this.groupName = group.groupName; + this.groupArn = group.groupArn; + } + + /** + * Attaches a managed policy to this group. + * @param arn The ARN of the managed policy to attach. + */ + public attachManagedPolicy(arn: string) { + this.managedPolicies.push(arn); + } +} diff --git a/packages/@aws-cdk/aws-iam/lib/identity-base.ts b/packages/@aws-cdk/aws-iam/lib/identity-base.ts index 026064f033cec..48dd488b86fac 100644 --- a/packages/@aws-cdk/aws-iam/lib/identity-base.ts +++ b/packages/@aws-cdk/aws-iam/lib/identity-base.ts @@ -9,7 +9,7 @@ export interface IIdentity extends IPrincipal, IResource { /** * Attaches an inline policy to this principal. * This is the same as calling `policy.addToXxx(principal)`. - * @param policy The policy resource to attach to this principal. + * @param policy The policy resource to attach to this principal [disable-awslint:ref-via-interface] */ attachInlinePolicy(policy: Policy): void; diff --git a/packages/@aws-cdk/aws-iam/lib/policy.ts b/packages/@aws-cdk/aws-iam/lib/policy.ts index 1f5059b4e1a78..415cc4ded027b 100644 --- a/packages/@aws-cdk/aws-iam/lib/policy.ts +++ b/packages/@aws-cdk/aws-iam/lib/policy.ts @@ -1,11 +1,18 @@ -import { Construct, Resource, Token } from '@aws-cdk/cdk'; -import { Group } from './group'; +import { Construct, IResource, Resource, Token } from '@aws-cdk/cdk'; +import { IGroup } from './group'; import { CfnPolicy } from './iam.generated'; import { PolicyDocument, PolicyStatement } from './policy-document'; import { IRole } from './role'; -import { User } from './user'; +import { IUser } from './user'; import { generatePolicyName, undefinedIfEmpty } from './util'; +export interface IPolicy extends IResource { + /** + * @attribute + */ + readonly policyName: string; +} + export interface PolicyProps { /** * The name of the policy. If you specify multiple policies for an entity, @@ -21,7 +28,7 @@ export interface PolicyProps { * Users to attach this policy to. * You can also use `attachToUser(user)` to attach this policy to a user. */ - readonly users?: User[]; + readonly users?: IUser[]; /** * Roles to attach this policy to. @@ -33,7 +40,7 @@ export interface PolicyProps { * Groups to attach this policy to. * You can also use `attachToGroup(group)` to attach this policy to a group. */ - readonly groups?: Group[]; + readonly groups?: IGroup[]; /** * Initial set of permissions to add to this policy document. @@ -48,7 +55,16 @@ export interface PolicyProps { * Policies](http://docs.aws.amazon.com/IAM/latest/UserGuide/policies_overview.html) * in the IAM User Guide guide. */ -export class Policy extends Resource { +export class Policy extends Resource implements IPolicy { + + public static fromPolicyName(scope: Construct, id: string, policyName: string): IPolicy { + class Import extends Resource implements IPolicy { + public readonly policyName = policyName; + } + + return new Import(scope, id); + } + /** * The policy document. */ @@ -62,8 +78,8 @@ export class Policy extends Resource { public readonly policyName: string; private readonly roles = new Array(); - private readonly users = new Array(); - private readonly groups = new Array(); + private readonly users = new Array(); + private readonly groups = new Array(); constructor(scope: Construct, id: string, props: PolicyProps = {}) { super(scope, id); @@ -108,7 +124,7 @@ export class Policy extends Resource { /** * Attaches this policy to a user. */ - public attachToUser(user: User) { + public attachToUser(user: IUser) { if (this.users.find(u => u === user)) { return; } this.users.push(user); user.attachInlinePolicy(this); @@ -126,7 +142,7 @@ export class Policy extends Resource { /** * Attaches this policy to a group. */ - public attachToGroup(group: Group) { + public attachToGroup(group: IGroup) { if (this.groups.find(g => g === group)) { return; } this.groups.push(group); group.attachInlinePolicy(this); diff --git a/packages/@aws-cdk/aws-iam/lib/user.ts b/packages/@aws-cdk/aws-iam/lib/user.ts index 08851a6b2f836..f0e1d9ac9f6eb 100644 --- a/packages/@aws-cdk/aws-iam/lib/user.ts +++ b/packages/@aws-cdk/aws-iam/lib/user.ts @@ -1,5 +1,5 @@ import { Construct, Resource, SecretValue } from '@aws-cdk/cdk'; -import { Group } from './group'; +import { IGroup } from './group'; import { CfnUser } from './iam.generated'; import { IIdentity } from './identity-base'; import { Policy } from './policy'; @@ -8,12 +8,17 @@ import { ArnPrincipal, PrincipalPolicyFragment } from './policy-document'; import { IPrincipal } from './principals'; import { AttachedPolicies, undefinedIfEmpty } from './util'; +export interface IUser extends IIdentity { + readonly userName: string; + addToGroup(group: IGroup): void; +} + export interface UserProps { /** * Groups to add this user to. You can also use `addToGroup` to add this * user to a group. */ - readonly groups?: Group[]; + readonly groups?: IGroup[]; /** * A list of ARNs for managed policies attacherd to this user. @@ -114,7 +119,7 @@ export class User extends Resource implements IIdentity { /** * Adds this user to a group. */ - public addToGroup(group: Group) { + public addToGroup(group: IGroup) { this.groups.push(group.groupName); } diff --git a/packages/@aws-cdk/aws-iam/lib/util.ts b/packages/@aws-cdk/aws-iam/lib/util.ts index 8fc2a504f9557..930ecd15b915f 100644 --- a/packages/@aws-cdk/aws-iam/lib/util.ts +++ b/packages/@aws-cdk/aws-iam/lib/util.ts @@ -1,5 +1,5 @@ import { Token } from '@aws-cdk/cdk'; -import { Policy } from './policy'; +import { IPolicy } from './policy'; const MAX_POLICY_NAME_LEN = 128; @@ -24,7 +24,7 @@ export function generatePolicyName(logicalId: string) { * Helper class that maintains the set of attached policies for a principal. */ export class AttachedPolicies { - private policies = new Array(); + private policies = new Array(); /** * Adds a policy to the list of attached policies. @@ -32,7 +32,7 @@ export class AttachedPolicies { * If this policy is already, attached, returns false. * If there is another policy attached with the same name, throws an exception. */ - public attach(policy: Policy) { + public attach(policy: IPolicy) { if (this.policies.find(p => p === policy)) { return; // already attached } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts index 870ef7055f63d..c1945bb0220c9 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/api.ts @@ -8,11 +8,12 @@ export class ApiEventSource implements lambda.IEventSource { } } - public bind(target: lambda.FunctionBase): void { - const id = 'ApiEventSourceA7A86A4F'; - let api = target.node.tryFindChild(id) as apigw.RestApi; + public bind(target: lambda.IFunction): void { + const id = `${target.node.uniqueId}:ApiEventSourceA7A86A4F`; + const stack = target.node.stack; + let api = stack.node.tryFindChild(id) as apigw.RestApi; if (!api) { - api = new apigw.RestApi(target, id, { + api = new apigw.RestApi(stack, id, { defaultIntegration: new apigw.LambdaIntegration(target), }); } diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts index 156ceea800db7..0c96fd5f13441 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/dynamodb.ts @@ -29,9 +29,8 @@ export class DynamoEventSource implements lambda.IEventSource { } } - public bind(target: lambda.FunctionBase) { - new lambda.EventSourceMapping(target, `DynamoDBEventSource:${this.table.node.uniqueId}`, { - target, + public bind(target: lambda.IFunction) { + target.addEventSourceMapping(`DynamoDBEventSource:${this.table.node.uniqueId}`, { batchSize: this.props.batchSize || 100, eventSourceArn: this.table.tableStreamArn, startingPosition: this.props.startingPosition diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts index 5893935ab375b..93102e456432e 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kinesis.ts @@ -29,9 +29,8 @@ export class KinesisEventSource implements lambda.IEventSource { } } - public bind(target: lambda.FunctionBase) { - new lambda.EventSourceMapping(target, `KinesisEventSource:${this.stream.node.uniqueId}`, { - target, + public bind(target: lambda.IFunction) { + target.addEventSourceMapping(`KinesisEventSource:${this.stream.node.uniqueId}`, { batchSize: this.props.batchSize || 100, startingPosition: this.props.startingPosition, eventSourceArn: this.stream.streamArn, diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/s3.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/s3.ts index be8a2253caf75..3b669ad26a0f2 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/s3.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/s3.ts @@ -24,7 +24,7 @@ export class S3EventSource implements lambda.IEventSource { } - public bind(target: lambda.FunctionBase) { + public bind(target: lambda.IFunction) { const filters = this.props.filters || []; for (const event of this.props.events) { this.bucket.onEvent(event, target, ...filters); diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/sns.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/sns.ts index f4f690f00dd7d..d2c40c1b4b47b 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/sns.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/sns.ts @@ -8,7 +8,7 @@ export class SnsEventSource implements lambda.IEventSource { constructor(readonly topic: sns.ITopic) { } - public bind(target: lambda.FunctionBase) { + public bind(target: lambda.IFunction) { this.topic.subscribeLambda(target); } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts index d372116fe49f2..2752fdd370ff4 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/sqs.ts @@ -24,9 +24,8 @@ export class SqsEventSource implements lambda.IEventSource { } } - public bind(target: lambda.FunctionBase) { - new lambda.EventSourceMapping(target, `SqsEventSource:${this.queue.node.uniqueId}`, { - target, + public bind(target: lambda.IFunction) { + target.addEventSourceMapping(`SqsEventSource:${this.queue.node.uniqueId}`, { batchSize: this.props.batchSize, eventSourceArn: this.queue.queueArn, }); diff --git a/packages/@aws-cdk/aws-lambda/lib/alias.ts b/packages/@aws-cdk/aws-lambda/lib/alias.ts index 0105a9052381d..4c4512a093ea8 100644 --- a/packages/@aws-cdk/aws-lambda/lib/alias.ts +++ b/packages/@aws-cdk/aws-lambda/lib/alias.ts @@ -1,7 +1,7 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import { CfnOutput, Construct } from '@aws-cdk/cdk'; import { FunctionAttributes, FunctionBase, IFunction } from './function-base'; -import { Version } from './lambda-version'; +import { IVersion } from './lambda-version'; import { CfnAlias } from './lambda.generated'; /** @@ -20,7 +20,7 @@ export interface AliasProps { * * Use lambda.addVersion() to obtain a new lambda version to refer to. */ - readonly version: Version; + readonly version: IVersion; /** * Name of this alias @@ -176,7 +176,7 @@ export interface VersionWeight { /** * The version to route traffic to */ - readonly version: Version; + readonly version: IVersion; /** * How much weight to assign to this version (0..1) diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts index 6b13dc66eb88b..073900ab07eec 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source-mapping.ts @@ -3,18 +3,13 @@ import { Resource } from '@aws-cdk/cdk'; import { IFunction } from './function-base'; import { CfnEventSourceMapping } from './lambda.generated'; -export interface EventSourceMappingProps { +export interface EventSourceMappingOptions { /** * The Amazon Resource Name (ARN) of the event source. Any record added to * this stream can invoke the Lambda function. */ readonly eventSourceArn: string; - /** - * The target AWS Lambda function. - */ - readonly target: IFunction; - /** * The largest number of records that AWS Lambda will retrieve from your event * source at the time of invoking your function. Your function receives an @@ -43,6 +38,13 @@ export interface EventSourceMappingProps { readonly startingPosition?: StartingPosition } +export interface EventSourceMappingProps extends EventSourceMappingOptions { + /** + * The target AWS Lambda function. + */ + readonly target: IFunction; +} + /** * Defines a Lambda EventSourceMapping resource. * diff --git a/packages/@aws-cdk/aws-lambda/lib/event-source.ts b/packages/@aws-cdk/aws-lambda/lib/event-source.ts index 1eec70b08c809..d4e6340c33dfa 100644 --- a/packages/@aws-cdk/aws-lambda/lib/event-source.ts +++ b/packages/@aws-cdk/aws-lambda/lib/event-source.ts @@ -1,4 +1,4 @@ -import { FunctionBase } from './function-base'; +import { IFunction } from './function-base'; /** * An abstract class which represents an AWS Lambda event source. @@ -9,5 +9,5 @@ export interface IEventSource { * function. * @param target That lambda function to bind to. */ - bind(target: FunctionBase): void; + bind(target: IFunction): void; } diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 12bb51a176a27..0ce47c4825319 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -7,6 +7,7 @@ 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'; +import { EventSourceMapping, EventSourceMappingOptions } from './event-source-mapping'; import { CfnPermission } from './lambda.generated'; import { Permission } from './permission'; @@ -44,6 +45,13 @@ export interface IFunction extends IResource, logs.ILogSubscriptionDestination, */ readonly isBoundToVpc: boolean; + /** + * Adds an event source that maps to this AWS Lambda function. + * @param id construct ID + * @param options mapping options + */ + addEventSourceMapping(id: string, options: EventSourceMappingOptions): EventSourceMapping; + /** * Adds a permission to the Lambda resource policy. * @param id The id Æ’or the permission construct @@ -218,6 +226,13 @@ export abstract class FunctionBase extends Resource implements IFunction { return !!this._connections; } + public addEventSourceMapping(id: string, options: EventSourceMappingOptions): EventSourceMapping { + return new EventSourceMapping(this, id, { + target: this, + ...options + }); + } + /** * Grant the given identity permissions to invoke this Lambda */ diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index 83499a1f72c7b..7111742b9a649 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -1,7 +1,20 @@ -import { Construct, Resource } from '@aws-cdk/cdk'; +import { Construct, IResource, Resource } from '@aws-cdk/cdk'; import { IFunction } from './function-base'; import { CfnVersion } from './lambda.generated'; +export interface IVersion extends IResource { + /** + * The most recently deployed version of this function. + * @attribute + */ + readonly version: string; + + /** + * The underlying AWS Lambda function. + */ + readonly lambda: IFunction; +} + /** * Properties for a new Lambda version */ @@ -28,6 +41,11 @@ export interface VersionProps { readonly lambda: IFunction; } +export interface VersionAttributes { + readonly version: string; + readonly lambda: IFunction; +} + /** * A single newly-deployed version of a Lambda function. * @@ -44,16 +62,17 @@ export interface VersionProps { * the right deployment, specify the `codeSha256` property while * creating the `Version. */ -export class Version extends Resource { - /** - * The most recently deployed version of this function. - * @attribute - */ - public readonly version: string; +export class Version extends Resource implements IVersion { - /** - * Lambda object this version is associated with - */ + public static fromVersionAttributes(scope: Construct, id: string, attrs: VersionAttributes): IVersion { + class Import extends Resource implements IVersion { + public readonly version = attrs.version; + public readonly lambda = attrs.lambda; + } + return new Import(scope, id); + } + + public readonly version: string; public readonly lambda: IFunction; constructor(scope: Construct, id: string, props: VersionProps) { diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index 6dac11b8d8676..16343feb2003d 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -54,15 +54,17 @@ export interface ILayerVersion extends IResource { export(): LayerVersionAttributes; /** - * Grants usage of this layer to specific entities. Usage within the same account where the layer is defined is always - * allowed and does not require calling this method. Note that the principal that creates the Lambda function using - * the layer (for example, a CloudFormation changeset execution role) also needs to have the - * ``lambda:GetLayerVersion`` permission on the layer version. + * Add permission for this layer version to specific entities. Usage within + * the same account where the layer is defined is always allowed and does not + * require calling this method. Note that the principal that creates the + * Lambda function using the layer (for example, a CloudFormation changeset + * execution role) also needs to have the ``lambda:GetLayerVersion`` + * permission on the layer version. * * @param id the ID of the grant in the construct tree. * @param grantee the identification of the grantee. */ - grantUsage(id: string, grantee: LayerVersionUsageGrantee): ILayerVersion + addPermission(id: string, grantee: LayerVersionPermission): void; } /** @@ -72,7 +74,7 @@ abstract class LayerVersionBase extends Resource implements ILayerVersion { public abstract readonly layerVersionArn: string; public abstract readonly compatibleRuntimes?: Runtime[]; - public grantUsage(id: string, grantee: LayerVersionUsageGrantee): ILayerVersion { + public addPermission(id: string, grantee: LayerVersionPermission) { if (grantee.organizationId != null && grantee.accountId !== '*') { throw new Error(`OrganizationId can only be specified if AwsAccountId is '*', but it is ${grantee.accountId}`); } @@ -83,7 +85,6 @@ abstract class LayerVersionBase extends Resource implements ILayerVersion { principal: grantee.accountId, organizationId: grantee.organizationId, }); - return this; } public export(): LayerVersionAttributes { @@ -97,7 +98,7 @@ abstract class LayerVersionBase extends Resource implements ILayerVersion { /** * Identification of an account (or organization) that is allowed to access a Lambda Layer Version. */ -export interface LayerVersionUsageGrantee { +export interface LayerVersionPermission { /** * The AWS Account id of the account that is authorized to use a Lambda Layer Version. The wild-card ``'*'`` can be * used to grant access to "any" account (or any account in an organization when ``organizationId`` is specified). @@ -231,9 +232,8 @@ export class SingletonLayerVersion extends Construct implements ILayerVersion { }; } - public grantUsage(id: string, grantee: LayerVersionUsageGrantee): ILayerVersion { - this.layerVersion.grantUsage(id, grantee); - return this; + public addPermission(id: string, grantee: LayerVersionPermission) { + this.layerVersion.addPermission(id, grantee); } private ensureLayerVersion(props: SingletonLayerVersionProps): ILayerVersion { diff --git a/packages/@aws-cdk/aws-lambda/package.json b/packages/@aws-cdk/aws-lambda/package.json index d4f3f3ee29f00..8eb4a273e2b3b 100644 --- a/packages/@aws-cdk/aws-lambda/package.json +++ b/packages/@aws-cdk/aws-lambda/package.json @@ -106,15 +106,5 @@ }, "engines": { "node": ">= 8.10.0" - }, - "awslint": { - "exclude": [ - "construct-base-is-private:@aws-cdk/aws-lambda.FunctionBase", - "grant-result:@aws-cdk/aws-lambda.LayerVersion.grantUsage", - "from-method:@aws-cdk/aws-lambda.Alias", - "from-attributes:fromAliasAttributes", - "from-method:@aws-cdk/aws-lambda.SingletonFunction", - "from-method:@aws-cdk/aws-lambda.Version" - ] } } diff --git a/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.ts b/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.ts index de01eb0c1d5f0..3d79d80a89d95 100644 --- a/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.ts +++ b/packages/@aws-cdk/aws-lambda/test/integ.layer-version.lit.ts @@ -18,7 +18,7 @@ const layer = new lambda.LayerVersion(stack, 'MyLayer', { }); // To grant usage by other AWS accounts -layer.grantUsage('remote-account-grant', { accountId: awsAccountId }); +layer.addPermission('remote-account-grant', { accountId: awsAccountId }); // To grant usage to all accounts in some AWS Ogranization // layer.grantUsage({ accountId: '*', organizationId }); diff --git a/packages/@aws-cdk/aws-lambda/test/test.layers.ts b/packages/@aws-cdk/aws-lambda/test/test.layers.ts index 11cebcb8bc634..244c58095ace8 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.layers.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.layers.ts @@ -40,8 +40,8 @@ export = testCase({ }); // WHEN - layer.grantUsage('GrantUsage-123456789012', { accountId: '123456789012' }); - layer.grantUsage('GrantUsage-o-123456', { accountId: '*', organizationId: 'o-123456' }); + layer.addPermission('GrantUsage-123456789012', { accountId: '123456789012' }); + layer.addPermission('GrantUsage-o-123456', { accountId: '*', organizationId: 'o-123456' }); // THEN expect(stack).to(haveResource('AWS::Lambda::LayerVersionPermission', { diff --git a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts index b14448e2c7afd..7c03a3766c166 100644 --- a/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts +++ b/packages/@aws-cdk/aws-logs/lib/cross-account-destination.ts @@ -17,7 +17,7 @@ export interface CrossAccountDestinationProps { * * The role must be assumable by 'logs.{REGION}.amazonaws.com'. */ - readonly role: iam.Role; + readonly role: iam.IRole; /** * The log destination target's ARN diff --git a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts index c83b41982dd43..921b37a84fdc8 100644 --- a/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts +++ b/packages/@aws-cdk/aws-route53/lib/hosted-zone.ts @@ -161,7 +161,7 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone { * @param delegate the zone being delegated to. * @param opts options for creating the DNS record, if any. */ - public addDelegation(delegate: PublicHostedZone, opts: ZoneDelegationOptions = {}): void { + public addDelegation(delegate: IPublicHostedZone, opts: ZoneDelegationOptions = {}): void { new ZoneDelegationRecord(this, `${this.zoneName} -> ${delegate.zoneName}`, { zone: this, delegatedZoneName: delegate.zoneName, diff --git a/packages/@aws-cdk/aws-s3/package.json b/packages/@aws-cdk/aws-s3/package.json index ef7d7b60e9e96..d68b9d5db225d 100644 --- a/packages/@aws-cdk/aws-s3/package.json +++ b/packages/@aws-cdk/aws-s3/package.json @@ -81,14 +81,5 @@ }, "engines": { "node": ">= 8.10.0" - }, - "awslint": { - "exclude": [ - "resource-attribute:@aws-cdk/aws-s3.IBucket.bucketDomainName", - "resource-attribute:@aws-cdk/aws-s3.IBucket.bucketDualStackDomainName", - "resource-attribute:@aws-cdk/aws-s3.IBucket.bucketRegionalDomainName", - "resource-attribute:@aws-cdk/aws-s3.IBucket.bucketWebsiteUrl", - "resource-interface:@aws-cdk/aws-s3.IBucketPolicy" - ] } } diff --git a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts index cf0161bc7cbd1..1cb7b4446a215 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/state-machine.ts @@ -27,7 +27,7 @@ export interface StateMachineProps { * * @default A role is automatically created */ - readonly role?: iam.Role; + readonly role?: iam.IRole; /** * Maximum run time for this state machine @@ -55,7 +55,7 @@ export class StateMachine extends Resource implements IStateMachine, events.IEve /** * Execution role of this state machine */ - public readonly role: iam.Role; + public readonly role: iam.IRole; /** * The name of the 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 7a98884d4881f..e2f40264d8758 100644 --- a/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts +++ b/packages/@aws-cdk/aws-stepfunctions/lib/states/task.ts @@ -270,6 +270,8 @@ export class Task extends State implements INextable { export interface IStepFunctionsTaskResource { /** * Return the properties required for using this object as a Task resource + * + * @param callingTask [disable-awslint:ref-via-interface] */ asStepFunctionsTaskResource(callingTask: Task): StepFunctionsTaskResourceProps; } diff --git a/tools/awslint/bin/awslint.ts b/tools/awslint/bin/awslint.ts index 9533216736233..68dc273e85327 100644 --- a/tools/awslint/bin/awslint.ts +++ b/tools/awslint/bin/awslint.ts @@ -5,9 +5,17 @@ import fs = require('fs-extra'); import reflect = require('jsii-reflect'); import path = require('path'); import yargs = require('yargs'); -import { AggregateLinter, attributesLinter, cfnResourceLinter, constructLinter, DiagnosticLevel, importsLinter, moduleLinter, resourceLinter } from '../lib'; - -const linter = new AggregateLinter(moduleLinter, constructLinter, cfnResourceLinter, resourceLinter, importsLinter, attributesLinter); +import { AggregateLinter, apiLinter, attributesLinter, cfnResourceLinter, constructLinter, DiagnosticLevel, importsLinter, moduleLinter, resourceLinter } from '../lib'; + +const linter = new AggregateLinter( + moduleLinter, + constructLinter, + cfnResourceLinter, + resourceLinter, + apiLinter, + importsLinter, + attributesLinter +); let stackTrace = false; diff --git a/tools/awslint/lib/linter.ts b/tools/awslint/lib/linter.ts index 1480c03774dde..fa84010818b81 100644 --- a/tools/awslint/lib/linter.ts +++ b/tools/awslint/lib/linter.ts @@ -1,5 +1,6 @@ import reflect = require('jsii-reflect'); import { PrimitiveType } from 'jsii-spec'; +import util = require('util'); export interface LinterOptions { /** @@ -85,17 +86,16 @@ export class Linter extends LinterBase { ctxs = [ ctxs ]; } - const results = new Array(); + const diag = new Array(); for (const ctx of ctxs) { for (const rule of Object.values(this._rules)) { - const evaluation = new Evaluation(ctx, rule, options); + const evaluation = new Evaluation(ctx, rule, diag, options); rule.eval(evaluation); - results.push(...evaluation.diagnostics); } } - return results; + return diag; } } @@ -105,18 +105,26 @@ export class Linter extends LinterBase { export class Evaluation { public readonly ctx: T; public readonly options: LinterOptions; - public diagnostics = new Array(); + private readonly curr: ConcreteRule; + private readonly diagnostics: Diagnostic[]; - constructor(ctx: T, rule: ConcreteRule, options: LinterOptions) { + constructor(ctx: T, rule: ConcreteRule, diagnostics: Diagnostic[], options: LinterOptions) { this.ctx = ctx; this.options = options; this.curr = rule; + this.diagnostics = diagnostics; } public assert(condition: any, scope: string, extra?: string): condition is true { + + // deduplicate: skip if this specific assertion ("rule:scope") was already examined + if (this.diagnostics.find(d => d.rule.code === this.curr.code && d.scope === scope)) { + return condition; + } + const include = this.shouldEvaluate(this.curr.code, scope); - const message = this.curr.message + (extra || ''); + const message = util.format(this.curr.message, extra || ''); let level: DiagnosticLevel; if (!include) { diff --git a/tools/awslint/lib/rules/api.ts b/tools/awslint/lib/rules/api.ts new file mode 100644 index 0000000000000..e8b80deb9811a --- /dev/null +++ b/tools/awslint/lib/rules/api.ts @@ -0,0 +1,146 @@ +import reflect = require('jsii-reflect'); +import { Linter } from '../linter'; +import { CfnResourceReflection } from './cfn-resource'; +import { ConstructReflection } from './construct'; +import { ResourceReflection } from './resource'; + +const EXCLUDE_ANNOTATION_REF_VIA_INTERFACE = '[disable-awslint:ref-via-interface]'; + +// lint all constructs that are not L1 resources +export const apiLinter = new Linter(a => ConstructReflection + .findAllConstructs(a) + .filter(c => !CfnResourceReflection.isCfnResource(c.classType))); + +apiLinter.add({ + code: 'ref-via-interface', + message: 'API should use interface and not the concrete class (%s). ' + + `If this is intentional, add "${EXCLUDE_ANNOTATION_REF_VIA_INTERFACE}" to element's jsdoc`, + eval: e => { + const cls = e.ctx.classType; + const visited = new Set(); + + assertClass(cls); + + function assertClass(type: reflect.ClassType): void { + if (visited.has(type.fqn)) { return; } + visited.add(type.fqn); + + for (const method of type.allMethods) { + assertMethod(method); + } + + if (type.initializer) { + assertMethod(type.initializer); + } + } + + function assertDataType(type: reflect.InterfaceType): void { + for (const property of type.allProperties) { + assertProperty(property); + } + } + + function assertInterface(type: reflect.InterfaceType): void { + if (visited.has(type.fqn)) { return; } + visited.add(type.fqn); + + if (type.datatype) { + assertDataType(type); + } + + for (const method of type.allMethods) { + assertMethod(method); + } + } + + function assertProperty(property: reflect.Property) { + + if (property.protected) { + return; + } + + const site = property.overrides ? property.overrides : property.parentType; + assertType(property.type, property.docs, `${site.fqn}.${property.name}`); + } + + function assertMethod(method: reflect.Callable) { + + if (method.protected) { + return; + } + + const site = method.overrides ? method.overrides : method.parentType; + const scope = `${site.fqn}.${method.name}`; + + let firstMethod: reflect.Callable | undefined = site.isClassType() || site.isInterfaceType() + ? site.allMethods.find(m => m.name === method.name) + : undefined; + + if (!firstMethod) { + firstMethod = method; + } + + for (const param of firstMethod.parameters) { + assertType(param.type, param.docs, `${scope}.${param.name}`); + } + + // note that we do not require that return values will use an interface + } + + function assertType(type: reflect.TypeReference, docs: reflect.Docs, scope: string): void { + if (type.primitive) { + return; + } + + if (type.void) { + return; + } + + if (type.arrayOfType) { + return assertType(type.arrayOfType, docs, scope); + } + + if (type.mapOfType) { + return assertType(type.mapOfType, docs, scope); + } + + if (type.unionOfTypes) { + for (const t of type.unionOfTypes) { + assertType(t, docs, scope); + } + return; + } + + // interfaces are okay + if (type.type && type.type.isInterfaceType()) { + return assertInterface(type.type); + } + + // enums are okay + if (type.type && type.type.isEnumType()) { + return; + } + + // classes are okay as long as they are not constructs + if (type.type && type.type.isClassType()) { + if (!ResourceReflection.isResourceClass(type.type)) { + return; + } + + if (type.type.fqn === ConstructReflection.CONSTRUCT_FQN) { + return; + } + + // allow exclusion of this rule + if (docs.summary.includes(EXCLUDE_ANNOTATION_REF_VIA_INTERFACE) || docs.remarks.includes(EXCLUDE_ANNOTATION_REF_VIA_INTERFACE)) { + return; + } + + e.assert(false, scope, type.type.fqn); + return; + } + + throw new Error(`invalid type reference: ${type.toString()}`); + } + } +}); \ No newline at end of file diff --git a/tools/awslint/lib/rules/cfn-resource.ts b/tools/awslint/lib/rules/cfn-resource.ts index a5b0655ff6a7f..c0270b1e7c94d 100644 --- a/tools/awslint/lib/rules/cfn-resource.ts +++ b/tools/awslint/lib/rules/cfn-resource.ts @@ -39,31 +39,35 @@ export class CfnResourceReflection { * Returns all CFN resource classes within an assembly. */ public static findAll(assembly: reflect.Assembly) { - return assembly.classes.filter(c => { - if (!c.system.includesAssembly(CORE_MODULE)) { - return false; - } - - // skip CfnResource itself - if (c.fqn === CFN_RESOURCE_BASE_CLASS_FQN) { - return false; - } - - if (!ConstructReflection.isConstructClass(c)) { - return false; - } - - const cfnResourceClass = c.system.findFqn(CFN_RESOURCE_BASE_CLASS_FQN); - if (!c.extends(cfnResourceClass)) { - return false; - } - - if (!c.name.startsWith('Cfn')) { - return false; - } - - return true; - }).map(c => new CfnResourceReflection(c)); + return assembly.classes + .filter(c => this.isCfnResource(c)) + .map(c => new CfnResourceReflection(c)); + } + + public static isCfnResource(c: reflect.ClassType) { + if (!c.system.includesAssembly(CORE_MODULE)) { + return false; + } + + // skip CfnResource itself + if (c.fqn === CFN_RESOURCE_BASE_CLASS_FQN) { + return false; + } + + if (!ConstructReflection.isConstructClass(c)) { + return false; + } + + const cfnResourceClass = c.system.findFqn(CFN_RESOURCE_BASE_CLASS_FQN); + if (!c.extends(cfnResourceClass)) { + return false; + } + + if (!c.name.startsWith('Cfn')) { + return false; + } + + return true; } public readonly classType: reflect.ClassType; diff --git a/tools/awslint/lib/rules/construct.ts b/tools/awslint/lib/rules/construct.ts index 500705432c1de..fbb624d3c2a97 100644 --- a/tools/awslint/lib/rules/construct.ts +++ b/tools/awslint/lib/rules/construct.ts @@ -2,14 +2,15 @@ import reflect = require('jsii-reflect'); import { Linter, MethodSignatureParameterExpectation } from '../linter'; import { CORE_MODULE } from './common'; -const CONSTRUCT_FQN = `${CORE_MODULE}.Construct`; -const CONSTRUCT_INTERFACE_FQN = `${CORE_MODULE}.IConstruct`; - export const constructLinter = new Linter(assembly => assembly.classes .filter(t => ConstructReflection.isConstructClass(t)) .map(construct => new ConstructReflection(construct))); export class ConstructReflection { + + public static readonly CONSTRUCT_FQN = `${CORE_MODULE}.Construct`; + public static readonly CONSTRUCT_INTERFACE_FQN = `${CORE_MODULE}.IConstruct`; + /** * Determines if a class is a construct. */ @@ -26,7 +27,7 @@ export class ConstructReflection { return false; } - return c.extends(c.system.findFqn(CONSTRUCT_FQN)); + return c.extends(c.system.findFqn(this.CONSTRUCT_FQN)); } public static findAllConstructs(assembly: reflect.Assembly) { @@ -49,7 +50,7 @@ export class ConstructReflection { constructor(public readonly classType: reflect.ClassType) { this.fqn = classType.fqn; this.sys = classType.system; - this.ROOT_CLASS = this.sys.findClass(CONSTRUCT_FQN); + this.ROOT_CLASS = this.sys.findClass(ConstructReflection.CONSTRUCT_FQN); this.interfaceFqn = `${classType.assembly.name}.I${classType.name}`; this.propsFqn = `${classType.assembly.name}.${classType.name}Props`; this.interfaceType = this.tryFindInterface(); @@ -104,7 +105,7 @@ constructLinter.add({ expectedParams.push({ name: 'scope', - type: CONSTRUCT_FQN + type: ConstructReflection.CONSTRUCT_FQN }); expectedParams.push({ @@ -180,7 +181,7 @@ constructLinter.add({ message: 'construct interface must extend core.IConstruct', eval: e => { if (!e.ctx.interfaceType) { return; } - const interfaceBase = e.ctx.sys.findInterface(CONSTRUCT_INTERFACE_FQN); + const interfaceBase = e.ctx.sys.findInterface(ConstructReflection.CONSTRUCT_INTERFACE_FQN); e.assert(e.ctx.interfaceType.extends(interfaceBase), e.ctx.interfaceType.fqn); } }); @@ -188,9 +189,10 @@ constructLinter.add({ constructLinter.add({ code: 'construct-base-is-private', message: 'prefer that the construct base class is private', + warning: true, eval: e => { if (!e.ctx.interfaceType) { return; } const baseFqn = `${e.ctx.classType.fqn}Base`; e.assert(!e.ctx.sys.tryFindFqn(baseFqn), baseFqn); } -}); \ No newline at end of file +}); diff --git a/tools/awslint/lib/rules/index.ts b/tools/awslint/lib/rules/index.ts index 3afad529f9eca..5edde09ca4515 100644 --- a/tools/awslint/lib/rules/index.ts +++ b/tools/awslint/lib/rules/index.ts @@ -4,3 +4,4 @@ export * from './resource'; export * from './imports'; export * from './cfn-resource'; export * from './attributes'; +export * from './api'; \ No newline at end of file diff --git a/tools/awslint/lib/rules/resource.ts b/tools/awslint/lib/rules/resource.ts index 9682e21af81aa..3161626e8275a 100644 --- a/tools/awslint/lib/rules/resource.ts +++ b/tools/awslint/lib/rules/resource.ts @@ -23,6 +23,15 @@ export enum AttributeSite { } export class ResourceReflection { + + /** + * @returns true if `classType` represents an AWS resource (i.e. extends `cdk.Resource`). + */ + public static isResourceClass(classType: reflect.ClassType) { + const baseResource = classType.system.findClass(RESOURCE_BASE_CLASS_FQN); + return classType.extends(baseResource) || getDocTag(classType, 'resource'); + } + /** * @returns all resource constructs (everything that extends `cdk.Resource`) */ @@ -31,11 +40,9 @@ export class ResourceReflection { return []; // not part of the dep stack } - const baseResource = assembly.system.findClass(RESOURCE_BASE_CLASS_FQN); - return ConstructReflection .findAllConstructs(assembly) - .filter(c => c.classType.extends(baseResource) || getDocTag(c.classType, 'resource')) + .filter(c => ResourceReflection.isResourceClass(c.classType)) .map(c => new ResourceReflection(c)); } @@ -71,6 +78,10 @@ export class ResourceReflection { const result = new Array(); for (const p of this.construct.classType.allProperties) { + if (p.protected) { + continue; // skip any protected properties + } + // an attribute property is a property which starts with the type name // (e.g. "bucketXxx") and/or has an @attribute doc tag. const tag = getDocTag(p, 'attribute'); From ee06ce0e0e2ad3c244e7c8ffcf6f46ee0268897d Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 11:22:38 +0300 Subject: [PATCH 02/11] cr fixes --- packages/@aws-cdk/aws-lambda/lib/lambda-version.ts | 7 +++++++ packages/@aws-cdk/aws-lambda/lib/layers.ts | 14 +++++++------- tools/awslint/lib/rules/api.ts | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts index 7111742b9a649..800062141a052 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-version.ts @@ -42,7 +42,14 @@ export interface VersionProps { } export interface VersionAttributes { + /** + * The version. + */ readonly version: string; + + /** + * The lambda function. + */ readonly lambda: IFunction; } diff --git a/packages/@aws-cdk/aws-lambda/lib/layers.ts b/packages/@aws-cdk/aws-lambda/lib/layers.ts index 16343feb2003d..553471d4c6688 100644 --- a/packages/@aws-cdk/aws-lambda/lib/layers.ts +++ b/packages/@aws-cdk/aws-lambda/lib/layers.ts @@ -62,9 +62,9 @@ export interface ILayerVersion extends IResource { * permission on the layer version. * * @param id the ID of the grant in the construct tree. - * @param grantee the identification of the grantee. + * @param permission the identification of the grantee. */ - addPermission(id: string, grantee: LayerVersionPermission): void; + addPermission(id: string, permission: LayerVersionPermission): void; } /** @@ -74,16 +74,16 @@ abstract class LayerVersionBase extends Resource implements ILayerVersion { public abstract readonly layerVersionArn: string; public abstract readonly compatibleRuntimes?: Runtime[]; - public addPermission(id: string, grantee: LayerVersionPermission) { - if (grantee.organizationId != null && grantee.accountId !== '*') { - throw new Error(`OrganizationId can only be specified if AwsAccountId is '*', but it is ${grantee.accountId}`); + public addPermission(id: string, permission: LayerVersionPermission) { + if (permission.organizationId != null && permission.accountId !== '*') { + throw new Error(`OrganizationId can only be specified if AwsAccountId is '*', but it is ${permission.accountId}`); } new CfnLayerVersionPermission(this, id, { action: 'lambda:GetLayerVersion', layerVersionArn: this.layerVersionArn, - principal: grantee.accountId, - organizationId: grantee.organizationId, + principal: permission.accountId, + organizationId: permission.organizationId, }); } diff --git a/tools/awslint/lib/rules/api.ts b/tools/awslint/lib/rules/api.ts index e8b80deb9811a..26d7ddcb02808 100644 --- a/tools/awslint/lib/rules/api.ts +++ b/tools/awslint/lib/rules/api.ts @@ -121,7 +121,7 @@ apiLinter.add({ return; } - // classes are okay as long as they are not constructs + // classes are okay as long as they are not resource constructs if (type.type && type.type.isClassType()) { if (!ResourceReflection.isResourceClass(type.type)) { return; From a7b0d9e2f443b4c16ae9b62574f0c6927b66882e Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 11:24:43 +0300 Subject: [PATCH 03/11] fix cycle requires --- packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 9108a1fd51e35..7f9af258c0dda 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -5,7 +5,6 @@ import s3 = require('@aws-cdk/aws-s3'); import { Construct, RemovalPolicy, Resource, Stack, Token } from '@aws-cdk/cdk'; import { Action, IPipeline, IStage } from "./action"; import { CfnPipeline } from './codepipeline.generated'; -import { CrossRegionScaffoldStack } from './cross-region-scaffold-stack'; import { Stage } from './stage'; import { validateName, validateSourceAction } from "./validation"; @@ -597,4 +596,7 @@ export abstract class CrossRegionScaffolding extends Stack { * The name of the S3 Bucket used for replicating the Pipeline's artifacts into the region. */ public abstract readonly replicationBucketName: string; -} \ No newline at end of file +} + +// cyclic dependency +import { CrossRegionScaffoldStack } from './cross-region-scaffold-stack'; From 5f72275dbf444237d20256852c374be5c10a4c53 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 12:18:51 +0300 Subject: [PATCH 04/11] Misc fixes * add VpcSubnet.isVpcSubnet * Do not use arbitrary duck-typing in EKS --- packages/@aws-cdk/aws-ec2/lib/vpc.ts | 10 ++++++++++ packages/@aws-cdk/aws-eks/lib/cluster.ts | 7 ++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index abc5951cd3366..e989f1fe7badf 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -1049,10 +1049,17 @@ export interface VpcSubnetProps { readonly mapPublicIpOnLaunch?: boolean; } +const IS_VPC_SUBNET = Symbol.for('@aws-cdk/aws-ec2.VpcSubnet'); + /** * Represents a new VPC subnet resource */ export class VpcSubnet extends cdk.Construct implements IVpcSubnet { + + public static isVpcSubnet(o: any): o is VpcSubnet { + return IS_VPC_SUBNET in o; + } + public static import(scope: cdk.Construct, id: string, props: VpcSubnetImportProps): IVpcSubnet { return new ImportedVpcSubnet(scope, id, props); } @@ -1081,6 +1088,9 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet { constructor(scope: cdk.Construct, id: string, props: VpcSubnetProps) { super(scope, id); + + Object.defineProperty(this, IS_VPC_SUBNET, { value: true }); + this.node.apply(new cdk.Tag(NAME_TAG, this.node.path)); this.availabilityZone = props.availabilityZone; diff --git a/packages/@aws-cdk/aws-eks/lib/cluster.ts b/packages/@aws-cdk/aws-eks/lib/cluster.ts index ad8fb05c02dbe..eee8de971dacc 100644 --- a/packages/@aws-cdk/aws-eks/lib/cluster.ts +++ b/packages/@aws-cdk/aws-eks/lib/cluster.ts @@ -1,5 +1,6 @@ import autoscaling = require('@aws-cdk/aws-autoscaling'); import ec2 = require('@aws-cdk/aws-ec2'); +import { VpcSubnet } from '@aws-cdk/aws-ec2'; import iam = require('@aws-cdk/aws-iam'); import { CfnOutput, Construct, IResource, Resource, Tag } from '@aws-cdk/cdk'; import { EksOptimizedAmi, nodeTypeForInstanceType } from './ami'; @@ -365,7 +366,7 @@ export class Cluster extends ClusterBase { */ private tagSubnets() { for (const subnet of this.vpc.privateSubnets) { - if (!isRealSubnetConstruct(subnet)) { + if (!VpcSubnet.isVpcSubnet(subnet)) { // Just give up, all of them will be the same. this.node.addWarning('Could not auto-tag private subnets with "kubernetes.io/role/internal-elb=1", please remember to do this manually'); return; @@ -376,10 +377,6 @@ export class Cluster extends ClusterBase { } } -function isRealSubnetConstruct(subnet: ec2.IVpcSubnet): subnet is ec2.VpcSubnet { - return (subnet as any).addDefaultRouteToIGW !== undefined; -} - /** * Options for adding worker nodes */ From a1d7a7b4e89c36f066e2e2e5a4396bb9ff12a5f1 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 12:24:19 +0300 Subject: [PATCH 05/11] fix ecs tests --- .../ec2/integ.event-task.lit.expected.json | 142 +++++++++--------- .../test/ec2/test.ec2-event-rule-target.ts | 2 +- 2 files changed, 72 insertions(+), 72 deletions(-) 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 cf64430816ede..059a84879db75 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 @@ -854,76 +854,6 @@ ] } }, - "TaskDefEventsRoleFB3B67B8": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "events.", - { - "Ref": "AWS::URLSuffix" - } - ] - ] - } - } - } - ], - "Version": "2012-10-17" - } - } - }, - "TaskDefEventsRoleDefaultPolicyA124E85B": { - "Type": "AWS::IAM::Policy", - "Properties": { - "PolicyDocument": { - "Statement": [ - { - "Action": "ecs:RunTask", - "Condition": { - "ArnEquals": { - "ecs:cluster": { - "Fn::GetAtt": [ - "EcsCluster97242B84", - "Arn" - ] - } - } - }, - "Effect": "Allow", - "Resource": { - "Ref": "TaskDef54694570" - } - }, - { - "Action": "iam:PassRole", - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "TaskDefExecutionRoleB4775C97", - "Arn" - ] - } - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "TaskDefEventsRoleDefaultPolicyA124E85B", - "Roles": [ - { - "Ref": "TaskDefEventsRoleFB3B67B8" - } - ] - } - }, "EventImageAdoptRepositoryDFAAC242": { "Type": "Custom::ECRAdoptedRepository", "Properties": { @@ -1136,13 +1066,83 @@ }, "RoleArn": { "Fn::GetAtt": [ - "TaskDefEventsRoleFB3B67B8", + "awsecsintegecsTaskDef8DD0C801EventsRoleC617AC5B", "Arn" ] } } ] } + }, + "awsecsintegecsTaskDef8DD0C801EventsRoleC617AC5B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "events.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "awsecsintegecsTaskDef8DD0C801EventsRoleDefaultPolicy2DFC09DA": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ecs:RunTask", + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "EcsCluster97242B84", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": { + "Ref": "TaskDef54694570" + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "awsecsintegecsTaskDef8DD0C801EventsRoleDefaultPolicy2DFC09DA", + "Roles": [ + { + "Ref": "awsecsintegecsTaskDef8DD0C801EventsRoleC617AC5B" + } + ] + } } }, "Parameters": { diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-event-rule-target.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-event-rule-target.ts index f706a7a497735..055d1b80fc890 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-event-rule-target.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-event-rule-target.ts @@ -51,7 +51,7 @@ export = { InputTransformer: { InputTemplate: "{\"argument\":\"hello\"}" }, - RoleArn: { "Fn::GetAtt": ["TaskDefEventsRoleFB3B67B8", "Arn"] } + RoleArn: { "Fn::GetAtt": ["TaskDefEventsRole7BD19E45", "Arn"] } } ] })); From 1edb61e97498e075e29b36183a9317b424369dce Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 15:32:52 +0300 Subject: [PATCH 06/11] fix event source tests --- .gitignore | 1 + .../aws-lambda-event-sources/test/test.api.ts | 20 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index ddd09c8280673..901c2922699cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.tsbuildinfo +.cdk.staging .vscode # VSCode extension diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/test.api.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/test.api.ts index 0076d043b54ad..44d00f76d9daa 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/test.api.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/test.api.ts @@ -20,12 +20,12 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::Resource', { PathPart: "foo", - ParentId: { "Fn::GetAtt": [ "MyFuncApiEventSourceA7A86A4FF4B434AC", "RootResourceId" ] } + ParentId: { "Fn::GetAtt": [ "MyFuncApiEventSourceA7A86A4FFB3F557C", "RootResourceId" ] } })); expect(stack).to(haveResource('AWS::ApiGateway::Method', { HttpMethod: 'GET', - ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4Ffoo73254F28" }, + ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4FfooCA6F87E4" }, })); test.done(); @@ -47,22 +47,22 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::Resource', { PathPart: "foo", - ParentId: { "Fn::GetAtt": [ "MyFuncApiEventSourceA7A86A4FF4B434AC", "RootResourceId" ] } + ParentId: { "Fn::GetAtt": [ "MyFuncApiEventSourceA7A86A4FFB3F557C", "RootResourceId" ] } })); expect(stack).to(haveResource('AWS::ApiGateway::Resource', { PathPart: "bar", - ParentId: { "Fn::GetAtt": [ "MyFuncApiEventSourceA7A86A4FF4B434AC", "RootResourceId" ] } + ParentId: { "Fn::GetAtt": [ "MyFuncApiEventSourceA7A86A4FFB3F557C", "RootResourceId" ] } })); expect(stack).to(haveResource('AWS::ApiGateway::Method', { HttpMethod: 'GET', - ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4Ffoo73254F28" }, + ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4FfooCA6F87E4" }, })); expect(stack).to(haveResource('AWS::ApiGateway::Method', { HttpMethod: 'POST', - ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4FbarFF0EF497" }, + ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4FbarDFB0F21B" }, })); test.done(); @@ -85,22 +85,22 @@ export = { // THEN expect(stack).to(haveResource('AWS::ApiGateway::Resource', { PathPart: "foo", - ParentId: { "Fn::GetAtt": [ "MyFuncApiEventSourceA7A86A4FF4B434AC", "RootResourceId" ] } + ParentId: { "Fn::GetAtt": [ "MyFuncApiEventSourceA7A86A4FFB3F557C", "RootResourceId" ] } })); expect(stack).to(haveResource('AWS::ApiGateway::Resource', { PathPart: "bar", - ParentId: { Ref: "MyFuncApiEventSourceA7A86A4Ffoo73254F28" } + ParentId: { Ref: "MyFuncApiEventSourceA7A86A4FfooCA6F87E4" } })); expect(stack).to(haveResource('AWS::ApiGateway::Method', { HttpMethod: 'GET', - ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4Ffoo73254F28" }, + ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4FfooCA6F87E4" }, })); expect(stack).to(haveResource('AWS::ApiGateway::Method', { HttpMethod: 'POST', - ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4Ffoobarzoo34214ADE" }, + ResourceId: { Ref: "MyFuncApiEventSourceA7A86A4Ffoobar028FFFDE" }, })); test.done(); From 549bf24c8ae1ecf4299f969fae9e1ffc7a8e478d Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 16:52:41 +0300 Subject: [PATCH 07/11] validate explicit stack name instead of construct id --- packages/@aws-cdk/cdk/lib/stack.ts | 8 +++---- packages/@aws-cdk/cdk/test/test.stack.ts | 27 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/cdk/lib/stack.ts b/packages/@aws-cdk/cdk/lib/stack.ts index 6f7a66206c42f..d8392af9aec31 100644 --- a/packages/@aws-cdk/cdk/lib/stack.ts +++ b/packages/@aws-cdk/cdk/lib/stack.ts @@ -143,16 +143,16 @@ export class Stack extends Construct { Object.defineProperty(this, STACK_SYMBOL, { value: true }); - if (name && !Stack.VALID_STACK_NAME_REGEX.test(name)) { - throw new Error(`Stack name must match the regular expression: ${Stack.VALID_STACK_NAME_REGEX.toString()}, got '${name}'`); - } - this.configuredEnv = props.env || {}; this.env = this.parseEnvironment(props.env); this.logicalIds = new LogicalIDs(props && props.namingScheme ? props.namingScheme : new HashedAddressingScheme()); this.name = props.stackName !== undefined ? props.stackName : this.calculateStackName(); this.autoDeploy = props && props.autoDeploy === false ? false : true; + + if (!Stack.VALID_STACK_NAME_REGEX.test(this.name)) { + throw new Error(`Stack name must match the regular expression: ${Stack.VALID_STACK_NAME_REGEX.toString()}, got '${name}'`); + } } /** diff --git a/packages/@aws-cdk/cdk/test/test.stack.ts b/packages/@aws-cdk/cdk/test/test.stack.ts index 4a7791a0778a4..8d7e37f85b135 100644 --- a/packages/@aws-cdk/cdk/test/test.stack.ts +++ b/packages/@aws-cdk/cdk/test/test.stack.ts @@ -396,6 +396,33 @@ export = { test.done(); }, + + 'stack construct id does not go through stack name validation if there is an explicit stack name'(test: Test) { + // GIVEN + const app = new App(); + + // WHEN + const stack = new Stack(app, 'invalid as : stack name, but thats fine', { + stackName: 'valid-stack-name' + }); + + // THEN + const session = app.run(); + test.deepEqual(stack.name, 'valid-stack-name'); + test.ok('valid-stack-name' in (session.manifest.artifacts || {})); + test.done(); + }, + + 'stack validation is performed on explicit stack name'(test: Test) { + // GIVEN + const app = new App(); + + // THEN + test.throws(() => new Stack(app, 'boom', { stackName: 'invalid:stack:name' }), + /Stack name must match the regular expression/); + + test.done(); + } }; class StackWithPostProcessor extends Stack { From 06ad62642fb9a632f925a663ecaf237298d09c30 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 17:37:57 +0300 Subject: [PATCH 08/11] update decdk snapshot --- .../test/__snapshots__/synth.test.js.snap | 382 +++++++++--------- 1 file changed, 191 insertions(+), 191 deletions(-) diff --git a/packages/decdk/test/__snapshots__/synth.test.js.snap b/packages/decdk/test/__snapshots__/synth.test.js.snap index 43b02931055f4..8a3fda1177c70 100644 --- a/packages/decdk/test/__snapshots__/synth.test.js.snap +++ b/packages/decdk/test/__snapshots__/synth.test.js.snap @@ -927,14 +927,14 @@ Object { exports[`lambda-events.json: lambda-events 1`] = ` Object { "Outputs": Object { - "HelloWorldFunctionApiEventSourceA7A86A4FEndpointF99496B3": Object { + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FEndpointC6202FF3": Object { "Value": Object { "Fn::Join": Array [ "", Array [ "https://", Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", }, ".execute-api.", Object { @@ -946,7 +946,7 @@ Object { }, "/", Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FDeploymentStageprod064E2683", + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FDeploymentStageprod6A86C016", }, "/", ], @@ -965,188 +965,6 @@ Object { }, }, "Resources": Object { - "HelloWorldFunctionApiEventSourceA7A86A4FAccount79978FBC": Object { - "DependsOn": Array [ - "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", - ], - "Properties": Object { - "CloudWatchRoleArn": Object { - "Fn::GetAtt": Array [ - "HelloWorldFunctionApiEventSourceA7A86A4FCloudWatchRole26206BB7", - "Arn", - ], - }, - }, - "Type": "AWS::ApiGateway::Account", - }, - "HelloWorldFunctionApiEventSourceA7A86A4FCloudWatchRole26206BB7": Object { - "Properties": Object { - "AssumeRolePolicyDocument": Object { - "Statement": Array [ - Object { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": Object { - "Service": Object { - "Fn::Join": Array [ - "", - Array [ - "apigateway.", - Object { - "Ref": "AWS::URLSuffix", - }, - ], - ], - }, - }, - }, - ], - "Version": "2012-10-17", - }, - "ManagedPolicyArns": Array [ - Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs", - ], - ], - }, - ], - }, - "Type": "AWS::IAM::Role", - }, - "HelloWorldFunctionApiEventSourceA7A86A4FDeployment1834FFEC33de358c7cd6f808076d7fb5561d9930": Object { - "DependsOn": Array [ - "HelloWorldFunctionApiEventSourceA7A86A4FhelloGET79ECD95F", - "HelloWorldFunctionApiEventSourceA7A86A4FhelloPOST0D6D6818", - "HelloWorldFunctionApiEventSourceA7A86A4FhelloF1478520", - ], - "Properties": Object { - "Description": "Automatically created by the RestApi construct", - "RestApiId": Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", - }, - }, - "Type": "AWS::ApiGateway::Deployment", - }, - "HelloWorldFunctionApiEventSourceA7A86A4FDeploymentStageprod064E2683": Object { - "Properties": Object { - "DeploymentId": Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FDeployment1834FFEC33de358c7cd6f808076d7fb5561d9930", - }, - "RestApiId": Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", - }, - "StageName": "prod", - }, - "Type": "AWS::ApiGateway::Stage", - }, - "HelloWorldFunctionApiEventSourceA7A86A4FF396C351": Object { - "Properties": Object { - "Name": "ApiEventSourceA7A86A4F", - }, - "Type": "AWS::ApiGateway::RestApi", - }, - "HelloWorldFunctionApiEventSourceA7A86A4FhelloF1478520": Object { - "Properties": Object { - "ParentId": Object { - "Fn::GetAtt": Array [ - "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", - "RootResourceId", - ], - }, - "PathPart": "hello", - "RestApiId": Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", - }, - }, - "Type": "AWS::ApiGateway::Resource", - }, - "HelloWorldFunctionApiEventSourceA7A86A4FhelloGET79ECD95F": Object { - "Properties": Object { - "AuthorizationType": "NONE", - "HttpMethod": "GET", - "Integration": Object { - "IntegrationHttpMethod": "POST", - "Type": "AWS_PROXY", - "Uri": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":apigateway:", - Object { - "Ref": "AWS::Region", - }, - ":lambda:path/2015-03-31/functions/", - Object { - "Fn::GetAtt": Array [ - "HelloWorldFunctionB2AB6E79", - "Arn", - ], - }, - "/invocations", - ], - ], - }, - }, - "ResourceId": Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FhelloF1478520", - }, - "RestApiId": Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", - }, - }, - "Type": "AWS::ApiGateway::Method", - }, - "HelloWorldFunctionApiEventSourceA7A86A4FhelloPOST0D6D6818": Object { - "Properties": Object { - "AuthorizationType": "NONE", - "HttpMethod": "POST", - "Integration": Object { - "IntegrationHttpMethod": "POST", - "Type": "AWS_PROXY", - "Uri": Object { - "Fn::Join": Array [ - "", - Array [ - "arn:", - Object { - "Ref": "AWS::Partition", - }, - ":apigateway:", - Object { - "Ref": "AWS::Region", - }, - ":lambda:path/2015-03-31/functions/", - Object { - "Fn::GetAtt": Array [ - "HelloWorldFunctionB2AB6E79", - "Arn", - ], - }, - "/invocations", - ], - ], - }, - }, - "ResourceId": Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FhelloF1478520", - }, - "RestApiId": Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", - }, - }, - "Type": "AWS::ApiGateway::Method", - }, "HelloWorldFunctionApiPermissionGEThello0F001828": Object { "Properties": Object { "Action": "lambda:InvokeFunction", @@ -1175,11 +993,11 @@ Object { }, ":", Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", }, "/", Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FDeploymentStageprod064E2683", + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FDeploymentStageprod6A86C016", }, "/GET/hello", ], @@ -1216,11 +1034,11 @@ Object { }, ":", Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", }, "/", Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FDeploymentStageprod064E2683", + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FDeploymentStageprod6A86C016", }, "/POST/hello", ], @@ -1257,7 +1075,7 @@ Object { }, ":", Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", }, "/test-invoke-stage/GET/hello", ], @@ -1294,7 +1112,7 @@ Object { }, ":", Object { - "Ref": "HelloWorldFunctionApiEventSourceA7A86A4FF396C351", + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", }, "/test-invoke-stage/POST/hello", ], @@ -1513,6 +1331,188 @@ Object { }, "Type": "AWS::DynamoDB::Table", }, + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F": Object { + "Properties": Object { + "Name": "lambdaeventsHelloWorldFunctionAB27BB65:ApiEventSourceA7A86A4F", + }, + "Type": "AWS::ApiGateway::RestApi", + }, + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FAccountF7734F1E": Object { + "DependsOn": Array [ + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", + ], + "Properties": Object { + "CloudWatchRoleArn": Object { + "Fn::GetAtt": Array [ + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FCloudWatchRole495FDB9D", + "Arn", + ], + }, + }, + "Type": "AWS::ApiGateway::Account", + }, + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FCloudWatchRole495FDB9D": Object { + "Properties": Object { + "AssumeRolePolicyDocument": Object { + "Statement": Array [ + Object { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": Object { + "Service": Object { + "Fn::Join": Array [ + "", + Array [ + "apigateway.", + Object { + "Ref": "AWS::URLSuffix", + }, + ], + ], + }, + }, + }, + ], + "Version": "2012-10-17", + }, + "ManagedPolicyArns": Array [ + Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs", + ], + ], + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FDeploymentFF3F4A1Aa119fa0e654d84489ef88606c21a73fb": Object { + "DependsOn": Array [ + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FhelloGET04FBC7F6", + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FhelloPOST53F177B1", + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FhelloC30B3CD9", + ], + "Properties": Object { + "Description": "Automatically created by the RestApi construct", + "RestApiId": Object { + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", + }, + }, + "Type": "AWS::ApiGateway::Deployment", + }, + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FDeploymentStageprod6A86C016": Object { + "Properties": Object { + "DeploymentId": Object { + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FDeploymentFF3F4A1Aa119fa0e654d84489ef88606c21a73fb", + }, + "RestApiId": Object { + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", + }, + "StageName": "prod", + }, + "Type": "AWS::ApiGateway::Stage", + }, + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FhelloC30B3CD9": Object { + "Properties": Object { + "ParentId": Object { + "Fn::GetAtt": Array [ + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", + "RootResourceId", + ], + }, + "PathPart": "hello", + "RestApiId": Object { + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", + }, + }, + "Type": "AWS::ApiGateway::Resource", + }, + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FhelloGET04FBC7F6": Object { + "Properties": Object { + "AuthorizationType": "NONE", + "HttpMethod": "GET", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "HelloWorldFunctionB2AB6E79", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FhelloC30B3CD9", + }, + "RestApiId": Object { + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, + "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FhelloPOST53F177B1": Object { + "Properties": Object { + "AuthorizationType": "NONE", + "HttpMethod": "POST", + "Integration": Object { + "IntegrationHttpMethod": "POST", + "Type": "AWS_PROXY", + "Uri": Object { + "Fn::Join": Array [ + "", + Array [ + "arn:", + Object { + "Ref": "AWS::Partition", + }, + ":apigateway:", + Object { + "Ref": "AWS::Region", + }, + ":lambda:path/2015-03-31/functions/", + Object { + "Fn::GetAtt": Array [ + "HelloWorldFunctionB2AB6E79", + "Arn", + ], + }, + "/invocations", + ], + ], + }, + }, + "ResourceId": Object { + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4FhelloC30B3CD9", + }, + "RestApiId": Object { + "Ref": "lambdaeventsHelloWorldFunctionAB27BB65ApiEventSourceA7A86A4F12449C5F", + }, + }, + "Type": "AWS::ApiGateway::Method", + }, }, } `; From c339fca71795546cd1ca8b7648f43677712b43c5 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 18:33:10 +0300 Subject: [PATCH 09/11] fix merge issues --- packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts | 3 ++- packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts | 4 ++-- packages/@aws-cdk/aws-ecs/lib/ec2/ec2-task-definition.ts | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) 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 adcdbf571ee9e..e65d9974a83de 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,6 +3,7 @@ import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); import { ITaskDefinition } from '../base/task-definition'; import { ICluster } from '../cluster'; +import { isEc2Compatible } from '../util'; /** * Properties to define an EC2 Event Task @@ -37,7 +38,7 @@ export class Ec2EventRuleTarget extends cdk.Construct implements events.IEventRu constructor(scope: cdk.Construct, id: string, props: Ec2EventRuleTargetProps) { super(scope, id); - if (!props.taskDefinition.isEc2Compatible) { + if (!isEc2Compatible(props.taskDefinition.compatibility)) { 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 f094cab23a46c..43337f9215256 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -116,8 +116,8 @@ export class Ec2Service extends BaseService implements IEc2Service, elb.ILoadBal cluster: props.cluster.clusterName, taskDefinition: props.taskDefinition.taskDefinitionArn, launchType: 'EC2', - placementConstraints: new cdk.Token(() => this.constraints.length > 0 ? this.constraints : undefined), - placementStrategies: new cdk.Token(() => this.strategies.length > 0 ? this.strategies : undefined), + placementConstraints: new Token(() => this.constraints.length > 0 ? this.constraints : undefined), + placementStrategies: new Token(() => this.strategies.length > 0 ? this.strategies : undefined), schedulingStrategy: props.daemon ? 'DAEMON' : 'REPLICA', }, props.cluster.clusterName, props.taskDefinition); 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 68d7cb2d1d918..d7fa0e32e9bac 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,6 +1,5 @@ import { Construct, Resource } from '@aws-cdk/cdk'; -import { CommonTaskDefinitionProps, Compatibility, ITaskDefinition, NetworkMode, PlacementConstraint, TaskDefinition } from '../base/task-definition'; -import { CommonTaskDefinitionProps, Compatibility, NetworkMode, TaskDefinition } from '../base/task-definition'; +import { CommonTaskDefinitionProps, Compatibility, ITaskDefinition, NetworkMode, TaskDefinition } from '../base/task-definition'; import { PlacementConstraint } from '../placement'; /** From c4b14863bf0f92d48c45cdecddcb3bb72da5e556 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 19:20:18 +0300 Subject: [PATCH 10/11] fix compat. --- packages/@aws-cdk/aws-ecs/lib/ec2/ec2-event-rule-target.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 e65d9974a83de..33f2df17307f7 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 @@ -1,9 +1,8 @@ import events = require ('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import { ITaskDefinition } from '../base/task-definition'; +import { Compatibility, ITaskDefinition } 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.compatibility === Compatibility.Fargate) { throw new Error('Supplied TaskDefinition is not configured for compatibility with EC2'); } From 9c767c1af262aab467ef5a6f104f1f91b19d62c6 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 8 May 2019 20:04:58 +0300 Subject: [PATCH 11/11] fix API --- .../aws-ecs/lib/base/task-definition.ts | 50 +++++++++++++------ .../aws-ecs/lib/ec2/ec2-task-definition.ts | 2 + .../lib/fargate/fargate-task-definition.ts | 2 + 3 files changed, 38 insertions(+), 16 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 05785943ad6ad..09fa5d7d28479 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/task-definition.ts @@ -20,6 +20,16 @@ export interface ITaskDefinition extends IResource { * What launch types this task definition should be compatible with. */ readonly compatibility: Compatibility; + + /** + * Return true if the task definition can be run on an EC2 cluster + */ + readonly isEc2Compatible: boolean; + + /** + * Return true if the task definition can be run on a Fargate cluster + */ + readonly isFargateCompatible: boolean; } /** @@ -113,10 +123,31 @@ export interface TaskDefinitionProps extends CommonTaskDefinitionProps { readonly memoryMiB?: string; } +abstract class TaskDefinitionBase extends Resource implements ITaskDefinition { + + public abstract readonly compatibility: Compatibility; + public abstract readonly taskDefinitionArn: string; + public abstract readonly executionRole?: iam.IRole; + + /** + * 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); + } +} + /** * Base class for Ecs and Fargate task definitions */ -export class TaskDefinition extends Resource implements ITaskDefinition { +export class TaskDefinition extends TaskDefinitionBase { /** * Imports a task definition by ARN. @@ -124,9 +155,10 @@ export class TaskDefinition extends Resource implements ITaskDefinition { * The task will have a compatibility of EC2+Fargate. */ public static fromTaskDefinitionArn(scope: Construct, id: string, taskDefinitionArn: string): ITaskDefinition { - class Import extends Resource implements ITaskDefinition { + class Import extends TaskDefinitionBase { public readonly taskDefinitionArn = taskDefinitionArn; public readonly compatibility = Compatibility.Ec2AndFargate; + public readonly executionRole?: iam.IRole = undefined; } return new Import(scope, id); @@ -313,20 +345,6 @@ export class TaskDefinition extends Resource implements ITaskDefinition { 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 */ 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 d7fa0e32e9bac..5c773a0cba82e 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 @@ -40,6 +40,8 @@ export class Ec2TaskDefinition extends TaskDefinition implements IEc2TaskDefinit class Import extends Resource implements IEc2TaskDefinition { public readonly taskDefinitionArn = ec2TaskDefinitionArn; public readonly compatibility = Compatibility.Ec2; + public readonly isEc2Compatible = true; + public readonly isFargateCompatible = false; } return new Import(scope, id); } diff --git a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts index 212102b660500..dcfff8539da7b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-task-definition.ts @@ -53,6 +53,8 @@ export class FargateTaskDefinition extends TaskDefinition implements IFargateTas class Import extends Resource implements IFargateTaskDefinition { public readonly taskDefinitionArn = fargateTaskDefinitionArn; public readonly compatibility = Compatibility.Fargate; + public readonly isEc2Compatible = false; + public readonly isFargateCompatible = true; } return new Import(scope, id);