Skip to content

Commit

Permalink
ApiGatewayV2: share domain name
Browse files Browse the repository at this point in the history
  • Loading branch information
fwang committed Oct 3, 2024
1 parent 14559ba commit 11d96c9
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 77 deletions.
102 changes: 65 additions & 37 deletions platform/src/components/aws/apigateway-websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ApiGatewayWebSocketRoute } from "./apigateway-websocket-route";
import { setupApiGatewayAccount } from "./helpers/apigateway-account";
import { apigatewayv2, cloudwatch } from "@pulumi/aws";
import { permission } from "./permission";
import { VisibleError } from "../error";

export interface ApiGatewayWebSocketArgs {
/**
Expand Down Expand Up @@ -206,7 +207,7 @@ export class ApiGatewayWebSocket extends Component implements Link.Linkable {
private constructorOpts: ComponentResourceOptions;
private api: apigatewayv2.Api;
private stage: apigatewayv2.Stage;
private apigDomain?: apigatewayv2.DomainName;
private apigDomain?: Output<apigatewayv2.DomainName>;
private apiMapping?: Output<apigatewayv2.ApiMapping>;
private logGroup: cloudwatch.LogGroup;

Expand Down Expand Up @@ -255,21 +256,28 @@ export class ApiGatewayWebSocket extends Component implements Link.Linkable {
function normalizeDomain() {
if (!args.domain) return;

// validate
output(args.domain).apply((domain) => {
if (typeof domain === "string") return;

if (!domain.name) throw new Error(`Missing "name" for domain.`);
if (domain.dns === false && !domain.cert)
throw new Error(`No "cert" provided for domain with disabled DNS.`);
});

// normalize
return output(args.domain).apply((domain) => {
// validate
if (typeof domain !== "string") {
if (domain.name && domain.nameId)
throw new VisibleError(
`Cannot configure both domain "name" and "nameId" for the "${name}" API.`,
);
if (!domain.name && !domain.nameId)
throw new VisibleError(
`Either domain "name" or "nameId" is required for the "${name}" API.`,
);
if (domain.dns === false && !domain.cert)
throw new VisibleError(
`Domain "cert" is required when "dns" is disabled for the "${name}" API.`,
);
}

// normalize
const norm = typeof domain === "string" ? { name: domain } : domain;

return {
name: norm.name,
nameId: norm.nameId,
path: norm.path,
dns: norm.dns === false ? undefined : norm.dns ?? awsDns(),
cert: norm.cert,
Expand Down Expand Up @@ -345,15 +353,16 @@ export class ApiGatewayWebSocket extends Component implements Link.Linkable {
}

function createSsl() {
if (!domain) return;
if (!domain) return output(undefined);

return domain.apply((domain) => {
if (domain.cert) return output(domain.cert);
if (domain.nameId) return output(undefined);

return new DnsValidatedCertificate(
`${name}Ssl`,
{
domainName: domain.name,
domainName: domain.name!,
dns: domain.dns!,
},
{ parent },
Expand All @@ -364,35 +373,43 @@ export class ApiGatewayWebSocket extends Component implements Link.Linkable {
function createDomainName() {
if (!domain || !certificateArn) return;

return new apigatewayv2.DomainName(
...transform(
args.transform?.domainName,
`${name}DomainName`,
{
domainName: domain?.name,
domainNameConfiguration: {
certificateArn,
endpointType: "REGIONAL",
securityPolicy: "TLS_1_2",
},
},
{ parent },
),
);
return all([domain, certificateArn]).apply(([domain, certificateArn]) => {
return domain.nameId
? apigatewayv2.DomainName.get(
`${name}DomainName`,
domain.nameId,
{},
{ parent },
)
: new apigatewayv2.DomainName(
...transform(
args.transform?.domainName,
`${name}DomainName`,
{
domainName: domain.name!,
domainNameConfiguration: {
certificateArn: certificateArn!,
endpointType: "REGIONAL",
securityPolicy: "TLS_1_2",
},
},
{ parent },
),
);
});
}

function createDnsRecords(): void {
if (!domain || !apigDomain) {
return;
}
if (!domain || !apigDomain) return;

domain.dns.apply((dns) => {
if (!dns) return;
domain.apply((domain) => {
if (!domain.dns) return;
if (domain.nameId) return;

dns.createAlias(
domain.dns.createAlias(
name,
{
name: domain.name,
name: domain.name!,
aliasName: apigDomain.domainNameConfiguration.targetDomainName,
aliasZone: apigDomain.domainNameConfiguration.hostedZoneId,
},
Expand Down Expand Up @@ -463,11 +480,22 @@ export class ApiGatewayWebSocket extends Component implements Link.Linkable {
* The underlying [resources](/docs/components/#nodes) this component creates.
*/
public get nodes() {
const self = this;
return {
/**
* The Amazon API Gateway V2 API.
*/
api: this.api,
/**
* The API Gateway HTTP API domain name.
*/
get domainName() {
if (!self.apigDomain)
throw new VisibleError(
`"nodes.domainName" is not available when domain is not configured for the "${self.constructorName}" API.`,
);
return self.apigDomain;
},
/**
* The CloudWatch LogGroup for the access logs.
*/
Expand Down Expand Up @@ -531,7 +559,7 @@ export class ApiGatewayWebSocket extends Component implements Link.Linkable {
*/
public route(
route: string,
handler: Input<string | FunctionArgs | FunctionArn>,
handler: Input<string | FunctionArgs | FunctionArn>,
args: ApiGatewayWebSocketRouteArgs = {},
) {
const prefix = this.constructorName;
Expand Down
94 changes: 55 additions & 39 deletions platform/src/components/aws/apigatewayv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ export class ApiGatewayV2 extends Component implements Link.Linkable {
private constructorArgs: ApiGatewayV2Args;
private constructorOpts: ComponentResourceOptions;
private api: apigatewayv2.Api;
private apigDomain?: apigatewayv2.DomainName;
private apigDomain?: Output<apigatewayv2.DomainName>;
private apiMapping?: Output<apigatewayv2.ApiMapping>;
private logGroup: cloudwatch.LogGroup;
private vpcLink?: apigatewayv2.VpcLink;
Expand Down Expand Up @@ -685,21 +685,28 @@ export class ApiGatewayV2 extends Component implements Link.Linkable {
function normalizeDomain() {
if (!args.domain) return;

// validate
output(args.domain).apply((domain) => {
if (typeof domain === "string") return;

if (!domain.name) throw new Error(`Missing "name" for domain.`);
if (domain.dns === false && !domain.cert)
throw new Error(`No "cert" provided for domain with disabled DNS.`);
});

// normalize
return output(args.domain).apply((domain) => {
const norm = typeof domain === "string" ? { name: domain } : domain;
// validate
if (typeof domain !== "string") {
if (domain.name && domain.nameId)
throw new VisibleError(
`Cannot configure both domain "name" and "nameId" for the "${name}" API.`,
);
if (!domain.name && !domain.nameId)
throw new VisibleError(
`Either domain "name" or "nameId" is required for the "${name}" API.`,
);
if (domain.dns === false && !domain.cert)
throw new VisibleError(
`Domain "cert" is required when "dns" is disabled for the "${name}" API.`,
);
}

// normalize
const norm = typeof domain === "string" ? { name: domain } : domain;
return {
name: norm.name,
nameId: norm.nameId,
path: norm.path,
dns: norm.dns === false ? undefined : norm.dns ?? awsDns(),
cert: norm.cert,
Expand Down Expand Up @@ -810,15 +817,16 @@ export class ApiGatewayV2 extends Component implements Link.Linkable {
}

function createSsl() {
if (!domain) return;
if (!domain) return output(undefined);

return domain.apply((domain) => {
if (domain.cert) return output(domain.cert);
if (domain.nameId) return output(undefined);

return new DnsValidatedCertificate(
`${name}Ssl`,
{
domainName: domain.name,
domainName: domain.name!,
dns: domain.dns!,
},
{ parent },
Expand All @@ -829,35 +837,43 @@ export class ApiGatewayV2 extends Component implements Link.Linkable {
function createDomainName() {
if (!domain || !certificateArn) return;

return new apigatewayv2.DomainName(
...transform(
args.transform?.domainName,
`${name}DomainName`,
{
domainName: domain?.name,
domainNameConfiguration: {
certificateArn,
endpointType: "REGIONAL",
securityPolicy: "TLS_1_2",
},
},
{ parent },
),
);
return all([domain, certificateArn]).apply(([domain, certificateArn]) => {
return domain.nameId
? apigatewayv2.DomainName.get(
`${name}DomainName`,
domain.nameId,
{},
{ parent },
)
: new apigatewayv2.DomainName(
...transform(
args.transform?.domainName,
`${name}DomainName`,
{
domainName: domain.name!,
domainNameConfiguration: {
certificateArn: certificateArn!,
endpointType: "REGIONAL",
securityPolicy: "TLS_1_2",
},
},
{ parent },
),
);
});
}

function createDnsRecords(): void {
if (!domain || !apigDomain) {
return;
}
if (!domain || !apigDomain) return;

domain.dns.apply((dns) => {
if (!dns) return;
domain.apply((domain) => {
if (!domain.dns) return;
if (domain.nameId) return;

dns.createAlias(
domain.dns.createAlias(
name,
{
name: domain.name,
name: domain.name!,
aliasName: apigDomain.domainNameConfiguration.targetDomainName,
aliasZone: apigDomain.domainNameConfiguration.hostedZoneId,
},
Expand Down Expand Up @@ -917,8 +933,8 @@ export class ApiGatewayV2 extends Component implements Link.Linkable {
*/
get domainName() {
if (!self.apigDomain)
throw new Error(
`"nodes.domainName" is not available when a "domain" is configured.`,
throw new VisibleError(
`"nodes.domainName" is not available when domain is not configured for the "${self.constructorName}" API.`,
);
return self.apigDomain;
},
Expand Down Expand Up @@ -1150,7 +1166,7 @@ export class ApiGatewayV2 extends Component implements Link.Linkable {
) {
if (!this.vpcLink)
throw new VisibleError(
"To add private routes, you need to have a VPC link. Pass `vpc` to the `ApiGatewayV2` component to create a VPC link.",
`To add private routes, you need to have a VPC link. Configure "vpc" for the "${this.constructorName}" API to create a VPC link.`,
);

const route = this.parseRoute(rawRoute);
Expand Down
18 changes: 17 additions & 1 deletion platform/src/components/aws/helpers/apigatewayv2-domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@ import { Input } from "../../input";
import { Dns } from "../../dns";

export interface ApiGatewayV2DomainArgs {
/**
* Use an existing API Gateway domain name.
*
* By default, a new API Gateway domain name is created. If you'd like to use an existing
* domain name, set the `nameId` to the ID of the domain name and **do not** pass in `name`.
*
* @example
* ```js
* {
* domain: {
* nameId: "example.com"
* }
* }
* ```
*/
nameId?: Input<string>;
/**
* The custom domain you want to use.
*
Expand All @@ -24,7 +40,7 @@ export interface ApiGatewayV2DomainArgs {
* }
* ```
*/
name: Input<string>;
name?: Input<string>;
/**
* The base mapping for the custom domain. This adds a suffix to the URL of the API.
*
Expand Down

0 comments on commit 11d96c9

Please sign in to comment.