Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(codepipeline-actions): use IBaseService instead of BaseService in EcsDeployActionProps #6412

Merged
merged 18 commits into from
Mar 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface EcsDeployActionProps extends codepipeline.CommonAwsActionProps
/**
* The ECS Service to deploy.
*/
readonly service: ecs.BaseService;
readonly service: ecs.IBaseService;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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,
});
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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();
},
},
};

Expand Down
19 changes: 18 additions & 1 deletion packages/@aws-cdk/aws-ecs/lib/base/base-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export interface IService extends IResource {
* @attribute
*/
readonly serviceArn: string;

/**
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
* The name of the service.
*
* @attribute
*/
readonly serviceName: string;
}

/**
Expand Down Expand Up @@ -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.
Expand Down
57 changes: 57 additions & 0 deletions packages/@aws-cdk/aws-ecs/lib/base/from-service-attributes.ts
Original file line number Diff line number Diff line change
@@ -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);
}
36 changes: 35 additions & 1 deletion packages/@aws-cdk/aws-ecs/lib/ec2/ec2-service.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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;

/**
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
* The service ARN.
*
* @default - either this, or {@link serviceName}, is required
*/
readonly serviceArn?: string;

/**
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
* 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.
*
Expand All @@ -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;
Expand Down
38 changes: 36 additions & 2 deletions packages/@aws-cdk/aws-ecs/lib/fargate/fargate-service.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;

/**
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
* The service ARN.
*
* @default - either this, or {@link serviceName}, is required
*/
readonly serviceArn?: string;

/**
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
* 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.
*
Expand All @@ -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.
*/
Expand Down
71 changes: 70 additions & 1 deletion packages/@aws-cdk/aws-ecs/test/ec2/test.ec2-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
},
},
};
Loading