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: add regional and edge support #86

Merged
merged 9 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/base-commands/twilio-client-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ class TwilioClientCommand extends BaseCommand {
buildClient(ClientClass) {
return new ClientClass(this.currentProfile.apiKey, this.currentProfile.apiSecret, {
accountSid: this.flags[CliFlags.ACCOUNT_SID] || this.currentProfile.accountSid,
edge: process.env.TWILIO_EDGE || this.userConfig.edge,
region: this.currentProfile.region,
httpClient: this.httpClient
});
Expand Down
17 changes: 14 additions & 3 deletions src/services/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,21 @@ class ConfigDataProfile {

class ConfigData {
constructor() {
this.edge = process.env.TWILIO_EDGE;
this.email = {};
this.prompts = {};
this.profiles = [];
this.activeProfile = null;
}

getProfileFromEnvironment() {
const { TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_API_KEY, TWILIO_API_SECRET } = process.env;
const {
TWILIO_ACCOUNT_SID,
TWILIO_AUTH_TOKEN,
TWILIO_API_KEY,
TWILIO_API_SECRET,
TWILIO_REGION
} = process.env;
if (!TWILIO_ACCOUNT_SID) return;

if (TWILIO_API_KEY && TWILIO_API_SECRET)
Expand All @@ -31,7 +38,8 @@ class ConfigData {
id: '${TWILIO_API_KEY}/${TWILIO_API_SECRET}',
accountSid: TWILIO_ACCOUNT_SID,
apiKey: TWILIO_API_KEY,
apiSecret: TWILIO_API_SECRET
apiSecret: TWILIO_API_SECRET,
region: TWILIO_REGION
};

if (TWILIO_AUTH_TOKEN)
Expand All @@ -40,7 +48,8 @@ class ConfigData {
id: '${TWILIO_ACCOUNT_SID}/${TWILIO_AUTH_TOKEN}',
accountSid: TWILIO_ACCOUNT_SID,
apiKey: TWILIO_ACCOUNT_SID,
apiSecret: TWILIO_AUTH_TOKEN
apiSecret: TWILIO_AUTH_TOKEN,
region: TWILIO_REGION
};
}

Expand Down Expand Up @@ -130,6 +139,7 @@ class ConfigData {
}

loadFromObject(configObj) {
this.edge = this.edge || configObj.edge;
this.email = configObj.email || {};
this.prompts = configObj.prompts || {};
// Note the historical 'projects' naming.
Expand Down Expand Up @@ -163,6 +173,7 @@ class Config {

async save(configData) {
configData = {
edge: configData.edge,
email: configData.email,
prompts: configData.prompts,
// Note the historical 'projects' naming.
Expand Down
44 changes: 31 additions & 13 deletions src/services/open-api-client.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const url = require('url');
const { logger } = require('./messaging/logging');
const { doesObjectHaveProperty } = require('./javascript-utilities');
const JsonSchemaConverter = require('./api-schema/json-converter');
Expand Down Expand Up @@ -34,26 +35,21 @@ class OpenApiClient {
const params = this.getParams(opts, operation);

if (!opts.uri) {
opts.uri = this.getUri(opts);
opts.uri = this.getUri(opts.uri, opts);
}

// If the URI is relative, determine the host and prepend it.
if (opts.uri.startsWith('/')) {
if (!opts.host) {
opts.host = path.server;
}

if (opts.region) {
const parts = opts.host.split('.');

// From 'https://api.twilio.com/' to 'https://api.{region}.twilio.com/'
if (parts.length > 1 && parts[1] !== opts.region) {
parts.splice(1, 0, opts.region);
opts.host = parts.join('.');
}
}

opts.host = this.getHost(opts.host, opts);
opts.uri = opts.host + opts.uri;
} else if (opts.uri) {
let uri = new url.URL(opts.uri);
uri.hostname = this.getHost(uri.hostname, opts);
uri.pathname = this.getUri(uri.pathname, opts);
opts.uri = uri.href;
}

opts.params = (isPost ? null : params);
Expand Down Expand Up @@ -81,7 +77,10 @@ class OpenApiClient {
return params;
}

getUri(opts) {
getUri(path, opts) {
if (path && path !== '/') {
return path;
}
// Evaluate the request path by replacing path parameters with their value
// from the request data.
return opts.path.replace(/{(.+?)}/g, (fullMatch, pathNode) => {
Expand All @@ -97,6 +96,25 @@ class OpenApiClient {
});
}

getHost(host, opts) {
if (opts.region || opts.edge) {
const domain = host.split('.').slice(-2).join('.');
const prefix = host.split('.' + domain)[0];
let [product, edge, region] = prefix.split('.');
if (edge && !region) {
region = edge;
edge = undefined;
}
opts.edge = opts.edge || edge;
opts.region = opts.region || region || (opts.edge ? 'us1' : undefined);
return [product,
opts.edge,
opts.region,
domain].filter(part => part).join('.');
}
return host;
}

parseResponse(domain, operation, response, requestOpts) {
if (response.body) {
const responseSchema = this.getResponseSchema(domain, operation, response.statusCode, requestOpts.headers.Accept);
Expand Down
13 changes: 7 additions & 6 deletions src/services/twilio-api/twilio-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class TwilioApiClient {
this.username = username;
this.password = password;
this.accountSid = opts.accountSid || this.username;
this.edge = opts.edge;
this.region = opts.region;

this.apiClient = new OpenApiClient({
Expand Down Expand Up @@ -149,7 +150,8 @@ class TwilioApiClient {
* @param {string} opts.method - The http method
* @param {string} opts.path - The request path
* @param {string} [opts.host] - The request host
* @param {string} [opts.region] - The request region
* @param {string} [opts.edge] - The request edge. Defaults to none.
* @param {string} [opts.region] - The request region. Default to us1 if edge defined
* @param {string} [opts.uri] - The request uri
* @param {string} [opts.username] - The username used for auth
* @param {string} [opts.password] - The password used for auth
Expand All @@ -164,7 +166,6 @@ class TwilioApiClient {

opts.username = opts.username || this.username;
opts.password = opts.password || this.password;
opts.region = opts.region || this.region;
opts.headers = opts.headers || {};
opts.data = opts.data || {};
opts.pathParams = opts.pathParams || {};
Expand All @@ -180,12 +181,12 @@ class TwilioApiClient {
opts.headers.Accept = 'application/json';
}

if (!opts.uri) {
if (opts.path.includes(TwilioApiFlags.ACCOUNT_SID) && !doesObjectHaveProperty(opts.pathParams, TwilioApiFlags.ACCOUNT_SID)) {
opts.pathParams[TwilioApiFlags.ACCOUNT_SID] = this.accountSid;
}
if (opts.path.includes(TwilioApiFlags.ACCOUNT_SID) && !doesObjectHaveProperty(opts.pathParams, TwilioApiFlags.ACCOUNT_SID)) {
opts.pathParams[TwilioApiFlags.ACCOUNT_SID] = this.accountSid;
}

opts.edge = opts.edge || this.edge;
opts.region = opts.region || this.region;
return this.apiClient.request(opts);
}
}
Expand Down
2 changes: 2 additions & 0 deletions test/base-commands/twilio-client-command.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('base-commands', () => {
expect(ctx.testCmd.twilioClient.username).to.equal(constants.FAKE_API_KEY);
expect(ctx.testCmd.twilioClient.password).to.equal(constants.FAKE_API_SECRET + 'MyFirstProfile');
expect(ctx.testCmd.twilioClient.region).to.equal(undefined);
expect(ctx.testCmd.twilioClient.edge).to.equal(undefined);
});

setUpTest(['-l', 'debug', '--account-sid', 'ACbaccbaccbaccbaccbaccbaccbaccbacc'], { commandClass: AccountSidClientCommand }).it(
Expand All @@ -75,6 +76,7 @@ describe('base-commands', () => {
expect(ctx.testCmd.twilioClient.username).to.equal(constants.FAKE_API_KEY);
expect(ctx.testCmd.twilioClient.password).to.equal(constants.FAKE_API_SECRET + 'MyFirstProfile');
expect(ctx.testCmd.twilioClient.region).to.equal(undefined);
expect(ctx.testCmd.twilioClient.edge).to.equal(undefined);
}
);

Expand Down
27 changes: 26 additions & 1 deletion test/services/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ describe('services', () => {
expect(profile.apiKey).to.equal(constants.FAKE_ACCOUNT_SID);
expect(profile.apiSecret).to.equal(FAKE_AUTH_TOKEN);
});

test.it('should return profile populated from AccountSid/ApiKey env vars', () => {
const configData = new ConfigData();
configData.addProfile('envProfile', constants.FAKE_ACCOUNT_SID);
Expand All @@ -91,6 +90,21 @@ describe('services', () => {
expect(profile.apiKey).to.equal(constants.FAKE_API_KEY);
expect(profile.apiSecret).to.equal(constants.FAKE_API_SECRET);
});

test.it('should return profile populated with region env var', () => {
const configData = new ConfigData();
configData.addProfile('envProfile', constants.FAKE_ACCOUNT_SID);

process.env.TWILIO_ACCOUNT_SID = constants.FAKE_ACCOUNT_SID;
process.env.TWILIO_AUTH_TOKEN = FAKE_AUTH_TOKEN;
process.env.TWILIO_REGION = 'region';

const profile = configData.getProfileById();
expect(profile.accountSid).to.equal(constants.FAKE_ACCOUNT_SID);
expect(profile.apiKey).to.equal(constants.FAKE_ACCOUNT_SID);
expect(profile.apiSecret).to.equal(FAKE_AUTH_TOKEN);
expect(profile.region).to.equal('region');
});
});

describe('ConfigData.activeProfile', () => {
Expand Down Expand Up @@ -205,6 +219,17 @@ describe('services', () => {
const saveMessage = await config.save(userConfig);
expect(saveMessage).to.contain(`${nestedConfig}${path.sep}config.json`);
});

test.it('uses env vars over config to set edge', async () => {
const config = new Config(tempConfigDir.name);
const userConfig = await config.load();

expect(userConfig.edge).to.be.undefined;
process.env.TWILIO_EDGE = 'edge';

const loadedConfig = await config.load();
expect(loadedConfig.edge).to.equal('edge');
});
});
});
});
Loading