Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ custom:
| createRoute53IPv6Record | `true` | Toggles whether or not the plugin will create an AAAA Alias record in Route53 mapping the `domainName` to the generated distribution domain name. If false, does not create a record. |
| route53Profile | `(none)` | Profile to use for accessing Route53 resources when Route53 records are in a different account |
| route53Region | `(none)` | Region to send Route53 services requests to (only applicable if also using route53Profile option) |
| endpointType | `EDGE` | Defines the endpoint type, accepts `REGIONAL` or `EDGE`. |
| endpointType | `EDGE` | Defines the endpoint type, accepts `PRIVATE`, `REGIONAL` or `EDGE`. |
| apiType | rest | Defines the api type, accepts `rest`, `http` or `websocket`. |
| tlsTruststoreUri | `undefined` | An Amazon S3 url that specifies the truststore for mutual TLS authentication, for example `s3://bucket-name/key-name`. The truststore can contain certificates from public or private certificate authorities. Be aware mutual TLS is only available for `regional` APIs. |
| tlsTruststoreVersion | `undefined` | The version of the S3 object that contains your truststore. To specify a version, you must have versioning enabled for the S3 bucket. |
Expand Down
3 changes: 3 additions & 0 deletions src/aws/acm-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class ACMWrapper {
if (domain.endpointType === Globals.endpointTypes.edge) {
errorMessage += ` The endpoint type '${Globals.endpointTypes.edge}' is used. ` +
`Make sure the needed ACM certificate exists in the '${Globals.defaultRegion}' region.`;
} else if (domain.endpointType === Globals.endpointTypes.private) {
errorMessage += ` The endpoint type '${Globals.endpointTypes.private}' is used. ` +
`Make sure the needed ACM certificate exists in the '${Globals.getRegion()}' region.`;
}
throw Error(errorMessage);
}
Expand Down
141 changes: 124 additions & 17 deletions src/aws/api-gateway-v1-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import {
GetBasePathMappingsCommandOutput,
GetDomainNameCommand,
GetDomainNameCommandOutput,
GetDomainNamesCommand,
GetDomainNamesCommandInput,
GetDomainNamesCommandOutput,
UpdateBasePathMappingCommand
} from "@aws-sdk/client-api-gateway";
import ApiGatewayMap = require("../models/api-gateway-map");
Expand Down Expand Up @@ -54,7 +57,8 @@ class APIGatewayV1Wrapper extends APIGatewayBase {
};

