Skip to content

Commit 1f67001

Browse files
Show errors when parsing of host in .databrickscfg fails (#488)
Fixes #479 Co-authored-by: Fabian Jakobs <fabian.jakobs@databricks.com>
1 parent f3411a6 commit 1f67001

File tree

3 files changed

+89
-19
lines changed

3 files changed

+89
-19
lines changed

packages/databricks-sdk-js/src/logging/loggingDecorators.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import "reflect-metadata";
22
import {NamedLogger} from "./NamedLogger";
33
import {getContextParamIndex, Context, ContextItems} from "../context";
44

5-
export function withLogContext(name: string, opName?: string) {
5+
export function withLogContext(
6+
name: string,
7+
opName?: string,
8+
override = false
9+
) {
610
return function (
711
_target: any,
812
_propertyKey: string,
@@ -23,7 +27,11 @@ export function withLogContext(name: string, opName?: string) {
2327
).copy();
2428

2529
const items: ContextItems = {
26-
logger,
30+
logger:
31+
(contextParam.logger && override) ||
32+
contextParam.logger === undefined
33+
? logger
34+
: contextParam.logger,
2735
opName: contextParam.opName ?? opName,
2836
rootClassName:
2937
contextParam.rootClassName ?? this.constructor.name,

packages/databricks-vscode/src/cli/CliWrapper.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {writeFile} from "node:fs/promises";
1212

1313
import {CliWrapper} from "./CliWrapper";
1414
import path from "node:path";
15+
import {Context} from "@databricks/databricks-sdk/dist/context";
16+
import {NamedLogger} from "@databricks/databricks-sdk/dist/logging";
1517

1618
const execFile = promisify(execFileCb);
1719

@@ -140,9 +142,28 @@ host = https://cloud.databricks.com/
140142
141143
[missing-host-token]
142144
nothing = true
145+
146+
[typo-host]
147+
host = example.com
143148
`
144149
);
145-
const profiles = await cli.listProfiles(path);
150+
151+
const logs: {level: string; msg?: string; meta: any}[] = [];
152+
const profiles = await cli.listProfiles(
153+
path,
154+
new Context({
155+
logger: NamedLogger.getOrCreate("cli-wrapper-test", {
156+
factory: (name) => {
157+
return {
158+
log: (level, msg, meta) => {
159+
console.error("here");
160+
logs.push({level, msg, meta});
161+
},
162+
};
163+
},
164+
}),
165+
})
166+
);
146167
assert.equal(profiles.length, 2);
147168

148169
assert.equal(profiles[0].name, "correct");
@@ -152,6 +173,10 @@ nothing = true
152173
assert.equal(profiles[1].name, "no-token");
153174
assert.equal(profiles[1].host, "https://cloud.databricks.com/");
154175
assert.equal(profiles[1].authType, "");
176+
177+
const typoLog = logs.find((log) => log.msg?.includes("typo-host"));
178+
assert.ok(typoLog !== undefined);
179+
assert.ok(typoLog.level === "error");
155180
});
156181
});
157182
});

packages/databricks-vscode/src/cli/CliWrapper.ts

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import {execFile as execFileCb, spawn} from "child_process";
2-
import {ExtensionContext} from "vscode";
2+
import {ExtensionContext, window, commands} from "vscode";
33
import {SyncDestinationMapper} from "../configuration/SyncDestination";
44
import {workspaceConfigs} from "../vscode-objs/WorkspaceConfigs";
55
import {promisify} from "node:util";
6+
import {withLogContext} from "@databricks/databricks-sdk/dist/logging";
7+
import {Loggers} from "../logger";
8+
import {Context, context} from "@databricks/databricks-sdk/dist/context";
69

710
const execFile = promisify(execFileCb);
811

@@ -21,18 +24,26 @@ export interface ConfigEntry {
2124
}
2225

2326
export type SyncType = "full" | "incremental";
27+
28+
function getValidHost(host: string) {
29+
if (host.match(/^(?:(?:https?):\/\/)?(.(?!:\/\/))+$/) !== null) {
30+
return new URL(host);
31+
} else {
32+
throw new TypeError("Invalid type for host");
33+
}
34+
}
2435
/**
2536
* Entrypoint for all wrapped CLI commands
2637
*
2738
* Righ now this is a placeholder for a future implementation
2839
* of the bricks CLI
2940
*/
3041
export class CliWrapper {
31-
constructor(private context: ExtensionContext) {}
42+
constructor(private extensionContext: ExtensionContext) {}
3243

3344
getTestBricksCommand(): Command {
3445
return {
35-
command: this.context.asAbsolutePath("./bin/bricks"),
46+
command: this.extensionContext.asAbsolutePath("./bin/bricks"),
3647
args: [],
3748
};
3849
}
@@ -44,7 +55,7 @@ export class CliWrapper {
4455
syncDestination: SyncDestinationMapper,
4556
syncType: SyncType
4657
): Command {
47-
const command = this.context.asAbsolutePath("./bin/bricks");
58+
const command = this.extensionContext.asAbsolutePath("./bin/bricks");
4859
const args = [
4960
"sync",
5061
"--remote-path",
@@ -62,13 +73,15 @@ export class CliWrapper {
6273

6374
private getListProfilesCommand(): Command {
6475
return {
65-
command: this.context.asAbsolutePath("./bin/bricks"),
76+
command: this.extensionContext.asAbsolutePath("./bin/bricks"),
6677
args: ["auth", "profiles", "--skip-validate"],
6778
};
6879
}
6980

81+
@withLogContext(Loggers.Extension)
7082
public async listProfiles(
71-
configfilePath?: string
83+
configfilePath?: string,
84+
@context ctx?: Context
7285
): Promise<Array<ConfigEntry>> {
7386
const cmd = await this.getListProfilesCommand();
7487
const res = await execFile(cmd.command, cmd.args, {
@@ -82,31 +95,55 @@ export class CliWrapper {
8295
});
8396
const profiles = JSON.parse(res.stdout).profiles || [];
8497
const result = [];
98+
8599
for (const profile of profiles) {
86-
result.push({
87-
name: profile.name,
88-
host: new URL(profile.host),
89-
accountId: profile.account_id,
90-
cloud: profile.cloud,
91-
authType: profile.auth_type,
92-
valid: profile.valid,
93-
});
100+
try {
101+
result.push({
102+
name: profile.name,
103+
host: getValidHost(profile.host),
104+
accountId: profile.account_id,
105+
cloud: profile.cloud,
106+
authType: profile.auth_type,
107+
valid: profile.valid,
108+
});
109+
} catch (e: unknown) {
110+
let msg: string;
111+
if (e instanceof TypeError) {
112+
msg = `Can't parse host for profile ${profile.name}`;
113+
} else {
114+
msg = `Error parsing profile ${profile.name}`;
115+
}
116+
ctx?.logger?.error(msg, e);
117+
window
118+
.showWarningMessage(
119+
msg,
120+
"Open Databricks Config File",
121+
"Ignore"
122+
)
123+
.then((choice) => {
124+
if (choice === "Open Databricks Config File") {
125+
commands.executeCommand(
126+
"databricks.connection.openDatabricksConfigFile"
127+
);
128+
}
129+
});
130+
}
94131
}
95132
return result;
96133
}
97134

98135
public async getBundleSchema(): Promise<string> {
99136
const execFile = promisify(execFileCb);
100137
const {stdout} = await execFile(
101-
this.context.asAbsolutePath("./bin/bricks"),
138+
this.extensionContext.asAbsolutePath("./bin/bricks"),
102139
["bundle", "schema"]
103140
);
104141
return stdout;
105142
}
106143

107144
getAddProfileCommand(profile: string, host: URL): Command {
108145
return {
109-
command: this.context.asAbsolutePath("./bin/bricks"),
146+
command: this.extensionContext.asAbsolutePath("./bin/bricks"),
110147
args: [
111148
"configure",
112149
"--no-interactive",

0 commit comments

Comments
 (0)