Skip to content

Commit

Permalink
feat(servicediscovery): AWS Cloud Map construct library (#1804)
Browse files Browse the repository at this point in the history
Add a construct library for AWS Cloud Map.

Partially based on work by @jogold.
  • Loading branch information
SoManyHs authored and rix0rrr committed Mar 13, 2019
1 parent c6c66e9 commit 1187366
Show file tree
Hide file tree
Showing 25 changed files with 2,858 additions and 11 deletions.
32 changes: 32 additions & 0 deletions packages/@aws-cdk/aws-servicediscovery/README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,34 @@
## The CDK Construct Library for AWS Service Discovery
This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project.

This package contains constructs for working with **AWS Cloud Map**

AWS Cloud Map is a fully managed service that you can use to create and
maintain a map of the backend services and resources that your applications
depend on.

For further information on AWS Cloud Map,
see the [AWS Cloud Map documentation](https://docs.aws.amazon.com/cloud-map)

The following example creates an AWS Cloud Map namespace that
supports API calls, creates a service in that namespace, and
registers an instance to it:

[Creating a Cloud Map service within an HTTP namespace](test/integ.service-with-http-namespace.lit.ts)

The following example creates an AWS Cloud Map namespace that
supports both API calls and DNS queries within a vpc, creates a
service in that namespace, and registers a loadbalancer as an
instance:

[Creating a Cloud Map service within a Private DNS namespace](test/integ.service-with-private-dns-namespace.lit.ts)

The following example creates an AWS Cloud Map namespace that
supports both API calls and public DNS queries, creates a service in
that namespace, and registers an IP instance:

[Creating a Cloud Map service within a Public namespace](test/integ.service-with-public-dns-namespace.lit.ts)

For DNS namespaces, you can also register instances to services with CNAME records:

[Creating a Cloud Map service within a Public namespace](test/integ.service-with-cname-record.lit.ts)
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import cdk = require('@aws-cdk/cdk');
import { BaseInstanceProps, InstanceBase } from './instance';
import { NamespaceType } from './namespace';
import { DnsRecordType, IService, RoutingPolicy } from './service';
import { CfnInstance } from './servicediscovery.generated';

/*
* Properties for an AliasTargetInstance
*/
export interface AliasTargetInstanceProps extends BaseInstanceProps {
/**
* DNS name of the target
*/
dnsName: string;

/**
* The Cloudmap service this resource is registered to.
*/
service: IService;
}

/*
* Instance that uses Route 53 Alias record type. Currently, the only resource types supported are Elastic Load
* Balancers.
*/
export class AliasTargetInstance extends InstanceBase {
/**
* The Id of the instance
*/
public readonly instanceId: string;

/**
* The Cloudmap service to which the instance is registered.
*/
public readonly service: IService;

/**
* The Route53 DNS name of the alias target
*/
public readonly dnsName: string;

constructor(scope: cdk.Construct, id: string, props: AliasTargetInstanceProps) {
super(scope, id);

if (props.service.namespace.type === NamespaceType.Http) {
throw new Error('Namespace associated with Service must be a DNS Namespace.');
}

// Should already be enforced when creating service, but validates if service is not instantiated with #createService
const dnsRecordType = props.service.dnsRecordType;
if (dnsRecordType !== DnsRecordType.A
&& dnsRecordType !== DnsRecordType.AAAA
&& dnsRecordType !== DnsRecordType.A_AAAA) {
throw new Error('Service must use `A` or `AAAA` records to register an AliasRecordTarget.');
}

if (props.service.routingPolicy !== RoutingPolicy.Weighted) {
throw new Error('Service must use `WEIGHTED` routing policy.');
}

const resource = new CfnInstance(this, 'Resource', {
instanceAttributes: {
AWS_ALIAS_DNS_NAME: props.dnsName,
...props.customAttributes
},
instanceId: props.instanceId || this.node.uniqueId,
serviceId: props.service.serviceId
});

this.service = props.service;
this.instanceId = resource.instanceId;
this.dnsName = props.dnsName;
}
}
72 changes: 72 additions & 0 deletions packages/@aws-cdk/aws-servicediscovery/lib/cname-instance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import cdk = require('@aws-cdk/cdk');
import { BaseInstanceProps, InstanceBase } from './instance';
import { NamespaceType } from './namespace';
import { DnsRecordType, IService } from './service';
import { CfnInstance } from './servicediscovery.generated';

/*
* Properties for a CnameInstance used for service#registerCnameInstance
*/
export interface CnameInstanceBaseProps extends BaseInstanceProps {
/**
* If the service configuration includes a CNAME record, the domain name that you want Route 53 to
* return in response to DNS queries, for example, example.com. This value is required if the
* service specified by ServiceId includes settings for an CNAME record.
*/
instanceCname: string;
}

/*
* Properties for a CnameInstance
*/
export interface CnameInstanceProps extends CnameInstanceBaseProps {
/**
* The Cloudmap service this resource is registered to.
*/
service: IService;
}

/*
* Instance that is accessible using a domain name (CNAME).
*/
export class CnameInstance extends InstanceBase {
/**
* The Id of the instance
*/
public readonly instanceId: string;

/**
* The Cloudmap service to which the instance is registered.
*/
public readonly service: IService;

/**
* The domain name returned by DNS queries for the instance
*/
public readonly instanceCname: string;

constructor(scope: cdk.Construct, id: string, props: CnameInstanceProps) {
super(scope, id);

if (props.service.namespace.type === NamespaceType.Http) {
throw new Error('Namespace associated with Service must be a DNS Namespace.');
}

if (props.service.dnsRecordType !== DnsRecordType.CNAME) {
throw new Error('A `CnameIntance` can only be used with a service using a `CNAME` record.');
}

const resource = new CfnInstance(this, 'Resource', {
instanceId: props.instanceId || this.uniqueInstanceId(),
serviceId: props.service.serviceId,
instanceAttributes: {
AWS_INSTANCE_CNAME: props.instanceCname,
...props.customAttributes
}
});

this.service = props.service;
this.instanceId = resource.instanceId;
this.instanceCname = props.instanceCname;
}
}
56 changes: 56 additions & 0 deletions packages/@aws-cdk/aws-servicediscovery/lib/http-namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import cdk = require('@aws-cdk/cdk');
import { BaseNamespaceProps, NamespaceBase, NamespaceType } from './namespace';
import { BaseServiceProps, Service } from './service';
import { CfnHttpNamespace } from './servicediscovery.generated';

// tslint:disable:no-empty-interface
export interface HttpNamespaceProps extends BaseNamespaceProps {}

/**
* Define an HTTP Namespace
*/
export class HttpNamespace extends NamespaceBase {
/**
* A name for the namespace.
*/
public readonly namespaceName: string;

/**
* Namespace Id for the namespace.
*/
public readonly namespaceId: string;

/**
* Namespace Arn for the namespace.
*/
public readonly namespaceArn: string;

/**
* Type of the namespace.
*/
public readonly type: NamespaceType;

constructor(scope: cdk.Construct, id: string, props: HttpNamespaceProps) {
super(scope, id);

const ns = new CfnHttpNamespace(this, 'Resource', {
name: props.name,
description: props.description
});

this.namespaceName = props.name;
this.namespaceId = ns.httpNamespaceId;
this.namespaceArn = ns.httpNamespaceArn;
this.type = NamespaceType.Http;
}

/**
* Creates a service within the namespace
*/
public createService(id: string, props?: BaseServiceProps): Service {
return new Service(this, id, {
namespace: this,
...props
});
}
}
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-servicediscovery/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
export * from './instance';
export * from './alias-target-instance';
export * from './cname-instance';
export * from './ip-instance';
export * from './non-ip-instance';
export * from './namespace';
export * from './http-namespace';
export * from './private-dns-namespace';
export * from './public-dns-namespace';
export * from './service';
// AWS::ServiceDiscovery CloudFormation Resources:
export * from './servicediscovery.generated';
55 changes: 55 additions & 0 deletions packages/@aws-cdk/aws-servicediscovery/lib/instance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import cdk = require('@aws-cdk/cdk');
import { IService } from './service';

export interface IInstance extends cdk.IConstruct {
/**
* The id of the instance resource
*/
readonly instanceId: string;

/**
* The Cloudmap service this resource is registered to.
*/
readonly service: IService;
}

/**
* Used when the resource that's associated with the service instance is accessible using values other than an IP
* address or a domain name (CNAME), i.e. for non-ip-instances
*/
export interface BaseInstanceProps {
/**
* The id of the instance resource
*
* @default Automatically generated name
*/
instanceId?: string;

/**
* Custom attributes of the instance.
*
* @default none
*/
customAttributes?: { [key: string]: string };
}

export abstract class InstanceBase extends cdk.Construct implements IInstance {
/**
* The Id of the instance
*/
public abstract readonly instanceId: string;

/**
* The Cloudmap service to which the instance is registered.
*/
public abstract readonly service: IService;

/**
* Generate a unique instance Id that is safe to pass to CloudMap
*/
protected uniqueInstanceId() {
// Max length of 64 chars, get the last 64 chars
const id = this.node.uniqueId;
return id.substring(Math.max(id.length - 64, 0), id.length);
}
}
Loading

0 comments on commit 1187366

Please sign in to comment.