Skip to content

Commit

Permalink
Added publish extension
Browse files Browse the repository at this point in the history
  • Loading branch information
upparekh committed Sep 1, 2021
1 parent b603675 commit 8564fdf
Show file tree
Hide file tree
Showing 5 changed files with 504 additions and 0 deletions.
28 changes: 28 additions & 0 deletions packages/@aws-cdk-containers/ecs-service-extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The `Service` construct provided by this module can be extended with optional `S
- [AWS AppMesh](https://aws.amazon.com/app-mesh/) for adding your application to a service mesh
- [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html), for exposing your service to the public
- [AWS FireLens](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html), for filtering and routing application logs
- Publisher to allow your service to publish events to SNS Topics or AWS EventBus
- [Community Extensions](#community-extensions), providing support for advanced use cases

The `ServiceExtension` class is an abstract class which you can also implement in
Expand Down Expand Up @@ -321,6 +322,33 @@ const environment = Environment.fromEnvironmentAttributes(stack, 'Environment',

```

## Publisher Extension

This service extension accepts a list of `IPublisher` resources that the service can publish events to. It sets up the corresponding publish permissions for the task role of the service.

### Publishing to SNS Topics

You can use this extension to publish events to SNS Topics.

```ts
nameDescription.add(new PublisherExtension({
publishers: [new PublisherTopic({
topic: new sns.Topic(stack, 'topic2'),
})],
}))
```

You can also provide a list of account IDs for each topic to allow the them to be able to subscribe to the given topic.

```ts
nameDescription.add(new PublisherExtension({
publishers: [new PublisherTopic({
topic: new sns.Topic(stack, 'topic2'),
allowedAccounts: ['123456789012'],
})],
}))
```

## Community Extensions

We encourage the development of Community Service Extensions that support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './cloudwatch-agent';
export * from './scale-on-cpu-utilization';
export * from './xray';
export * from './assign-public-ip';
export * from './publisher';
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import * as ecs from '@aws-cdk/aws-ecs';
import * as iam from '@aws-cdk/aws-iam';
import * as sns from '@aws-cdk/aws-sns';
import { Service } from '../service';
import { Container } from './container';
import { ContainerMutatingHook, ServiceExtension } from './extension-interfaces';

// Keep this import separate from other imports to reduce chance for merge conflicts with v2-main
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct } from '@aws-cdk/core';

/**
* An interface that will be implemented by all the resources that can be published events to.
*/
export interface IPublisher {
publish(taskDefinition: ecs.TaskDefinition): void;
}

/**
* The settings for the `PublisherTopic` class.
*/
export interface PublisherTopicProps {
/**
* The SNS Topic to publish events to.
*/
readonly topic: sns.ITopic;

/**
* The accounts allowed to subscribe to the given `topic`.
*
* @default none
*/
readonly allowedAccounts?: string[];
}

/**
* The `PublisherTopic` class represents SNS Topic resource that can be published to by the parent service.
*/

export class PublisherTopic implements IPublisher {
public readonly topic: sns.ITopic;

constructor(props: PublisherTopicProps) {
this.topic = props.topic;

if (props.allowedAccounts) {
const principals = [];
for (const account of props.allowedAccounts) {
principals.push(new iam.AccountPrincipal(account));
}

this.topic.addToResourcePolicy(new iam.PolicyStatement({
actions: ['sns:Subscribe'],
resources: [this.topic.topicArn],
principals,
}));
}
}

public publish(taskDefinition: ecs.TaskDefinition) {
this.topic.grantPublish(taskDefinition.taskRole);
}
}

/**
* The settings for the Publisher extension.
*/
export interface PublisherExtensionProps {
/**
* The list of publishable resources for this service.
*/
readonly publishers: IPublisher[];
}

/**
* Settings for the hook which mutates the application container
* to add the publisher resource ARNs to its environment.
*/
interface ContainerMutatingProps {
/**
* The resource name and ARN to be added to the container environment.
*/
readonly environment: { [key: string]: string };
}

/**
* This hook modifies the application container's environment to
* add the publisher resource ARNs.
*/
class PublisherExtensionMutatingHook extends ContainerMutatingHook {
private environment: { [key: string]: string };

constructor(props: ContainerMutatingProps) {
super();
this.environment = props.environment;
}

public mutateContainerDefinition(props: ecs.ContainerDefinitionOptions): ecs.ContainerDefinitionOptions {
return {
...props,

environment: { ...(props.environment || {}), ...this.environment },
} as ecs.ContainerDefinitionOptions;
}
}

/**
* This extension accepts a list of `IPublisher` resources that the parent service can publish events to. It sets up
* the corresponding publish permissions for the task role of the parent service.
*/
export class PublisherExtension extends ServiceExtension {
private props: PublisherExtensionProps;

private environment: { [key: string]: string } = {};

constructor(props: PublisherExtensionProps) {
super('publisher');

this.props = props;
}

// @ts-ignore - Ignore unused params that are required for abstract class extend
public prehook(service: Service, scope: Construct) {
this.parentService = service;

for (const resource of this.props.publishers) {
if (resource instanceof PublisherTopic) {
this.environment[`${service.id.toUpperCase()}_${resource.topic.node.id.toUpperCase()}_ARN`] = resource.topic.topicArn;
}
}
}

/**
* Add hooks to the main application extension so that it is modified to
* add the publisher resource ARNs to the container environment.
*/
public addHooks() {
const container = this.parentService.serviceDescription.get('service-container') as Container;

if (!container) {
throw new Error('Publisher Extension requires an application extension');
}

container.addContainerMutatingHook(new PublisherExtensionMutatingHook({
environment: this.environment,
}));
}

/**
* After the task definition has been created, this hook grants SNS publish permissions to the task role.
*
* @param taskDefinition The created task definition
*/
public useTaskDefinition(taskDefinition: ecs.TaskDefinition) {
for (const resource of this.props.publishers) {
resource.publish(taskDefinition);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@aws-cdk/aws-route53": "0.0.0",
"@aws-cdk/aws-route53-targets": "0.0.0",
"@aws-cdk/aws-servicediscovery": "0.0.0",
"@aws-cdk/aws-sns": "0.0.0",
"@aws-cdk/aws-sqs": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/custom-resources": "0.0.0",
Expand All @@ -89,6 +90,7 @@
"@aws-cdk/aws-route53": "0.0.0",
"@aws-cdk/aws-route53-targets": "0.0.0",
"@aws-cdk/aws-servicediscovery": "0.0.0",
"@aws-cdk/aws-sns": "0.0.0",
"@aws-cdk/aws-sqs": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/custom-resources": "0.0.0",
Expand Down
Loading

0 comments on commit 8564fdf

Please sign in to comment.