Skip to content

Commit

Permalink
Merge pull request #323 from bryan-hunter/add-create-domain-to-deploy
Browse files Browse the repository at this point in the history
add autoDomain option to config
  • Loading branch information
jconstance-amplify committed Jun 1, 2020
2 parents 3174bf4 + 9ff4569 commit d890548
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 4 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -71,6 +71,7 @@ custom:
endpointType: 'regional'
securityPolicy: tls_1_2
apiType: rest
autoDomain: false
```

Multiple API types mapped to different domains can also be supported with the following structure. The key is the API Gateway API type.
Expand Down Expand Up @@ -119,6 +120,7 @@ custom:
| enabled | true | Sometimes there are stages for which is not desired to have custom domain names. This flag allows the developer to disable the plugin for such cases. Accepts either `boolean` or `string` values and defaults to `true` for backwards compatibility. |
securityPolicy | tls_1_2 | The security policy to apply to the custom domain name. Accepts `tls_1_0` or `tls_1_2`|
allowPathMatching | false | When updating an existing api mapping this will match on the basePath instead of the API ID to find existing mappings for an upsate. This should only be used when changing API types. For example, migrating a REST API to an HTTP API. See Changing API Types for more information. |
| autoDomain | `false` | Toggles whether or not the plugin will run `create_domain/delete_domain` as part of `sls deploy/remove` so that multiple commands are not required. |


## Running
Expand Down
17 changes: 13 additions & 4 deletions src/index.ts
Expand Up @@ -54,7 +54,7 @@ class ServerlessCustomDomain {
this.hooks = {
"after:deploy:deploy": this.hookWrapper.bind(this, this.setupBasePathMappings),
"after:info:info": this.hookWrapper.bind(this, this.domainSummaries),
"before:deploy:deploy": this.hookWrapper.bind(this, this.updateCloudFormationOutputs),
"before:deploy:deploy": this.hookWrapper.bind(this, this.createOrGetDomainForCfOutputs),
"before:remove:remove": this.hookWrapper.bind(this, this.removeBasePathMappings),
"create_domain:create": this.hookWrapper.bind(this, this.createDomains),
"delete_domain:delete": this.hookWrapper.bind(this, this.deleteDomains),
Expand Down Expand Up @@ -83,7 +83,6 @@ class ServerlessCustomDomain {
await Promise.all(this.domains.map(async (domain) => {
try {
if (!domain.domainInfo) {

domain.certificateArn = await this.getCertArn(domain);

await this.createCustomDomain(domain);
Expand Down Expand Up @@ -130,9 +129,14 @@ class ServerlessCustomDomain {
}

/**
* Lifecycle function to add domain info to the CloudFormation stack's Outputs
* Lifecycle function to createDomain before deploy and add domain info to the CloudFormation stack's Outputs
*/
public async updateCloudFormationOutputs(): Promise<void> {
public async createOrGetDomainForCfOutputs(): Promise<void> {
const autoDomain = this.serverless.service.custom.customDomain.autoDomain;
if (autoDomain === true) {
this.serverless.cli.log("Creating domain name before deploy.");
await this.createDomains();
}

await this.getDomainInfo();

Expand Down Expand Up @@ -200,6 +204,11 @@ class ServerlessCustomDomain {
}
}
}));
const autoDomain = this.serverless.service.custom.customDomain.autoDomain;
if (autoDomain === true) {
this.serverless.cli.log("Deleting domain name after removing base path mapping.");
await this.deleteDomains();
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Expand Up @@ -25,6 +25,7 @@ export interface ServerlessInstance { // tslint:disable-line
hostedZonePrivate: boolean | undefined,
enabled: boolean | string | undefined,
securityPolicy: string | undefined,
autoDomain: boolean | undefined,
},
},
};
Expand Down
168 changes: 168 additions & 0 deletions test/unit-tests/index.test.ts
Expand Up @@ -64,6 +64,7 @@ const constructPlugin = (customDomainOptions) => {
custom: {
customDomain: {
apiType: customDomainOptions.apiType,
autoDomain: customDomainOptions.autoDomain,
basePath: customDomainOptions.basePath,
certificateArn: customDomainOptions.certificateArn,
certificateName: customDomainOptions.certificateName,
Expand Down Expand Up @@ -1576,4 +1577,171 @@ describe("Custom Domain Plugin", () => {
AWS.restore();
});
});

describe("autoDomain deploy", () => {
it("Should be disabled by default", () => {
const plugin = constructPlugin({ domainName: "test_domain" });
plugin.initializeVariables();
expect(plugin.serverless.service.custom.customDomain.autoDomain).to.equal(undefined);
});

it("createOrGetDomainForCfOutputs should call createDomain when autoDomain is true", async () => {
AWS.mock("ApiGatewayV2", "getDomainName", (params, callback) => {
callback(null, params);
});
const plugin = constructPlugin({
autoDomain: true,
basePath: "test_basepath",
createRoute53Record: false,
domainName: "test_domain",
restApiId: "test_rest_api_id",
});
plugin.initializeVariables();

plugin.apigateway = new aws.APIGateway();
plugin.apigatewayV2 = new aws.ApiGatewayV2();
plugin.cloudformation = new aws.CloudFormation();

plugin.domains[0].apiMapping = {ApiMappingId: "test_mapping_id"};

const spy = chai.spy.on(plugin.apigatewayV2, "getDomainName");

await plugin.createOrGetDomainForCfOutputs();

expect(plugin.serverless.service.custom.customDomain.autoDomain).to.equal(true);
expect(spy).to.have.been.called();
});

it("createOrGetDomainForCfOutputs should not call createDomain when autoDomain is not true", async () => {
AWS.mock("ApiGatewayV2", "getDomainName", (params, callback) => {
callback(null, params);
});

const plugin = constructPlugin({
autoDomain: false,
basePath: "test_basepath",
createRoute53Record: false,
domainName: "test_domain",
restApiId: "test_rest_api_id",
});
plugin.initializeVariables();

plugin.apigateway = new aws.APIGateway();
plugin.apigatewayV2 = new aws.ApiGatewayV2();
plugin.cloudformation = new aws.CloudFormation();

plugin.domains[0].apiMapping = {ApiMappingId: "test_mapping_id"};

const spy1 = chai.spy.on(plugin.apigateway, "createDomainName");
const spy2 = chai.spy.on(plugin.apigatewayV2, "createDomainName");

await plugin.createOrGetDomainForCfOutputs();

expect(plugin.serverless.service.custom.customDomain.autoDomain).to.equal(false);
expect(spy1).to.have.not.been.called();
expect(spy2).to.have.not.been.called();
});

it("removeBasePathMapping should call deleteDomain when autoDomain is true", async () => {
AWS.mock("CloudFormation", "describeStackResource", (params, callback) => {
callback(null, {
StackResourceDetail:
{
LogicalResourceId: "ApiGatewayRestApi",
PhysicalResourceId: "test_rest_api_id",
},
});
});
AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => {
callback(null, {
Items: [
{ ApiId: "test_rest_api_id", MappingKey: "test", ApiMappingId: "test_mapping_id", Stage: "test" },
],
});
});
AWS.mock("ApiGatewayV2", "deleteApiMapping", (params, callback) => {
callback(null, params);
});
AWS.mock("ApiGatewayV2", "deleteDomainName", (params, callback) => {
callback(null, params);
});
AWS.mock("ApiGatewayV2", "getDomainName", (params, callback) => {
callback(null, params);
});

const plugin = constructPlugin({
autoDomain: true,
basePath: "test_basepath",
createRoute53Record: false,
domainName: "test_domain",
restApiId: "test_rest_api_id",
});
plugin.initializeVariables();

plugin.apigatewayV2 = new aws.ApiGatewayV2();
plugin.cloudformation = new aws.CloudFormation();

plugin.domains[0].apiMapping = {ApiMappingId: "test_mapping_id"};

const spy = chai.spy.on(plugin.apigatewayV2, "deleteDomainName");

await plugin.removeBasePathMappings();

expect(plugin.serverless.service.custom.customDomain.autoDomain).to.equal(true);
expect(spy).to.have.been.called.with({DomainName: "test_domain"});
});

it("removeBasePathMapping should not call deleteDomain when autoDomain is not true", async () => {
AWS.mock("CloudFormation", "describeStackResource", (params, callback) => {
callback(null, {
StackResourceDetail:
{
LogicalResourceId: "ApiGatewayRestApi",
PhysicalResourceId: "test_rest_api_id",
},
});
});
AWS.mock("ApiGatewayV2", "getApiMappings", (params, callback) => {
callback(null, {
Items: [
{ ApiId: "test_rest_api_id", MappingKey: "test", ApiMappingId: "test_mapping_id", Stage: "test" },
],
});
});
AWS.mock("ApiGatewayV2", "deleteApiMapping", (params, callback) => {
callback(null, params);
});
AWS.mock("ApiGatewayV2", "deleteDomainName", (params, callback) => {
callback(null, params);
});
AWS.mock("ApiGatewayV2", "getDomainName", (params, callback) => {
callback(null, params);
});

const plugin = constructPlugin({
autoDomain: false,
basePath: "test_basepath",
createRoute53Record: false,
domainName: "test_domain",
restApiId: "test_rest_api_id",
});
plugin.initializeVariables();

plugin.apigatewayV2 = new aws.ApiGatewayV2();
plugin.cloudformation = new aws.CloudFormation();

plugin.domains[0].apiMapping = {ApiMappingId: "test_mapping_id"};

const spy = chai.spy.on(plugin.apigatewayV2, "deleteDomainName");

await plugin.removeBasePathMappings();

expect(plugin.serverless.service.custom.customDomain.autoDomain).to.equal(false);
expect(spy).to.have.not.been.called();
});

afterEach(() => {
consoleOutput = [];
});
});
});

0 comments on commit d890548

Please sign in to comment.