diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts index 245764f12bb56..c7bc50dccf5c8 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/ecs/deploy-action.ts @@ -39,7 +39,7 @@ export interface EcsDeployActionProps extends codepipeline.CommonAwsActionProps /** * The ECS Service to deploy. */ - readonly service: ecs.BaseService; + readonly service: ecs.IBaseService; } /** diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/test.ecs-deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/test.ecs-deploy-action.ts index 89902677ed82d..dfa0a546cbd8c 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/test.ecs-deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/ecs/test.ecs-deploy-action.ts @@ -1,6 +1,8 @@ +import { expect, haveResourceLike } from '@aws-cdk/assert'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as cpactions from '../../lib'; @@ -80,6 +82,71 @@ export = { test.done(); }, + + 'can be created by existing service'(test: Test) { + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'Vpc'); + const service = ecs.FargateService.fromFargateServiceAttributes(stack, 'FargateService', { + serviceName: 'service-name', + cluster: ecs.Cluster.fromClusterAttributes(stack, 'Cluster', { + vpc, + securityGroups: [], + clusterName: 'cluster-name', + }), + }); + const artifact = new codepipeline.Artifact('Artifact'); + const bucket = new s3.Bucket(stack, 'PipelineBucket', { + versioned: true, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + const source = new cpactions.S3SourceAction({ + actionName: 'Source', + output: artifact, + bucket, + bucketKey: 'key', + }); + const action = new cpactions.EcsDeployAction({ + actionName: 'ECS', + service, + imageFile: artifact.atPath('imageFile.json'), + }); + new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [source], + }, + { + stageName: 'Deploy', + actions: [action], + } + ], + }); + + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + Stages: [ + {}, + { + Actions: [ + { + Name: 'ECS', + ActionTypeId: { + Category: "Deploy", + Provider: "ECS" + }, + Configuration: { + ClusterName: "cluster-name", + ServiceName: "service-name", + FileName: "imageFile.json" + } + } + ] + } + ] + })); + + test.done(); + }, }, }; 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 c83c54b03f379..dd51713f7937c 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -22,6 +22,13 @@ export interface IService extends IResource { * @attribute */ readonly serviceArn: string; + + /** + * The name of the service. + * + * @attribute + */ + readonly serviceName: string; } /** @@ -245,11 +252,21 @@ class NetworkListenerConfig extends ListenerConfig { } } +/** + * The interface for BaseService. + */ +export interface IBaseService extends IService { + /** + * The cluster that hosts the service. + */ + readonly cluster: ICluster; +} + /** * The base class for Ec2Service and FargateService services. */ export abstract class BaseService extends Resource - implements IService, elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget, elb.ILoadBalancerTarget { + implements IBaseService, elbv2.IApplicationLoadBalancerTarget, elbv2.INetworkLoadBalancerTarget, elb.ILoadBalancerTarget { /** * The security groups which manage the allowed network traffic for the service. diff --git a/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts b/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts new file mode 100644 index 0000000000000..9128c00b0430d --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts @@ -0,0 +1,57 @@ +import { Construct, Resource, Stack } from '@aws-cdk/core'; +import { IBaseService } from '../base/base-service'; +import { ICluster } from '../cluster'; + +/** + * The properties to import from the service. + */ +export interface ServiceAttributes { + /** + * The cluster that hosts the service. + */ + readonly cluster: ICluster; + + /** + * The service ARN. + * + * @default - either this, or {@link serviceName}, is required + */ + readonly serviceArn?: string; + + /** + * The name of the service. + * + * @default - either this, or {@link serviceArn}, is required + */ + readonly serviceName?: string; +} + +export function fromServiceAtrributes(scope: Construct, id: string, attrs: ServiceAttributes): IBaseService { + if ((attrs.serviceArn && attrs.serviceName) || (!attrs.serviceArn && !attrs.serviceName)) { + throw new Error('You can only specify either serviceArn or serviceName.'); + } + + const stack = Stack.of(scope); + let name: string; + let arn: string; + if (attrs.serviceName) { + name = attrs.serviceName as string; + arn = stack.formatArn({ + partition: stack.partition, + service: 'ecs', + region: stack.region, + account: stack.account, + resource: 'service', + resourceName: name, + }); + } else { + arn = attrs.serviceArn as string; + name = stack.parseArn(arn).resourceName as string; + } + class Import extends Resource implements IBaseService { + public readonly serviceArn = arn; + public readonly serviceName = name; + public readonly cluster = attrs.cluster; + } + return new Import(scope, id); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts index 13877c0785126..64367f32d54aa 100644 --- a/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts @@ -1,7 +1,9 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import { Construct, Lazy, Resource, Stack } from '@aws-cdk/core'; -import { BaseService, BaseServiceOptions, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; +import { BaseService, BaseServiceOptions, IBaseService, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; +import { fromServiceAtrributes } from '../base/from-service-attributes'; import { NetworkMode, TaskDefinition } from '../base/task-definition'; +import { ICluster } from '../cluster'; import { CfnService } from '../ecs.generated'; import { PlacementConstraint, PlacementStrategy } from '../placement'; @@ -87,6 +89,30 @@ export interface IEc2Service extends IService { } +/** + * The properties to import from the service using the EC2 launch type. + */ +export interface Ec2ServiceAttributes { + /** + * The cluster that hosts the service. + */ + readonly cluster: ICluster; + + /** + * The service ARN. + * + * @default - either this, or {@link serviceName}, is required + */ + readonly serviceArn?: string; + + /** + * The name of the service. + * + * @default - either this, or {@link serviceArn}, is required + */ + readonly serviceName?: string; +} + /** * This creates a service using the EC2 launch type on an ECS cluster. * @@ -100,10 +126,18 @@ export class Ec2Service extends BaseService implements IEc2Service { public static fromEc2ServiceArn(scope: Construct, id: string, ec2ServiceArn: string): IEc2Service { class Import extends Resource implements IEc2Service { public readonly serviceArn = ec2ServiceArn; + public readonly serviceName = Stack.of(scope).parseArn(ec2ServiceArn).resourceName as string; } return new Import(scope, id); } + /** + * Imports from the specified service attrributes. + */ + public static fromEc2ServiceAttributes(scope: Construct, id: string, attrs: Ec2ServiceAttributes): IBaseService { + return fromServiceAtrributes(scope, id, attrs); + } + private readonly constraints: CfnService.PlacementConstraintProperty[]; private readonly strategies: CfnService.PlacementStrategyProperty[]; private readonly daemon: boolean; 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 d2c064df93d6f..f8b741a584604 100644 --- a/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts @@ -1,7 +1,9 @@ import * as ec2 from '@aws-cdk/aws-ec2'; import * as cdk from '@aws-cdk/core'; -import { BaseService, BaseServiceOptions, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; +import { BaseService, BaseServiceOptions, IBaseService, IService, LaunchType, PropagatedTagSource } from '../base/base-service'; +import { fromServiceAtrributes } from '../base/from-service-attributes'; import { TaskDefinition } from '../base/task-definition'; +import { ICluster } from '../cluster'; /** * The properties for defining a service using the Fargate launch type. @@ -65,6 +67,30 @@ export interface IFargateService extends IService { } +/** + * The properties to import from the service using the Fargate launch type. + */ +export interface FargateServiceAttributes { + /** + * The cluster that hosts the service. + */ + readonly cluster: ICluster; + + /** + * The service ARN. + * + * @default - either this, or {@link serviceName}, is required + */ + readonly serviceArn?: string; + + /** + * The name of the service. + * + * @default - either this, or {@link serviceArn}, is required + */ + readonly serviceName?: string; +} + /** * This creates a service using the Fargate launch type on an ECS cluster. * @@ -73,15 +99,23 @@ export interface IFargateService extends IService { export class FargateService extends BaseService implements IFargateService { /** - * Import a task definition from the specified task definition ARN. + * Imports from the specified service ARN. */ public static fromFargateServiceArn(scope: cdk.Construct, id: string, fargateServiceArn: string): IFargateService { class Import extends cdk.Resource implements IFargateService { public readonly serviceArn = fargateServiceArn; + public readonly serviceName = cdk.Stack.of(scope).parseArn(fargateServiceArn).resourceName as string; } return new Import(scope, id); } + /** + * Imports from the specified service attrributes. + */ + public static fromFargateServiceAttributes(scope: cdk.Construct, id: string, attrs: FargateServiceAttributes): IBaseService { + return fromServiceAtrributes(scope, id, attrs); + } + /** * Constructs a new instance of the FargateService class. */ diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts index 2cc7349ac6545..61582bfaa8928 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts @@ -1936,5 +1936,74 @@ export = { }); test.done(); - } + }, + + 'When import an EC2 Service': { + 'with serviceArn'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const service = ecs.Ec2Service.fromEc2ServiceAttributes(stack, 'EcsService', { + serviceArn: 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service', + cluster, + }); + + // THEN + test.equal(service.serviceArn, 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service'); + test.equal(service.serviceName, 'my-http-service'); + + test.done(); + }, + + 'with serviceName'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const pseudo = new cdk.ScopedAws(stack); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const service = ecs.Ec2Service.fromEc2ServiceAttributes(stack, 'EcsService', { + serviceName: 'my-http-service', + cluster, + }); + + // THEN + test.deepEqual(stack.resolve(service.serviceArn), stack.resolve(`arn:${pseudo.partition}:ecs:${pseudo.region}:${pseudo.accountId}:service/my-http-service`)); + test.equal(service.serviceName, 'my-http-service'); + + test.done(); + }, + + 'throws an exception if both serviceArn and serviceName were provided for fromEc2ServiceAttributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + test.throws(() => { + ecs.Ec2Service.fromEc2ServiceAttributes(stack, 'EcsService', { + serviceArn: 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service', + serviceName: 'my-http-service', + cluster, + }); + }, /only specify either serviceArn or serviceName/); + + test.done(); + }, + + 'throws an exception if neither serviceArn nor serviceName were provided for fromEc2ServiceAttributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + test.throws(() => { + ecs.Ec2Service.fromEc2ServiceAttributes(stack, 'EcsService', { + cluster, + }); + }, /only specify either serviceArn or serviceName/); + + test.done(); + }, + }, }; diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts index d953b16d78ed7..b798ead19f96f 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/test.fargate-service.ts @@ -1487,5 +1487,74 @@ export = { }); test.done(); - } + }, + + 'When import a Fargate Service': { + 'with serviceArn'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const service = ecs.FargateService.fromFargateServiceAttributes(stack, 'EcsService', { + serviceArn: 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service', + cluster, + }); + + // THEN + test.equal(service.serviceArn, 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service'); + test.equal(service.serviceName, 'my-http-service'); + + test.done(); + }, + + 'with serviceName'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const pseudo = new cdk.ScopedAws(stack); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + // WHEN + const service = ecs.FargateService.fromFargateServiceAttributes(stack, 'EcsService', { + serviceName: 'my-http-service', + cluster, + }); + + // THEN + test.deepEqual(stack.resolve(service.serviceArn), stack.resolve(`arn:${pseudo.partition}:ecs:${pseudo.region}:${pseudo.accountId}:service/my-http-service`)); + test.equal(service.serviceName, 'my-http-service'); + + test.done(); + }, + + 'throws an exception if both serviceArn and serviceName were provided for fromEc2ServiceAttributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + test.throws(() => { + ecs.FargateService.fromFargateServiceAttributes(stack, 'EcsService', { + serviceArn: 'arn:aws:ecs:us-west-2:123456789012:service/my-http-service', + serviceName: 'my-http-service', + cluster, + }); + }, /only specify either serviceArn or serviceName/); + + test.done(); + }, + + 'throws an exception if neither serviceArn nor serviceName were provided for fromEc2ServiceAttributes'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const cluster = new ecs.Cluster(stack, 'EcsCluster'); + + test.throws(() => { + ecs.FargateService.fromFargateServiceAttributes(stack, 'EcsService', { + cluster, + }); + }, /only specify either serviceArn or serviceName/); + + test.done(); + }, + }, };