Skip to content

Commit d2fc6a8

Browse files
authored
VSCode: Add task to start dbx sync (#53)
1 parent cee580c commit d2fc6a8

File tree

8 files changed

+273
-35
lines changed

8 files changed

+273
-35
lines changed

packages/databricks-vscode/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
"onCommand:databricks.connection.openDatabricksConfigFile",
4343
"onCommand:databricks.connection.attachCluster",
4444
"onCommand:databricks.connection.detachCluster",
45+
"onCommand:databricks.cli.startSync",
46+
"onCommand:databricks.cli.startSyncFull",
47+
"onCommand:databricks.cli.stopSync",
4548
"onCommand:databricks.cluster.refresh",
4649
"onCommand:databricks.cluster.filterByAll",
4750
"onCommand:databricks.cluster.filterByMe",
@@ -81,6 +84,18 @@
8184
"title": "Detach cluster",
8285
"icon": "$(debug-disconnect)"
8386
},
87+
{
88+
"command": "databricks.cli.startSync",
89+
"title": "Databricks: Start synchronization"
90+
},
91+
{
92+
"command": "databricks.cli.startSyncFull",
93+
"title": "Databricks: Start synchronization (full sync)"
94+
},
95+
{
96+
"command": "databricks.cli.stopSync",
97+
"title": "Databricks: Stop synchronization"
98+
},
8499
{
85100
"command": "databricks.cluster.filterByAll",
86101
"title": "All"
@@ -199,6 +214,22 @@
199214
"label": "Filter clusters ...",
200215
"icon": "$(filter)"
201216
}
217+
],
218+
"problemMatchers": [
219+
{
220+
"name": "bricks-sync",
221+
"owner": "bricks",
222+
"fileLocation": "relative",
223+
"pattern": {
224+
"regexp": "Putting (/Repos/.*)$",
225+
"file": 1
226+
},
227+
"background": {
228+
"activeOnStart": true,
229+
"beginsPattern": "Syncing from",
230+
"endsPattern": "Done(\\. Watching for changes\\.\\.\\.)$"
231+
}
232+
}
202233
]
203234
},
204235
"vsce": {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import {
2+
ProcessExecution,
3+
Task,
4+
TaskExecution,
5+
TaskRevealKind,
6+
tasks,
7+
TaskScope,
8+
window,
9+
workspace,
10+
} from "vscode";
11+
import {ConnectionManager} from "../configuration/ConnectionManager";
12+
import {CliWrapper} from "./CliWrapper";
13+
14+
export class CliCommands {
15+
currentTaskExecution?: TaskExecution;
16+
17+
constructor(
18+
private connection: ConnectionManager,
19+
private cli: CliWrapper
20+
) {}
21+
22+
stopSyncCommand() {
23+
return () => {
24+
if (this.currentTaskExecution) {
25+
this.currentTaskExecution.terminate();
26+
}
27+
};
28+
}
29+
30+
startSyncCommand(syncType: "full" | "incremental") {
31+
return async () => {
32+
const workspacePath = workspace.rootPath;
33+
const me = this.connection.me;
34+
const pathMapper = this.connection.pathMapper;
35+
const profile = this.connection.profile;
36+
37+
if (!workspacePath) {
38+
window.showErrorMessage(
39+
"Can't start sync: No workspace opened!"
40+
);
41+
return;
42+
}
43+
44+
if (!me || !profile) {
45+
window.showErrorMessage(
46+
"Can't start sync: Databricks connection not configured!"
47+
);
48+
return;
49+
}
50+
if (!pathMapper) {
51+
window.showErrorMessage(
52+
"Can't start sync: Databricks synchronization destination not configured!"
53+
);
54+
return;
55+
}
56+
57+
if (this.currentTaskExecution) {
58+
this.currentTaskExecution.terminate();
59+
}
60+
61+
const {command, args} = this.cli.getSyncCommand(
62+
profile,
63+
me,
64+
pathMapper,
65+
syncType
66+
);
67+
68+
const task = new Task(
69+
{
70+
type: "bricks",
71+
},
72+
TaskScope.Workspace,
73+
"$(rocket) Databricks Sync",
74+
"bricks",
75+
new ProcessExecution(command, args, {
76+
cwd: workspacePath,
77+
})
78+
);
79+
task.isBackground = true;
80+
task.problemMatchers = ["$bricks-sync"];
81+
task.presentationOptions.echo = true;
82+
task.presentationOptions.reveal = TaskRevealKind.Always;
83+
84+
this.currentTaskExecution = await tasks.executeTask(task);
85+
};
86+
}
87+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import * as assert from "assert";
2+
import {Uri} from "vscode";
3+
import {PathMapper} from "../configuration/PathMapper";
4+
5+
import {CliWrapper} from "./CliWrapper";
6+
7+
describe(__filename, () => {
8+
it("should create sync command", () => {
9+
const cli = new CliWrapper();
10+
const mapper = new PathMapper(
11+
Uri.file(
12+
"/Workspace/Repos/fabian.jakobs@databricks.com/notebook-best-practices"
13+
),
14+
Uri.file("/Users/fabian.jakobs/Desktop/notebook-best-practices")
15+
);
16+
17+
const {command, args} = cli.getSyncCommand(
18+
"DEFAULT",
19+
"fabian@databricks.com",
20+
mapper,
21+
"incremental"
22+
);
23+
24+
assert.equal(
25+
[command, ...args].join(" "),
26+
"dbx sync repo --profile DEFAULT --user fabian@databricks.com --dest-repo notebook-best-practices"
27+
);
28+
});
29+
30+
it("should create full sync command", () => {
31+
const cli = new CliWrapper();
32+
const mapper = new PathMapper(
33+
Uri.file(
34+
"/Workspace/Repos/fabian.jakobs@databricks.com/notebook-best-practices"
35+
),
36+
Uri.file("/Users/fabian.jakobs/Desktop/notebook-best-practices")
37+
);
38+
39+
const {command, args} = cli.getSyncCommand(
40+
"DEFAULT",
41+
"fabian@databricks.com",
42+
mapper,
43+
"full"
44+
);
45+
46+
assert.equal(
47+
[command, ...args].join(" "),
48+
"dbx sync repo --profile DEFAULT --user fabian@databricks.com --dest-repo notebook-best-practices --full-sync"
49+
);
50+
});
51+
52+
it("should create an 'add profile' command", () => {
53+
const cli = new CliWrapper();
54+
55+
const {command, args} = cli.getAddProfileCommand(
56+
"DEFAULT",
57+
new URL("https://databricks.com")
58+
);
59+
60+
assert.equal(
61+
[command, ...args].join(" "),
62+
"databricks configure --profile DEFAULT --host https://databricks.com/ --token"
63+
);
64+
});
65+
});

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

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import {spawn} from "child_process";
2+
import {PathMapper} from "../configuration/PathMapper";
3+
4+
interface Command {
5+
command: string;
6+
args: string[];
7+
}
28

39
/**
410
* Entrypoint for all wrapped CLI commands
@@ -9,26 +15,58 @@ import {spawn} from "child_process";
915
export class CliWrapper {
1016
constructor() {}
1117

18+
/**
19+
* Constructs the dbx sync command
20+
*/
21+
getSyncCommand(
22+
profile: string,
23+
me: string,
24+
pathMapper: PathMapper,
25+
syncType: "full" | "incremental"
26+
): Command {
27+
const command = "dbx";
28+
const args = [
29+
"sync",
30+
"repo",
31+
"--profile",
32+
profile,
33+
"--user",
34+
me,
35+
"--dest-repo",
36+
pathMapper.remoteWorkspaceName,
37+
];
38+
39+
if (syncType === "full") {
40+
args.push("--full-sync");
41+
}
42+
43+
return {command, args};
44+
}
45+
46+
getAddProfileCommand(profile: string, host: URL): Command {
47+
return {
48+
command: "databricks",
49+
args: [
50+
"configure",
51+
"--profile",
52+
profile,
53+
"--host",
54+
host.href,
55+
"--token",
56+
],
57+
};
58+
}
59+
1260
async addProfile(
1361
name: string,
1462
host: URL,
1563
token: string
1664
): Promise<{stdout: string; stderr: string}> {
1765
return new Promise((resolve, reject) => {
18-
let child = spawn(
19-
"databricks",
20-
[
21-
"configure",
22-
"--profile",
23-
name,
24-
"--host",
25-
host.href,
26-
"--token",
27-
],
28-
{
29-
stdio: ["pipe", 0, 0],
30-
}
31-
);
66+
const {command, args} = this.getAddProfileCommand(name, host);
67+
let child = spawn(command, args, {
68+
stdio: ["pipe", 0, 0],
69+
});
3270

3371
child.stdin!.write(token);
3472
child.stdin!.end();

packages/databricks-vscode/src/configuration/PathMapper.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,15 @@ describe(__filename, () => {
3838
"/Workspace/Repos/fabian.jakobs@databricks.com/notebook-best-practices/jobs"
3939
);
4040
});
41+
42+
it("should get repo name", async () => {
43+
let mapper = new PathMapper(
44+
Uri.file(
45+
"/Workspace/Repos/fabian.jakobs@databricks.com/notebook-best-practices"
46+
),
47+
Uri.file("/Users/fabian.jakobs/Desktop/notebook-best-practices")
48+
);
49+
50+
assert.equal(mapper.remoteWorkspaceName, "notebook-best-practices");
51+
});
4152
});

packages/databricks-vscode/src/configuration/PathMapper.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@ import {Uri} from "vscode";
66
* on the Databricks driver
77
*/
88
export class PathMapper {
9-
readonly repo: string;
10-
11-
constructor(readonly repoPath: Uri, readonly workspacePath: Uri) {
12-
this.repo = path.basename(repoPath.path);
13-
}
9+
constructor(readonly repoPath: Uri, readonly workspacePath: Uri) {}
1410

1511
localToRemoteDir(localPath: Uri): string {
1612
return path.dirname(this.localToRemote(localPath));
@@ -20,4 +16,8 @@ export class PathMapper {
2016
let relativePath = localPath.path.replace(this.workspacePath.path, "");
2117
return Uri.joinPath(this.repoPath, relativePath).path;
2218
}
19+
20+
get remoteWorkspaceName(): string {
21+
return path.basename(this.repoPath.path);
22+
}
2323
}

packages/databricks-vscode/src/extension.test.ts

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

packages/databricks-vscode/src/extension.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {ClusterModel} from "./cluster/ClusterModel";
77
import {ClusterCommands} from "./cluster/ClusterCommands";
88
import {ConfigurationDataProvider} from "./configuration/ConfigurationDataProvider";
99
import {WorkflowCommands} from "./workflow/WorkflowCommands";
10+
import {CliCommands} from "./cli/CliCommands";
1011

1112
export function activate(context: ExtensionContext) {
1213
let cli = new CliWrapper();
@@ -77,6 +78,7 @@ export function activate(context: ExtensionContext) {
7778
context.subscriptions.push(
7879
clusterModel,
7980
clusterTreeDataProvider,
81+
window.registerTreeDataProvider("clusterList", clusterTreeDataProvider),
8082

8183
commands.registerCommand(
8284
"databricks.cluster.refresh",
@@ -99,7 +101,26 @@ export function activate(context: ExtensionContext) {
99101
clusterCommands
100102
)
101103
);
102-
window.registerTreeDataProvider("clusterList", clusterTreeDataProvider);
104+
105+
// Tasks
106+
const cliCommands = new CliCommands(connectionManager, cli);
107+
context.subscriptions.push(
108+
commands.registerCommand(
109+
"databricks.cli.startSync",
110+
cliCommands.startSyncCommand("incremental"),
111+
cliCommands
112+
),
113+
commands.registerCommand(
114+
"databricks.cli.startSyncFull",
115+
cliCommands.startSyncCommand("full"),
116+
cliCommands
117+
),
118+
commands.registerCommand(
119+
"databricks.cli.stopSync",
120+
cliCommands.stopSyncCommand(),
121+
cliCommands
122+
)
123+
);
103124
}
104125

105126
// this method is called when your extension is deactivated

0 commit comments

Comments
 (0)