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

feat(apigatewayv2): http api - domain url for a stage #15973

Merged
merged 8 commits into from
Aug 12, 2021
6 changes: 6 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ with 3 API mapping resources across different APIs and Stages.
| api | beta | `https://${domainName}/bar` |
| apiDemo | $default | `https://${domainName}/demo` |

You can retrieve the full domain URL with mapping key using the `domainUrl` property as so -

```ts
const demoDomainUrl = apiDemo.defaultStage.domainUrl; // returns "https://example.com/demo"
```

### Managing access

API Gateway supports multiple mechanisms for [controlling and managing access to your HTTP
Expand Down
6 changes: 6 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/lib/common/api-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export class ApiMapping extends Resource implements IApiMapping {
*/
public readonly mappingKey?: string;

/**
* API domain name
*/
public readonly domainName: IDomainName;

constructor(scope: Construct, id: string, props: ApiMappingProps) {
super(scope, id);

Expand Down Expand Up @@ -121,5 +126,6 @@ export class ApiMapping extends Resource implements IApiMapping {

this.apiMappingId = resource.ref;
this.mappingKey = props.apiMappingKey;
this.domainName = props.domainName;
}
}
11 changes: 10 additions & 1 deletion packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export abstract class StageBase extends Resource implements IStage {
public abstract readonly stageName: string;
protected abstract readonly baseApi: IApi;

/**
* The created ApiMapping if domain mapping has been added
* @internal
*/
protected _apiMapping?: ApiMapping

/**
* The URL to this stage.
*/
Expand All @@ -45,7 +51,10 @@ export abstract class StageBase extends Resource implements IStage {
* @internal
*/
protected _addDomainMapping(domainMapping: DomainMappingOptions) {
new ApiMapping(this, `${domainMapping.domainName}${domainMapping.mappingKey}`, {
if (this._apiMapping) {
throw new Error('Only one ApiMapping allowed per Stage');
}
this._apiMapping = new ApiMapping(this, `${domainMapping.domainName}${domainMapping.mappingKey}`, {
api: this.baseApi,
domainName: domainMapping.domainName,
stage: this,
Expand Down
11 changes: 11 additions & 0 deletions packages/@aws-cdk/aws-apigatewayv2/lib/http/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,15 @@ export class HttpStage extends HttpStageBase {
const urlPath = this.stageName === DEFAULT_STAGE_NAME ? '' : this.stageName;
return `https://${this.api.apiId}.execute-api.${s.region}.${s.urlSuffix}/${urlPath}`;
}

/**
* The custom domain URL to this stage
*/
public get domainUrl(): string {
if (!this._apiMapping) {
throw new Error('domainUrl is not available when no API mapping is associated with the Stage');
}

return `https://${this._apiMapping.domainName.name}/${this._apiMapping.mappingKey ?? ''}`;
}
}
47 changes: 45 additions & 2 deletions packages/@aws-cdk/aws-apigatewayv2/test/http/stage.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Template } from '@aws-cdk/assertions';
import { Certificate } from '@aws-cdk/aws-certificatemanager';
import { Metric } from '@aws-cdk/aws-cloudwatch';
import { Stack } from '@aws-cdk/core';
import { HttpApi, HttpStage } from '../../lib';

import { DomainName, HttpApi, HttpStage } from '../../lib';

describe('HttpStage', () => {
test('default', () => {
Expand Down Expand Up @@ -116,4 +116,47 @@ describe('HttpStage', () => {
const metricNames = metrics.map(m => m.metricName);
expect(metricNames).toEqual(['4xx', '5xx', 'DataProcessed', 'Latency', 'IntegrationLatency', 'Count']);
});
});

describe('HttpStage with domain mapping', () => {
const domainName = 'example.com';
const certArn = 'arn:aws:acm:us-east-1:111111111111:certificate';

test('domainUrl returns the correct path', () => {
const stack = new Stack();
const api = new HttpApi(stack, 'Api', {
createDefaultStage: false,
});

const dn = new DomainName(stack, 'DN', {
domainName,
certificate: Certificate.fromCertificateArn(stack, 'cert', certArn),
});

const stage = new HttpStage(stack, 'DefaultStage', {
httpApi: api,
domainMapping: {
domainName: dn,
},
});

expect(stage.domainUrl).toBe(`https://${domainName}/`);
});

test('domainUrl throws error if domainMapping is not configured', () => {
const stack = new Stack();
const api = new HttpApi(stack, 'Api', {
createDefaultStage: false,
});

const stage = new HttpStage(stack, 'DefaultStage', {
httpApi: api,
});

const t = () => {
stage.domainUrl;
};

expect(t).toThrow(/domainUrl is not available when no API mapping is associated with the Stage/);
});
});