Skip to content

Commit 87cca3c

Browse files
authored
refactor(serverless-api): update user-agent string for better debugging (#292)
1 parent b755329 commit 87cca3c

File tree

22 files changed

+320
-34
lines changed

22 files changed

+320
-34
lines changed

packages/plugin-assets/src/client.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const pkgJson = require('../package.json');
2+
const { TwilioServerlessApiClient } = require('@twilio-labs/serverless-api');
3+
4+
function getTwilioClient(apiKey, apiSecret) {
5+
return new TwilioServerlessApiClient({
6+
username: apiKey,
7+
password: apiSecret,
8+
userAgentExtensions: [`@twilio-labs/plugin-assets/${pkgJson.version}`],
9+
});
10+
}
11+
12+
module.exports = { getTwilioClient: getTwilioClient };

packages/plugin-assets/src/init.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const { TwilioServerlessApiClient } = require('@twilio-labs/serverless-api');
21
const {
32
createService,
43
} = require('@twilio-labs/serverless-api/dist/api/services');
@@ -9,6 +8,7 @@ const {
98
const { TwilioCliError } = require('@twilio/cli-core').services.error;
109

1110
const { couldNotGetEnvironment } = require('./errorMessages');
11+
const { getTwilioClient } = require('./client');
1212

1313
async function createServiceAndEnvironment(client, serviceName) {
1414
const serviceSid = await createService(serviceName, client);
@@ -36,10 +36,7 @@ async function init({
3636
serviceName,
3737
}) {
3838
logger.debug('Loading config');
39-
const client = new TwilioServerlessApiClient({
40-
username: apiKey,
41-
password: apiSecret,
42-
});
39+
const client = getTwilioClient(apiKey, apiSecret);
4340
const config = await pluginConfig.getConfig();
4441
if (
4542
config[accountSid] &&

packages/plugin-assets/src/list.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
const { TwilioServerlessApiClient } = require('@twilio-labs/serverless-api');
21
const {
32
getEnvironment,
43
} = require('@twilio-labs/serverless-api/dist/api/environments');
54
const { getBuild } = require('@twilio-labs/serverless-api/dist/api/builds');
65
const { TwilioCliError } = require('@twilio/cli-core').services.error;
76
const { couldNotGetEnvironment, couldNotGetBuild } = require('./errorMessages');
7+
const { getTwilioClient } = require('./client');
88

99
async function list({ pluginConfig, apiKey, apiSecret, accountSid, logger }) {
1010
let environment;
@@ -15,10 +15,7 @@ async function list({ pluginConfig, apiKey, apiSecret, accountSid, logger }) {
1515
config[accountSid].environmentSid
1616
) {
1717
const { serviceSid, environmentSid } = config[accountSid];
18-
const client = new TwilioServerlessApiClient({
19-
username: apiKey,
20-
password: apiSecret,
21-
});
18+
const client = getTwilioClient(apiKey, apiSecret);
2219
try {
2320
logger.debug(
2421
`Fetching environment with sid ${environmentSid} from service with sid ${serviceSid}`

packages/plugin-assets/src/upload.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const ora = require('ora');
22
const inquirer = require('inquirer');
3-
const { TwilioServerlessApiClient } = require('@twilio-labs/serverless-api');
43
const { TwilioCliError } = require('@twilio/cli-core').services.error;
54
const {
65
getEnvironment,
@@ -31,6 +30,8 @@ const {
3130
debugFlagMessage,
3231
} = require('./errorMessages');
3332

33+
const { getTwilioClient } = require('./client');
34+
3435
function getUtils(spinner, logger) {
3536
function debug(message) {
3637
const wasSpinning = spinner.isSpinning;
@@ -328,10 +329,7 @@ async function upload({
328329
config[accountSid].environmentSid
329330
) {
330331
const { serviceSid, environmentSid } = config[accountSid];
331-
const client = new TwilioServerlessApiClient({
332-
username: apiKey,
333-
password: apiSecret,
334-
});
332+
const client = getTwilioClient(apiKey, apiSecret);
335333
const environment = await getEnvironmentWithClient(
336334
client,
337335
environmentSid,

packages/plugin-serverless/src/utils.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const path = require('path');
22
const camelCase = require('lodash.camelcase');
33
const { flags } = require('@oclif/command');
4+
const pkgJson = require('../package.json');
45

56
function convertYargsOptionsToOclifFlags(options) {
67
const aliasMap = new Map();
@@ -73,6 +74,10 @@ function normalizeFlags(flags, aliasMap, argv) {
7374

7475
function createExternalCliOptions(flags, twilioClient) {
7576
const profile = flags.profile;
77+
const pluginInfo = {
78+
version: pkgJson.version,
79+
name: pkgJson.name,
80+
};
7681

7782
if (
7883
(typeof flags.username === 'string' && flags.username.length > 0) ||
@@ -85,6 +90,7 @@ function createExternalCliOptions(flags, twilioClient) {
8590
profile: undefined,
8691
logLevel: undefined,
8792
outputFormat: undefined,
93+
pluginInfo,
8894
};
8995
}
9096

@@ -95,6 +101,7 @@ function createExternalCliOptions(flags, twilioClient) {
95101
profile,
96102
logLevel: undefined,
97103
outputFormat: undefined,
104+
pluginInfo,
98105
};
99106
}
100107

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
platform: () => 'darwin',
3+
arch: () => 'x64',
4+
};
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
import {
2-
ClientConfig,
3-
AccountSidConfig,
4-
UsernameConfig,
5-
} from '../types/client';
1+
import { AccountSidConfig, UsernameConfig } from '../types/client';
62

73
export const DEFAULT_TEST_CLIENT_CONFIG: AccountSidConfig = {
84
accountSid: 'ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
9-
authToken: '<SECRET>',
5+
authToken: 'SECRET',
106
};
117

128
export const DEFAULT_TEST_CLIENT_CONFIG_USERNAME_PASSWORD: UsernameConfig = {
139
username: 'ACyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy',
14-
password: '<PASSWORD>',
10+
password: 'PASSWORD',
1511
};
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import nock from 'nock';
2+
import { UsernameConfig } from '../../dist';
3+
import { getPaginatedResource } from '../api/utils/pagination';
4+
import TwilioServerlessApiClient from '../client';
5+
import { DEFAULT_TEST_CLIENT_CONFIG_USERNAME_PASSWORD } from '../__fixtures__/base-fixtures';
6+
7+
jest.mock('os');
8+
jest.mock('../utils/package-info');
9+
10+
const DEFAULT_USER_AGENT = `@twilio-labs/serverless-api-test/1.0.0-test (darwin x64) node/${process.version}`;
11+
const DEFAULT_CREDENTIALS = {
12+
user: DEFAULT_TEST_CLIENT_CONFIG_USERNAME_PASSWORD.username,
13+
pass: DEFAULT_TEST_CLIENT_CONFIG_USERNAME_PASSWORD.password,
14+
};
15+
16+
const DEFAULT_HEADERS = {
17+
'user-agent': DEFAULT_USER_AGENT,
18+
accept: 'application/json',
19+
'accept-encoding': 'gzip, deflate, br',
20+
};
21+
22+
describe('API integration tests', () => {
23+
let apiNock: nock.Scope,
24+
config: UsernameConfig,
25+
apiClient: TwilioServerlessApiClient;
26+
beforeAll(() => {
27+
nock.disableNetConnect();
28+
});
29+
30+
beforeEach(() => {
31+
apiNock = nock('https://serverless.twilio.com', {
32+
reqheaders: DEFAULT_HEADERS,
33+
});
34+
config = DEFAULT_TEST_CLIENT_CONFIG_USERNAME_PASSWORD;
35+
apiClient = new TwilioServerlessApiClient(config);
36+
});
37+
38+
afterAll(() => {
39+
nock.enableNetConnect();
40+
});
41+
42+
describe('basic requests', () => {
43+
test('handles get requests', async () => {
44+
const scope = apiNock
45+
.get('/v1/Services')
46+
.basicAuth(DEFAULT_CREDENTIALS)
47+
.reply(200, { services: [] });
48+
49+
await apiClient.request('get', 'Services');
50+
scope.done();
51+
});
52+
53+
test('handles post request', async () => {
54+
const scope = apiNock
55+
.post('/v1/Services', 'UniqueName=test-app')
56+
.basicAuth(DEFAULT_CREDENTIALS)
57+
.reply(200, {});
58+
await apiClient.request('post', 'Services', {
59+
form: {
60+
UniqueName: 'test-app',
61+
},
62+
});
63+
64+
scope.done();
65+
});
66+
67+
test('handles delete request', async () => {
68+
const scope = apiNock
69+
.delete('/v1/Services/ZS11111111111111111111111111111111')
70+
.basicAuth(DEFAULT_CREDENTIALS)
71+
.reply(200, {});
72+
73+
await apiClient.request(
74+
'delete',
75+
'Services/ZS11111111111111111111111111111111'
76+
);
77+
scope.done();
78+
});
79+
80+
test('passes user-agent extensions', async () => {
81+
apiNock = nock('https://serverless.twilio.com', {
82+
reqheaders: {
83+
...DEFAULT_HEADERS,
84+
'user-agent':
85+
DEFAULT_USER_AGENT +
86+
' twilio-run/1.0.0-test plugin-serverless/1.2.0-test',
87+
},
88+
});
89+
config = DEFAULT_TEST_CLIENT_CONFIG_USERNAME_PASSWORD;
90+
apiClient = new TwilioServerlessApiClient({
91+
...config,
92+
userAgentExtensions: [
93+
'twilio-run/1.0.0-test',
94+
'plugin-serverless/1.2.0-test',
95+
],
96+
});
97+
98+
const scope = apiNock
99+
.get('/v1/Services')
100+
.basicAuth(DEFAULT_CREDENTIALS)
101+
.reply(200, { services: [] });
102+
103+
await apiClient.request('get', 'Services');
104+
scope.done();
105+
});
106+
});
107+
108+
describe('paginated requests', () => {
109+
const BASE_PAGE_URL =
110+
'https://serverless.twilio.com/v1/Services?PageSize=50&Page=';
111+
const BASE_PAGE_INFO = {
112+
first_page_url: BASE_PAGE_URL + '0',
113+
key: 'services',
114+
next_page_url: null,
115+
page_size: 50,
116+
previous_page_url: null,
117+
url: BASE_PAGE_URL,
118+
};
119+
const EXAMPLE_SERVICES = [
120+
{ sid: 'ZS11111111111111111111111111111111' },
121+
{ sid: 'ZS11111111111111111111111111111112' },
122+
{ sid: 'ZS11111111111111111111111111111113' },
123+
];
124+
125+
test('handles single page', async () => {
126+
apiNock
127+
.get('/v1/Services')
128+
.basicAuth(DEFAULT_CREDENTIALS)
129+
.reply(200, { services: [EXAMPLE_SERVICES[0]], meta: BASE_PAGE_INFO });
130+
131+
const resp = await getPaginatedResource(apiClient, 'Services');
132+
expect(resp.length).toEqual(1);
133+
});
134+
135+
test('handles multi page requests', async () => {
136+
apiNock
137+
.get('/v1/Services')
138+
.basicAuth(DEFAULT_CREDENTIALS)
139+
.reply(200, {
140+
services: [EXAMPLE_SERVICES[0]],
141+
meta: { ...BASE_PAGE_INFO, next_page_url: BASE_PAGE_URL + '1' },
142+
})
143+
.get('/v1/Services?PageSize=50&Page=1')
144+
.basicAuth(DEFAULT_CREDENTIALS)
145+
.reply(200, {
146+
services: [EXAMPLE_SERVICES[1]],
147+
meta: {
148+
...BASE_PAGE_INFO,
149+
previous_page_url: BASE_PAGE_URL + '0',
150+
next_page_url: BASE_PAGE_URL + '2',
151+
},
152+
})
153+
.get('/v1/Services?PageSize=50&Page=2')
154+
.basicAuth(DEFAULT_CREDENTIALS)
155+
.reply(200, { services: [EXAMPLE_SERVICES[2]], meta: BASE_PAGE_INFO });
156+
157+
const resp = await getPaginatedResource(apiClient, 'Services');
158+
expect(resp.length).toEqual(3);
159+
});
160+
});
161+
});

packages/serverless-api/src/client.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import {
7878
} from './types/env';
7979
import { ClientApiError, convertApiErrorsAndThrow } from './utils/error';
8080
import { getListOfFunctionsAndAssets, SearchConfig } from './utils/fs';
81+
import getUserAgent from './utils/user-agent';
8182

8283
const log = debug('twilio-serverless-api:client');
8384

@@ -98,7 +99,7 @@ export function createGotClient(config: ClientConfig): GotClient {
9899
username: username,
99100
password: password,
100101
headers: {
101-
'User-Agent': 'twilio-serverless-api',
102+
'User-Agent': getUserAgent(config.userAgentExtensions),
102103
},
103104
}) as GotClient;
104105
if (process.env.HTTP_PROXY) {
@@ -148,6 +149,7 @@ export class TwilioServerlessApiClient extends events.EventEmitter {
148149
debug.enable(process.env.DEBUG || '');
149150
super();
150151
this.config = config;
152+
151153
this.client = createGotClient(config);
152154
this.limit = pLimit(config.concurrency || CONCURRENCY);
153155
}
@@ -505,8 +507,13 @@ export class TwilioServerlessApiClient extends events.EventEmitter {
505507
*/
506508
async activateBuild(activateConfig: ActivateConfig): Promise<ActivateResult> {
507509
try {
508-
let { buildSid, targetEnvironment, serviceSid, sourceEnvironment, env } =
509-
activateConfig;
510+
let {
511+
buildSid,
512+
targetEnvironment,
513+
serviceSid,
514+
sourceEnvironment,
515+
env,
516+
} = activateConfig;
510517

511518
if (!buildSid && !sourceEnvironment) {
512519
const error = new Error(
@@ -847,6 +854,7 @@ export class TwilioServerlessApiClient extends events.EventEmitter {
847854
statusCodes: [429],
848855
errorCodes: [],
849856
};
857+
850858
return this.limit(() => this.client[method](path, options));
851859
}
852860
}

packages/serverless-api/src/types/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ type BaseClientConfig = {
1717
* Number of retry attempts the client will make on a failure
1818
*/
1919
retryLimit?: number;
20+
/**
21+
* Additional information to pass to the User-Agent. !!!Should not contain sensitive information
22+
*/
23+
userAgentExtensions?: string[];
2024
};
2125

2226
export type AccountSidConfig = BaseClientConfig & {

0 commit comments

Comments
 (0)