Skip to content

Commit ad95183

Browse files
authored
Unified auth for JS SDK (#309)
1 parent 3bde6b5 commit ad95183

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1741
-596
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ out/
1414
**/coverage/
1515
**/.nyc_output/
1616
.databricks/**
17+
packages/databricks-sdk-js/src/config/testdata/azure/Library
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {Config, ConfigOptions} from "./config/Config";
2+
import {ApiClient, ClientOptions} from "./api-client";
3+
import {ClustersService} from "./apis/clusters";
4+
import {CommandExecutionService} from "./apis/commands";
5+
import {DbfsService} from "./apis/dbfs";
6+
import {LibrariesService} from "./apis/libraries";
7+
import {PermissionsService} from "./apis/permissions";
8+
import {ReposService} from "./apis/repos";
9+
import {
10+
CurrentUserService,
11+
UsersService,
12+
GroupsService,
13+
ServicePrincipalsService,
14+
} from "./apis/scim";
15+
import {WorkspaceService} from "./apis/workspace";
16+
import {WorkspaceConfService} from "./apis/workspaceconf";
17+
import {JobsService} from "./apis/jobs";
18+
19+
export class WorkspaceClient {
20+
readonly config: Config;
21+
readonly apiClient: ApiClient;
22+
23+
constructor(config: ConfigOptions | Config, options: ClientOptions = {}) {
24+
if (!(config instanceof Config)) {
25+
config = new Config(config);
26+
}
27+
28+
this.config = config as Config;
29+
this.apiClient = new ApiClient(this.config, options);
30+
}
31+
32+
get clusters() {
33+
return new ClustersService(this.apiClient);
34+
}
35+
36+
get dbfs() {
37+
return new DbfsService(this.apiClient);
38+
}
39+
40+
get commands() {
41+
return new CommandExecutionService(this.apiClient);
42+
}
43+
44+
get jobs() {
45+
return new JobsService(this.apiClient);
46+
}
47+
48+
get libraries() {
49+
return new LibrariesService(this.apiClient);
50+
}
51+
52+
get repos() {
53+
return new ReposService(this.apiClient);
54+
}
55+
56+
get currentUser() {
57+
return new CurrentUserService(this.apiClient);
58+
}
59+
60+
get users() {
61+
return new UsersService(this.apiClient);
62+
}
63+
64+
get groups() {
65+
return new GroupsService(this.apiClient);
66+
}
67+
68+
get servicePrincipals() {
69+
return new ServicePrincipalsService(this.apiClient);
70+
}
71+
72+
get workspace() {
73+
return new WorkspaceService(this.apiClient);
74+
}
75+
76+
get workspaceConf() {
77+
return new WorkspaceConfService(this.apiClient);
78+
}
79+
80+
get permissions() {
81+
return new PermissionsService(this.apiClient);
82+
}
83+
}

packages/databricks-sdk-js/src/api-client.test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import ".";
33
import assert from "node:assert";
44
import {ApiClient} from "./api-client";
5+
import {Config} from ".";
56

67
// eslint-disable-next-line @typescript-eslint/no-var-requires
78
const sdkVersion = require("../package.json").version;
@@ -12,12 +13,20 @@ describe(__filename, () => {
1213
});
1314

1415
it("should create proper user agent", () => {
15-
const ua = new ApiClient({extraUserAgent: {unit: "3.4.5"}}).userAgent();
16+
const ua = new ApiClient(
17+
new Config({
18+
authType: "pat",
19+
}),
20+
{
21+
product: "unit",
22+
productVersion: "3.4.5",
23+
}
24+
).userAgent();
1625
assert.equal(
1726
ua,
1827
`unit/3.4.5 databricks-sdk-js/${sdkVersion} nodejs/${process.version.slice(
1928
1
20-
)} os/${process.platform}`
29+
)} os/${process.platform} auth/pat`
2130
);
2231
});
2332
});

packages/databricks-sdk-js/src/api-client.ts

Lines changed: 45 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
22
import * as https from "node:https";
33
import {TextDecoder} from "node:util";
4-
import {fromDefaultChain} from "./auth/fromChain";
54
import {fetch} from "./fetch";
65
import {ExposedLoggers, Utils, withLogContext} from "./logging";
76
// eslint-disable-next-line @typescript-eslint/no-unused-vars
87
import {context} from "./context";
98
import {Context} from "./context";
9+
import {Headers, Config} from "./config/Config";
1010
import retry, {RetriableError} from "./retries/retries";
1111
import Time, {TimeUnits} from "./retries/Time";
12-
import {CredentialProvider} from "./auth/types";
1312

1413
// eslint-disable-next-line @typescript-eslint/no-var-requires
1514
const sdkVersion = require("../package.json").version;
@@ -43,53 +42,52 @@ function logAndReturnError(
4342
return error;
4443
}
4544

46-
export class ApiClient {
47-
private agent: https.Agent;
48-
private _host?: URL;
45+
export type ProductVersion = `${number}.${number}.${number}`;
4946

50-
private credentialProvider: CredentialProvider;
51-
private readonly extraUserAgent: Record<string, string>;
47+
export interface ClientOptions {
48+
agent?: https.Agent;
49+
product?: string;
50+
productVersion?: ProductVersion;
51+
userAgentExtra?: Record<string, string>;
52+
}
5253

53-
get host(): Promise<URL> {
54-
return (async () => {
55-
if (!this._host) {
56-
const credentials = await this.credentialProvider();
57-
this._host = credentials.host;
58-
}
59-
return this._host;
60-
})();
54+
export class ApiClient {
55+
private agent: https.Agent;
56+
readonly product: string;
57+
readonly productVersion: ProductVersion;
58+
readonly userAgentExtra: Record<string, string>;
59+
60+
constructor(readonly config: Config, options: ClientOptions = {}) {
61+
this.agent =
62+
options.agent ||
63+
new https.Agent({
64+
keepAlive: true,
65+
keepAliveMsecs: 15_000,
66+
rejectUnauthorized: config.insecureSkipVerify === false,
67+
timeout: (config.httpTimeoutSeconds || 5) * 1000,
68+
});
69+
70+
this.product = options.product || "unknown";
71+
this.productVersion = options.productVersion || "0.0.0";
72+
this.userAgentExtra = options.userAgentExtra || {};
6173
}
6274

63-
constructor({
64-
credentialProvider = fromDefaultChain,
65-
extraUserAgent = {},
66-
}: {
67-
credentialProvider?: CredentialProvider;
68-
extraUserAgent?: Record<string, string>;
69-
}) {
70-
this.credentialProvider = credentialProvider;
71-
this.extraUserAgent = extraUserAgent;
72-
73-
this.agent = new https.Agent({
74-
keepAlive: true,
75-
keepAliveMsecs: 15_000,
76-
});
75+
get host(): Promise<URL> {
76+
return this.config.getHost();
7777
}
7878

7979
userAgent(): string {
80-
const pairs: Array<string> = [];
81-
for (const [key, value] of Object.entries(this.extraUserAgent)) {
82-
pairs.push(`${key}/${value}`);
83-
}
84-
85-
pairs.push(
80+
const pairs = [
81+
`${this.product}/${this.productVersion}`,
8682
`databricks-sdk-js/${sdkVersion}`,
8783
`nodejs/${process.version.slice(1)}`,
88-
`os/${process.platform}`
89-
);
84+
`os/${process.platform}`,
85+
`auth/${this.config.authType}`,
86+
];
9087

91-
// TODO: add ability of per-request extra-information,
92-
// so that we can track sub-functionality, like in Terraform
88+
for (const [key, value] of Object.entries(this.userAgentExtra)) {
89+
pairs.push(`${key}/${value}`);
90+
}
9391
return pairs.join(" ");
9492
}
9593

@@ -100,15 +98,15 @@ export class ApiClient {
10098
payload?: any,
10199
@context context?: Context
102100
): Promise<Record<string, unknown>> {
103-
const credentials = await this.credentialProvider();
104-
const headers = {
105-
"Authorization": `Bearer ${credentials.token}`,
101+
const headers: Headers = {
106102
"User-Agent": this.userAgent(),
107103
"Content-Type": "text/json",
108104
};
109105

106+
await this.config.authenticate(headers);
107+
110108
// create a copy of the URL, so that we can modify it
111-
const url = new URL(credentials.host.toString());
109+
const url = new URL(this.config.host!);
112110
url.pathname = path;
113111

114112
const options: any = {
@@ -128,7 +126,10 @@ export class ApiClient {
128126
const response = await retry<
129127
Awaited<Awaited<ReturnType<typeof fetch>>["response"]>
130128
>({
131-
timeout: new Time(10, TimeUnits.seconds),
129+
timeout: new Time(
130+
this.config.retryTimeoutSeconds || 300,
131+
TimeUnits.seconds
132+
),
132133
fn: async () => {
133134
let response;
134135
try {

packages/databricks-sdk-js/src/apis/commands/commands.integ.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
22

3-
import {CommandExecutionService} from "../..";
43
import assert from "assert";
54

65
import {IntegrationTestSetup, sleep} from "../../test/IntegrationTestSetup";
@@ -15,7 +14,7 @@ describe(__filename, function () {
1514
});
1615

1716
it("should execute python with low level API", async () => {
18-
const commandsApi = new CommandExecutionService(integSetup.client);
17+
const commandsApi = integSetup.client.commands;
1918

2019
const context = await commandsApi.createAndWait({
2120
clusterId: integSetup.cluster.id,

packages/databricks-sdk-js/src/apis/jobs/jobs.integ.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
22

3-
import {JobsService, DbfsService} from "../..";
43
import assert from "assert";
54

65
import {IntegrationTestSetup, sleep} from "../../test/IntegrationTestSetup";
@@ -15,9 +14,9 @@ describe(__filename, function () {
1514
});
1615

1716
it("should run a notebook job", async () => {
18-
const jobsService = new JobsService(integSetup.client);
17+
const jobsService = integSetup.client.jobs;
1918

20-
const dbfsApi = new DbfsService(integSetup.client);
19+
const dbfsApi = integSetup.client.dbfs;
2120
const jobPath = `/tmp/sdk-js-integ-${integSetup.testRunId}.py`;
2221

2322
await dbfsApi.put({

packages/databricks-sdk-js/src/auth/Token.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/databricks-sdk-js/src/auth/fromAzureCli.integ.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

packages/databricks-sdk-js/src/auth/fromAzureCli.ts

Lines changed: 0 additions & 52 deletions
This file was deleted.

0 commit comments

Comments
 (0)