const isEdgeType = domain.endpointType === Globals.endpointTypes.edge;
if (isEdgeType) {
const isPrivateType = domain.endpointType === Globals.endpointTypes.private;
if (isEdgeType || isPrivateType) {
params.certificateArn = domain.certificateArn;
} else {
params.regionalCertificateArn = domain.certificateArn;
Expand Down Expand Up @@ -88,12 +92,36 @@ class APIGatewayV1Wrapper extends APIGatewayBase {
* @param silent: To issue an error or not. Not by default.
*/
public async getCustomDomain (domain: DomainConfig, silent: boolean = true): Promise<DomainInfo> {
const isPrivateType = domain.endpointType === Globals.endpointTypes.private;

// For private domains, we need to fetch domainNameId first
let domainNameId: string | undefined;
if (isPrivateType) {
domainNameId = await this.getDomainNameIdForPrivateDomain(domain);
if (!domainNameId) {
if (!silent) {
throw new Error(
`V1 - Unable to find domainNameId for private domain '${domain.givenDomainName}'`
);
}
Logging.logWarning(`V1 - '${domain.givenDomainName}' does not exist or is not a private domain.`);
return;
}
}

// Make API call
try {
const commandParams: any = {
domainName: domain.givenDomainName
};

// Add domainNameId for private domains
if (isPrivateType && domainNameId) {
commandParams.domainNameId = domainNameId;
}

const domainInfo: GetDomainNameCommandOutput = await this.apiGateway.send(
new GetDomainNameCommand({
domainName: domain.givenDomainName
})
new GetDomainNameCommand(commandParams)
);
return new DomainInfo(domainInfo);
} catch (err) {
Expand All @@ -106,25 +134,80 @@ class APIGatewayV1Wrapper extends APIGatewayBase {
}
}

/**
* Helper method to get domainNameId for private custom domains
* First checks domainInfo if available, otherwise fetches it
* @param domain: DomainConfig
* @returns Promise<string | undefined> The domainNameId if found, undefined otherwise
*/
private async getDomainNameIdForPrivateDomain (domain: DomainConfig): Promise<string | undefined> {
const isPrivateType = domain.endpointType === Globals.endpointTypes.private;
if (!isPrivateType) {
return undefined;
}

// First try to get it from domainInfo if available
if (domain.domainInfo?.domainNameId) {
return domain.domainInfo.domainNameId;
}

// Otherwise, fetch it by listing domains
try {
const items = await getAWSPagedResults<{ domainName: string; domainNameId?: string; endpointConfiguration?: { types?: string[] } }, GetDomainNamesCommandInput, GetDomainNamesCommandOutput>(
this.apiGateway,
"items",
"position",
"position",
new GetDomainNamesCommand({})
);

const matchingDomain = items.find(
(item) => item.domainName === domain.givenDomainName &&
item.endpointConfiguration?.types?.includes(Globals.endpointTypes.private)
);

return matchingDomain?.domainNameId;
} catch (err) {
Logging.logWarning(`V1 - Unable to list domain names to find domainNameId: ${err.message}`);
return undefined;
}
}

public async deleteCustomDomain (domain: DomainConfig): Promise<void> {
// Make API call
try {
await this.apiGateway.send(new DeleteDomainNameCommand({
const domainNameId = await this.getDomainNameIdForPrivateDomain(domain);
const commandParams: any = {
domainName: domain.givenDomainName
}));
};

// Add domainNameId for private domains
if (domainNameId) {
commandParams.domainNameId = domainNameId;
}

await this.apiGateway.send(new DeleteDomainNameCommand(commandParams));
} catch (err) {
throw new Error(`V1 - Failed to delete custom domain '${domain.givenDomainName}':\n${err.message}`);
}
}

public async createBasePathMapping (domain: DomainConfig): Promise<void> {
try {
await this.apiGateway.send(new CreateBasePathMappingCommand({
const domainNameId = await this.getDomainNameIdForPrivateDomain(domain);
const commandParams: any = {
basePath: domain.basePath,
domainName: domain.givenDomainName,
restApiId: domain.apiId,
stage: domain.stage
}));
};

// Add domainNameId for private domains
if (domainNameId) {
commandParams.domainNameId = domainNameId;
}

await this.apiGateway.send(new CreateBasePathMappingCommand(commandParams));
Logging.logInfo(`V1 - Created API mapping '${domain.basePath}' for '${domain.givenDomainName}'`);
} catch (err) {
throw new Error(
Expand All @@ -135,14 +218,22 @@ class APIGatewayV1Wrapper extends APIGatewayBase {

public async getBasePathMappings (domain: DomainConfig): Promise<ApiGatewayMap[]> {
try {
const domainNameId = await this.getDomainNameIdForPrivateDomain(domain);
const commandParams: any = {
domainName: domain.givenDomainName
};

// Add domainNameId for private domains
if (domainNameId) {
commandParams.domainNameId = domainNameId;
}

const items = await getAWSPagedResults<BasePathMapping, GetBasePathMappingsCommandInput, GetBasePathMappingsCommandOutput>(
this.apiGateway,
"items",
"position",
"position",
new GetBasePathMappingsCommand({
domainName: domain.givenDomainName
})
new GetBasePathMappingsCommand(commandParams)
);
return items.map((item) => {
return new ApiGatewayMap(item.restApiId, item.basePath, item.stage, null);
Expand All @@ -159,15 +250,23 @@ class APIGatewayV1Wrapper extends APIGatewayBase {
Logging.logInfo(`V1 - Updating API mapping from '${domain.apiMapping.basePath}'
to '${domain.basePath}' for '${domain.givenDomainName}'`);
try {
await this.apiGateway.send(new UpdateBasePathMappingCommand({
const domainNameId = await this.getDomainNameIdForPrivateDomain(domain);
const commandParams: any = {
basePath: domain.apiMapping.basePath,
domainName: domain.givenDomainName,
patchOperations: [{
op: "replace",
path: "/basePath",
value: domain.basePath
}]
}));
};

// Add domainNameId for private domains
if (domainNameId) {
commandParams.domainNameId = domainNameId;
}

await this.apiGateway.send(new UpdateBasePathMappingCommand(commandParams));
} catch (err) {
throw new Error(
`V1 - Unable to update base path mapping for '${domain.givenDomainName}':\n${err.message}`
Expand All @@ -177,11 +276,19 @@ class APIGatewayV1Wrapper extends APIGatewayBase {

public async deleteBasePathMapping (domain: DomainConfig): Promise<void> {
try {
const domainNameId = await this.getDomainNameIdForPrivateDomain(domain);
const commandParams: any = {
basePath: domain.apiMapping.basePath,
domainName: domain.givenDomainName
};

// Add domainNameId for private domains
if (domainNameId) {
commandParams.domainNameId = domainNameId;
}

await this.apiGateway.send(
new DeleteBasePathMappingCommand({
basePath: domain.apiMapping.basePath,
domainName: domain.givenDomainName
})
new DeleteBasePathMappingCommand(commandParams)
);
Logging.logInfo(`V1 - Removed '${domain.apiMapping.basePath}' base path mapping`);
} catch (err) {
Expand Down
Loading