From bbf7eb5d74d310dd62179d6e220f6948c475df48 Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Fri, 15 May 2020 16:17:08 -0700 Subject: [PATCH] feat: add region support This adds support for specifying region support for the serverless API re twilio-labs/serverless-toolkit#10 --- src/__tests__/client.test.ts | 58 +++++++++++++++++++ src/api/assets.ts | 5 +- src/api/functions.ts | 5 +- src/api/utils/__tests__/api-client.test.ts | 65 ++++++++++++++++++++++ src/api/utils/api-client.ts | 14 +++++ src/client.ts | 16 +++--- src/types/client.ts | 8 +++ src/types/generic.ts | 5 +- 8 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 src/__tests__/client.test.ts create mode 100644 src/api/utils/__tests__/api-client.test.ts create mode 100644 src/api/utils/api-client.ts diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts new file mode 100644 index 0000000..06a845a --- /dev/null +++ b/src/__tests__/client.test.ts @@ -0,0 +1,58 @@ +import { createGotClient } from '../client'; +import { DEFAULT_TEST_CLIENT_CONFIG } from '../__fixtures__/base-fixtures'; + +describe('createGotClient', () => { + test('works with default configuration', () => { + const config = DEFAULT_TEST_CLIENT_CONFIG; + const client = createGotClient(config); + const options = client.defaults.options; + expect(options.prefixUrl).toBe('https://serverless.twilio.com/v1/'); + expect(options.responseType).toBe('json'); + expect((options as any).username).toBe( + DEFAULT_TEST_CLIENT_CONFIG.accountSid + ); + expect((options as any).password).toBe( + DEFAULT_TEST_CLIENT_CONFIG.authToken + ); + expect(client.twilioClientConfig).toEqual(config); + }); + + test('works with region configuration', () => { + const config = { + ...DEFAULT_TEST_CLIENT_CONFIG, + region: 'dev', + }; + const client = createGotClient(config); + const options = client.defaults.options; + expect(options.prefixUrl).toBe('https://serverless.dev.twilio.com/v1/'); + expect(options.responseType).toBe('json'); + expect((options as any).username).toBe( + DEFAULT_TEST_CLIENT_CONFIG.accountSid + ); + expect((options as any).password).toBe( + DEFAULT_TEST_CLIENT_CONFIG.authToken + ); + expect(client.twilioClientConfig).toEqual(config); + }); + + test('works with region & edge configuration', () => { + const config = { + ...DEFAULT_TEST_CLIENT_CONFIG, + edge: 'sydney', + region: 'au1', + }; + const client = createGotClient(config); + const options = client.defaults.options; + expect(options.prefixUrl).toBe( + 'https://serverless.sydney.au1.twilio.com/v1/' + ); + expect(options.responseType).toBe('json'); + expect((options as any).username).toBe( + DEFAULT_TEST_CLIENT_CONFIG.accountSid + ); + expect((options as any).password).toBe( + DEFAULT_TEST_CLIENT_CONFIG.authToken + ); + expect(client.twilioClientConfig).toEqual(config); + }); +}); diff --git a/src/api/assets.ts b/src/api/assets.ts index ead6ecf..cd6955d 100644 --- a/src/api/assets.ts +++ b/src/api/assets.ts @@ -14,8 +14,9 @@ import { VersionResource, } from '../types'; import { getContentType } from '../utils/content-type'; -import { getPaginatedResource } from './utils/pagination'; import { ClientApiError } from '../utils/error'; +import { getApiUrl } from './utils/api-client'; +import { getPaginatedResource } from './utils/pagination'; const log = debug('twilio-serverless-api:assets'); @@ -149,7 +150,7 @@ async function createAssetVersion( `Services/${serviceSid}/Assets/${asset.sid}/Versions`, { responseType: 'text', - prefixUrl: 'https://serverless-upload.twilio.com/v1', + prefixUrl: getApiUrl(client.twilioClientConfig, 'serverless-upload'), body: form, } ); diff --git a/src/api/functions.ts b/src/api/functions.ts index 3bab708..c54bd7a 100644 --- a/src/api/functions.ts +++ b/src/api/functions.ts @@ -12,8 +12,9 @@ import { VersionResource, } from '../types'; import { getContentType } from '../utils/content-type'; -import { getPaginatedResource } from './utils/pagination'; import { ClientApiError } from '../utils/error'; +import { getApiUrl } from './utils/api-client'; +import { getPaginatedResource } from './utils/pagination'; const log = debug('twilio-serverless-api:functions'); @@ -148,7 +149,7 @@ async function createFunctionVersion( `Services/${serviceSid}/Functions/${fn.sid}/Versions`, { responseType: 'text', - prefixUrl: 'https://serverless-upload.twilio.com/v1', + prefixUrl: getApiUrl(client.twilioClientConfig, 'serverless-upload'), body: form, } ); diff --git a/src/api/utils/__tests__/api-client.test.ts b/src/api/utils/__tests__/api-client.test.ts new file mode 100644 index 0000000..e996a40 --- /dev/null +++ b/src/api/utils/__tests__/api-client.test.ts @@ -0,0 +1,65 @@ +import { DEFAULT_TEST_CLIENT_CONFIG } from '../../../__fixtures__/base-fixtures'; +import { getApiUrl } from '../api-client'; + +describe('getApiUrl', () => { + beforeEach(() => { + delete process.env.TWILIO_EDGE; + delete process.env.TWILIO_REGION; + }); + + test('returns base URL with standard options', () => { + const url = getApiUrl(DEFAULT_TEST_CLIENT_CONFIG); + expect(url).toBe('https://serverless.twilio.com/v1'); + }); + + test('handles different product base urls', () => { + const url = getApiUrl(DEFAULT_TEST_CLIENT_CONFIG, 'serverless-upload'); + expect(url).toBe('https://serverless-upload.twilio.com/v1'); + }); + + test('works with region config', () => { + const url = getApiUrl({ ...DEFAULT_TEST_CLIENT_CONFIG, region: 'dev' }); + expect(url).toBe('https://serverless.dev.twilio.com/v1'); + }); + + test('works with region config and a product', () => { + const url = getApiUrl( + { ...DEFAULT_TEST_CLIENT_CONFIG, region: 'dev' }, + 'serverless-upload' + ); + expect(url).toBe('https://serverless-upload.dev.twilio.com/v1'); + }); + + test('works with edge config', () => { + const url = getApiUrl({ ...DEFAULT_TEST_CLIENT_CONFIG, edge: 'sydney' }); + expect(url).toBe('https://serverless.sydney.twilio.com/v1'); + }); + + test('works with region and edge config', () => { + const url = getApiUrl({ + ...DEFAULT_TEST_CLIENT_CONFIG, + edge: 'sydney', + region: 'au1', + }); + expect(url).toBe('https://serverless.sydney.au1.twilio.com/v1'); + }); + + test('works with region env variable', () => { + process.env.TWILIO_REGION = 'stage'; + const url = getApiUrl(DEFAULT_TEST_CLIENT_CONFIG); + expect(url).toBe('https://serverless.stage.twilio.com/v1'); + }); + + test('works with edge env variable', () => { + process.env.TWILIO_EDGE = 'sydney'; + const url = getApiUrl(DEFAULT_TEST_CLIENT_CONFIG); + expect(url).toBe('https://serverless.sydney.twilio.com/v1'); + }); + + test('works with region & edge env variable', () => { + process.env.TWILIO_EDGE = 'sydney'; + process.env.TWILIO_REGION = 'au2'; + const url = getApiUrl(DEFAULT_TEST_CLIENT_CONFIG); + expect(url).toBe('https://serverless.sydney.au2.twilio.com/v1'); + }); +}); diff --git a/src/api/utils/api-client.ts b/src/api/utils/api-client.ts new file mode 100644 index 0000000..cc2c804 --- /dev/null +++ b/src/api/utils/api-client.ts @@ -0,0 +1,14 @@ +import { ClientConfig } from '../../types'; + +export function getApiUrl( + config: ClientConfig, + product = 'serverless', + apiVersion = 'v1' +): string { + const configEdge = config.edge || process.env.TWILIO_EDGE; + const configRegion = config.region || process.env.TWILIO_REGION; + + const edge = configEdge ? `${configEdge}.` : ''; + const region = configRegion ? `${configRegion}.` : ''; + return `https://${product}.${edge}${region}twilio.com/${apiVersion}`; +} diff --git a/src/client.ts b/src/client.ts index 91da2f1..9b7892d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2,7 +2,6 @@ import debug from 'debug'; import events from 'events'; -import got from './got'; import { getOrCreateAssetResources, uploadAsset } from './api/assets'; import { activateBuild, @@ -22,15 +21,19 @@ import { } from './api/environments'; import { getOrCreateFunctionResources, - uploadFunction, isFunctionSid, listFunctionResources, + uploadFunction, } from './api/functions'; +import { listOnePageLogResources } from './api/logs'; import { createService, findServiceSid, listServices } from './api/services'; +import { getApiUrl } from './api/utils/api-client'; import { listVariablesForEnvironment, setEnvironmentVariables, } from './api/variables'; +import got from './got'; +import { LogsStream } from './streams/logs'; import { ActivateConfig, ActivateResult, @@ -46,23 +49,22 @@ import { LogsConfig, } from './types'; import { DeployStatus } from './types/consts'; -import { getListOfFunctionsAndAssets, SearchConfig } from './utils/fs'; -import { LogsStream } from './streams/logs'; -import { listOnePageLogResources } from './api/logs'; import { ClientApiError, convertApiErrorsAndThrow } from './utils/error'; +import { getListOfFunctionsAndAssets, SearchConfig } from './utils/fs'; const log = debug('twilio-serverless-api:client'); export function createGotClient(config: ClientConfig): GotClient { const client = got.extend({ - prefixUrl: 'https://serverless.twilio.com/v1', + prefixUrl: getApiUrl(config), responseType: 'json', username: config.accountSid, password: config.authToken, headers: { 'User-Agent': 'twilio-serverless-api', }, - }); + }) as GotClient; + client.twilioClientConfig = config; return client; } diff --git a/src/types/client.ts b/src/types/client.ts index b26e1f9..f9564be 100644 --- a/src/types/client.ts +++ b/src/types/client.ts @@ -12,4 +12,12 @@ export type ClientConfig = { * Twilio Auth Token or API Secret */ authToken: string; + /** + * Twilio Region + */ + region?: string; + /** + * Twilio Edge + */ + edge?: string; }; diff --git a/src/types/generic.ts b/src/types/generic.ts index c9d6be7..1b98f4f 100644 --- a/src/types/generic.ts +++ b/src/types/generic.ts @@ -1,8 +1,11 @@ /** @module @twilio-labs/serverless-api */ import { Got } from 'got'; +import { ClientConfig } from './client'; -export type GotClient = Got; +export type GotClient = Got & { + twilioClientConfig: ClientConfig; +}; export type EnvironmentVariables = { [key: string]: string | undefined;