diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 64a5dfd0..e854e459 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -45,6 +45,11 @@ jobs: with: node-version: ${{ matrix.node-version }} - uses: actions/checkout@v3 + - name: Set up Python 3.10 for Node 14 + if: ${{ matrix.node-version == '14' }} + uses: actions/setup-python@v4 + with: + python-version: '3.10' - name: Cache node modules uses: actions/cache@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index b49dd8bd..0d13bf63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## 1.10.0 + +- Rename `clientId` parameter to `userAgentEntry` in connect call to standardize across sql drivers (databricks/databricks-sql-nodejs#281) + ## 1.9.0 - Support iterable interface for IOperation (databricks/databricks-sql-nodejs#252) diff --git a/examples/repl b/examples/repl index d214549c..c546511b 100755 --- a/examples/repl +++ b/examples/repl @@ -15,7 +15,7 @@ async function initClient({ host, endpointId, token }) { host, path: `/sql/2.0/warehouses/${endpointId}`, token, - clientId: 'REPL', + userAgentEntry: 'REPL', }); } diff --git a/lib/DBSQLClient.ts b/lib/DBSQLClient.ts index d3d9427c..85888d16 100644 --- a/lib/DBSQLClient.ts +++ b/lib/DBSQLClient.ts @@ -111,7 +111,7 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I socketTimeout: options.socketTimeout, proxy: options.proxy, headers: { - 'User-Agent': buildUserAgentString(options.clientId), + 'User-Agent': buildUserAgentString(options.userAgentEntry), }, }; } @@ -160,6 +160,17 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I * const session = client.connect({host, path, token}); */ public async connect(options: ConnectionOptions, authProvider?: IAuthentication): Promise { + const deprecatedClientId = (options as any).clientId; + if (deprecatedClientId !== undefined) { + this.logger.log( + LogLevel.warn, + 'Warning: The "clientId" option is deprecated. Please use "userAgentEntry" instead.', + ); + if (!options.userAgentEntry) { + options.userAgentEntry = deprecatedClientId; + } + } + this.authProvider = this.createAuthProvider(options, authProvider); this.connectionProvider = this.createConnectionProvider(options); diff --git a/lib/contracts/IDBSQLClient.ts b/lib/contracts/IDBSQLClient.ts index 0c0cee89..73fedde1 100644 --- a/lib/contracts/IDBSQLClient.ts +++ b/lib/contracts/IDBSQLClient.ts @@ -30,7 +30,7 @@ export type ConnectionOptions = { host: string; port?: number; path: string; - clientId?: string; + userAgentEntry?: string; socketTimeout?: number; proxy?: ProxyOptions; } & AuthOptions; diff --git a/lib/utils/buildUserAgentString.ts b/lib/utils/buildUserAgentString.ts index ce26fcfd..cd021e93 100644 --- a/lib/utils/buildUserAgentString.ts +++ b/lib/utils/buildUserAgentString.ts @@ -11,7 +11,21 @@ function getOperatingSystemVersion(): string { return `${os.type()} ${os.release()}`; } -export default function buildUserAgentString(clientId?: string): string { - const extra = [clientId, getNodeVersion(), getOperatingSystemVersion()].filter(Boolean); +function redactInternalToken(userAgentEntry: string): string { + const internalTokenPrefixes = ['dkea', 'dskea', 'dapi', 'dsapi', 'dose']; + for (const prefix of internalTokenPrefixes) { + if (userAgentEntry.startsWith(prefix)) { + return ''; + } + } + return userAgentEntry; +} + +export default function buildUserAgentString(userAgentEntry?: string): string { + if (userAgentEntry) { + userAgentEntry = redactInternalToken(userAgentEntry); + } + + const extra = [userAgentEntry, getNodeVersion(), getOperatingSystemVersion()].filter(Boolean); return `${productName}/${packageVersion} (${extra.join('; ')})`; } diff --git a/package-lock.json b/package-lock.json index 36f6317d..ad6c2729 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@databricks/sql", - "version": "1.9.0", + "version": "1.10.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@databricks/sql", - "version": "1.9.0", + "version": "1.10.0", "license": "Apache 2.0", "dependencies": { "apache-arrow": "^13.0.0", diff --git a/package.json b/package.json index 74f608eb..25b875dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@databricks/sql", - "version": "1.9.0", + "version": "1.10.0", "description": "Driver for connection to Databricks SQL via Thrift API.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/tests/unit/DBSQLClient.test.ts b/tests/unit/DBSQLClient.test.ts index 5caf8420..f4ac593f 100644 --- a/tests/unit/DBSQLClient.test.ts +++ b/tests/unit/DBSQLClient.test.ts @@ -79,6 +79,25 @@ describe('DBSQLClient.connect', () => { expect(thriftConnectionStub.on.called).to.be.true; }); + + it('should log a warning when deprecated clientId is passed', async () => { + const client = new DBSQLClient(); + const logSpy = sinon.spy((client as any).logger, 'log'); + + const optionsWithDeprecated = { + ...connectOptions, + clientId: 'clientId', + }; + + await client.connect(optionsWithDeprecated as any); + + const warningRegex = /Warning: The "clientId" option is deprecated\. Please use "userAgentEntry" instead\./; + const callFound = logSpy.getCalls().some((call) => warningRegex.test(call.args[1])); + + expect(callFound).to.be.true; + + logSpy.restore(); + }); }); describe('DBSQLClient.openSession', () => { diff --git a/tests/unit/utils/utils.test.ts b/tests/unit/utils/utils.test.ts index f94e88c4..ed96326e 100644 --- a/tests/unit/utils/utils.test.ts +++ b/tests/unit/utils/utils.test.ts @@ -19,14 +19,16 @@ describe('buildUserAgentString', () => { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent // // UserAgent ::= '/' '(' ')' + // where: // ProductName ::= 'NodejsDatabricksSqlConnector' - // ::= [ ';' ] 'Node.js' ';' + // ProductVersion ::= three period-separated digits + // ::= [ ';' ] 'Node.js' ';' // // Examples: - // - with provided: NodejsDatabricksSqlConnector/0.1.8-beta.1 (Client ID; Node.js 16.13.1; Darwin 21.5.0) - // - without provided: NodejsDatabricksSqlConnector/0.1.8-beta.1 (Node.js 16.13.1; Darwin 21.5.0) + // - with provided: NodejsDatabricksSqlConnector/0.1.8-beta.1 (; Node.js 16.13.1; Darwin 21.5.0) + // - without provided: NodejsDatabricksSqlConnector/0.1.8-beta.1 (Node.js 16.13.1; Darwin 21.5.0) - function checkUserAgentString(ua: string, clientId?: string) { + function checkUserAgentString(ua: string, userAgentEntry?: string) { // Prefix: 'NodejsDatabricksSqlConnector/' // Version: three period-separated digits and optional suffix const re = @@ -36,23 +38,30 @@ describe('buildUserAgentString', () => { const { comment } = match?.groups ?? {}; - expect(comment.split(';').length).to.be.gte(2); // at least Node and OS version should be there + const parts = comment.split(';').map((s) => s.trim()); + expect(parts.length).to.be.gte(2); // at least Node and OS version should be there - if (clientId) { - expect(comment.trim()).to.satisfy((s: string) => s.startsWith(`${clientId};`)); + if (userAgentEntry) { + expect(comment.trim()).to.satisfy((s: string) => s.startsWith(`${userAgentEntry};`)); } } - it('matches pattern with clientId', () => { - const clientId = 'Some Client ID'; - const ua = buildUserAgentString(clientId); - checkUserAgentString(ua, clientId); + it('matches pattern with userAgentEntry', () => { + const userAgentEntry = 'Some user agent'; + const ua = buildUserAgentString(userAgentEntry); + checkUserAgentString(ua, userAgentEntry); }); - it('matches pattern without clientId', () => { + it('matches pattern without userAgentEntry', () => { const ua = buildUserAgentString(); checkUserAgentString(ua); }); + + it('should redact internal token in userAgentEntry', () => { + const userAgentEntry = 'dkea-internal-token'; + const userAgentString = buildUserAgentString(userAgentEntry); + expect(userAgentString).to.include(''); + }); }); describe('formatProgress', () => {